建立 MAG160C 逆向工程交接仓库
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
__version__ = '0.33'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,144 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: common/construct_utils.py
|
||||
#
|
||||
# Some complementary construct utilities
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from struct import Struct
|
||||
from typing import IO, TYPE_CHECKING, Any, NoReturn
|
||||
|
||||
from ..construct import (
|
||||
Subconstruct, ConstructError, ArrayError, SizeofError, Construct, StaticField, FieldError
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterable
|
||||
|
||||
from ..construct import Container
|
||||
|
||||
|
||||
class RepeatUntilExcluding(Subconstruct):
|
||||
""" A version of construct's RepeatUntil that doesn't include the last
|
||||
element (which casued the repeat to exit) in the return value.
|
||||
|
||||
Only parsing is currently implemented.
|
||||
|
||||
P.S. removed some code duplication
|
||||
"""
|
||||
__slots__ = ("predicate",)
|
||||
def __init__(self, predicate: Callable[[Any, Container], bool], subcon: Construct) -> None:
|
||||
Subconstruct.__init__(self, subcon)
|
||||
self.predicate = predicate
|
||||
self._clear_flag(self.FLAG_COPY_CONTEXT)
|
||||
self._set_flag(self.FLAG_DYNAMIC)
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> list[Any]:
|
||||
obj = []
|
||||
try:
|
||||
context_for_subcon = context
|
||||
if self.subcon.conflags & self.FLAG_COPY_CONTEXT:
|
||||
context_for_subcon = context.__copy__()
|
||||
|
||||
while True:
|
||||
subobj = self.subcon._parse(stream, context_for_subcon)
|
||||
if self.predicate(subobj, context):
|
||||
break
|
||||
obj.append(subobj)
|
||||
except ConstructError as ex:
|
||||
raise ArrayError("missing terminator", ex)
|
||||
return obj
|
||||
def _build(self, obj: Iterable[Any], stream: IO[bytes], context: Container) -> NoReturn:
|
||||
raise NotImplementedError('no building')
|
||||
def _sizeof(self, context: Container) -> int:
|
||||
raise SizeofError("can't calculate size")
|
||||
|
||||
|
||||
class _NamedConstruct(Construct):
|
||||
if TYPE_CHECKING:
|
||||
name: str # instead of `str|None` from Construct to save us from `is None` checks everywhere
|
||||
|
||||
|
||||
class ULEB128(_NamedConstruct):
|
||||
"""A construct based parser for ULEB128 encoding.
|
||||
"""
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> int:
|
||||
value = 0
|
||||
shift = 0
|
||||
while True:
|
||||
data = stream.read(1)
|
||||
if len(data) != 1:
|
||||
raise FieldError("unexpected end of stream while parsing a ULEB128 encoded value")
|
||||
b = data[0]
|
||||
value |= (b & 0x7F) << shift
|
||||
shift += 7
|
||||
if b & 0x80 == 0:
|
||||
return value
|
||||
|
||||
|
||||
class SLEB128(_NamedConstruct):
|
||||
"""A construct based parser for SLEB128 encoding.
|
||||
"""
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> int:
|
||||
value = 0
|
||||
shift = 0
|
||||
while True:
|
||||
data = stream.read(1)
|
||||
if len(data) != 1:
|
||||
raise FieldError("unexpected end of stream while parsing a SLEB128 encoded value")
|
||||
b = data[0]
|
||||
value |= (b & 0x7F) << shift
|
||||
shift += 7
|
||||
if b & 0x80 == 0:
|
||||
return value | (~0 << shift) if b & 0x40 else value
|
||||
|
||||
|
||||
class StreamOffset(_NamedConstruct):
|
||||
"""
|
||||
Captures the current stream offset
|
||||
|
||||
Parameters:
|
||||
* name - the name of the value
|
||||
|
||||
Example:
|
||||
StreamOffset("item_offset")
|
||||
"""
|
||||
__slots__ = ()
|
||||
def __init__(self, name: str) -> None:
|
||||
Construct.__init__(self, name)
|
||||
self._set_flag(self.FLAG_DYNAMIC)
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> int:
|
||||
return stream.tell()
|
||||
def _build(self, obj: None, stream: IO[bytes], context: Container) -> None:
|
||||
context[self.name] = stream.tell()
|
||||
def _sizeof(self, context: Container) -> int:
|
||||
return 0
|
||||
|
||||
_UBInt24_packer = Struct(">BH")
|
||||
_ULInt24_packer = Struct("<HB")
|
||||
|
||||
class UBInt24(StaticField):
|
||||
"""unsigned, big endian 24-bit integer"""
|
||||
def __init__(self, name: str) -> None:
|
||||
StaticField.__init__(self, name, 3)
|
||||
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> int:
|
||||
(h, l) = _UBInt24_packer.unpack(StaticField._parse(self, stream, context))
|
||||
return l | (h << 16)
|
||||
|
||||
def _build(self, obj: int, stream: IO[bytes], context: Container) -> None:
|
||||
StaticField._build(self, _UBInt24_packer.pack(obj >> 16, obj & 0xFFFF), stream, context)
|
||||
|
||||
class ULInt24(StaticField):
|
||||
"""unsigned, little endian 24-bit integer"""
|
||||
def __init__(self, name: str) -> None:
|
||||
StaticField.__init__(self, name, 3)
|
||||
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> int:
|
||||
(l, h) = _ULInt24_packer.unpack(StaticField._parse(self, stream, context))
|
||||
return l | (h << 16)
|
||||
|
||||
def _build(self, obj: int, stream: IO[bytes], context: Container) -> None:
|
||||
StaticField._build(self, _ULInt24_packer.pack(obj & 0xFFFF, obj >> 16), stream, context)
|
||||
@@ -0,0 +1,22 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: common/exceptions.py
|
||||
#
|
||||
# Exception classes for elftools
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
class ELFError(Exception):
|
||||
pass
|
||||
|
||||
class ELFRelocationError(ELFError):
|
||||
pass
|
||||
|
||||
class ELFParseError(ELFError):
|
||||
pass
|
||||
|
||||
class ELFCompressionError(ELFError):
|
||||
pass
|
||||
|
||||
class DWARFError(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,132 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: common/utils.py
|
||||
#
|
||||
# Miscellaneous utilities for elftools
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import IO, TYPE_CHECKING, Any, TypeVar, overload
|
||||
|
||||
from .exceptions import ELFParseError, ELFError, DWARFError
|
||||
from ..construct import ConstructError
|
||||
import os
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping
|
||||
|
||||
from ..construct import Construct, FormatField
|
||||
from ..dwarf.dwarfinfo import DebugSectionDescriptor
|
||||
from .construct_utils import SLEB128, ULEB128, UBInt24, ULInt24
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_K = TypeVar("_K")
|
||||
_V = TypeVar("_V")
|
||||
|
||||
|
||||
def merge_dicts(*dicts: Mapping[_K, _V]) -> dict[_K, _V]:
|
||||
"Given any number of dicts, merges them into a new one."""
|
||||
result: dict[_K, _V] = {}
|
||||
for d in dicts:
|
||||
result.update(d)
|
||||
return result
|
||||
|
||||
def bytes2str(b: bytes) -> str:
|
||||
"""Decode a bytes object into a string."""
|
||||
return b.decode('latin-1')
|
||||
|
||||
|
||||
# Use @overload to get more specific type, e.g. [SU][BLN]{EB,Int}{8,16,24,32,64,128} -> int
|
||||
@overload
|
||||
def struct_parse(struct: FormatField[_T] | ULEB128 | SLEB128 | UBInt24 | ULInt24, stream: IO[bytes], stream_pos: int | None = ...) -> _T: ...
|
||||
@overload
|
||||
def struct_parse(struct: Construct, stream: IO[bytes], stream_pos: int | None = ...) -> Any: ...
|
||||
def struct_parse(struct: Construct, stream: IO[bytes], stream_pos: int | None = None) -> Any:
|
||||
""" Convenience function for using the given struct to parse a stream.
|
||||
If stream_pos is provided, the stream is seeked to this position before
|
||||
the parsing is done. Otherwise, the current position of the stream is
|
||||
used.
|
||||
Wraps the error thrown by construct with ELFParseError.
|
||||
"""
|
||||
try:
|
||||
if stream_pos is not None:
|
||||
stream.seek(stream_pos)
|
||||
return struct.parse_stream(stream)
|
||||
except ConstructError as e:
|
||||
raise ELFParseError(str(e))
|
||||
|
||||
|
||||
def parse_cstring_from_stream(stream: IO[bytes], stream_pos: int | None = None) -> bytes | None:
|
||||
""" Parse a C-string from the given stream. The string is returned without
|
||||
the terminating \x00 byte. If the terminating byte wasn't found, None
|
||||
is returned (the stream is exhausted).
|
||||
If stream_pos is provided, the stream is seeked to this position before
|
||||
the parsing is done. Otherwise, the current position of the stream is
|
||||
used.
|
||||
Note: a bytes object is returned here, because this is what's read from
|
||||
the binary file.
|
||||
"""
|
||||
if stream_pos is not None:
|
||||
stream.seek(stream_pos)
|
||||
CHUNKSIZE = 64
|
||||
chunks = []
|
||||
while True:
|
||||
chunk, sep, _tail = stream.read(CHUNKSIZE).partition(b'\x00')
|
||||
chunks.append(chunk)
|
||||
if sep:
|
||||
return b''.join(chunks)
|
||||
if len(chunk) < CHUNKSIZE:
|
||||
return None
|
||||
|
||||
|
||||
def elf_assert(cond: object, msg: str = '') -> None:
|
||||
""" Assert that cond is True, otherwise raise ELFError(msg)
|
||||
"""
|
||||
_assert_with_exception(cond, msg, ELFError)
|
||||
|
||||
|
||||
def dwarf_assert(cond: object, msg: str = '') -> None:
|
||||
""" Assert that cond is True, otherwise raise DWARFError(msg)
|
||||
"""
|
||||
_assert_with_exception(cond, msg, DWARFError)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def preserve_stream_pos(stream: IO[bytes]) -> Iterator[None]:
|
||||
""" Usage:
|
||||
# stream has some position FOO (return value of stream.tell())
|
||||
with preserve_stream_pos(stream):
|
||||
# do stuff that manipulates the stream
|
||||
# stream still has position FOO
|
||||
"""
|
||||
saved_pos = stream.tell()
|
||||
yield
|
||||
stream.seek(saved_pos)
|
||||
|
||||
|
||||
def roundup(num: int, bits: int) -> int:
|
||||
""" Round up a number to nearest multiple of 2^bits. The result is a number
|
||||
where the least significant bits passed in bits are 0.
|
||||
"""
|
||||
return (num - 1 | (1 << bits) - 1) + 1
|
||||
|
||||
|
||||
def save_dwarf_section(section: DebugSectionDescriptor, filename: str) -> None:
|
||||
"""Debug helper: dump section contents into a file
|
||||
Section is expected to be one of the debug_xxx_sec elements of DWARFInfo
|
||||
"""
|
||||
stream = section.stream
|
||||
with preserve_stream_pos(stream), open(filename, 'wb') as file:
|
||||
stream.seek(0, os.SEEK_SET)
|
||||
data = stream.read(section.size)
|
||||
file.write(data)
|
||||
|
||||
|
||||
#------------------------- PRIVATE -------------------------
|
||||
|
||||
def _assert_with_exception(cond: object, msg: str, exception_type: type[BaseException]) -> None:
|
||||
if not cond:
|
||||
raise exception_type(msg)
|
||||
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
#### ####
|
||||
## #### ## ## #### ###### ##### ## ## #### ###### ## ##
|
||||
## ## ## ### ## ## ## ## ## ## ## ## ## #### ##
|
||||
## ## ## ###### ### ## ##### ## ## ## ## ##
|
||||
## ## ## ## ### ## ## ## ## ## ## ## ## ##
|
||||
#### #### ## ## #### ## ## ## ##### #### ## ######
|
||||
|
||||
Parsing made even more fun (and faster too)
|
||||
|
||||
Homepage:
|
||||
http://construct.wikispaces.com (including online tutorial)
|
||||
|
||||
Typical usage:
|
||||
>>> from ..construct import *
|
||||
|
||||
Hands-on example:
|
||||
>>> from ..construct import *
|
||||
>>> s = Struct("foo",
|
||||
... UBInt8("a"),
|
||||
... UBInt16("b"),
|
||||
... )
|
||||
>>> s.parse(b"\\x01\\x02\\x03")
|
||||
Container({'a': 1, 'b': 515})
|
||||
>>> print(s.parse(b"\\x01\\x02\\x03"))
|
||||
Container({'a': 1, 'b': 515})
|
||||
>>> s.build(Container(a=1, b=0x0203))
|
||||
b'\\x01\\x02\\x03'
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from .lib.container import *
|
||||
from .core import *
|
||||
from .adapters import *
|
||||
from .macros import *
|
||||
from .debug import Probe, Debugger
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# Metadata
|
||||
#===============================================================================
|
||||
__author__: str = "tomer filiba (tomerfiliba [at] gmail.com)"
|
||||
__maintainer__: str = "Corbin Simpson <MostAwesomeDude@gmail.com>"
|
||||
__version__: str = "2.06"
|
||||
|
||||
#===============================================================================
|
||||
# Shorthand expressions
|
||||
#===============================================================================
|
||||
Bits = BitField
|
||||
Byte = UBInt8
|
||||
Bytes = Field
|
||||
Const = ConstAdapter
|
||||
Tunnel = TunnelAdapter
|
||||
Embed = Embedded
|
||||
|
||||
#===============================================================================
|
||||
# Deprecated names
|
||||
# Next scheduled name cleanout: 2.1
|
||||
#===============================================================================
|
||||
import functools
|
||||
import warnings
|
||||
from types import FunctionType
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
_P = ParamSpec('_P')
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
def deprecated(f: Callable[_P, _T]) -> Callable[_P, _T]:
|
||||
assert isinstance(f, (FunctionType, type))
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
|
||||
warnings.warn(
|
||||
"This name is deprecated, use %s instead" % f.__name__,
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
MetaBytes = deprecated(MetaField)
|
||||
GreedyRepeater = deprecated(GreedyRange)
|
||||
OptionalGreedyRepeater = deprecated(OptionalGreedyRange)
|
||||
Repeater = deprecated(Range)
|
||||
StrictRepeater = deprecated(Array)
|
||||
MetaRepeater = deprecated(Array)
|
||||
OneOfValidator = deprecated(OneOf)
|
||||
NoneOfValidator = deprecated(NoneOf)
|
||||
|
||||
#===============================================================================
|
||||
# exposed names
|
||||
#===============================================================================
|
||||
__all__ = [
|
||||
'AdaptationError', 'Adapter', 'Alias', 'Aligned', 'AlignedStruct',
|
||||
'Anchor', 'Array', 'ArrayError', 'BFloat32', 'BFloat64', 'Bit', 'BitField',
|
||||
'BitIntegerAdapter', 'BitIntegerError', 'BitStruct', 'Bits', 'Bitwise',
|
||||
'Buffered', 'Byte', 'Bytes', 'CString', 'CStringAdapter', 'Const',
|
||||
'ConstAdapter', 'ConstError', 'Construct', 'ConstructError', 'Container',
|
||||
'Debugger', 'Embed', 'Embedded', 'EmbeddedBitStruct', 'Enum', 'ExprAdapter',
|
||||
'Field', 'FieldError', 'Flag', 'FlagsAdapter', 'FlagsContainer',
|
||||
'FlagsEnum', 'FormatField', 'GreedyRange', 'GreedyRepeater',
|
||||
'HexDumpAdapter', 'If', 'IfThenElse', 'IndexingAdapter', 'LFloat32',
|
||||
'LFloat64', 'LazyBound', 'LengthValueAdapter', 'ListContainer',
|
||||
'MappingAdapter', 'MappingError', 'MetaArray', 'MetaBytes', 'MetaField',
|
||||
'MetaRepeater', 'NFloat32', 'NFloat64', 'Nibble', 'NoneOf',
|
||||
'NoneOfValidator', 'Octet', 'OnDemand', 'OnDemandPointer', 'OneOf',
|
||||
'OneOfValidator', 'OpenRange', 'Optional', 'OptionalGreedyRange',
|
||||
'OptionalGreedyRepeater', 'PaddedStringAdapter', 'Padding',
|
||||
'PaddingAdapter', 'PaddingError', 'PascalString', 'Pass', 'Peek',
|
||||
'Pointer', 'PrefixedArray', 'Probe', 'Range', 'RangeError', 'Reconfig',
|
||||
'Rename', 'RepeatUntil', 'Repeater', 'Restream', 'SBInt16', 'SBInt32',
|
||||
'SBInt64', 'SBInt8', 'SLInt16', 'SLInt32', 'SLInt64', 'SLInt8', 'SNInt16',
|
||||
'SNInt32', 'SNInt64', 'SNInt8', 'Select', 'SelectError', 'Sequence',
|
||||
'SizeofError', 'SlicingAdapter', 'StaticField', 'StrictRepeater', 'String',
|
||||
'StringAdapter', 'Struct', 'Subconstruct', 'Switch', 'SwitchError',
|
||||
'SymmetricMapping', 'Terminator', 'TerminatorError', 'Tunnel',
|
||||
'TunnelAdapter', 'UBInt16', 'UBInt32', 'UBInt64', 'UBInt8', 'ULInt16',
|
||||
'ULInt32', 'ULInt64', 'ULInt8', 'UNInt16', 'UNInt32', 'UNInt64', 'UNInt8',
|
||||
'Union', 'ValidationError', 'Validator', 'Value', "Magic",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,500 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from .core import Adapter, AdaptationError, Pass
|
||||
from .lib import int_to_bin, bin_to_int, swap_bytes
|
||||
from .lib import FlagsContainer, HexString
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Hashable, Mapping, Sized
|
||||
|
||||
from .core import Construct, _Pass
|
||||
from .lib import Container, ListContainer
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BitIntegerError", "MappingError", "ConstError", "ValidationError", "PaddingError",
|
||||
"BitIntegerAdapter", "MappingAdapter", "FlagsAdapter", "StringAdapter", "PaddedStringAdapter",
|
||||
"LengthValueAdapter", "CStringAdapter", "TunnelAdapter", "ExprAdapter", "HexDumpAdapter", "ConstAdapter",
|
||||
"SlicingAdapter", "IndexingAdapter", "PaddingAdapter",
|
||||
"Validator", "OneOf", "NoneOf",
|
||||
]
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# exceptions
|
||||
#===============================================================================
|
||||
class BitIntegerError(AdaptationError):
|
||||
__slots__ = ()
|
||||
class MappingError(AdaptationError):
|
||||
__slots__ = ()
|
||||
class ConstError(AdaptationError):
|
||||
__slots__ = ()
|
||||
class ValidationError(AdaptationError):
|
||||
__slots__ = ()
|
||||
class PaddingError(AdaptationError):
|
||||
__slots__ = ()
|
||||
|
||||
#===============================================================================
|
||||
# adapters
|
||||
#===============================================================================
|
||||
class BitIntegerAdapter(Adapter):
|
||||
"""
|
||||
Adapter for bit-integers (converts bitstrings to integers, and vice versa).
|
||||
See BitField.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to adapt
|
||||
* width - the size of the subcon, in bits
|
||||
* swapped - whether to swap byte order (little endian/big endian).
|
||||
default is False (big endian)
|
||||
* signed - whether the value is signed (two's complement). the default
|
||||
is False (unsigned)
|
||||
* bytesize - number of bits per byte, used for byte-swapping (if swapped).
|
||||
default is 8.
|
||||
"""
|
||||
__slots__ = ("width", "swapped", "signed", "bytesize")
|
||||
def __init__(self, subcon: Construct, width: int, swapped: bool = False, signed: bool = False,
|
||||
bytesize: int = 8) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.width = width
|
||||
self.swapped = swapped
|
||||
self.signed = signed
|
||||
self.bytesize = bytesize
|
||||
def _encode(self, obj: int, context: Container) -> bytes:
|
||||
if obj < 0 and not self.signed:
|
||||
raise BitIntegerError("object is negative, but field is not signed",
|
||||
obj)
|
||||
obj2 = int_to_bin(obj, width = self.width)
|
||||
if self.swapped:
|
||||
obj2 = swap_bytes(obj2, bytesize = self.bytesize)
|
||||
return obj2
|
||||
def _decode(self, obj: bytes, context: Container) -> int:
|
||||
if self.swapped:
|
||||
obj = swap_bytes(obj, bytesize = self.bytesize)
|
||||
return bin_to_int(obj, signed = self.signed)
|
||||
|
||||
class MappingAdapter(Adapter):
|
||||
"""
|
||||
Adapter that maps objects to other objects.
|
||||
See SymmetricMapping and Enum.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to map
|
||||
* decoding - the decoding (parsing) mapping (a dict)
|
||||
* encoding - the encoding (building) mapping (a dict)
|
||||
* decdefault - the default return value when the object is not found
|
||||
in the decoding mapping. if no object is given, an exception is raised.
|
||||
if `Pass` is used, the unmapped object will be passed as-is
|
||||
* encdefault - the default return value when the object is not found
|
||||
in the encoding mapping. if no object is given, an exception is raised.
|
||||
if `Pass` is used, the unmapped object will be passed as-is
|
||||
"""
|
||||
__slots__ = ("encoding", "decoding", "encdefault", "decdefault")
|
||||
def __init__(self, subcon: Construct, decoding: Mapping[Any, Any], encoding: Mapping[Any, Any],
|
||||
decdefault: Hashable | _Pass = NotImplemented,
|
||||
encdefault: Hashable | _Pass = NotImplemented,
|
||||
) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.decoding = decoding
|
||||
self.encoding = encoding
|
||||
self.decdefault = decdefault
|
||||
self.encdefault = encdefault
|
||||
def _encode(self, obj: Hashable, context: Container) -> Hashable:
|
||||
try:
|
||||
return self.encoding[obj]
|
||||
except (KeyError, TypeError):
|
||||
if self.encdefault is NotImplemented:
|
||||
raise MappingError("no encoding mapping for %r [%s]" % (
|
||||
obj, self.subcon.name))
|
||||
if self.encdefault is Pass:
|
||||
return obj
|
||||
return self.encdefault
|
||||
def _decode(self, obj: Hashable, context: Container) -> Hashable:
|
||||
try:
|
||||
return self.decoding[obj]
|
||||
except (KeyError, TypeError):
|
||||
if self.decdefault is NotImplemented:
|
||||
raise MappingError("no decoding mapping for %r [%s]" % (
|
||||
obj, self.subcon.name))
|
||||
if self.decdefault is Pass:
|
||||
return obj
|
||||
return self.decdefault
|
||||
|
||||
class FlagsAdapter(Adapter):
|
||||
"""
|
||||
Adapter for flag fields. Each flag is extracted from the number, resulting
|
||||
in a FlagsContainer object. Not intended for direct usage.
|
||||
See FlagsEnum.
|
||||
|
||||
Parameters
|
||||
* subcon - the subcon to extract
|
||||
* flags - a dictionary mapping flag-names to their value
|
||||
"""
|
||||
__slots__ = ("flags",)
|
||||
def __init__(self, subcon: Construct, flags: dict[str, int]) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.flags = flags
|
||||
def _encode(self, obj: FlagsContainer, context: Container) -> int:
|
||||
flags = 0
|
||||
for name, value in self.flags.items():
|
||||
if getattr(obj, name, False):
|
||||
flags |= value
|
||||
return flags
|
||||
def _decode(self, obj: int, context: Container) -> FlagsContainer:
|
||||
obj2 = FlagsContainer()
|
||||
for name, value in self.flags.items():
|
||||
setattr(obj2, name, bool(obj & value))
|
||||
return obj2
|
||||
|
||||
class StringAdapter(Adapter):
|
||||
"""
|
||||
Adapter for strings. Converts a sequence of characters into a python
|
||||
string, and optionally handles character encoding.
|
||||
See String.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to convert
|
||||
* encoding - the character encoding name (e.g., "utf8"), or None to
|
||||
return raw bytes (usually 8-bit ASCII).
|
||||
"""
|
||||
__slots__ = ("encoding",)
|
||||
def __init__(self, subcon: Construct, encoding: str | None = None) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.encoding = encoding
|
||||
def _encode(self, obj: bytes | str, context: Container) -> bytes:
|
||||
if self.encoding:
|
||||
assert isinstance(obj, str)
|
||||
obj = obj.encode(self.encoding)
|
||||
assert isinstance(obj, bytes)
|
||||
return obj
|
||||
def _decode(self, obj: bytes, context: Container) -> bytes | str:
|
||||
if self.encoding:
|
||||
return obj.decode(self.encoding)
|
||||
return obj
|
||||
|
||||
class PaddedStringAdapter(Adapter):
|
||||
r"""
|
||||
Adapter for padded strings.
|
||||
See String.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to adapt
|
||||
* padchar - the padding character. default is b"\x00".
|
||||
* paddir - the direction where padding is placed ("right", "left", or
|
||||
"center"). the default is "right".
|
||||
* trimdir - the direction where trimming will take place ("right" or
|
||||
"left"). the default is "right". trimming is only meaningful for
|
||||
building, when the given string is too long.
|
||||
"""
|
||||
__slots__ = ("padchar", "paddir", "trimdir")
|
||||
def __init__(self, subcon: Construct, padchar: bytes = b"\x00", paddir: Literal["right", "left", "center"] = "right",
|
||||
trimdir: Literal["right", "left"] = "right") -> None:
|
||||
if paddir not in ("right", "left", "center"):
|
||||
raise ValueError("paddir must be 'right', 'left' or 'center'",
|
||||
paddir)
|
||||
if trimdir not in ("right", "left"):
|
||||
raise ValueError("trimdir must be 'right' or 'left'", trimdir)
|
||||
Adapter.__init__(self, subcon)
|
||||
self.padchar = padchar
|
||||
self.paddir = paddir
|
||||
self.trimdir = trimdir
|
||||
def _decode(self, obj: bytes, context: Container) -> bytes:
|
||||
if self.paddir == "right":
|
||||
obj = obj.rstrip(self.padchar)
|
||||
elif self.paddir == "left":
|
||||
obj = obj.lstrip(self.padchar)
|
||||
else:
|
||||
obj = obj.strip(self.padchar)
|
||||
return obj
|
||||
def _encode(self, obj: bytes, context: Container) -> bytes:
|
||||
size = self._sizeof(context)
|
||||
if self.paddir == "right":
|
||||
obj = obj.ljust(size, self.padchar)
|
||||
elif self.paddir == "left":
|
||||
obj = obj.rjust(size, self.padchar)
|
||||
else:
|
||||
obj = obj.center(size, self.padchar)
|
||||
if len(obj) > size:
|
||||
if self.trimdir == "right":
|
||||
obj = obj[:size]
|
||||
else:
|
||||
obj = obj[-size:]
|
||||
return obj
|
||||
|
||||
class LengthValueAdapter(Adapter):
|
||||
"""
|
||||
Adapter for length-value pairs. It extracts only the value from the
|
||||
pair, and calculates the length based on the value.
|
||||
See PrefixedArray and PascalString.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon returning a length-value pair
|
||||
"""
|
||||
__slots__ = ()
|
||||
def _encode(self, obj: Sized, context: Container) -> tuple[int, Sized]:
|
||||
return (len(obj), obj)
|
||||
def _decode(self, obj: tuple[int, Sized] | ListContainer, context: Container) -> Sized:
|
||||
return obj[1]
|
||||
|
||||
class CStringAdapter(StringAdapter):
|
||||
r"""
|
||||
Adapter for C-style strings (strings terminated by a terminator char).
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to convert
|
||||
* terminators - a sequence of terminator chars. default is b"\x00".
|
||||
* encoding - the character encoding to use (e.g., "utf8"), or None to
|
||||
return raw-bytes. the terminator characters are not affected by the
|
||||
encoding.
|
||||
"""
|
||||
__slots__ = ("terminators",)
|
||||
def __init__(self, subcon: Construct, terminators: bytes = b"\x00", encoding: str | None = None) -> None:
|
||||
StringAdapter.__init__(self, subcon, encoding = encoding)
|
||||
self.terminators = terminators
|
||||
def _encode(self, obj: bytes | str, context: Container) -> bytes:
|
||||
return StringAdapter._encode(self, obj, context) + self.terminators[0:1]
|
||||
def _decode(self, obj: list[bytes], context: Container) -> bytes | str: # type: ignore[override] # ty: ignore[invalid-method-override]
|
||||
# This violates the Liskov Substitution Principle: should be `obj: bytes`, but RepeatUntil() converts `bytes` to `list[byte]`
|
||||
return StringAdapter._decode(self, b''.join(obj[:-1]), context)
|
||||
|
||||
class TunnelAdapter(Adapter):
|
||||
"""
|
||||
Adapter for tunneling (as in protocol tunneling). A tunnel is construct
|
||||
nested upon another (layering). For parsing, the lower layer first parses
|
||||
the data (note: it must return a string!), then the upper layer is called
|
||||
to parse that data (bottom-up). For building it works in a top-down manner;
|
||||
first the upper layer builds the data, then the lower layer takes it and
|
||||
writes it to the stream.
|
||||
|
||||
Parameters:
|
||||
* subcon - the lower layer subcon
|
||||
* inner_subcon - the upper layer (tunneled/nested) subcon
|
||||
|
||||
Example:
|
||||
# a pascal string containing compressed data (zlib encoding), so first
|
||||
# the string is read, decompressed, and finally re-parsed as an array
|
||||
# of UBInt16
|
||||
TunnelAdapter(
|
||||
PascalString("data", encoding = "zlib"),
|
||||
GreedyRange(UBInt16("elements"))
|
||||
)
|
||||
"""
|
||||
__slots__ = ("inner_subcon",)
|
||||
def __init__(self, subcon: Construct, inner_subcon: Construct) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.inner_subcon = inner_subcon
|
||||
def _decode(self, obj: bytes, context: Container) -> Any:
|
||||
return self.inner_subcon._parse(BytesIO(obj), context)
|
||||
def _encode(self, obj: Any, context: Container) -> bytes:
|
||||
stream = BytesIO()
|
||||
self.inner_subcon._build(obj, stream, context)
|
||||
return stream.getvalue()
|
||||
|
||||
class ExprAdapter(Adapter):
|
||||
"""
|
||||
A generic adapter that accepts 'encoder' and 'decoder' as parameters. You
|
||||
can use ExprAdapter instead of writing a full-blown class when only a
|
||||
simple expression is needed.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to adapt
|
||||
* encoder - a function that takes (obj, context) and returns an encoded
|
||||
version of obj
|
||||
* decoder - a function that takes (obj, context) and returns a decoded
|
||||
version of obj
|
||||
|
||||
Example:
|
||||
ExprAdapter(UBInt8("foo"),
|
||||
encoder = lambda obj, ctx: obj / 4,
|
||||
decoder = lambda obj, ctx: obj * 4,
|
||||
)
|
||||
"""
|
||||
__slots__ = ("__encode", "__decode")
|
||||
def __init__(self, subcon: Construct, encoder: Callable[[Any, Container], bytes], decoder: Callable[[bytes, Container], Any]) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.__encode = encoder
|
||||
self.__decode = decoder
|
||||
def _encode(self, obj: Any, context: Container) -> Any:
|
||||
return self.__encode(obj, context)
|
||||
def _decode(self, obj: Any, context: Container) -> Any:
|
||||
return self.__decode(obj, context)
|
||||
|
||||
class HexDumpAdapter(Adapter):
|
||||
"""
|
||||
Adapter for hex-dumping strings. It returns a HexString, which is a string
|
||||
"""
|
||||
__slots__ = ("linesize",)
|
||||
def __init__(self, subcon: Construct, linesize: int = 16) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.linesize = linesize
|
||||
def _encode(self, obj: Any, context: Container) -> Any:
|
||||
return obj
|
||||
def _decode(self, obj: bytes, context: Container) -> HexString:
|
||||
return HexString(obj, linesize = self.linesize)
|
||||
|
||||
class ConstAdapter(Adapter):
|
||||
"""
|
||||
Adapter for enforcing a constant value ("magic numbers"). When decoding,
|
||||
the return value is checked; when building, the value is substituted in.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to validate
|
||||
* value - the expected value
|
||||
|
||||
Example:
|
||||
Const(Field("signature", 2), "MZ")
|
||||
"""
|
||||
__slots__ = ("value",)
|
||||
def __init__(self, subcon: Construct, value: object) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.value = value
|
||||
def _encode(self, obj: object, context: Container) -> object:
|
||||
if obj is None or obj == self.value:
|
||||
return self.value
|
||||
else:
|
||||
raise ConstError("expected %r, found %r" % (self.value, obj))
|
||||
def _decode(self, obj: object, context: Container) -> object:
|
||||
if obj != self.value:
|
||||
raise ConstError("expected %r, found %r" % (self.value, obj))
|
||||
return obj
|
||||
|
||||
class SlicingAdapter(Adapter):
|
||||
"""
|
||||
Adapter for slicing a list (getting a slice from that list)
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to slice
|
||||
* start - start index
|
||||
* stop - stop index (or None for up-to-end)
|
||||
* step - step (or None for every element)
|
||||
"""
|
||||
__slots__ = ("start", "stop", "step")
|
||||
def __init__(self, subcon: Construct, start: int, stop: int | None = None) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
def _encode(self, obj: list[Any], context: Container) -> list[Any]:
|
||||
if self.start is None:
|
||||
return obj
|
||||
return [None] * self.start + obj
|
||||
def _decode(self, obj: list[Any], context: Container) -> list[Any]:
|
||||
return obj[self.start:self.stop]
|
||||
|
||||
class IndexingAdapter(Adapter):
|
||||
"""
|
||||
Adapter for indexing a list (getting a single item from that list)
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to index
|
||||
* index - the index of the list to get
|
||||
"""
|
||||
__slots__ = ("index",)
|
||||
def __init__(self, subcon: Construct, index: int) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
if type(index) is not int:
|
||||
raise TypeError("index must be an integer", type(index))
|
||||
self.index = index
|
||||
def _encode(self, obj: Any, context: Container) -> list[Any]:
|
||||
return [None] * self.index + [obj]
|
||||
def _decode(self, obj: list[Any], context: Container) -> Any:
|
||||
return obj[self.index]
|
||||
|
||||
class PaddingAdapter(Adapter):
|
||||
r"""
|
||||
Adapter for padding.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to pad
|
||||
* pattern - the padding pattern (character as byte). default is b"\x00"
|
||||
* strict - whether or not to verify, during parsing, that the given
|
||||
padding matches the padding pattern. default is False (unstrict)
|
||||
"""
|
||||
__slots__ = ("pattern", "strict")
|
||||
def __init__(self, subcon: Construct, pattern: bytes = b"\x00", strict: bool = False) -> None:
|
||||
Adapter.__init__(self, subcon)
|
||||
self.pattern = pattern
|
||||
self.strict = strict
|
||||
def _encode(self, obj: None, context: Container) -> bytes:
|
||||
return self._sizeof(context) * self.pattern
|
||||
def _decode(self, obj: bytes, context: Container) -> bytes:
|
||||
if self.strict:
|
||||
expected = self._sizeof(context) * self.pattern
|
||||
if obj != expected:
|
||||
raise PaddingError("expected %r, found %r" % (expected, obj))
|
||||
return obj
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# validators
|
||||
#===============================================================================
|
||||
class Validator(Adapter):
|
||||
"""
|
||||
Abstract class: validates a condition on the encoded/decoded object.
|
||||
Override _validate(obj, context) in deriving classes.
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to validate
|
||||
"""
|
||||
__slots__ = ()
|
||||
def _decode(self, obj: object, context: Container) -> object:
|
||||
if not self._validate(obj, context):
|
||||
raise ValidationError("invalid object", obj)
|
||||
return obj
|
||||
def _encode(self, obj: object, context: Container) -> object:
|
||||
return self._decode(obj, context)
|
||||
def _validate(self, obj: object, context: Container) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
class OneOf(Validator):
|
||||
"""
|
||||
Validates that the object is one of the listed values.
|
||||
|
||||
:param ``Construct`` subcon: object to validate
|
||||
:param iterable valids: a set of valid values
|
||||
|
||||
>>> from ..construct import UBInt8
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).parse(b"\\x05")
|
||||
5
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).parse(b"\\x08") # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: ('invalid object', 8)
|
||||
>>>
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).build(5)
|
||||
b'\\x05'
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).build(9) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: ('invalid object', 9)
|
||||
"""
|
||||
__slots__ = ("valids",)
|
||||
def __init__(self, subcon: Construct, valids: list[object]) -> None:
|
||||
Validator.__init__(self, subcon)
|
||||
self.valids = valids
|
||||
def _validate(self, obj: object, context: Container) -> bool:
|
||||
return obj in self.valids
|
||||
|
||||
class NoneOf(Validator):
|
||||
"""
|
||||
Validates that the object is none of the listed values.
|
||||
|
||||
:param ``Construct`` subcon: object to validate
|
||||
:param iterable invalids: a set of invalid values
|
||||
|
||||
>>> from ..construct import UBInt8
|
||||
>>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse(b"\\x08")
|
||||
8
|
||||
>>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse(b"\\x06") # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: ('invalid object', 6)
|
||||
"""
|
||||
__slots__ = ("invalids",)
|
||||
def __init__(self, subcon: Construct, invalids: list[object]) -> None:
|
||||
Validator.__init__(self, subcon)
|
||||
self.invalids = invalids
|
||||
def _validate(self, obj: object, context: Container) -> bool:
|
||||
return obj not in self.invalids
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Debugging utilities for constructs
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
import pdb
|
||||
import inspect
|
||||
from typing import IO, Any
|
||||
|
||||
from .core import Construct, Subconstruct
|
||||
from .lib import HexString, Container, ListContainer
|
||||
|
||||
|
||||
class Probe(Construct):
|
||||
"""
|
||||
A probe: dumps the context, stack frames, and stream content to the screen
|
||||
to aid the debugging process.
|
||||
See also Debugger.
|
||||
|
||||
Parameters:
|
||||
* name - the display name
|
||||
* show_stream - whether or not to show stream contents. default is True.
|
||||
the stream must be seekable.
|
||||
* show_context - whether or not to show the context. default is True.
|
||||
* show_stack - whether or not to show the upper stack frames. default
|
||||
is True.
|
||||
* stream_lookahead - the number of bytes to dump when show_stack is set.
|
||||
default is 100.
|
||||
|
||||
Example:
|
||||
Struct("foo",
|
||||
UBInt8("a"),
|
||||
Probe("between a and b"),
|
||||
UBInt8("b"),
|
||||
)
|
||||
"""
|
||||
__slots__ = (
|
||||
"printname", "show_stream", "show_context", "show_stack",
|
||||
"stream_lookahead"
|
||||
)
|
||||
counter = 0
|
||||
|
||||
def __init__(self, name: str | None = None, show_stream: bool = True,
|
||||
show_context: bool = True, show_stack: bool = True,
|
||||
stream_lookahead: int = 100) -> None:
|
||||
Construct.__init__(self, None)
|
||||
if name is None:
|
||||
Probe.counter += 1
|
||||
name = "<unnamed %d>" % (Probe.counter,)
|
||||
self.printname = name
|
||||
self.show_stream = show_stream
|
||||
self.show_context = show_context
|
||||
self.show_stack = show_stack
|
||||
self.stream_lookahead = stream_lookahead
|
||||
def __repr__(self) -> str:
|
||||
return "%s(%r)" % (self.__class__.__name__, self.printname)
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> None:
|
||||
self.printout(stream, context)
|
||||
def _build(self, obj: Any, stream: IO[bytes], context: Container) -> None:
|
||||
self.printout(stream, context)
|
||||
def _sizeof(self, context: Container) -> int:
|
||||
return 0
|
||||
|
||||
def printout(self, stream: IO[bytes], context: Container) -> None:
|
||||
obj = Container()
|
||||
if self.show_stream:
|
||||
obj.stream_position = stream.tell()
|
||||
follows = stream.read(self.stream_lookahead)
|
||||
if not follows:
|
||||
obj.following_stream_data = "EOF reached"
|
||||
else:
|
||||
stream.seek(-len(follows), 1)
|
||||
obj.following_stream_data = HexString(follows)
|
||||
|
||||
if self.show_context:
|
||||
obj.context = context
|
||||
|
||||
if self.show_stack:
|
||||
obj.stack = ListContainer()
|
||||
frames = [s[0] for s in inspect.stack()][1:-1]
|
||||
frames.reverse()
|
||||
for f in frames:
|
||||
a = Container()
|
||||
a.update(f.f_locals)
|
||||
obj.stack.append(a)
|
||||
|
||||
print("=" * 80)
|
||||
print("Probe", self.printname)
|
||||
print(obj)
|
||||
print("=" * 80)
|
||||
|
||||
class Debugger(Subconstruct):
|
||||
"""
|
||||
A pdb-based debugger. When an exception occurs in the subcon, a debugger
|
||||
will appear and allow you to debug the error (and even fix on-the-fly).
|
||||
|
||||
Parameters:
|
||||
* subcon - the subcon to debug
|
||||
|
||||
Example:
|
||||
Debugger(
|
||||
Enum(UBInt8("foo"),
|
||||
a = 1,
|
||||
b = 2,
|
||||
c = 3
|
||||
)
|
||||
)
|
||||
"""
|
||||
__slots__ = ("retval",)
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> Any:
|
||||
try:
|
||||
return self.subcon._parse(stream, context)
|
||||
except Exception:
|
||||
self.retval = NotImplemented
|
||||
self.handle_exc("(you can set the value of 'self.retval', "
|
||||
"which will be returned)")
|
||||
if self.retval is NotImplemented:
|
||||
raise
|
||||
else:
|
||||
return self.retval
|
||||
def _build(self, obj: Any, stream: IO[bytes], context: Container) -> None:
|
||||
try:
|
||||
self.subcon._build(obj, stream, context)
|
||||
except Exception:
|
||||
self.handle_exc()
|
||||
def handle_exc(self, msg: str | None = None) -> None:
|
||||
print("=" * 80)
|
||||
print("Debugging exception of %s:" % (self.subcon,))
|
||||
print("".join(traceback.format_exception(*sys.exc_info())[1:]))
|
||||
if msg:
|
||||
print(msg)
|
||||
pdb.post_mortem(sys.exc_info()[2])
|
||||
print("=" * 80)
|
||||
@@ -0,0 +1,7 @@
|
||||
from .binary import (
|
||||
int_to_bin, bin_to_int, swap_bytes, encode_bin, decode_bin)
|
||||
from .bitstream import BitStreamReader, BitStreamWriter
|
||||
from .container import (Container, FlagsContainer, ListContainer,
|
||||
LazyContainer)
|
||||
from .hex import HexString, hexdump
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def int_to_bin(number: int, width: int = 32) -> bytes:
|
||||
r"""
|
||||
Convert an integer into its binary representation in a bytes object.
|
||||
Width is the amount of bits to generate. If width is larger than the actual
|
||||
amount of bits required to represent number in binary, sign-extension is
|
||||
used. If it's smaller, the representation is trimmed to width bits.
|
||||
Each "bit" is either '\x00' or '\x01'. The MSBit is first.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> int_to_bin(19, 5)
|
||||
b'\x01\x00\x00\x01\x01'
|
||||
>>> int_to_bin(19, 8)
|
||||
b'\x00\x00\x00\x01\x00\x00\x01\x01'
|
||||
"""
|
||||
if number < 0:
|
||||
number += 1 << width
|
||||
i = width - 1
|
||||
bits = bytearray(width)
|
||||
while number and i >= 0:
|
||||
bits[i] = number & 1
|
||||
number >>= 1
|
||||
i -= 1
|
||||
return bytes(bits)
|
||||
|
||||
|
||||
_bit_values: dict[int, int] = {
|
||||
0: 0,
|
||||
1: 1,
|
||||
48: 0, # '0'
|
||||
49: 1, # '1'
|
||||
}
|
||||
|
||||
def bin_to_int(bits: bytes, signed: bool = False) -> int:
|
||||
r"""
|
||||
Logical opposite of int_to_bin. Both '0' and '\x00' are considered zero,
|
||||
and both '1' and '\x01' are considered one. Set sign to True to interpret
|
||||
the number as a 2-s complement signed integer.
|
||||
"""
|
||||
number = 0
|
||||
bias = 0
|
||||
if signed and _bit_values[bits[0]] == 1:
|
||||
bits = bits[1:]
|
||||
bias = 1 << len(bits)
|
||||
for b in bits:
|
||||
number <<= 1
|
||||
number |= _bit_values[b]
|
||||
return number - bias
|
||||
|
||||
|
||||
def swap_bytes(bits: bytes, bytesize: int = 8) -> bytes:
|
||||
r"""
|
||||
Bits is a b'' object containing a binary representation. Assuming each
|
||||
bytesize bits constitute a bytes, perform a endianness byte swap. Example:
|
||||
|
||||
>>> swap_bytes(b'00011011', 2)
|
||||
b'11100100'
|
||||
"""
|
||||
i = 0
|
||||
l = len(bits)
|
||||
output = [b""] * ((l // bytesize) + 1)
|
||||
j = len(output) - 1
|
||||
while i < l:
|
||||
output[j] = bits[i : i + bytesize]
|
||||
i += bytesize
|
||||
j -= 1
|
||||
return b"".join(output)
|
||||
|
||||
|
||||
_char_to_bin = {}
|
||||
_bin_to_char = {}
|
||||
for i in range(256):
|
||||
ch = bytes((i,))
|
||||
bin = int_to_bin(i, 8)
|
||||
_char_to_bin[i] = bin
|
||||
_bin_to_char[bin] = ch
|
||||
|
||||
|
||||
def encode_bin(data: bytes) -> bytes:
|
||||
r"""
|
||||
Create a binary representation of the given b'' object. Assume 8-bit
|
||||
ASCII. Example:
|
||||
|
||||
>>> encode_bin(b'ab')
|
||||
b'\x00\x01\x01\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x01\x00'
|
||||
"""
|
||||
return b"".join(_char_to_bin[ch] for ch in data)
|
||||
|
||||
|
||||
def decode_bin(data: bytes) -> bytes:
|
||||
"""
|
||||
Logical opposite of decode_bin.
|
||||
"""
|
||||
if len(data) & 7:
|
||||
raise ValueError("Data length must be a multiple of 8")
|
||||
i = 0
|
||||
j = 0
|
||||
l = len(data) // 8
|
||||
chars = [b""] * l
|
||||
while j < l:
|
||||
chars[j] = _bin_to_char[data[i:i+8]]
|
||||
i += 8
|
||||
j += 1
|
||||
return b"".join(chars)
|
||||
@@ -0,0 +1,101 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from typing import IO, TYPE_CHECKING
|
||||
|
||||
from .binary import encode_bin, decode_bin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Buffer # 3.12+
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
|
||||
class BitStream(io.RawIOBase, IO[bytes]):
|
||||
|
||||
__slots__ = ("substream",)
|
||||
|
||||
def __init__(self, substream: IO[bytes]) -> None:
|
||||
self.substream = substream
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
class BitStreamReader(BitStream):
|
||||
|
||||
__slots__ = ("buffer", "total_size")
|
||||
|
||||
def __init__(self, substream: IO[bytes]) -> None:
|
||||
super().__init__(substream)
|
||||
self.total_size = 0
|
||||
self.buffer = b""
|
||||
|
||||
def close(self) -> None:
|
||||
if self.total_size % 8 != 0:
|
||||
raise ValueError("total size of read data must be a multiple of 8",
|
||||
self.total_size)
|
||||
|
||||
def tell(self) -> int:
|
||||
return self.substream.tell()
|
||||
|
||||
def seek(self, pos: int, whence: int = 0) -> int:
|
||||
self.buffer = b""
|
||||
self.total_size = 0
|
||||
self.substream.seek(pos, whence)
|
||||
return 0
|
||||
|
||||
def read(self, count: int = -1) -> bytes:
|
||||
if count < 0:
|
||||
raise ValueError("count cannot be negative")
|
||||
|
||||
l = len(self.buffer)
|
||||
if count == 0:
|
||||
data = b""
|
||||
elif count <= l:
|
||||
data = self.buffer[:count]
|
||||
self.buffer = self.buffer[count:]
|
||||
else:
|
||||
data = self.buffer
|
||||
count -= l
|
||||
bytes = count // 8
|
||||
if count & 7:
|
||||
bytes += 1
|
||||
buf = encode_bin(self.substream.read(bytes))
|
||||
data += buf[:count]
|
||||
self.buffer = buf[count:]
|
||||
self.total_size += len(data)
|
||||
return data
|
||||
|
||||
|
||||
class BitStreamWriter(BitStream):
|
||||
|
||||
__slots__ = ("buffer", "pos")
|
||||
|
||||
def __init__(self, substream: IO[bytes]) -> None:
|
||||
super().__init__(substream)
|
||||
self.buffer: list[bytes] = []
|
||||
self.pos = 0
|
||||
|
||||
def close(self) -> None:
|
||||
self.flush()
|
||||
|
||||
def flush(self) -> None:
|
||||
bytes = decode_bin(b"".join(self.buffer))
|
||||
self.substream.write(bytes)
|
||||
self.buffer = []
|
||||
self.pos = 0
|
||||
|
||||
def tell(self) -> int:
|
||||
return self.substream.tell() + self.pos // 8
|
||||
|
||||
def seek(self, pos: int, whence: int = 0) -> int:
|
||||
self.flush()
|
||||
return self.substream.seek(pos, whence)
|
||||
|
||||
def write(self, data: Buffer) -> int:
|
||||
if not data:
|
||||
return 0
|
||||
if type(data) is not bytes:
|
||||
raise TypeError("data must be a bytes, not %r" % (type(data),))
|
||||
self.buffer.append(data)
|
||||
return len(data)
|
||||
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
Various containers.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableMapping
|
||||
from functools import wraps
|
||||
from pprint import pformat
|
||||
from typing import IO, TYPE_CHECKING, Any, Literal, overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
from typing import Concatenate, ParamSpec, TypeVar
|
||||
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
from ..core import Construct
|
||||
from .hex import HexString
|
||||
|
||||
_P = ParamSpec('_P')
|
||||
_R = TypeVar('_R')
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
__all__ = [
|
||||
"recursion_lock",
|
||||
"Container", "FlagsContainer", "ListContainer", "LazyContainer",
|
||||
]
|
||||
|
||||
|
||||
def recursion_lock(
|
||||
retval: _R,
|
||||
lock_name: str = "__recursion_lock__",
|
||||
) -> Callable[[Callable[Concatenate[Any, _P], _T]], Callable[Concatenate[Any, _P], _T | _R]]:
|
||||
|
||||
def decorator(
|
||||
func: Callable[Concatenate[Any, _P], _T],
|
||||
) -> Callable[Concatenate[Any, _P], _T | _R]:
|
||||
@wraps(func)
|
||||
def wrapper(self: Any, *args: _P.args, **kw: _P.kwargs) -> _T | _R:
|
||||
if getattr(self, lock_name, False):
|
||||
return retval
|
||||
setattr(self, lock_name, True)
|
||||
try:
|
||||
return func(self, *args, **kw)
|
||||
finally:
|
||||
setattr(self, lock_name, False)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
class Container(MutableMapping[str, Any]):
|
||||
"""
|
||||
A generic container of attributes.
|
||||
|
||||
Containers are the common way to express parsed data.
|
||||
"""
|
||||
|
||||
def __init__(self, **kw: Any) -> None:
|
||||
self.__dict__ = kw
|
||||
|
||||
# The core dictionary interface.
|
||||
|
||||
@overload
|
||||
def __getitem__(self, name: Literal[
|
||||
"ch_addralign", "ch_size",
|
||||
"length",
|
||||
"n_descsz", "n_offset", "n_namesz",
|
||||
"sh_addralign", "sh_flags", "sh_size",
|
||||
"bloom_size", "nbuckets", "nchains",
|
||||
]) -> int: ...
|
||||
@overload
|
||||
def __getitem__(self, name: Literal[
|
||||
"ch_type",
|
||||
"sh_type",
|
||||
"n_name", "n_type",
|
||||
"tag", "vendor_name",
|
||||
]) -> str: ...
|
||||
@overload
|
||||
def __getitem__(self, name: Literal[
|
||||
"buckets", "chains",
|
||||
]) -> list[int]: ...
|
||||
@overload
|
||||
def __getitem__(self, name: str) -> Any: ...
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
return self.__dict__[name]
|
||||
|
||||
def __delitem__(self, name: str) -> None:
|
||||
del self.__dict__[name]
|
||||
|
||||
def __setitem__(self, name: str, value: Any) -> None:
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
return iter(self.__dict__)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__dict__.keys())
|
||||
|
||||
# Copy interface.
|
||||
|
||||
def copy(self) -> Self:
|
||||
return self.__class__(**self.__dict__)
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self.__dict__))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "%s(%s)" % (self.__class__.__name__, str(self.__dict__))
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# elftools.construct.debug Probe.printout()
|
||||
stream_position: int
|
||||
following_stream_data: str | HexString
|
||||
context: Container
|
||||
stack: ListContainer
|
||||
# allow arbitray attributes
|
||||
def __setattr__(self, name: str, value: object) -> None: ...
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
class FlagsContainer(Container):
|
||||
"""
|
||||
A container providing pretty-printing for flags.
|
||||
|
||||
Only set flags are displayed.
|
||||
"""
|
||||
|
||||
@recursion_lock("<...>")
|
||||
def __str__(self) -> str:
|
||||
d = dict((k, self[k]) for k in self
|
||||
if self[k] and not k.startswith("_"))
|
||||
return "%s(%s)" % (self.__class__.__name__, pformat(d))
|
||||
|
||||
class ListContainer(list[Any]):
|
||||
"""
|
||||
A container for lists.
|
||||
"""
|
||||
|
||||
__slots__ = ("__recursion_lock__",)
|
||||
|
||||
@recursion_lock("[...]")
|
||||
def __str__(self) -> str:
|
||||
return pformat(self)
|
||||
|
||||
class LazyContainer:
|
||||
|
||||
__slots__ = ("subcon", "stream", "pos", "context", "_value")
|
||||
|
||||
def __init__(self, subcon: Construct, stream: IO[bytes], pos: int, context: Container) -> None:
|
||||
self.subcon = subcon
|
||||
self.stream = stream
|
||||
self.pos = pos
|
||||
self.context = context
|
||||
self._value = NotImplemented
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, LazyContainer) and self._value == other._value
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return not (self == other)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__pretty_str__()
|
||||
|
||||
def __pretty_str__(self, nesting: int = 1, indentation: str = " ") -> str:
|
||||
if self._value is NotImplemented:
|
||||
text = "<unread>"
|
||||
elif hasattr(self._value, "__pretty_str__"):
|
||||
text = self._value.__pretty_str__(nesting, indentation)
|
||||
else:
|
||||
text = str(self._value)
|
||||
return "%s: %s" % (self.__class__.__name__, text)
|
||||
|
||||
def read(self) -> Any:
|
||||
self.stream.seek(self.pos)
|
||||
return self.subcon._parse(self.stream, self.context)
|
||||
|
||||
def dispose(self) -> None:
|
||||
del self.subcon
|
||||
del self.stream
|
||||
del self.context
|
||||
del self.pos
|
||||
|
||||
def _get_value(self) -> Any:
|
||||
if self._value is NotImplemented:
|
||||
self._value = self.read()
|
||||
return self._value
|
||||
|
||||
value = property(_get_value)
|
||||
|
||||
has_value = property(lambda self: self._value is not NotImplemented)
|
||||
@@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
|
||||
# Map an integer in the inclusive range 0-255 to its string byte representation
|
||||
_printable = {i: chr(i) if 32 <= i < 128 else "." for i in range(256)}
|
||||
|
||||
|
||||
def hexdump(data: bytes, linesize: int) -> list[str]:
|
||||
"""
|
||||
data is a bytes object. The returned result is a string.
|
||||
"""
|
||||
prettylines = []
|
||||
if len(data) < 65536:
|
||||
fmt = "%%04X %%-%ds %%s"
|
||||
else:
|
||||
fmt = "%%08X %%-%ds %%s"
|
||||
fmt = fmt % (3 * linesize - 1,)
|
||||
for i in range(0, len(data), linesize):
|
||||
line = data[i : i + linesize]
|
||||
hextext = line.hex(" ")
|
||||
rawtext = "".join(_printable[b] for b in line)
|
||||
prettylines.append(fmt % (i, hextext, rawtext))
|
||||
return prettylines
|
||||
|
||||
|
||||
class HexString(bytes):
|
||||
"""
|
||||
Represents bytes that will be hex-dumped to a string when its string
|
||||
representation is requested.
|
||||
"""
|
||||
def __init__(self, data: bytes, linesize: int = 16) -> None:
|
||||
self.linesize = linesize
|
||||
|
||||
def __new__(cls, data: bytes, *args: object, **kwargs: object) -> Self:
|
||||
return bytes.__new__(cls, data)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self:
|
||||
return "''"
|
||||
sep = "\n"
|
||||
return sep + sep.join(
|
||||
hexdump(self, self.linesize))
|
||||
@@ -0,0 +1,676 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sys import maxsize
|
||||
from typing import TYPE_CHECKING, Any, Literal, TypedDict
|
||||
|
||||
from .lib import (BitStreamReader, BitStreamWriter, Container, encode_bin,
|
||||
decode_bin)
|
||||
from .core import (Struct, MetaField, StaticField, FormatField,
|
||||
OnDemand, Pointer, Switch, Value, RepeatUntil, MetaArray, Sequence, Range,
|
||||
Select, Pass, SizeofError, Buffered, Restream, Reconfig)
|
||||
from .adapters import (BitIntegerAdapter, PaddingAdapter,
|
||||
ConstAdapter, CStringAdapter, LengthValueAdapter, IndexingAdapter,
|
||||
PaddedStringAdapter, FlagsAdapter, StringAdapter, MappingAdapter)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Hashable, Mapping
|
||||
|
||||
from typing_extensions import Unpack # Py3.11+
|
||||
|
||||
from .adapters import Adapter
|
||||
from .core import Construct, Subconstruct, _Pass
|
||||
|
||||
Length = int | Callable[[Container], int]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Field", "BitField", "Padding", "Flag",
|
||||
"Bit", "Nibble", "Octet",
|
||||
"UBInt8", "UBInt16", "UBInt32", "UBInt64",
|
||||
"SBInt8", "SBInt16", "SBInt32", "SBInt64",
|
||||
"ULInt8", "ULInt16", "ULInt32", "ULInt64",
|
||||
"SLInt8", "SLInt16", "SLInt32", "SLInt64",
|
||||
"UNInt8", "UNInt16", "UNInt32", "UNInt64",
|
||||
"SNInt8", "SNInt16", "SNInt32", "SNInt64",
|
||||
"BFloat32", "LFloat32", "NFloat32",
|
||||
"BFloat64", "LFloat64", "NFloat64",
|
||||
"Array", "PrefixedArray", "OpenRange", "GreedyRange", "OptionalGreedyRange",
|
||||
"Optional", "Bitwise", "Aligned", "SeqOfOne", "Embedded", "Rename", "Alias",
|
||||
"SymmetricMapping", "Enum", "FlagsEnum",
|
||||
"AlignedStruct", "BitStruct", "EmbeddedBitStruct",
|
||||
"String", "PascalString", "CString",
|
||||
"IfThenElse", "If",
|
||||
"OnDemandPointer", "Magic",
|
||||
]
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# fields
|
||||
#===============================================================================
|
||||
def Field(name: str | None, length: Length) -> MetaField | StaticField:
|
||||
"""
|
||||
A field consisting of a specified number of bytes.
|
||||
|
||||
:param str name: the name of the field
|
||||
:param length: the length of the field. the length can be either an integer
|
||||
(StaticField), or a function that takes the context as an argument and
|
||||
returns the length (MetaField)
|
||||
"""
|
||||
if isinstance(length, int):
|
||||
return StaticField(name, length)
|
||||
else:
|
||||
return MetaField(name, length)
|
||||
|
||||
def BitField(name: str, length: Length, swapped: bool = False, signed: bool = False, bytesize: int = 8) -> BitIntegerAdapter:
|
||||
r"""
|
||||
BitFields, as the name suggests, are fields that operate on raw, unaligned
|
||||
bits, and therefore must be enclosed in a BitStruct. Using them is very
|
||||
similar to all normal fields: they take a name and a length (in bits).
|
||||
|
||||
:param str name: name of the field
|
||||
:param int length: number of bits in the field, or a function that takes
|
||||
the context as its argument and returns the length
|
||||
:param bool swapped: whether the value is byte-swapped
|
||||
:param bool signed: whether the value is signed
|
||||
:param int bytesize: number of bits per byte, for byte-swapping
|
||||
|
||||
>>> foo = BitStruct("foo",
|
||||
... BitField("a", 3),
|
||||
... Flag("b"),
|
||||
... Padding(3),
|
||||
... Nibble("c"),
|
||||
... BitField("d", 5),
|
||||
... )
|
||||
>>> foo.parse(b"\xe1\x1f")
|
||||
Container({'a': 7, 'b': False, 'c': 8, 'd': 31})
|
||||
>>> foo = BitStruct("foo",
|
||||
... BitField("a", 3),
|
||||
... Flag("b"),
|
||||
... Padding(3),
|
||||
... Nibble("c"),
|
||||
... Struct("bar",
|
||||
... Nibble("d"),
|
||||
... Bit("e"),
|
||||
... )
|
||||
... )
|
||||
>>> foo.parse(b"\xe1\x1f")
|
||||
Container({'a': 7, 'b': False, 'c': 8, 'bar': Container({'d': 15, 'e': 1})})
|
||||
"""
|
||||
|
||||
assert isinstance(length, int) # FIXME: Field(len=f()) is supported, but not BitIntegerAdapter(width=f())
|
||||
return BitIntegerAdapter(Field(name, length),
|
||||
length,
|
||||
swapped=swapped,
|
||||
signed=signed,
|
||||
bytesize=bytesize
|
||||
)
|
||||
|
||||
def Padding(length: Length, pattern: bytes = b"\x00", strict: bool = False) -> PaddingAdapter:
|
||||
r"""a padding field (value is discarded)
|
||||
* length - the length of the field. the length can be either an integer,
|
||||
or a function that takes the context as an argument and returns the
|
||||
length
|
||||
* pattern - the padding pattern (character/byte) to use. default is b"\x00"
|
||||
* strict - whether or not to raise an exception is the actual padding
|
||||
pattern mismatches the desired pattern. default is False.
|
||||
"""
|
||||
return PaddingAdapter(Field(None, length),
|
||||
pattern = pattern,
|
||||
strict = strict,
|
||||
)
|
||||
|
||||
def Flag(name: str, truth: int = 1, falsehood: int = 0, default: bool = False) -> MappingAdapter:
|
||||
"""
|
||||
A flag.
|
||||
|
||||
Flags are usually used to signify a Boolean value, and this construct
|
||||
maps values onto the ``bool`` type.
|
||||
|
||||
.. note:: This construct works with both bit and byte contexts.
|
||||
|
||||
.. warning:: Flags default to False, not True. This is different from the
|
||||
C and Python way of thinking about truth, and may be subject to change
|
||||
in the future.
|
||||
|
||||
:param str name: field name
|
||||
:param int truth: value of truth (default 1)
|
||||
:param int falsehood: value of falsehood (default 0)
|
||||
:param bool default: default value (default False)
|
||||
"""
|
||||
|
||||
return SymmetricMapping(Field(name, 1),
|
||||
{True : bytes((truth,)), False : bytes((falsehood,))},
|
||||
default = default,
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# field shortcuts
|
||||
#===============================================================================
|
||||
def Bit(name: str) -> BitIntegerAdapter:
|
||||
"""a 1-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 1)
|
||||
def Nibble(name: str) -> BitIntegerAdapter:
|
||||
"""a 4-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 4)
|
||||
def Octet(name: str) -> BitIntegerAdapter:
|
||||
"""an 8-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 8)
|
||||
|
||||
def UBInt8(name: str) -> FormatField[int]:
|
||||
"""unsigned, big endian 8-bit integer"""
|
||||
return FormatField(name, ">", "B")
|
||||
def UBInt16(name: str) -> FormatField[int]:
|
||||
"""unsigned, big endian 16-bit integer"""
|
||||
return FormatField(name, ">", "H")
|
||||
def UBInt32(name: str) -> FormatField[int]:
|
||||
"""unsigned, big endian 32-bit integer"""
|
||||
return FormatField(name, ">", "L")
|
||||
def UBInt64(name: str) -> FormatField[int]:
|
||||
"""unsigned, big endian 64-bit integer"""
|
||||
return FormatField(name, ">", "Q")
|
||||
|
||||
def SBInt8(name: str) -> FormatField[int]:
|
||||
"""signed, big endian 8-bit integer"""
|
||||
return FormatField(name, ">", "b")
|
||||
def SBInt16(name: str) -> FormatField[int]:
|
||||
"""signed, big endian 16-bit integer"""
|
||||
return FormatField(name, ">", "h")
|
||||
def SBInt32(name: str) -> FormatField[int]:
|
||||
"""signed, big endian 32-bit integer"""
|
||||
return FormatField(name, ">", "l")
|
||||
def SBInt64(name: str) -> FormatField[int]:
|
||||
"""signed, big endian 64-bit integer"""
|
||||
return FormatField(name, ">", "q")
|
||||
|
||||
def ULInt8(name: str) -> FormatField[int]:
|
||||
"""unsigned, little endian 8-bit integer"""
|
||||
return FormatField(name, "<", "B")
|
||||
def ULInt16(name: str) -> FormatField[int]:
|
||||
"""unsigned, little endian 16-bit integer"""
|
||||
return FormatField(name, "<", "H")
|
||||
def ULInt32(name: str) -> FormatField[int]:
|
||||
"""unsigned, little endian 32-bit integer"""
|
||||
return FormatField(name, "<", "L")
|
||||
def ULInt64(name: str) -> FormatField[int]:
|
||||
"""unsigned, little endian 64-bit integer"""
|
||||
return FormatField(name, "<", "Q")
|
||||
|
||||
def SLInt8(name: str) -> FormatField[int]:
|
||||
"""signed, little endian 8-bit integer"""
|
||||
return FormatField(name, "<", "b")
|
||||
def SLInt16(name: str) -> FormatField[int]:
|
||||
"""signed, little endian 16-bit integer"""
|
||||
return FormatField(name, "<", "h")
|
||||
def SLInt32(name: str) -> FormatField[int]:
|
||||
"""signed, little endian 32-bit integer"""
|
||||
return FormatField(name, "<", "l")
|
||||
def SLInt64(name: str) -> FormatField[int]:
|
||||
"""signed, little endian 64-bit integer"""
|
||||
return FormatField(name, "<", "q")
|
||||
|
||||
def UNInt8(name: str) -> FormatField[int]:
|
||||
"""unsigned, native endianity 8-bit integer"""
|
||||
return FormatField(name, "=", "B")
|
||||
def UNInt16(name: str) -> FormatField[int]:
|
||||
"""unsigned, native endianity 16-bit integer"""
|
||||
return FormatField(name, "=", "H")
|
||||
def UNInt32(name: str) -> FormatField[int]:
|
||||
"""unsigned, native endianity 32-bit integer"""
|
||||
return FormatField(name, "=", "L")
|
||||
def UNInt64(name: str) -> FormatField[int]:
|
||||
"""unsigned, native endianity 64-bit integer"""
|
||||
return FormatField(name, "=", "Q")
|
||||
|
||||
def SNInt8(name: str) -> FormatField[int]:
|
||||
"""signed, native endianity 8-bit integer"""
|
||||
return FormatField(name, "=", "b")
|
||||
def SNInt16(name: str) -> FormatField[int]:
|
||||
"""signed, native endianity 16-bit integer"""
|
||||
return FormatField(name, "=", "h")
|
||||
def SNInt32(name: str) -> FormatField[int]:
|
||||
"""signed, native endianity 32-bit integer"""
|
||||
return FormatField(name, "=", "l")
|
||||
def SNInt64(name: str) -> FormatField[int]:
|
||||
"""signed, native endianity 64-bit integer"""
|
||||
return FormatField(name, "=", "q")
|
||||
|
||||
def BFloat32(name: str) -> FormatField[float]:
|
||||
"""big endian, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, ">", "f")
|
||||
def LFloat32(name: str) -> FormatField[float]:
|
||||
"""little endian, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, "<", "f")
|
||||
def NFloat32(name: str) -> FormatField[float]:
|
||||
"""native endianity, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, "=", "f")
|
||||
|
||||
def BFloat64(name: str) -> FormatField[float]:
|
||||
"""big endian, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, ">", "d")
|
||||
def LFloat64(name: str) -> FormatField[float]:
|
||||
"""little endian, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, "<", "d")
|
||||
def NFloat64(name: str) -> FormatField[float]:
|
||||
"""native endianity, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, "=", "d")
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# arrays
|
||||
#===============================================================================
|
||||
def Array(count: Length, subcon: Construct) -> MetaArray:
|
||||
r"""
|
||||
Repeats the given unit a fixed number of times.
|
||||
|
||||
:param int count: number of times to repeat
|
||||
:param ``Construct`` subcon: construct to repeat
|
||||
|
||||
>>> c = Array(4, UBInt8("foo"))
|
||||
>>> c.parse(b"\x01\x02\x03\x04")
|
||||
[1, 2, 3, 4]
|
||||
>>> c.parse(b"\x01\x02\x03\x04\x05\x06")
|
||||
[1, 2, 3, 4]
|
||||
>>> c.build([5,6,7,8])
|
||||
b'\x05\x06\x07\x08'
|
||||
>>> c.build([5,6,7,8,9]) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ArrayError: expected 4, found 5
|
||||
"""
|
||||
|
||||
if isinstance(count, int):
|
||||
con = MetaArray(lambda ctx: count, subcon)
|
||||
con._clear_flag(con.FLAG_DYNAMIC)
|
||||
else:
|
||||
con = MetaArray(count, subcon)
|
||||
return con
|
||||
|
||||
def PrefixedArray(subcon: Construct, length_field: Construct = UBInt8("length")) -> LengthValueAdapter:
|
||||
"""an array prefixed by a length field.
|
||||
* subcon - the subcon to be repeated
|
||||
* length_field - a construct returning an integer
|
||||
"""
|
||||
assert length_field.name is not None
|
||||
name = length_field.name
|
||||
return LengthValueAdapter(
|
||||
Sequence(subcon.name,
|
||||
length_field,
|
||||
Array(lambda ctx: ctx[name], subcon),
|
||||
nested = False
|
||||
)
|
||||
)
|
||||
|
||||
def OpenRange(mincount: int, subcon: Construct) -> Range:
|
||||
return Range(mincount, maxsize, subcon)
|
||||
|
||||
def GreedyRange(subcon: Construct) -> Range:
|
||||
r"""
|
||||
Repeats the given unit one or more times.
|
||||
|
||||
:param ``Construct`` subcon: construct to repeat
|
||||
|
||||
>>> from ..construct import GreedyRange, UBInt8
|
||||
>>> c = GreedyRange(UBInt8("foo"))
|
||||
>>> c.parse(b"\x01")
|
||||
[1]
|
||||
>>> c.parse(b"\x01\x02\x03")
|
||||
[1, 2, 3]
|
||||
>>> c.parse(b"\x01\x02\x03\x04\x05\x06")
|
||||
[1, 2, 3, 4, 5, 6]
|
||||
>>> c.parse(b"") # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
RangeError: expected 1..2147483647, found 0
|
||||
>>> c.build([1,2])
|
||||
b'\x01\x02'
|
||||
>>> c.build([]) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
RangeError: expected 1..2147483647, found 0
|
||||
"""
|
||||
|
||||
return OpenRange(1, subcon)
|
||||
|
||||
def OptionalGreedyRange(subcon: Construct) -> Range:
|
||||
r"""
|
||||
Repeats the given unit zero or more times. This repeater can't
|
||||
fail, as it accepts lists of any length.
|
||||
|
||||
:param ``Construct`` subcon: construct to repeat
|
||||
|
||||
>>> from ..construct import OptionalGreedyRange, UBInt8
|
||||
>>> c = OptionalGreedyRange(UBInt8("foo"))
|
||||
>>> c.parse(b"")
|
||||
[]
|
||||
>>> c.parse(b"\x01\x02")
|
||||
[1, 2]
|
||||
>>> c.build([])
|
||||
b''
|
||||
>>> c.build([1,2])
|
||||
b'\x01\x02'
|
||||
"""
|
||||
|
||||
return OpenRange(0, subcon)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# subconstructs
|
||||
#===============================================================================
|
||||
def Optional(subcon: Construct) -> Select:
|
||||
"""an optional construct. if parsing fails, returns None.
|
||||
* subcon - the subcon to optionally parse or build
|
||||
"""
|
||||
return Select(subcon.name, subcon, Pass)
|
||||
|
||||
def Bitwise(subcon: Construct) -> Subconstruct:
|
||||
"""converts the stream to bits, and passes the bitstream to subcon
|
||||
* subcon - a bitwise construct (usually BitField)
|
||||
"""
|
||||
# subcons larger than MAX_BUFFER will be wrapped by Restream instead
|
||||
# of Buffered. implementation details, don't stick your nose in :)
|
||||
MAX_BUFFER = 1024 * 8
|
||||
def resizer(length: int) -> int:
|
||||
if length & 7:
|
||||
raise SizeofError("size must be a multiple of 8", length)
|
||||
return length >> 3
|
||||
if not subcon._is_flag(subcon.FLAG_DYNAMIC) and subcon.sizeof() < MAX_BUFFER:
|
||||
con: Subconstruct = Buffered(subcon,
|
||||
encoder = decode_bin,
|
||||
decoder = encode_bin,
|
||||
resizer = resizer
|
||||
)
|
||||
else:
|
||||
con = Restream(subcon,
|
||||
stream_reader = BitStreamReader,
|
||||
stream_writer = BitStreamWriter,
|
||||
resizer = resizer)
|
||||
return con
|
||||
|
||||
def Aligned(subcon: Construct, modulus: int = 4, pattern: bytes = b"\x00") -> IndexingAdapter:
|
||||
r"""aligns subcon to modulus boundary using padding pattern
|
||||
* subcon - the subcon to align
|
||||
* modulus - the modulus boundary (default is 4)
|
||||
* pattern - the padding pattern (default is \x00)
|
||||
"""
|
||||
if modulus < 2:
|
||||
raise ValueError("modulus must be >= 2", modulus)
|
||||
def padlength(ctx: Container) -> int:
|
||||
return (modulus - (subcon._sizeof(ctx) % modulus)) % modulus
|
||||
return SeqOfOne(subcon.name,
|
||||
subcon,
|
||||
# ??????
|
||||
# ??????
|
||||
# ??????
|
||||
# ??????
|
||||
Padding(padlength, pattern = pattern),
|
||||
nested = False,
|
||||
)
|
||||
|
||||
def SeqOfOne(name: str | None, *args: Construct, **kw: bool) -> IndexingAdapter:
|
||||
"""a sequence of one element. only the first element is meaningful, the
|
||||
rest are discarded
|
||||
* name - the name of the sequence
|
||||
* args - subconstructs
|
||||
* kw - any keyword arguments to Sequence
|
||||
"""
|
||||
return IndexingAdapter(Sequence(name, *args, **kw), index = 0)
|
||||
|
||||
def Embedded(subcon: Construct) -> Reconfig:
|
||||
"""embeds a struct into the enclosing struct.
|
||||
* subcon - the struct to embed
|
||||
"""
|
||||
return Reconfig(subcon.name, subcon, subcon.FLAG_EMBED)
|
||||
|
||||
def Rename(newname: str, subcon: Construct) -> Reconfig:
|
||||
"""renames an existing construct
|
||||
* newname - the new name
|
||||
* subcon - the subcon to rename
|
||||
"""
|
||||
return Reconfig(newname, subcon)
|
||||
|
||||
def Alias(newname: str, oldname: str) -> Value[Any]:
|
||||
"""creates an alias for an existing element in a struct
|
||||
* newname - the new name
|
||||
* oldname - the name of an existing element
|
||||
"""
|
||||
return Value(newname, lambda ctx: ctx[oldname])
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# mapping
|
||||
#===============================================================================
|
||||
def SymmetricMapping(subcon: Construct, mapping: Mapping[Any, Any], default: Hashable | _Pass = NotImplemented) -> MappingAdapter:
|
||||
"""defines a symmetrical mapping: a->b, b->a.
|
||||
* subcon - the subcon to map
|
||||
* mapping - the encoding mapping (a dict); the decoding mapping is
|
||||
achieved by reversing this mapping
|
||||
* default - the default value to use when no mapping is found. if no
|
||||
default value is given, and exception is raised. setting to Pass would
|
||||
return the value "as is" (unmapped)
|
||||
"""
|
||||
reversed_mapping = dict((v, k) for k, v in mapping.items())
|
||||
return MappingAdapter(subcon,
|
||||
encoding = mapping,
|
||||
decoding = reversed_mapping,
|
||||
encdefault = default,
|
||||
decdefault = default,
|
||||
)
|
||||
|
||||
def Enum(subcon: Construct, **kw: Any) -> MappingAdapter:
|
||||
"""a set of named values mapping.
|
||||
* subcon - the subcon to map
|
||||
* kw - keyword arguments which serve as the encoding mapping
|
||||
* _default_ - an optional, keyword-only argument that specifies the
|
||||
default value to use when the mapping is undefined. if not given,
|
||||
and exception is raised when the mapping is undefined. use `Pass` to
|
||||
pass the unmapped value as-is
|
||||
"""
|
||||
return SymmetricMapping(subcon, kw, kw.pop("_default_", NotImplemented))
|
||||
|
||||
def FlagsEnum(subcon: Construct, **kw: Any) -> FlagsAdapter:
|
||||
"""a set of flag values mapping.
|
||||
* subcon - the subcon to map
|
||||
* kw - keyword arguments which serve as the encoding mapping
|
||||
"""
|
||||
return FlagsAdapter(subcon, kw)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# structs
|
||||
#===============================================================================
|
||||
class _AlignedStruct(TypedDict, total=False):
|
||||
modulus: int
|
||||
pattern: bytes
|
||||
|
||||
|
||||
def AlignedStruct(name: str, *subcons: Construct, **kw: Unpack[_AlignedStruct]) -> Struct:
|
||||
"""a struct of aligned fields
|
||||
* name - the name of the struct
|
||||
* subcons - the subcons that make up this structure
|
||||
* kw - keyword arguments to pass to Aligned: 'modulus' and 'pattern'
|
||||
"""
|
||||
return Struct(name, *(Aligned(sc, **kw) for sc in subcons))
|
||||
|
||||
def BitStruct(name: str, *subcons: Construct) -> Subconstruct:
|
||||
"""a struct of bitwise fields
|
||||
* name - the name of the struct
|
||||
* subcons - the subcons that make up this structure
|
||||
"""
|
||||
return Bitwise(Struct(name, *subcons))
|
||||
|
||||
def EmbeddedBitStruct(*subcons: Construct) -> Subconstruct:
|
||||
"""an embedded BitStruct. no name is necessary.
|
||||
* subcons - the subcons that make up this structure
|
||||
"""
|
||||
return Bitwise(Embedded(Struct(None, *subcons)))
|
||||
|
||||
#===============================================================================
|
||||
# strings
|
||||
#===============================================================================
|
||||
def String(name: str, length: int, encoding: str | None = None, padchar: bytes | None = None, paddir: Literal["right", "left", "center"] = "right",
|
||||
trimdir: Literal["right", "left"] = "right") -> Adapter:
|
||||
r"""
|
||||
A configurable, fixed-length string field.
|
||||
|
||||
The padding character must be specified for padding and trimming to work.
|
||||
|
||||
:param str name: name
|
||||
:param int length: length, in bytes
|
||||
:param str encoding: encoding (e.g. "utf8") or None for no encoding
|
||||
:param bytes padchar: optional character to pad out strings
|
||||
:param str paddir: direction to pad out strings; one of "right", "left",
|
||||
or "center"
|
||||
:param str trim: direction to trim strings; one of "right", "left"
|
||||
|
||||
>>> from ..construct import String
|
||||
>>> String("foo", 5).parse(b"hello")
|
||||
b'hello'
|
||||
>>>
|
||||
>>> String("foo", 12, encoding="utf8").parse(b"hello joh\xd4\x83n")
|
||||
'hello joh\u0503n'
|
||||
>>>
|
||||
>>> foo = String("foo", 10, padchar=b"X", paddir="right")
|
||||
>>> foo.parse(b"helloXXXXX")
|
||||
b'hello'
|
||||
>>> foo.build(b"hello")
|
||||
b'helloXXXXX'
|
||||
"""
|
||||
|
||||
con: Adapter = StringAdapter(Field(name, length), encoding=encoding)
|
||||
if padchar is not None:
|
||||
con = PaddedStringAdapter(con, padchar=padchar, paddir=paddir,
|
||||
trimdir=trimdir)
|
||||
return con
|
||||
|
||||
def PascalString(name: str, length_field: FormatField[int] = UBInt8("length"), encoding: str | None = None) -> StringAdapter:
|
||||
r"""
|
||||
A length-prefixed string.
|
||||
|
||||
``PascalString`` is named after the string types of Pascal, which are
|
||||
length-prefixed. Lisp strings also follow this convention.
|
||||
|
||||
The length field will appear in the same ``Container`` as the
|
||||
``PascalString``, with the given name.
|
||||
|
||||
:param str name: name
|
||||
:param ``Construct`` length_field: a field which will store the length of
|
||||
the string
|
||||
:param str encoding: encoding (e.g. "utf8") or None for no encoding
|
||||
|
||||
>>> foo = PascalString("foo")
|
||||
>>> foo.parse(b"\x05hello")
|
||||
b'hello'
|
||||
>>> foo.build(b"hello world")
|
||||
b'\x0bhello world'
|
||||
>>>
|
||||
>>> foo = PascalString("foo", length_field = UBInt16("length"))
|
||||
>>> foo.parse(b"\x00\x05hello")
|
||||
b'hello'
|
||||
>>> foo.build(b"hello")
|
||||
b'\x00\x05hello'
|
||||
"""
|
||||
|
||||
return StringAdapter(
|
||||
LengthValueAdapter(
|
||||
Sequence(name,
|
||||
length_field,
|
||||
Field("data", lambda ctx: ctx[length_field.name]),
|
||||
)
|
||||
),
|
||||
encoding=encoding,
|
||||
)
|
||||
|
||||
def CString(name: str, terminators: bytes = b"\x00", encoding: str | None = None,
|
||||
char_field: Construct = Field(None, 1)) -> Reconfig:
|
||||
r"""
|
||||
A string ending in a terminator.
|
||||
|
||||
``CString`` is similar to the strings of C, C++, and other related
|
||||
programming languages.
|
||||
|
||||
By default, the terminator is the NULL byte (b``0x00``).
|
||||
|
||||
:param str name: name
|
||||
:param iterable terminators: sequence of valid terminators, in order of
|
||||
preference
|
||||
:param str encoding: encoding (e.g. "utf8") or None for no encoding
|
||||
:param ``Construct`` char_field: construct representing a single character
|
||||
|
||||
>>> foo = CString("foo")
|
||||
>>> foo.parse(b"hello\x00")
|
||||
b'hello'
|
||||
>>> foo.build(b"hello")
|
||||
b'hello\x00'
|
||||
>>> foo = CString("foo", terminators = b"XYZ")
|
||||
>>> foo.parse(b"helloX")
|
||||
b'hello'
|
||||
>>> foo.parse(b"helloY")
|
||||
b'hello'
|
||||
>>> foo.parse(b"helloZ")
|
||||
b'hello'
|
||||
>>> foo.build(b"hello")
|
||||
b'helloX'
|
||||
"""
|
||||
|
||||
return Rename(name,
|
||||
CStringAdapter(
|
||||
RepeatUntil(lambda obj, ctx: obj in terminators, char_field),
|
||||
terminators=terminators,
|
||||
encoding=encoding,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# conditional
|
||||
#===============================================================================
|
||||
def IfThenElse(name: str | None, predicate: Callable[[Container], bool], then_subcon: Construct, else_subcon: Construct) -> Switch[bool]:
|
||||
"""an if-then-else conditional construct: if the predicate indicates True,
|
||||
`then_subcon` will be used; otherwise `else_subcon`
|
||||
* name - the name of the construct
|
||||
* predicate - a function taking the context as an argument and returning
|
||||
True or False
|
||||
* then_subcon - the subcon that will be used if the predicate returns True
|
||||
* else_subcon - the subcon that will be used if the predicate returns False
|
||||
"""
|
||||
return Switch(name, lambda ctx: bool(predicate(ctx)),
|
||||
{
|
||||
True : then_subcon,
|
||||
False : else_subcon,
|
||||
}
|
||||
)
|
||||
|
||||
def If(predicate: Callable[[Container], bool], subcon: Construct, elsevalue: object | None = None) -> Switch[bool]:
|
||||
"""an if-then conditional construct: if the predicate indicates True,
|
||||
subcon will be used; otherwise, `elsevalue` will be returned instead.
|
||||
* predicate - a function taking the context as an argument and returning
|
||||
True or False
|
||||
* subcon - the subcon that will be used if the predicate returns True
|
||||
* elsevalue - the value that will be used should the predicate return False.
|
||||
by default this value is None.
|
||||
"""
|
||||
return IfThenElse(subcon.name,
|
||||
predicate,
|
||||
subcon,
|
||||
Value("elsevalue", lambda ctx: elsevalue)
|
||||
)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# misc
|
||||
#===============================================================================
|
||||
def OnDemandPointer(offsetfunc: Callable[[Container], int], subcon: Construct, force_build: bool = True) -> OnDemand:
|
||||
"""an on-demand pointer.
|
||||
* offsetfunc - a function taking the context as an argument and returning
|
||||
the absolute stream position
|
||||
* subcon - the subcon that will be parsed from the `offsetfunc()` stream
|
||||
position on demand
|
||||
* force_build - see OnDemand. by default True.
|
||||
"""
|
||||
return OnDemand(Pointer(offsetfunc, subcon),
|
||||
advance_stream = False,
|
||||
force_build = force_build
|
||||
)
|
||||
|
||||
def Magic(data: bytes) -> ConstAdapter:
|
||||
return ConstAdapter(Field(None, len(data)), data)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,90 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/abbrevtable.py
|
||||
#
|
||||
# DWARF abbreviation table
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO, TYPE_CHECKING, Any
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
class AbbrevTable:
|
||||
""" Represents a DWARF abbreviation table.
|
||||
"""
|
||||
__slots__ = ('structs', 'stream', 'offset', '_abbrev_map')
|
||||
def __init__(self, structs: DWARFStructs, stream: IO[bytes], offset: int) -> None:
|
||||
""" Create new abbreviation table. Parses the actual table from the
|
||||
stream and stores it internally.
|
||||
|
||||
structs:
|
||||
A DWARFStructs instance for parsing the data
|
||||
|
||||
stream, offset:
|
||||
The stream and offset into the stream where this abbreviation
|
||||
table lives.
|
||||
"""
|
||||
self.structs = structs
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
|
||||
self._abbrev_map = self._parse_abbrev_table()
|
||||
|
||||
def get_abbrev(self, code: int) -> AbbrevDecl:
|
||||
""" Get the AbbrevDecl for a given code. Raise KeyError if no
|
||||
declaration for this code exists.
|
||||
"""
|
||||
return self._abbrev_map[code]
|
||||
|
||||
def _parse_abbrev_table(self) -> dict[int, AbbrevDecl]:
|
||||
""" Parse the abbrev table from the stream
|
||||
"""
|
||||
map: dict[int, AbbrevDecl] = {}
|
||||
self.stream.seek(self.offset)
|
||||
while True:
|
||||
decl_code: int = struct_parse(
|
||||
struct=self.structs.the_Dwarf_uleb128,
|
||||
stream=self.stream)
|
||||
if decl_code == 0:
|
||||
break
|
||||
declaration = struct_parse(
|
||||
struct=self.structs.Dwarf_abbrev_declaration,
|
||||
stream=self.stream)
|
||||
map[decl_code] = AbbrevDecl(decl_code, declaration)
|
||||
return map
|
||||
|
||||
|
||||
class AbbrevDecl:
|
||||
""" Wraps a parsed abbreviation declaration, exposing its fields with
|
||||
dict-like access, and adding some convenience methods.
|
||||
|
||||
The abbreviation declaration represents an "entry" that points to it.
|
||||
"""
|
||||
__slots__ = ('code', 'decl', '_has_children')
|
||||
def __init__(self, code: int, decl: Container) -> None:
|
||||
self.code = code
|
||||
self.decl = decl
|
||||
self._has_children = decl['children_flag'] == 'DW_CHILDREN_yes'
|
||||
|
||||
def has_children(self) -> bool:
|
||||
return self._has_children
|
||||
|
||||
def iter_attr_specs(self) -> Iterator[tuple[str, str]]:
|
||||
""" Iterate over the attribute specifications for the entry. Yield
|
||||
(name, form) pairs.
|
||||
"""
|
||||
for attr_spec in self['attr_spec']:
|
||||
yield attr_spec.name, attr_spec.form
|
||||
|
||||
def __getitem__(self, entry: str) -> Any:
|
||||
return self.decl[entry]
|
||||
@@ -0,0 +1,142 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/aranges.py
|
||||
#
|
||||
# DWARF aranges section decoding (.debug_aranges)
|
||||
#
|
||||
# Dorothy Chen (dorothchen@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO, TYPE_CHECKING, NamedTuple
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from bisect import bisect_right
|
||||
import math
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..construct.core import Construct
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
# An entry in the aranges table;
|
||||
# begin_addr: The beginning address in the CU
|
||||
# length: The length of the address range in this entry
|
||||
# info_offset: The CU's offset into .debug_info
|
||||
# see 6.1.2 in DWARF4 docs for explanation of the remaining fields
|
||||
class ARangeEntry(NamedTuple):
|
||||
begin_addr: int
|
||||
length: int
|
||||
info_offset: int
|
||||
unit_length: int
|
||||
version: int
|
||||
address_size: int
|
||||
segment_size: int
|
||||
|
||||
|
||||
class ARanges:
|
||||
""" ARanges table in DWARF
|
||||
|
||||
stream, size:
|
||||
A stream holding the .debug_aranges section, and its size
|
||||
|
||||
structs:
|
||||
A DWARFStructs instance for parsing the data
|
||||
"""
|
||||
def __init__(self, stream: IO[bytes], size: int, structs: DWARFStructs) -> None:
|
||||
self.stream = stream
|
||||
self.size = size
|
||||
self.structs = structs
|
||||
|
||||
# Get entries of aranges table in the form of ARangeEntry tuples
|
||||
self.entries = self._get_entries()
|
||||
|
||||
# Sort entries by the beginning address
|
||||
self.entries.sort(key=lambda entry: entry.begin_addr)
|
||||
|
||||
# Create list of keys (first addresses) for better searching
|
||||
self.keys = [entry.begin_addr for entry in self.entries]
|
||||
|
||||
|
||||
def cu_offset_at_addr(self, addr: int) -> int | None:
|
||||
""" Given an address, get the offset of the CU it belongs to, where
|
||||
'offset' refers to the offset in the .debug_info section.
|
||||
"""
|
||||
tup = self.entries[bisect_right(self.keys, addr) - 1]
|
||||
if tup.begin_addr <= addr < tup.begin_addr + tup.length:
|
||||
return tup.info_offset
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
#------ PRIVATE ------#
|
||||
def _get_entries(self, need_empty: bool = False) -> list[ARangeEntry]:
|
||||
""" Populate self.entries with ARangeEntry tuples for each range of addresses
|
||||
|
||||
Terminating null entries of CU blocks are not returned, unless
|
||||
need_empty is set to True and the CU block contains nothing but
|
||||
a null entry. The null entry will have both address and length
|
||||
set to 0.
|
||||
"""
|
||||
self.stream.seek(0)
|
||||
entries: list[ARangeEntry] = []
|
||||
offset = 0
|
||||
|
||||
# one loop == one "set" == one CU
|
||||
while offset < self.size :
|
||||
aranges_header = struct_parse(self.structs.Dwarf_aranges_header,
|
||||
self.stream, offset)
|
||||
addr_size = self._get_addr_size_struct(aranges_header["address_size"])
|
||||
|
||||
# No segmentation
|
||||
if aranges_header["segment_size"] == 0:
|
||||
# pad to nearest multiple of tuple size
|
||||
tuple_size: int = aranges_header["address_size"] * 2
|
||||
fp = self.stream.tell()
|
||||
seek_to = int(math.ceil(fp/float(tuple_size)) * tuple_size)
|
||||
self.stream.seek(seek_to)
|
||||
|
||||
# We now have a binary with empty arange sections - nothing but a NULL entry.
|
||||
# To keep compatibility with readelf, we need to return those.
|
||||
# A two level list would be a prettier solution, but this will be compatible.
|
||||
got_entries = False
|
||||
|
||||
# entries in this set/CU
|
||||
addr: int = struct_parse(addr_size('addr'), self.stream)
|
||||
length: int = struct_parse(addr_size('length'), self.stream)
|
||||
while addr != 0 or length != 0 or (not got_entries and need_empty):
|
||||
# 'begin_addr length info_offset version address_size segment_size'
|
||||
entries.append(
|
||||
ARangeEntry(begin_addr=addr,
|
||||
length=length,
|
||||
info_offset=aranges_header["debug_info_offset"],
|
||||
unit_length=aranges_header["unit_length"],
|
||||
version=aranges_header["version"],
|
||||
address_size=aranges_header["address_size"],
|
||||
segment_size=aranges_header["segment_size"]))
|
||||
got_entries = True
|
||||
if addr != 0 or length != 0:
|
||||
addr = struct_parse(addr_size('addr'), self.stream)
|
||||
length = struct_parse(addr_size('length'), self.stream)
|
||||
|
||||
# Segmentation exists in executable
|
||||
elif aranges_header["segment_size"] != 0:
|
||||
raise NotImplementedError("Segmentation not implemented")
|
||||
|
||||
offset = (offset
|
||||
+ aranges_header.unit_length
|
||||
+ self.structs.initial_length_field_size())
|
||||
|
||||
return entries
|
||||
|
||||
def _get_addr_size_struct(self, addr_header_value: int) -> Callable[[str], Construct]:
|
||||
""" Given this set's header value (int) for the address size,
|
||||
get the Construct representation of that size
|
||||
"""
|
||||
if addr_header_value == 4:
|
||||
return self.structs.Dwarf_uint32
|
||||
else:
|
||||
assert addr_header_value == 8
|
||||
return self.structs.Dwarf_uint64
|
||||
@@ -0,0 +1,780 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/callframe.py
|
||||
#
|
||||
# DWARF call frame information
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import os
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, cast
|
||||
from warnings import warn
|
||||
|
||||
from ..common.utils import (
|
||||
struct_parse, dwarf_assert, preserve_stream_pos)
|
||||
from ..construct import Struct, Switch
|
||||
from ..construct.lib.container import Container
|
||||
from .enums import DW_EH_encoding_flags
|
||||
from .structs import DWARFStructs
|
||||
from .constants import DW_CFA
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..construct.core import Construct
|
||||
from ..construct.lib.container import ListContainer
|
||||
|
||||
|
||||
Line = dict[Any, Any]
|
||||
# TypedDict only supprts `str` as key, but "Line" mixes str|int.
|
||||
# class Line(TypedDict, total=False):
|
||||
# pc: int
|
||||
# cfa: CFARule
|
||||
# "int": RegisterRule
|
||||
|
||||
|
||||
Augmentation = dict[str | bool, int | Container | Literal[True]]
|
||||
# TypedDict only supprts `str` as key, but "Stack Frame" is signaled as `True: True`.
|
||||
# class Augmentation(TypedDict, total=False):
|
||||
# length: int
|
||||
# LSDA_encoding: int
|
||||
# FDE_encoding: int
|
||||
# personality: Container
|
||||
# "True": Literal[True]
|
||||
|
||||
|
||||
class CallFrameInfo:
|
||||
""" DWARF CFI (Call Frame Info)
|
||||
|
||||
Note that this also supports unwinding information as found in .eh_frame
|
||||
sections: its format differs slightly from the one in .debug_frame. See
|
||||
<http://www.airs.com/blog/archives/460>.
|
||||
|
||||
stream, size:
|
||||
A stream holding the .debug_frame section, and the size of the
|
||||
section in it.
|
||||
|
||||
address:
|
||||
Virtual address for this section. This is used to decode relative
|
||||
addresses.
|
||||
|
||||
base_structs:
|
||||
The structs to be used as the base for parsing this section.
|
||||
Eventually, each entry gets its own structs based on the initial
|
||||
length field it starts with. The address_size, however, is taken
|
||||
from base_structs. This appears to be a limitation of the DWARFv3
|
||||
standard, fixed in v4.
|
||||
A discussion I had on dwarf-discuss confirms this.
|
||||
So for DWARFv4 we'll take the address size from the CIE header,
|
||||
but for earlier versions will use the elfclass of the containing
|
||||
file; more sophisticated methods are used by libdwarf and others,
|
||||
such as guessing which CU contains which FDEs (based on their
|
||||
address ranges) and taking the address_size from those CUs.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
size: int,
|
||||
address: int,
|
||||
base_structs: DWARFStructs,
|
||||
for_eh_frame: bool = False,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.size = size
|
||||
self.address = address
|
||||
self.base_structs = base_structs
|
||||
self.entries: list[CFIEntry | ZERO] | None = None
|
||||
|
||||
# Map between an offset in the stream and the entry object found at this
|
||||
# offset. Useful for assigning CIE to FDEs according to the CIE_pointer
|
||||
# header field which contains a stream offset.
|
||||
self._entry_cache: dict[int, CFIEntry] = {}
|
||||
|
||||
# The .eh_frame and .debug_frame section use almost the same CFI
|
||||
# encoding, but there are tiny variations we need to handle during
|
||||
# parsing.
|
||||
self.for_eh_frame = for_eh_frame
|
||||
|
||||
def get_entries(self) -> list[CFIEntry | ZERO]:
|
||||
""" Get a list of entries that constitute this CFI. The list consists
|
||||
of CIE or FDE objects, in the order of their appearance in the
|
||||
section.
|
||||
"""
|
||||
if self.entries is None:
|
||||
self.entries = self._parse_entries()
|
||||
return self.entries
|
||||
|
||||
#-------------------------
|
||||
|
||||
def _parse_entries(self) -> list[CFIEntry | ZERO]:
|
||||
entries = []
|
||||
offset = 0
|
||||
while offset < self.size:
|
||||
entries.append(self._parse_entry_at(offset))
|
||||
offset = self.stream.tell()
|
||||
return entries
|
||||
|
||||
def _parse_entry_at(self, offset: int) -> CFIEntry | ZERO:
|
||||
""" Parse an entry from self.stream starting with the given offset.
|
||||
Return the entry object. self.stream will point right after the
|
||||
entry (even if pulled from the cache).
|
||||
"""
|
||||
if offset in self._entry_cache:
|
||||
entry = self._entry_cache[offset]
|
||||
self.stream.seek(entry.header.length +
|
||||
entry.structs.initial_length_field_size(), os.SEEK_CUR)
|
||||
return entry
|
||||
|
||||
entry_length: int = struct_parse(
|
||||
self.base_structs.the_Dwarf_uint32, self.stream, offset)
|
||||
|
||||
if self.for_eh_frame and entry_length == 0:
|
||||
return ZERO(offset)
|
||||
|
||||
dwarf_format = 64 if entry_length == 0xFFFFFFFF else 32
|
||||
|
||||
# Theoretically possible to have a DWARF bitness transition here.
|
||||
# DWARF version doesn't matter (CIEs are versioned separately), endianness can't change.
|
||||
# The structs are cached though, so no extraneous creation.
|
||||
entry_structs = DWARFStructs(
|
||||
little_endian=self.base_structs.little_endian,
|
||||
dwarf_format=dwarf_format,
|
||||
address_size=self.base_structs.address_size)
|
||||
|
||||
# Read the next field to see whether this is a CIE or FDE
|
||||
CIE_id: int = struct_parse(
|
||||
entry_structs.the_Dwarf_offset, self.stream)
|
||||
|
||||
if self.for_eh_frame:
|
||||
is_CIE = CIE_id == 0
|
||||
else:
|
||||
is_CIE = (
|
||||
(dwarf_format == 32 and CIE_id == 0xFFFFFFFF) or
|
||||
CIE_id == 0xFFFFFFFFFFFFFFFF)
|
||||
|
||||
# Parse the header, which goes up to and excluding the sequence of
|
||||
# instructions.
|
||||
if is_CIE:
|
||||
header_struct = (entry_structs.EH_CIE_header
|
||||
if self.for_eh_frame else
|
||||
entry_structs.Dwarf_CIE_header)
|
||||
header = struct_parse(
|
||||
header_struct, self.stream, offset)
|
||||
else:
|
||||
header = self._parse_fde_header(entry_structs, offset)
|
||||
|
||||
# If the augmentation string is not empty, hope to find a length field
|
||||
# in order to skip the data specified augmentation.
|
||||
lsda_pointer: int | None = None
|
||||
aug_dict: Augmentation | None = None
|
||||
if is_CIE:
|
||||
aug_bytes, aug_dict = self._parse_cie_augmentation(
|
||||
header, entry_structs)
|
||||
else:
|
||||
cie = self._parse_cie_for_fde(offset, header, entry_structs)
|
||||
assert isinstance(cie, CFIEntry)
|
||||
aug_bytes = self._read_augmentation_data(entry_structs)
|
||||
lsda_encoding = cast(int, cie.augmentation_dict.get('LSDA_encoding', DW_EH_encoding_flags['DW_EH_PE_omit']))
|
||||
if lsda_encoding != DW_EH_encoding_flags['DW_EH_PE_omit']:
|
||||
# parse LSDA pointer
|
||||
lsda_pointer = self._parse_lsda_pointer(entry_structs,
|
||||
self.stream.tell() - len(aug_bytes),
|
||||
lsda_encoding)
|
||||
|
||||
# For convenience, compute the end offset for this entry
|
||||
end_offset: int = (
|
||||
offset + header.length +
|
||||
entry_structs.initial_length_field_size())
|
||||
|
||||
# At this point self.stream is at the start of the instruction list
|
||||
# for this entry
|
||||
instructions = self._parse_instructions(
|
||||
entry_structs, self.stream.tell(), end_offset)
|
||||
|
||||
if is_CIE:
|
||||
entry = CIE(
|
||||
header=header, instructions=instructions, offset=offset,
|
||||
augmentation_dict=aug_dict,
|
||||
augmentation_bytes=aug_bytes,
|
||||
structs=entry_structs)
|
||||
|
||||
else: # FDE
|
||||
cie = self._parse_cie_for_fde(offset, header, entry_structs)
|
||||
assert isinstance(cie, CIE)
|
||||
entry = FDE(
|
||||
header=header, instructions=instructions, offset=offset,
|
||||
structs=entry_structs, cie=cie,
|
||||
augmentation_bytes=aug_bytes,
|
||||
lsda_pointer=lsda_pointer,
|
||||
)
|
||||
self._entry_cache[offset] = entry
|
||||
return entry
|
||||
|
||||
def _parse_instructions(
|
||||
self,
|
||||
structs: DWARFStructs,
|
||||
offset: int,
|
||||
end_offset: int,
|
||||
) -> list[CallFrameInstruction]:
|
||||
""" Parse a list of CFI instructions from self.stream, starting with
|
||||
the offset and until (not including) end_offset.
|
||||
Return a list of CallFrameInstruction objects.
|
||||
"""
|
||||
instructions = []
|
||||
while offset < end_offset:
|
||||
raw_opcode: int = struct_parse(structs.the_Dwarf_uint8, self.stream, offset)
|
||||
|
||||
opcode, *args = DW_CFA.parse_raw_opcode(raw_opcode)
|
||||
match opcode:
|
||||
case DW_CFA.advance_loc | DW_CFA.restore | DW_CFA.nop | DW_CFA.remember_state | DW_CFA.restore_state | DW_CFA.AARCH64_negate_ra_state:
|
||||
pass
|
||||
case DW_CFA.offset:
|
||||
args += [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
case DW_CFA.set_loc:
|
||||
args = [struct_parse(structs.the_Dwarf_target_addr, self.stream)]
|
||||
case DW_CFA.advance_loc1:
|
||||
args = [struct_parse(structs.the_Dwarf_uint8, self.stream)]
|
||||
case DW_CFA.advance_loc2:
|
||||
args = [struct_parse(structs.the_Dwarf_uint16, self.stream)]
|
||||
case DW_CFA.advance_loc4:
|
||||
args = [struct_parse(structs.the_Dwarf_uint32, self.stream)]
|
||||
case DW_CFA.offset_extended | DW_CFA.register | DW_CFA.def_cfa | DW_CFA.val_offset:
|
||||
args = [
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream),
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
case DW_CFA.restore_extended | DW_CFA.undefined | DW_CFA.same_value | DW_CFA.def_cfa_register | DW_CFA.def_cfa_offset:
|
||||
args = [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
case DW_CFA.def_cfa_offset_sf:
|
||||
args = [struct_parse(structs.the_Dwarf_sleb128, self.stream)]
|
||||
case DW_CFA.def_cfa_expression:
|
||||
struct = structs.Dwarf_dw_form['DW_FORM_block']
|
||||
assert struct is not None
|
||||
args = [struct_parse(struct, self.stream)]
|
||||
case DW_CFA.expression | DW_CFA.val_expression:
|
||||
struct = structs.Dwarf_dw_form['DW_FORM_block']
|
||||
assert struct is not None
|
||||
args = [
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream),
|
||||
struct_parse(struct, self.stream)]
|
||||
case DW_CFA.offset_extended_sf | DW_CFA.def_cfa_sf | DW_CFA.val_offset_sf:
|
||||
args = [
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream),
|
||||
struct_parse(structs.the_Dwarf_sleb128, self.stream)]
|
||||
case DW_CFA.GNU_args_size:
|
||||
args = [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
case _:
|
||||
dwarf_assert(False, f'Unknown CFI opcode: {raw_opcode:#04x}')
|
||||
|
||||
instructions.append(CallFrameInstruction(opcode=opcode, args=args))
|
||||
offset = self.stream.tell()
|
||||
return instructions
|
||||
|
||||
def _parse_cie_for_fde(
|
||||
self,
|
||||
fde_offset: int,
|
||||
fde_header: Container,
|
||||
entry_structs: DWARFStructs,
|
||||
) -> CFIEntry | ZERO:
|
||||
""" Parse the CIE that corresponds to an FDE.
|
||||
"""
|
||||
# Determine the offset of the CIE that corresponds to this FDE
|
||||
if self.for_eh_frame:
|
||||
# CIE_pointer contains the offset for a reverse displacement from
|
||||
# the section offset of the CIE_pointer field itself (not from the
|
||||
# FDE header offset).
|
||||
cie_displacement: int = fde_header['CIE_pointer']
|
||||
cie_offset: int = (fde_offset + entry_structs.dwarf_format // 8
|
||||
- cie_displacement)
|
||||
else:
|
||||
cie_offset = fde_header['CIE_pointer']
|
||||
|
||||
# Then read it
|
||||
with preserve_stream_pos(self.stream):
|
||||
return self._parse_entry_at(cie_offset)
|
||||
|
||||
def _parse_cie_augmentation(
|
||||
self,
|
||||
header: Container,
|
||||
entry_structs: DWARFStructs,
|
||||
) -> tuple[bytes, Augmentation]:
|
||||
""" Parse CIE augmentation data from the annotation string in `header`.
|
||||
|
||||
Return a tuple that contains 1) the augmentation data as a string
|
||||
(without the length field) and 2) the augmentation data as a dict.
|
||||
"""
|
||||
augmentation: bytes | None = header.get('augmentation')
|
||||
if not augmentation:
|
||||
return (b'', {})
|
||||
|
||||
# Ignore armcc augmentations.
|
||||
if augmentation.startswith(b'armcc'):
|
||||
return (b'', {})
|
||||
|
||||
# Augmentation parsing works in minimal mode here: we need the length
|
||||
# field to be able to skip unhandled augmentation fields.
|
||||
assert augmentation.startswith(b'z'), (
|
||||
'Unhandled augmentation string: {}'.format(repr(augmentation)))
|
||||
|
||||
available_fields: dict[str, Construct | Literal[True]] = {
|
||||
'z': entry_structs.Dwarf_uleb128('length'),
|
||||
'L': entry_structs.Dwarf_uint8('LSDA_encoding'),
|
||||
'R': entry_structs.Dwarf_uint8('FDE_encoding'),
|
||||
'S': True,
|
||||
'P': Struct(
|
||||
'personality',
|
||||
entry_structs.Dwarf_uint8('encoding'),
|
||||
Switch('function', lambda ctx: ctx.encoding & 0x0f, {
|
||||
enc: fld_cons('function')
|
||||
for enc, fld_cons
|
||||
in self._eh_encoding_to_field(entry_structs).items()})),
|
||||
}
|
||||
|
||||
# Build the Struct we will be using to parse the augmentation data.
|
||||
# Stop as soon as we are not able to match the augmentation string.
|
||||
fields: list[Construct] = []
|
||||
aug_dict: Augmentation = {}
|
||||
|
||||
for b in augmentation:
|
||||
try:
|
||||
fld = available_fields[chr(b)]
|
||||
except KeyError:
|
||||
break
|
||||
|
||||
if fld is True:
|
||||
aug_dict[fld] = True
|
||||
else:
|
||||
fields.append(fld)
|
||||
|
||||
# Read the augmentation twice: once with the Struct, once for the raw
|
||||
# bytes. Read the raw bytes last so we are sure we leave the stream
|
||||
# pointing right after the augmentation: the Struct may be incomplete
|
||||
# (missing trailing fields) due to an unknown char: see the KeyError
|
||||
# above.
|
||||
offset = self.stream.tell()
|
||||
struct = Struct('Augmentation_Data', *fields)
|
||||
aug_dict.update(struct_parse(struct, self.stream, offset))
|
||||
self.stream.seek(offset)
|
||||
aug_bytes = self._read_augmentation_data(entry_structs)
|
||||
return (aug_bytes, aug_dict)
|
||||
|
||||
def _read_augmentation_data(self, entry_structs: DWARFStructs) -> bytes:
|
||||
""" Read augmentation data.
|
||||
|
||||
This assumes that the augmentation string starts with 'z', i.e. that
|
||||
augmentation data is prefixed by a length field, which is not returned.
|
||||
"""
|
||||
if not self.for_eh_frame:
|
||||
return b''
|
||||
|
||||
augmentation_data_length: int = struct_parse(
|
||||
Struct('Dummy_Augmentation_Data',
|
||||
entry_structs.Dwarf_uleb128('length')),
|
||||
self.stream)['length']
|
||||
return self.stream.read(augmentation_data_length)
|
||||
|
||||
def _parse_lsda_pointer(self, structs: DWARFStructs, stream_offset: int, encoding: int) -> int:
|
||||
""" Parse bytes to get an LSDA pointer.
|
||||
|
||||
The basic encoding (lower four bits of the encoding) describes how the values are encoded in a CIE or an FDE.
|
||||
The modifier (upper four bits of the encoding) describes how the raw values, after decoded using a basic
|
||||
encoding, should be modified before using.
|
||||
|
||||
Ref: https://www.airs.com/blog/archives/460
|
||||
"""
|
||||
assert encoding != DW_EH_encoding_flags['DW_EH_PE_omit']
|
||||
basic_encoding = encoding & 0x0f
|
||||
modifier = encoding & 0xf0
|
||||
|
||||
formats = self._eh_encoding_to_field(structs)
|
||||
|
||||
ptr: int = struct_parse(
|
||||
Struct('Augmentation_Data',
|
||||
formats[basic_encoding]('LSDA_pointer')),
|
||||
self.stream, stream_pos=stream_offset)['LSDA_pointer']
|
||||
|
||||
if modifier == DW_EH_encoding_flags['DW_EH_PE_absptr']:
|
||||
pass
|
||||
|
||||
elif modifier == DW_EH_encoding_flags['DW_EH_PE_pcrel']:
|
||||
ptr += self.address + stream_offset
|
||||
|
||||
else:
|
||||
assert False, 'Unsupported encoding modifier for LSDA pointer: {:#x}'.format(modifier)
|
||||
|
||||
return ptr
|
||||
|
||||
def _parse_fde_header(self, entry_structs: DWARFStructs, offset: int) -> Container:
|
||||
""" Compute a struct to parse the header of the current FDE.
|
||||
"""
|
||||
if not self.for_eh_frame:
|
||||
return struct_parse(entry_structs.Dwarf_FDE_header, self.stream,
|
||||
offset)
|
||||
|
||||
fields: list[Construct] = [entry_structs.Dwarf_initial_length('length'),
|
||||
entry_structs.Dwarf_offset('CIE_pointer')]
|
||||
|
||||
# Parse the couple of header fields that are always here so we can
|
||||
# fetch the corresponding CIE.
|
||||
minimal_header = struct_parse(Struct('eh_frame_minimal_header',
|
||||
*fields), self.stream, offset)
|
||||
cie = self._parse_cie_for_fde(offset, minimal_header, entry_structs)
|
||||
assert isinstance(cie, CFIEntry)
|
||||
initial_location_offset = self.stream.tell()
|
||||
|
||||
# Try to parse the initial location. We need the initial location in
|
||||
# order to create a meaningful FDE, so assume it's there. Omission does
|
||||
# not seem to happen in practice.
|
||||
encoding = cast(int, cie.augmentation_dict['FDE_encoding'])
|
||||
assert encoding != DW_EH_encoding_flags['DW_EH_PE_omit']
|
||||
basic_encoding = encoding & 0x0f
|
||||
encoding_modifier = encoding & 0xf0
|
||||
|
||||
# Depending on the specified encoding, complete the header Struct
|
||||
formats = self._eh_encoding_to_field(entry_structs)
|
||||
fields.append(formats[basic_encoding]('initial_location'))
|
||||
fields.append(formats[basic_encoding]('address_range'))
|
||||
|
||||
result = struct_parse(Struct('Dwarf_FDE_header', *fields),
|
||||
self.stream, offset)
|
||||
|
||||
if encoding_modifier == 0:
|
||||
pass
|
||||
|
||||
elif encoding_modifier == DW_EH_encoding_flags['DW_EH_PE_pcrel']:
|
||||
# Start address is relative to the address of the
|
||||
# "initial_location" field.
|
||||
result['initial_location'] += (
|
||||
self.address + initial_location_offset)
|
||||
else:
|
||||
assert False, 'Unsupported encoding: {:#x}'.format(encoding)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _eh_encoding_to_field(
|
||||
entry_structs: DWARFStructs,
|
||||
) -> dict[int, Callable[[str], Construct]]:
|
||||
"""
|
||||
Return a mapping from basic encodings (DW_EH_encoding_flags) the
|
||||
corresponding field constructors (for instance
|
||||
entry_structs.Dwarf_uint32).
|
||||
"""
|
||||
return {
|
||||
DW_EH_encoding_flags['DW_EH_PE_absptr']:
|
||||
entry_structs.Dwarf_target_addr,
|
||||
DW_EH_encoding_flags['DW_EH_PE_uleb128']:
|
||||
entry_structs.Dwarf_uleb128,
|
||||
DW_EH_encoding_flags['DW_EH_PE_udata2']:
|
||||
entry_structs.Dwarf_uint16,
|
||||
DW_EH_encoding_flags['DW_EH_PE_udata4']:
|
||||
entry_structs.Dwarf_uint32,
|
||||
DW_EH_encoding_flags['DW_EH_PE_udata8']:
|
||||
entry_structs.Dwarf_uint64,
|
||||
|
||||
DW_EH_encoding_flags['DW_EH_PE_sleb128']:
|
||||
entry_structs.Dwarf_sleb128,
|
||||
DW_EH_encoding_flags['DW_EH_PE_sdata2']:
|
||||
entry_structs.Dwarf_int16,
|
||||
DW_EH_encoding_flags['DW_EH_PE_sdata4']:
|
||||
entry_structs.Dwarf_int32,
|
||||
DW_EH_encoding_flags['DW_EH_PE_sdata8']:
|
||||
entry_structs.Dwarf_int64,
|
||||
}
|
||||
|
||||
|
||||
def instruction_name(opcode: DW_CFA) -> str:
|
||||
""" Given an opcode, return the instruction name.
|
||||
"""
|
||||
warn("Switch to DW_CFA.FQN", DeprecationWarning, stacklevel=2)
|
||||
return opcode.FQN
|
||||
|
||||
|
||||
class CallFrameInstruction:
|
||||
""" An instruction in the CFI section. opcode is the instruction
|
||||
opcode, numeric - as it appears in the section. args is a list of
|
||||
arguments (including arguments embedded in the low bits of some
|
||||
instructions, when applicable), decoded from the stream.
|
||||
"""
|
||||
def __init__(self, opcode: DW_CFA, args: list[Any]) -> None:
|
||||
self.opcode = opcode
|
||||
self.args = args
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.opcode.FQN} ({self.opcode.value:#02x}): {self.args}"
|
||||
|
||||
|
||||
class CFIEntry:
|
||||
""" A common base class for CFI entries.
|
||||
Contains a header and a list of instructions (CallFrameInstruction).
|
||||
offset: the offset of this entry from the beginning of the section
|
||||
cie: for FDEs, a CIE pointer is required
|
||||
augmentation_dict: Augmentation data as a parsed struct (dict): see
|
||||
CallFrameInfo._parse_cie_augmentation and
|
||||
http://www.airs.com/blog/archives/460.
|
||||
augmentation_bytes: Augmentation data as a chain of bytes: see
|
||||
CallFrameInfo._parse_cie_augmentation and
|
||||
http://www.airs.com/blog/archives/460.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
structs: DWARFStructs,
|
||||
instructions: list[CallFrameInstruction],
|
||||
offset: int,
|
||||
augmentation_dict: Augmentation | None = None,
|
||||
augmentation_bytes: bytes | None = b'',
|
||||
cie: CIE | None = None,
|
||||
) -> None:
|
||||
self.header = header
|
||||
self.structs = structs
|
||||
self.instructions = instructions
|
||||
self.offset = offset
|
||||
self.cie = cie
|
||||
self.augmentation_dict = augmentation_dict or {}
|
||||
self.augmentation_bytes = augmentation_bytes
|
||||
|
||||
def get_decoded(self) -> DecodedCallFrameTable:
|
||||
""" Decode the CFI contained in this entry and return a
|
||||
DecodedCallFrameTable object representing it. See the documentation
|
||||
of that class to understand how to interpret the decoded table.
|
||||
"""
|
||||
return self._decode_CFI_table
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
@cached_property
|
||||
def _decode_CFI_table(self) -> DecodedCallFrameTable:
|
||||
""" Decode the instructions contained in the given CFI entry and return
|
||||
a DecodedCallFrameTable.
|
||||
"""
|
||||
last_line_in_CIE: Line | None = None
|
||||
if isinstance(self, CIE):
|
||||
# For a CIE, initialize cur_line to an "empty" line
|
||||
cie = self
|
||||
cur_line: Line = dict(pc=0, cfa=CFARule(reg=None, offset=0))
|
||||
reg_order = []
|
||||
else: # FDE
|
||||
# For a FDE, we need to decode the attached CIE first, because its
|
||||
# decoded table is needed. Its "initial instructions" describe a
|
||||
# line that serves as the base (first) line in the FDE's table.
|
||||
assert self.cie is not None
|
||||
cie = self.cie
|
||||
cie_decoded_table = cie.get_decoded()
|
||||
pc = self['initial_location']
|
||||
if cie_decoded_table.table:
|
||||
last_line_in_CIE = copy.copy(cie_decoded_table.table[-1])
|
||||
cur_line = dict(last_line_in_CIE, pc=pc)
|
||||
else:
|
||||
cur_line = dict(cfa=CFARule(reg=None, offset=0), pc=pc)
|
||||
reg_order = copy.copy(cie_decoded_table.reg_order)
|
||||
|
||||
table: list[Line] = []
|
||||
|
||||
# Keeps a stack for the use of DW_CFA.{remember|restore}_state
|
||||
# instructions.
|
||||
line_stack: list[Line] = []
|
||||
|
||||
def _add_to_order(regnum: int) -> None:
|
||||
# DW_CFA.restore and others remove registers from cur_line,
|
||||
# but they stay in reg_order. Avoid duplicates.
|
||||
if regnum not in reg_order:
|
||||
reg_order.append(regnum)
|
||||
|
||||
for instr in self.instructions:
|
||||
# Throughout this loop, cur_line is the current line. Some
|
||||
# instructions add it to the table, but most instructions just
|
||||
# update it without adding it to the table.
|
||||
match instr.opcode:
|
||||
case DW_CFA.set_loc:
|
||||
table.append(copy.copy(cur_line))
|
||||
cur_line['pc'] = instr.args[0]
|
||||
case DW_CFA.advance_loc1 | DW_CFA.advance_loc2 | DW_CFA.advance_loc4 | DW_CFA.advance_loc:
|
||||
table.append(copy.copy(cur_line))
|
||||
cur_line['pc'] += instr.args[0] * cie['code_alignment_factor']
|
||||
case DW_CFA.def_cfa:
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=instr.args[1])
|
||||
case DW_CFA.def_cfa_sf:
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=instr.args[1] * cie['code_alignment_factor'])
|
||||
case DW_CFA.def_cfa_register:
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=cur_line['cfa'].offset)
|
||||
case DW_CFA.def_cfa_offset:
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=cur_line['cfa'].reg,
|
||||
offset=instr.args[0])
|
||||
case DW_CFA.def_cfa_offset_sf:
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=cur_line['cfa'].reg,
|
||||
offset=instr.args[0] * cie['data_alignment_factor'])
|
||||
case DW_CFA.def_cfa_expression:
|
||||
cur_line['cfa'] = CFARule(expr=instr.args[0])
|
||||
case DW_CFA.undefined:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(RegisterRule.UNDEFINED)
|
||||
case DW_CFA.same_value:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(RegisterRule.SAME_VALUE)
|
||||
case DW_CFA.offset | DW_CFA.offset_extended | DW_CFA.offset_extended_sf:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.OFFSET,
|
||||
instr.args[1] * cie['data_alignment_factor'])
|
||||
case DW_CFA.val_offset | DW_CFA.val_offset_sf:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.VAL_OFFSET,
|
||||
instr.args[1] * cie['data_alignment_factor'])
|
||||
case DW_CFA.register:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.REGISTER,
|
||||
instr.args[1])
|
||||
case DW_CFA.expression:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.EXPRESSION,
|
||||
instr.args[1])
|
||||
case DW_CFA.val_expression:
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.VAL_EXPRESSION,
|
||||
instr.args[1])
|
||||
case DW_CFA.restore | DW_CFA.restore_extended as cfa:
|
||||
_add_to_order(instr.args[0])
|
||||
dwarf_assert(
|
||||
isinstance(self, FDE),
|
||||
f'{cfa.FQN} instruction must be in a FDE')
|
||||
assert last_line_in_CIE is not None
|
||||
if instr.args[0] in last_line_in_CIE:
|
||||
cur_line[instr.args[0]] = last_line_in_CIE[instr.args[0]]
|
||||
else:
|
||||
cur_line.pop(instr.args[0], None)
|
||||
case DW_CFA.remember_state:
|
||||
line_stack.append(copy.deepcopy(cur_line))
|
||||
case DW_CFA.restore_state:
|
||||
pc = cur_line['pc']
|
||||
cur_line = line_stack.pop()
|
||||
cur_line['pc'] = pc
|
||||
case DW_CFA.nop | DW_CFA.AARCH64_negate_ra_state:
|
||||
pass
|
||||
case _:
|
||||
dwarf_assert(False, f"Unknown CFI opcode: {instr.opcode:#02x}")
|
||||
|
||||
# The current line is appended to the table after all instructions
|
||||
# have ended, if there were instructions.
|
||||
if cur_line['cfa'].reg is not None or len(cur_line) > 2:
|
||||
table.append(cur_line)
|
||||
|
||||
return DecodedCallFrameTable(table=table, reg_order=reg_order)
|
||||
|
||||
|
||||
# A CIE and FDE have exactly the same functionality, except that a FDE has
|
||||
# a pointer to its CIE. The functionality was wholly encapsulated in CFIEntry,
|
||||
# so the CIE and FDE classes exists separately for identification (instead
|
||||
# of having an explicit "entry_type" field in CFIEntry).
|
||||
#
|
||||
class CIE(CFIEntry):
|
||||
pass
|
||||
|
||||
|
||||
class FDE(CFIEntry):
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
structs: DWARFStructs,
|
||||
instructions: list[CallFrameInstruction],
|
||||
offset: int,
|
||||
augmentation_bytes: bytes | None = None,
|
||||
cie: CIE | None = None,
|
||||
lsda_pointer: int | None = None,
|
||||
) -> None:
|
||||
super().__init__(header, structs, instructions, offset, augmentation_bytes=augmentation_bytes, cie=cie)
|
||||
self.lsda_pointer = lsda_pointer
|
||||
|
||||
|
||||
class ZERO:
|
||||
""" End marker for the sequence of CIE/FDE.
|
||||
|
||||
This is specific to `.eh_frame` sections: this kind of entry does not exist
|
||||
in pure DWARF. `readelf` displays these as "ZERO terminator", hence the
|
||||
class name.
|
||||
"""
|
||||
def __init__(self, offset: int) -> None:
|
||||
self.offset = offset
|
||||
|
||||
|
||||
class RegisterRule:
|
||||
""" Register rules are used to find registers in call frames. Each rule
|
||||
consists of a type (enumeration following DWARFv3 section 6.4.1)
|
||||
and an optional argument to augment the type.
|
||||
"""
|
||||
UNDEFINED = 'UNDEFINED'
|
||||
SAME_VALUE = 'SAME_VALUE'
|
||||
OFFSET = 'OFFSET'
|
||||
VAL_OFFSET = 'VAL_OFFSET'
|
||||
REGISTER = 'REGISTER'
|
||||
EXPRESSION = 'EXPRESSION'
|
||||
VAL_EXPRESSION = 'VAL_EXPRESSION'
|
||||
ARCHITECTURAL = 'ARCHITECTURAL'
|
||||
|
||||
def __init__(self, type: str, arg: int | ListContainer | None = None) -> None:
|
||||
self.type = type
|
||||
self.arg = arg
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return 'RegisterRule(%s, %s)' % (self.type, self.arg)
|
||||
|
||||
|
||||
class CFARule:
|
||||
""" A CFA rule is used to compute the CFA for each location. It either
|
||||
consists of a register+offset, or a DWARF expression.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
reg: int | None = None,
|
||||
offset: int | None = None,
|
||||
expr: ListContainer | None = None,
|
||||
) -> None:
|
||||
self.reg = reg
|
||||
self.offset = offset
|
||||
self.expr = expr
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return 'CFARule(reg=%s, offset=%s, expr=%s)' % (
|
||||
self.reg, self.offset, self.expr)
|
||||
|
||||
|
||||
# Represents the decoded CFI for an entry, which is just a large table,
|
||||
# according to DWARFv3 section 6.4.1
|
||||
#
|
||||
# DecodedCallFrameTable is a simple named tuple to group together the table
|
||||
# and the register appearance order.
|
||||
#
|
||||
# table:
|
||||
#
|
||||
# A list of dicts that represent "lines" in the decoded table. Each line has
|
||||
# some special dict entries: 'pc' for the location/program counter (LOC),
|
||||
# and 'cfa' for the CFARule to locate the CFA on that line.
|
||||
# The other entries are keyed by register numbers with RegisterRule values,
|
||||
# and describe the rules for these registers.
|
||||
#
|
||||
# reg_order:
|
||||
#
|
||||
# A list of register numbers that are described in the table by the order of
|
||||
# their appearance.
|
||||
#
|
||||
class DecodedCallFrameTable(NamedTuple):
|
||||
table: list[Line]
|
||||
reg_order: list[int]
|
||||
@@ -0,0 +1,300 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/compileunit.py
|
||||
#
|
||||
# DWARF compile unit
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from bisect import bisect_right
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .die import DIE
|
||||
from ..common.utils import dwarf_assert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .abbrevtable import AbbrevTable
|
||||
from .dwarfinfo import DWARFInfo
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
class CompileUnit:
|
||||
""" A DWARF compilation unit (CU).
|
||||
|
||||
A normal compilation unit typically represents the text and data
|
||||
contributed to an executable by a single relocatable object file.
|
||||
It may be derived from several source files,
|
||||
including pre-processed "include files"
|
||||
|
||||
Serves as a container and context to DIEs that describe objects and code
|
||||
belonging to a compilation unit.
|
||||
|
||||
CU header entries can be accessed as dict keys from this object, i.e.
|
||||
cu = CompileUnit(...)
|
||||
cu['version'] # version field of the CU header
|
||||
|
||||
To get the top-level DIE describing the compilation unit, call the
|
||||
get_top_DIE method.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
dwarfinfo: DWARFInfo,
|
||||
structs: DWARFStructs,
|
||||
cu_offset: int,
|
||||
cu_die_offset: int,
|
||||
) -> None:
|
||||
""" header:
|
||||
CU header for this compile unit
|
||||
|
||||
dwarfinfo:
|
||||
The DWARFInfo context object which created this one
|
||||
|
||||
structs:
|
||||
A DWARFStructs instance suitable for this compile unit
|
||||
|
||||
cu_offset:
|
||||
Offset in the stream to the beginning of this CU (its header)
|
||||
|
||||
cu_die_offset:
|
||||
Offset in the stream of the top DIE of this CU
|
||||
"""
|
||||
self.dwarfinfo = dwarfinfo
|
||||
self.header = header
|
||||
self.structs = structs
|
||||
self.cu_offset = cu_offset
|
||||
self.cu_die_offset = cu_die_offset
|
||||
|
||||
# A list of DIEs belonging to this CU.
|
||||
# This list is lazily constructed as DIEs are iterated over.
|
||||
self._dielist: list[DIE] = []
|
||||
# A list of file offsets, corresponding (by index) to the DIEs
|
||||
# in `self._dielist`. This list exists separately from
|
||||
# `self._dielist` to make it binary searchable, enabling the
|
||||
# DIE population strategy used in `iter_DIE_children`.
|
||||
# Like `self._dielist`, this list is lazily constructed
|
||||
# as DIEs are iterated over.
|
||||
self._diemap: list[int] = []
|
||||
|
||||
def dwarf_format(self) -> int:
|
||||
""" Get the DWARF format (32 or 64) for this CU
|
||||
"""
|
||||
return self.structs.dwarf_format
|
||||
|
||||
def get_abbrev_table(self) -> AbbrevTable:
|
||||
""" Get the abbreviation table (AbbrevTable object) for this CU
|
||||
"""
|
||||
return self._abbrev_table
|
||||
|
||||
@cached_property
|
||||
def _abbrev_table(self) -> AbbrevTable:
|
||||
return self.dwarfinfo.get_abbrev_table(self['debug_abbrev_offset'])
|
||||
|
||||
def get_top_DIE(self) -> DIE:
|
||||
""" Get the top DIE (which is either a DW_TAG_compile_unit or
|
||||
DW_TAG_partial_unit) of this CU
|
||||
"""
|
||||
|
||||
# Note that a top DIE always has minimal offset and is therefore
|
||||
# at the beginning of our lists, so no bisect is required.
|
||||
if self._diemap:
|
||||
return self._dielist[0]
|
||||
|
||||
assert self.dwarfinfo.debug_info_sec is not None
|
||||
top = DIE(
|
||||
cu=self,
|
||||
stream=self.dwarfinfo.debug_info_sec.stream,
|
||||
offset=self.cu_die_offset)
|
||||
|
||||
self._dielist.insert(0, top)
|
||||
self._diemap.insert(0, self.cu_die_offset)
|
||||
|
||||
top._translate_indirect_attributes() # Can't translate indirect attributes until the top DIE has been parsed to the end
|
||||
|
||||
return top
|
||||
|
||||
def has_top_DIE(self) -> bool:
|
||||
""" Returns whether the top DIE in this CU has already been parsed and cached.
|
||||
No parsing on demand!
|
||||
"""
|
||||
return bool(self._diemap)
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return self['unit_length'] + self.structs.initial_length_field_size()
|
||||
|
||||
def get_DIE_from_refaddr(self, refaddr: int) -> DIE:
|
||||
""" Obtain a DIE contained in this CU from a reference.
|
||||
|
||||
refaddr:
|
||||
The offset into the .debug_info section, which must be
|
||||
contained in this CU or a DWARFError will be raised.
|
||||
|
||||
When using a reference class attribute with a form that is
|
||||
relative to the compile unit, add unit add the compile unit's
|
||||
.cu_addr before calling this function.
|
||||
"""
|
||||
# All DIEs are after the cu header and within the unit
|
||||
dwarf_assert(
|
||||
self.cu_die_offset <= refaddr < self.cu_offset + self.size,
|
||||
'refaddr %s not in DIE range of CU %s' % (refaddr, self.cu_offset))
|
||||
|
||||
return self._get_cached_DIE(refaddr)
|
||||
|
||||
def iter_DIEs(self) -> Iterator[DIE]:
|
||||
""" Iterate over all the DIEs in the CU, in order of their appearance.
|
||||
Note that null DIEs will also be returned.
|
||||
"""
|
||||
assert self.dwarfinfo.debug_info_sec is not None
|
||||
stm = self.dwarfinfo.debug_info_sec.stream
|
||||
pos = self.cu_die_offset
|
||||
end_pos = self.cu_offset + self.size
|
||||
|
||||
die = self.get_top_DIE()
|
||||
yield die
|
||||
pos += die.size
|
||||
parent: DIE | None = die
|
||||
i = 1
|
||||
while pos < end_pos:
|
||||
if i < len(self._diemap) and self._diemap[i] == pos: # DIE already cached
|
||||
die = self._dielist[i]
|
||||
else:
|
||||
die = DIE(self, stm, pos)
|
||||
self._dielist.insert(i, die)
|
||||
self._diemap.insert(i, pos)
|
||||
i += 1
|
||||
|
||||
die._parent = parent
|
||||
|
||||
if die.tag is None and parent is not None:
|
||||
parent._terminator = die
|
||||
parent = parent._parent
|
||||
|
||||
if die.has_children:
|
||||
parent = die
|
||||
|
||||
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
|
||||
# Falls back to subtree traversal in the supplemental DWARF. Any way to streamline that too?
|
||||
supp_die = die.get_DIE_from_attribute('DW_AT_import')
|
||||
yield from supp_die.cu._iter_DIE_subtree(supp_die)
|
||||
else:
|
||||
yield die
|
||||
|
||||
pos += die.size
|
||||
|
||||
|
||||
def iter_DIE_children(self, die: DIE) -> Iterator[DIE]:
|
||||
""" Given a DIE, yields either its children, without null DIE list
|
||||
terminator, or nothing, if that DIE has no children.
|
||||
|
||||
The null DIE terminator is saved in that DIE when iteration ended.
|
||||
"""
|
||||
if not die.has_children:
|
||||
return
|
||||
|
||||
# `cur_offset` tracks the stream offset of the next DIE to yield
|
||||
# as we iterate over our children,
|
||||
cur_offset = die.offset + die.size
|
||||
|
||||
while True:
|
||||
child = self._get_cached_DIE(cur_offset)
|
||||
|
||||
child.set_parent(die)
|
||||
|
||||
if child.is_null():
|
||||
die._terminator = child
|
||||
return
|
||||
|
||||
yield child
|
||||
|
||||
if not child.has_children:
|
||||
cur_offset += child.size
|
||||
elif "DW_AT_sibling" in child.attributes:
|
||||
sibling = child.attributes["DW_AT_sibling"]
|
||||
if sibling.form in ('DW_FORM_ref1', 'DW_FORM_ref2',
|
||||
'DW_FORM_ref4', 'DW_FORM_ref8',
|
||||
'DW_FORM_ref', 'DW_FORM_ref_udata'):
|
||||
cur_offset = sibling.value + self.cu_offset
|
||||
elif sibling.form == 'DW_FORM_ref_addr':
|
||||
cur_offset = sibling.value
|
||||
else:
|
||||
raise NotImplementedError('sibling in form %s' % sibling.form)
|
||||
else:
|
||||
# If no DW_AT_sibling attribute is provided by the producer
|
||||
# then the whole child subtree must be parsed to find its next
|
||||
# sibling. There is one zero byte representing null DIE
|
||||
# terminating children list. It is used to locate child subtree
|
||||
# bounds.
|
||||
|
||||
# If children are not parsed yet, this instruction will manage
|
||||
# to recursive call of this function which will result in
|
||||
# setting of `_terminator` attribute of the `child`.
|
||||
if child._terminator is None:
|
||||
for _ in self.iter_DIE_children(child):
|
||||
pass
|
||||
assert child._terminator is not None
|
||||
|
||||
cur_offset = child._terminator.offset + child._terminator.size
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _iter_DIE_subtree(self, die: DIE) -> Iterator[DIE]:
|
||||
""" Given a DIE, this yields it with its subtree including null DIEs
|
||||
(child list terminators).
|
||||
"""
|
||||
# If the die is an imported unit, replace it with what it refers to if
|
||||
# we can
|
||||
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
|
||||
die = die.get_DIE_from_attribute('DW_AT_import')
|
||||
yield die
|
||||
if die.has_children:
|
||||
for c in die.iter_children():
|
||||
yield from die.cu._iter_DIE_subtree(c)
|
||||
assert die._terminator is not None
|
||||
yield die._terminator
|
||||
|
||||
def _get_cached_DIE(self, offset: int) -> DIE:
|
||||
""" Given a DIE offset, look it up in the cache. If not present,
|
||||
parse the DIE and insert it into the cache.
|
||||
|
||||
offset:
|
||||
The offset of the DIE in the debug_info section to retrieve.
|
||||
|
||||
The stream reference is copied from the top DIE. The top die will
|
||||
also be parsed and cached if needed.
|
||||
|
||||
See also get_DIE_from_refaddr(self, refaddr).
|
||||
"""
|
||||
# The top die must be in the cache if any DIE is in the cache.
|
||||
# The stream is the same for all DIEs in this CU, so populate
|
||||
# the top DIE and obtain a reference to its stream.
|
||||
top_die_stream = self.get_top_DIE().stream
|
||||
|
||||
# `offset` is the offset in the stream of the DIE we want to return.
|
||||
# The map is maintined as a parallel array to the list. We call
|
||||
# bisect each time to ensure new DIEs are inserted in the correct
|
||||
# order within both `self._dielist` and `self._diemap`.
|
||||
i = bisect_right(self._diemap, offset)
|
||||
|
||||
# Note that `self._diemap` cannot be empty because a the top DIE
|
||||
# was inserted by the call to .get_top_DIE(). Also it has the minimal
|
||||
# offset, so the bisect_right insert point will always be at least 1.
|
||||
if offset == self._diemap[i - 1]:
|
||||
die = self._dielist[i - 1]
|
||||
else:
|
||||
die = DIE(cu=self, stream=top_die_stream, offset=offset)
|
||||
self._dielist.insert(i, die)
|
||||
self._diemap.insert(i, offset)
|
||||
|
||||
return die
|
||||
@@ -0,0 +1,277 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/constants.py
|
||||
#
|
||||
# Constants and flags
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
|
||||
class _IntEnum(int, Enum): # Py3.11: enum.ReprEnum
|
||||
def __repr__(self) -> str:
|
||||
return int.__str__(self.value)
|
||||
|
||||
@property # Py3.11+: enum.property
|
||||
def FQN(self) -> str:
|
||||
return f"{self.__class__.__name__}_{self.name}"
|
||||
|
||||
|
||||
class DW_INL(_IntEnum):
|
||||
"""Inline codes."""
|
||||
not_inlined = 0
|
||||
inlined = 1
|
||||
declared_not_inlined = 2
|
||||
declared_inlined = 3
|
||||
|
||||
|
||||
class DW_LANG(_IntEnum):
|
||||
"""Source languages."""
|
||||
C89 = 0x0001
|
||||
C = 0x0002
|
||||
Ada83 = 0x0003
|
||||
C_plus_plus = 0x0004
|
||||
Cobol74 = 0x0005
|
||||
Cobol85 = 0x0006
|
||||
Fortran77 = 0x0007
|
||||
Fortran90 = 0x0008
|
||||
Pascal83 = 0x0009
|
||||
Modula2 = 0x000a
|
||||
Java = 0x000b
|
||||
C99 = 0x000c
|
||||
Ada95 = 0x000d
|
||||
Fortran95 = 0x000e
|
||||
PLI = 0x000f
|
||||
ObjC = 0x0010
|
||||
ObjC_plus_plus = 0x0011
|
||||
UPC = 0x0012
|
||||
D = 0x0013
|
||||
Python = 0x0014
|
||||
OpenCL = 0x0015
|
||||
Go = 0x0016
|
||||
Modula3 = 0x0017
|
||||
Haskell = 0x0018
|
||||
C_plus_plus_03 = 0x0019
|
||||
C_plus_plus_11 = 0x001a
|
||||
OCaml = 0x001b
|
||||
Rust = 0x001c
|
||||
C11 = 0x001d
|
||||
Swift = 0x001e
|
||||
Julia = 0x001f
|
||||
Dylan = 0x0020
|
||||
C_plus_plus_14 = 0x0021
|
||||
Fortran03 = 0x0022
|
||||
Fortran08 = 0x0023
|
||||
RenderScript = 0x0024
|
||||
BLISS = 0x0025
|
||||
Mips_Assembler = 0x8001
|
||||
Upc = 0x8765
|
||||
HP_Bliss = 0x8003
|
||||
HP_Basic91 = 0x8004
|
||||
HP_Pascal91 = 0x8005
|
||||
HP_IMacro = 0x8006
|
||||
HP_Assembler = 0x8007
|
||||
GOOGLE_RenderScript = 0x8e57
|
||||
BORLAND_Delphi = 0xb000
|
||||
|
||||
|
||||
class DW_ATE(_IntEnum):
|
||||
"""Encodings."""
|
||||
void = 0x0
|
||||
address = 0x1
|
||||
boolean = 0x2
|
||||
complex_float = 0x3
|
||||
float = 0x4
|
||||
signed = 0x5
|
||||
signed_char = 0x6
|
||||
unsigned = 0x7
|
||||
unsigned_char = 0x8
|
||||
imaginary_float = 0x9
|
||||
packed_decimal = 0xa
|
||||
numeric_string = 0xb
|
||||
edited = 0xc
|
||||
signed_fixed = 0xd
|
||||
unsigned_fixed = 0xe
|
||||
decimal_float = 0xf
|
||||
UTF = 0x10
|
||||
UCS = 0x11
|
||||
ASCII = 0x12
|
||||
lo_user = 0x80
|
||||
hi_user = 0xff
|
||||
HP_float80 = 0x80
|
||||
HP_complex_float80 = 0x81
|
||||
HP_float128 = 0x82
|
||||
HP_complex_float128 = 0x83
|
||||
HP_floathpintel = 0x84
|
||||
HP_imaginary_float80 = 0x85
|
||||
HP_imaginary_float128 = 0x86
|
||||
|
||||
|
||||
class DW_ACCESS(_IntEnum):
|
||||
"""Access."""
|
||||
public = 1
|
||||
protected = 2
|
||||
private = 3
|
||||
|
||||
|
||||
class DW_VIS(_IntEnum):
|
||||
"""Visibility."""
|
||||
local = 1
|
||||
exported = 2
|
||||
qualified = 3
|
||||
|
||||
|
||||
class DW_VIRTUALITY(_IntEnum):
|
||||
"""Virtuality."""
|
||||
none = 0
|
||||
virtual = 1
|
||||
pure_virtual = 2
|
||||
|
||||
|
||||
class DW_ID(_IntEnum):
|
||||
"""ID cases."""
|
||||
case_sensitive = 0
|
||||
up_case = 1
|
||||
down_case = 2
|
||||
case_insensitive = 3
|
||||
|
||||
|
||||
class DW_CC(_IntEnum):
|
||||
"""Calling conventions."""
|
||||
normal = 0x1
|
||||
program = 0x2
|
||||
nocall = 0x3
|
||||
pass_by_reference = 0x4
|
||||
pass_by_valuee = 0x5
|
||||
|
||||
|
||||
class DW_ORD(_IntEnum):
|
||||
"""Orderings."""
|
||||
row_major = 0
|
||||
col_major = 1
|
||||
|
||||
|
||||
class DW_LNS(_IntEnum):
|
||||
"""Line program opcodes."""
|
||||
copy = 0x01
|
||||
advance_pc = 0x02
|
||||
advance_line = 0x03
|
||||
set_file = 0x04
|
||||
set_column = 0x05
|
||||
negate_stmt = 0x06
|
||||
set_basic_block = 0x07
|
||||
const_add_pc = 0x08
|
||||
fixed_advance_pc = 0x09
|
||||
set_prologue_end = 0x0a
|
||||
set_epilogue_begin = 0x0b
|
||||
set_isa = 0x0c
|
||||
|
||||
|
||||
class DW_LNE(_IntEnum):
|
||||
"""Line program extended opcodes."""
|
||||
end_sequence = 0x01
|
||||
set_address = 0x02
|
||||
define_file = 0x03
|
||||
set_discriminator = 0x04
|
||||
lo_user = 0x80
|
||||
hi_user = 0xff
|
||||
|
||||
|
||||
class DW_LNCT(_IntEnum):
|
||||
"""Line program header content types."""
|
||||
path = 0x01
|
||||
directory_index = 0x02
|
||||
timestamp = 0x03
|
||||
size = 0x04
|
||||
MD5 = 0x05
|
||||
lo_user = 0x2000
|
||||
LLVM_source = 0x2001
|
||||
LLVM_is_MD5 = 0x2002
|
||||
hi_user = 0x3fff
|
||||
|
||||
|
||||
class DW_CFA(_IntEnum):
|
||||
"""
|
||||
Call frame instructions.
|
||||
|
||||
Note that the first 3 instructions have the so-called "primary opcode"
|
||||
(as described in DWARFv3 7.23), so only their highest 2 bits take part
|
||||
in the opcode decoding. They are kept as constants with the low bits masked
|
||||
out, and the callframe module knows how to handle this.
|
||||
The other instructions use an "extended opcode" encoded just in the low 6
|
||||
bits, with the high 2 bits, so these constants are exactly as they would
|
||||
appear in an actual file.
|
||||
"""
|
||||
advance_loc = 0b01000000
|
||||
offset = 0b10000000
|
||||
restore = 0b11000000
|
||||
|
||||
nop = 0x00
|
||||
set_loc = 0x01
|
||||
advance_loc1 = 0x02
|
||||
advance_loc2 = 0x03
|
||||
advance_loc4 = 0x04
|
||||
offset_extended = 0x05
|
||||
restore_extended = 0x06
|
||||
undefined = 0x07
|
||||
same_value = 0x08
|
||||
register = 0x09
|
||||
remember_state = 0x0a
|
||||
restore_state = 0x0b
|
||||
def_cfa = 0x0c
|
||||
def_cfa_register = 0x0d
|
||||
def_cfa_offset = 0x0e
|
||||
def_cfa_expression = 0x0f
|
||||
expression = 0x10
|
||||
offset_extended_sf = 0x11
|
||||
def_cfa_sf = 0x12
|
||||
def_cfa_offset_sf = 0x13
|
||||
val_offset = 0x14
|
||||
val_offset_sf = 0x15
|
||||
val_expression = 0x16
|
||||
AARCH64_negate_ra_state = 0x2d
|
||||
GNU_window_save = 0x2d # Used on SPARC, not in the corpus
|
||||
GNU_args_size = 0x2e
|
||||
|
||||
@classmethod
|
||||
def parse_raw_opcode(cls, /, opcode: int, *, __MASK: int = 0b11_00_0000) -> tuple[Self, int] | tuple[Self]:
|
||||
"""Extract primary or extended opcode from raw byte."""
|
||||
if primary := opcode & __MASK:
|
||||
return (cls(primary), opcode & ~__MASK)
|
||||
return (cls(opcode),)
|
||||
|
||||
|
||||
class DW_UT(_IntEnum):
|
||||
"""
|
||||
Compilation unit types.
|
||||
|
||||
DWARFv5 introduces the "unit_type" field to each CU header, allowing
|
||||
individual CUs to indicate whether they're complete, partial, and so forth.
|
||||
See DWARFv5 3.1 ("Unit Entries") and 7.5.1 ("Unit Headers").
|
||||
"""
|
||||
compile = 0x01
|
||||
type = 0x02
|
||||
partial = 0x03
|
||||
skeleton = 0x04
|
||||
split_compile = 0x05
|
||||
split_type = 0x06
|
||||
lo_user = 0x80
|
||||
hi_user = 0xff
|
||||
|
||||
|
||||
# Add back legacy names `DW_UT_type = DW_UT.type` for `from .constants import *`.
|
||||
# These are invisible to typing as the members are added dynamically by code!
|
||||
# Use __members__ to also add aliases like DW_CFA.{AARCH64_negate_ra_state,GNU_window_save}.
|
||||
globals().update({
|
||||
f"{enum_name}_{member_name}": member.value
|
||||
for enum_name, enum in globals().items()
|
||||
if enum_name.startswith("DW_") and issubclass(enum, _IntEnum)
|
||||
for member_name, member in enum.__members__.items()
|
||||
})
|
||||
@@ -0,0 +1,249 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/datatype_cpp.py
|
||||
#
|
||||
# First draft at restoring the source level name a C/C++ datatype
|
||||
# from DWARF data. Aiming at compatibility with llvm-dwarfdump v15.
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..common.utils import bytes2str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .die import DIE
|
||||
|
||||
|
||||
cpp_symbols = dict(
|
||||
pointer = "*",
|
||||
reference = "&",
|
||||
const = "const",
|
||||
volatile = "volatile")
|
||||
|
||||
def describe_cpp_datatype(var_die: DIE) -> str:
|
||||
return str(parse_cpp_datatype(var_die))
|
||||
|
||||
def parse_cpp_datatype(var_die: DIE) -> TypeDesc:
|
||||
"""Given a DIE that describes a variable, a parameter, or a member
|
||||
with DW_AT_type in it, tries to return the C++ datatype as a string
|
||||
|
||||
Returns a TypeDesc.
|
||||
|
||||
Does not follow typedefs, doesn't resolve array element types
|
||||
or struct members. Not good for a debugger.
|
||||
"""
|
||||
t = TypeDesc()
|
||||
|
||||
if 'DW_AT_type' not in var_die.attributes:
|
||||
t.tag = ''
|
||||
return t
|
||||
|
||||
type_die = var_die.get_DIE_from_attribute('DW_AT_type')
|
||||
|
||||
mods: list[str] = []
|
||||
# Unlike readelf, dwarfdump doesn't chase typedefs
|
||||
while type_die.tag in ('DW_TAG_const_type', 'DW_TAG_volatile_type', 'DW_TAG_pointer_type', 'DW_TAG_reference_type'):
|
||||
modifier = _strip_type_tag(type_die) # const/volatile/reference/pointer
|
||||
mods.insert(0, modifier)
|
||||
if 'DW_AT_type' not in type_die.attributes: # void* is encoded as a pointer to nothing
|
||||
t.name = t.tag = "void"
|
||||
t.modifiers = tuple(mods)
|
||||
return t
|
||||
type_die = type_die.get_DIE_from_attribute('DW_AT_type')
|
||||
|
||||
# From this point on, type_die doesn't change
|
||||
t.tag = _strip_type_tag(type_die)
|
||||
t.modifiers = tuple(mods)
|
||||
|
||||
if t.tag in ('ptr_to_member', 'subroutine'):
|
||||
if t.tag == 'ptr_to_member':
|
||||
ptr_prefix = DIE_name(type_die.get_DIE_from_attribute('DW_AT_containing_type')) + "::"
|
||||
type_die = type_die.get_DIE_from_attribute('DW_AT_type')
|
||||
elif "DW_AT_object_pointer" in type_die.attributes: # Older compiler... Subroutine, but with an object pointer
|
||||
ptr_prefix = DIE_name(DIE_type(DIE_type(type_die.get_DIE_from_attribute('DW_AT_object_pointer')))) + "::"
|
||||
else: # Not a pointer to member
|
||||
ptr_prefix = ''
|
||||
|
||||
if t.tag == 'subroutine':
|
||||
params = ", ".join(
|
||||
format_function_param(p, p)
|
||||
for p in type_die.iter_children()
|
||||
if p.tag in ("DW_TAG_formal_parameter", "DW_TAG_unspecified_parameters") and 'DW_AT_artificial' not in p.attributes
|
||||
)
|
||||
if 'DW_AT_type' in type_die.attributes:
|
||||
datatype = parse_cpp_datatype(type_die)
|
||||
is_pointer = datatype.modifiers and datatype.modifiers[-1] == 'pointer'
|
||||
retval_type = str(datatype)
|
||||
if not is_pointer:
|
||||
retval_type += " "
|
||||
else:
|
||||
retval_type = "void "
|
||||
|
||||
if mods and mods[-1] == 'pointer':
|
||||
mods.pop()
|
||||
t.modifiers = tuple(mods)
|
||||
t.name = "%s(%s*)(%s)" % (retval_type, ptr_prefix, params)
|
||||
else:
|
||||
t.name = "%s(%s)" % (retval_type, params)
|
||||
return t
|
||||
elif DIE_is_ptr_to_member_struct(type_die):
|
||||
dt = parse_cpp_datatype(next(type_die.iter_children())) # The first element is pfn, a function pointer with a this
|
||||
dt.modifiers = tuple(dt.modifiers[:-1]) # Pop the extra pointer
|
||||
dt.tag = "ptr_to_member_type" # Not a function pointer per se
|
||||
return dt
|
||||
elif t.tag == 'array':
|
||||
t.dimensions = tuple(_array_subtype_size(sub)
|
||||
for sub
|
||||
in type_die.iter_children()
|
||||
if sub.tag == 'DW_TAG_subrange_type')
|
||||
t.name = describe_cpp_datatype(type_die)
|
||||
return t
|
||||
|
||||
# Now the nonfunction types
|
||||
# Blank name is sometimes legal (unnamed unions, etc)
|
||||
|
||||
t.name = safe_DIE_name(type_die, t.tag + " ")
|
||||
|
||||
# Check the nesting - important for parameters
|
||||
parent = type_die.get_parent()
|
||||
scopes: list[str] = []
|
||||
while parent and parent.tag in ('DW_TAG_class_type', 'DW_TAG_structure_type', 'DW_TAG_union_type', 'DW_TAG_namespace'):
|
||||
scopes.insert(0, safe_DIE_name(parent, _strip_type_tag(parent) + " "))
|
||||
# If unnamed scope, fall back to scope type - like "structure "
|
||||
parent = parent.get_parent()
|
||||
t.scopes = tuple(scopes)
|
||||
|
||||
return t
|
||||
|
||||
#--------------------------------------------------
|
||||
|
||||
class TypeDesc:
|
||||
""" Encapsulates a description of a datatype, as parsed from DWARF DIEs.
|
||||
Not enough to display the variable in the debugger, but enough
|
||||
to produce a type description string similar to those of llvm-dwarfdump.
|
||||
|
||||
name - name for primitive datatypes, element name for arrays, the
|
||||
whole name for functions and function pouinters
|
||||
|
||||
modifiers - a collection of "const"/"pointer"/"reference", from the
|
||||
chain of DIEs preceeding the real type DIE
|
||||
|
||||
scopes - a collection of struct/class/namespace names, parents of the
|
||||
real type DIE
|
||||
|
||||
tag - the tag of the real type DIE, stripped of initial DW_TAG_ and
|
||||
final _type
|
||||
|
||||
dimensions - the collection of array dimensions, if the type is an
|
||||
array. -1 means an array of unknown dimension.
|
||||
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
self.name: str
|
||||
self.modifiers: tuple[str, ...] = () # Reads left to right
|
||||
self.scopes: tuple[str, ...] = () # Reads left to right
|
||||
self.tag: str | None = None
|
||||
self.dimensions: tuple[int, ...] | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
# Some reference points from dwarfdump:
|
||||
# const->pointer->const->char = const char *const
|
||||
# const->reference->const->int = const const int &
|
||||
# const->reference->int = const int &
|
||||
name = str(self.name)
|
||||
mods = self.modifiers
|
||||
|
||||
parts = []
|
||||
# Initial const/volatile applies to the var ifself, other consts apply to the pointee
|
||||
if mods and mods[0] in ('const', 'volatile'):
|
||||
parts.append(mods[0])
|
||||
mods = mods[1:]
|
||||
|
||||
# ref->const in the end, const goes in front
|
||||
if mods[-2:] == ("reference", "const"):
|
||||
parts.append("const")
|
||||
mods = mods[0:-1]
|
||||
|
||||
if self.scopes:
|
||||
name = '::'.join(self.scopes)+'::' + name
|
||||
parts.append(name)
|
||||
|
||||
if mods:
|
||||
parts.append("".join(cpp_symbols[mod] for mod in mods))
|
||||
|
||||
if self.dimensions:
|
||||
dims = "".join('[%s]' % (str(dim) if dim > 0 else '',)
|
||||
for dim in self.dimensions)
|
||||
else:
|
||||
dims = ''
|
||||
|
||||
return " ".join(parts)+dims
|
||||
|
||||
def DIE_name(die: DIE) -> str:
|
||||
return bytes2str(die.attributes['DW_AT_name'].value)
|
||||
|
||||
def safe_DIE_name(die: DIE, default: str = '') -> str:
|
||||
return bytes2str(die.attributes['DW_AT_name'].value) if 'DW_AT_name' in die.attributes else default
|
||||
|
||||
def DIE_type(die: DIE) -> DIE:
|
||||
return die.get_DIE_from_attribute("DW_AT_type")
|
||||
|
||||
class ClassDesc:
|
||||
def __init__(self) -> None:
|
||||
self.scopes: tuple[str, ...] = ()
|
||||
self.const_member: bool = False
|
||||
|
||||
def get_class_spec_if_member(func_spec: DIE, the_func: DIE) -> ClassDesc | None:
|
||||
if 'DW_AT_object_pointer' in the_func.attributes:
|
||||
this_param = the_func.get_DIE_from_attribute('DW_AT_object_pointer')
|
||||
this_type = parse_cpp_datatype(this_param)
|
||||
class_spec = ClassDesc()
|
||||
class_spec.scopes = (*this_type.scopes, this_type.name)
|
||||
class_spec.const_member = any(("const", "pointer") == this_type.modifiers[i:i+2]
|
||||
for i in range(len(this_type.modifiers))) # const -> pointer -> const for this arg of const
|
||||
return class_spec
|
||||
|
||||
# Check the parent element chain - could be a class
|
||||
parent = func_spec.get_parent()
|
||||
|
||||
scopes: list[str] = []
|
||||
while parent and parent.tag in ("DW_TAG_class_type", "DW_TAG_structure_type", "DW_TAG_namespace"):
|
||||
scopes.insert(0, DIE_name(parent))
|
||||
parent = parent.get_parent()
|
||||
if scopes:
|
||||
cs = ClassDesc()
|
||||
cs.scopes = tuple(scopes)
|
||||
return cs
|
||||
|
||||
return None
|
||||
|
||||
def format_function_param(param_spec: DIE, param: DIE) -> str:
|
||||
if param_spec.tag == 'DW_TAG_formal_parameter':
|
||||
type = parse_cpp_datatype(param_spec)
|
||||
return str(type)
|
||||
else: # unspecified_parameters AKA variadic
|
||||
return "..."
|
||||
|
||||
def DIE_is_ptr_to_member_struct(type_die: DIE) -> bool:
|
||||
if type_die.tag == 'DW_TAG_structure_type':
|
||||
members = tuple(die for die in type_die.iter_children() if die.tag == "DW_TAG_member")
|
||||
return len(members) == 2 and safe_DIE_name(members[0]) == "__pfn" and safe_DIE_name(members[1]) == "__delta"
|
||||
return False
|
||||
|
||||
def _strip_type_tag(die: DIE) -> str:
|
||||
"""Given a DIE with DW_TAG_foo_type, returns foo"""
|
||||
if not isinstance(die.tag, str): # User-defined tag
|
||||
return ""
|
||||
return die.tag[7:-5]
|
||||
|
||||
def _array_subtype_size(sub: DIE) -> int:
|
||||
if 'DW_AT_upper_bound' in sub.attributes:
|
||||
return sub.attributes['DW_AT_upper_bound'].value + 1
|
||||
if 'DW_AT_count' in sub.attributes:
|
||||
return sub.attributes['DW_AT_count'].value
|
||||
else:
|
||||
return -1
|
||||
|
||||
@@ -0,0 +1,721 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/descriptions.py
|
||||
#
|
||||
# Textual descriptions of the various values and enums of DWARF
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Any, Literal, overload
|
||||
|
||||
from .constants import (
|
||||
DW_ACCESS, DW_ATE, DW_CC, DW_CFA, DW_ID, DW_INL, DW_LANG, DW_ORD, DW_VIRTUALITY, DW_VIS,
|
||||
)
|
||||
from .dwarf_expr import DWARFExprParser
|
||||
from .die import DIE
|
||||
from ..common.utils import preserve_stream_pos, dwarf_assert, bytes2str
|
||||
from .callframe import CIE, FDE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from typing import TypeVar
|
||||
|
||||
from .callframe import CallFrameInstruction, CFARule, CFIEntry, RegisterRule
|
||||
from .die import AttributeValue
|
||||
from .structs import DWARFStructs
|
||||
|
||||
_INT = TypeVar("_INT", bound=int)
|
||||
|
||||
|
||||
def set_global_machine_arch(machine_arch: str) -> None:
|
||||
global _MACHINE_ARCH
|
||||
_MACHINE_ARCH = machine_arch
|
||||
|
||||
|
||||
def describe_attr_value(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
""" Given an attribute attr, return the textual representation of its
|
||||
value, suitable for tools like readelf.
|
||||
|
||||
To cover all cases, this function needs some extra arguments:
|
||||
|
||||
die: the DIE this attribute was extracted from
|
||||
section_offset: offset in the stream of the section the DIE belongs to
|
||||
"""
|
||||
descr_func = _ATTR_DESCRIPTION_MAP[attr.form]
|
||||
val_description = descr_func(attr, die, section_offset)
|
||||
|
||||
# For some attributes we can display further information
|
||||
extra_info_func = _EXTRA_INFO_DESCRIPTION_MAP[attr.name]
|
||||
extra_info = extra_info_func(attr, die, section_offset)
|
||||
return str(val_description) + '\t' + extra_info
|
||||
|
||||
|
||||
def describe_CFI_instructions(entry: CFIEntry) -> str:
|
||||
""" Given a CFI entry (CIE or FDE), return the textual description of its
|
||||
instructions.
|
||||
"""
|
||||
def _assert_FDE_instruction(instr: CallFrameInstruction) -> None:
|
||||
dwarf_assert(
|
||||
isinstance(entry, FDE),
|
||||
'Unexpected instruction "%s" for a CIE' % instr)
|
||||
|
||||
def _full_reg_name(regnum: int) -> str:
|
||||
regname = describe_reg_name(regnum, _MACHINE_ARCH, False)
|
||||
if regname:
|
||||
return 'r%s (%s)' % (regnum, regname)
|
||||
else:
|
||||
return 'r%s' % regnum
|
||||
|
||||
if isinstance(entry, CIE):
|
||||
cie = entry
|
||||
pc: int | None = None
|
||||
else: # FDE
|
||||
assert entry.cie is not None
|
||||
cie = entry.cie
|
||||
pc = entry['initial_location']
|
||||
|
||||
s = ''
|
||||
for instr in entry.instructions:
|
||||
name = instr.opcode.FQN
|
||||
match instr.opcode:
|
||||
case DW_CFA.offset | DW_CFA.offset_extended | DW_CFA.offset_extended_sf | DW_CFA.val_offset | DW_CFA.val_offset_sf:
|
||||
s += ' %s: %s at cfa%+d\n' % (
|
||||
name, _full_reg_name(instr.args[0]),
|
||||
instr.args[1] * cie['data_alignment_factor'])
|
||||
case DW_CFA.restore | DW_CFA.restore_extended | DW_CFA.undefined | DW_CFA.same_value | DW_CFA.def_cfa_register:
|
||||
s += ' %s: %s\n' % (name, _full_reg_name(instr.args[0]))
|
||||
case DW_CFA.register:
|
||||
s += ' %s: %s in %s' % (
|
||||
name, _full_reg_name(instr.args[0]),
|
||||
_full_reg_name(instr.args[1]))
|
||||
case DW_CFA.set_loc:
|
||||
pc = instr.args[0]
|
||||
assert pc is not None
|
||||
s += ' %s: %08x\n' % (name, pc)
|
||||
case DW_CFA.advance_loc1 | DW_CFA.advance_loc2 | DW_CFA.advance_loc4 | DW_CFA.advance_loc:
|
||||
_assert_FDE_instruction(instr)
|
||||
assert pc is not None
|
||||
factored_offset: int = instr.args[0] * cie['code_alignment_factor']
|
||||
s += ' %s: %s to %08x\n' % (
|
||||
name, factored_offset, factored_offset + pc)
|
||||
pc += factored_offset
|
||||
case DW_CFA.remember_state | DW_CFA.restore_state | DW_CFA.nop | DW_CFA.AARCH64_negate_ra_state:
|
||||
s += ' %s\n' % name
|
||||
case DW_CFA.def_cfa:
|
||||
s += ' %s: %s ofs %s\n' % (
|
||||
name, _full_reg_name(instr.args[0]), instr.args[1])
|
||||
case DW_CFA.def_cfa_sf:
|
||||
s += ' %s: %s ofs %s\n' % (
|
||||
name, _full_reg_name(instr.args[0]),
|
||||
instr.args[1] * cie['data_alignment_factor'])
|
||||
case DW_CFA.def_cfa_offset | DW_CFA.GNU_args_size:
|
||||
s += ' %s: %s\n' % (name, instr.args[0])
|
||||
case DW_CFA.def_cfa_offset_sf:
|
||||
assert entry.cie is not None
|
||||
s += ' %s: %s\n' % (name, instr.args[0]*entry.cie['data_alignment_factor'])
|
||||
case DW_CFA.def_cfa_expression:
|
||||
expr_dumper = ExprDumper(entry.structs)
|
||||
# readelf output is missing a colon for DW_CFA.def_cfa_expression
|
||||
s += ' %s (%s)\n' % (name, expr_dumper.dump_expr(instr.args[0]))
|
||||
case DW_CFA.expression:
|
||||
expr_dumper = ExprDumper(entry.structs)
|
||||
s += ' %s: %s (%s)\n' % (
|
||||
name, _full_reg_name(instr.args[0]),
|
||||
expr_dumper.dump_expr(instr.args[1]))
|
||||
case _:
|
||||
s += ' %s: <??>\n' % name
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def describe_CFI_register_rule(rule: RegisterRule) -> str:
|
||||
s = _DESCR_CFI_REGISTER_RULE_TYPE[rule.type]
|
||||
if rule.type in ('OFFSET', 'VAL_OFFSET'):
|
||||
assert isinstance(rule.arg, int)
|
||||
s += '%+d' % rule.arg
|
||||
elif rule.type == 'REGISTER':
|
||||
assert isinstance(rule.arg, int)
|
||||
reg = describe_reg_name(rule.arg)
|
||||
s += reg
|
||||
return s
|
||||
|
||||
|
||||
def describe_CFI_CFA_rule(rule: CFARule) -> str:
|
||||
if rule.expr:
|
||||
return 'exp'
|
||||
else:
|
||||
assert isinstance(rule.reg, int)
|
||||
assert isinstance(rule.offset, int)
|
||||
return '%s%+d' % (describe_reg_name(rule.reg), rule.offset)
|
||||
|
||||
|
||||
def describe_DWARF_expr(expr: Any, structs: DWARFStructs, cu_offset: int | None = None) -> str:
|
||||
""" Textual description of a DWARF expression encoded in 'expr'.
|
||||
structs should come from the entity encompassing the expression - it's
|
||||
needed to be able to parse it correctly.
|
||||
"""
|
||||
# Since this function can be called a lot, initializing a fresh new
|
||||
# ExprDumper per call is expensive. So a rudimentary caching scheme is in
|
||||
# place to create only one such dumper per instance of structs.
|
||||
cache_key = id(structs)
|
||||
if cache_key not in _DWARF_EXPR_DUMPER_CACHE:
|
||||
_DWARF_EXPR_DUMPER_CACHE[cache_key] = \
|
||||
ExprDumper(structs)
|
||||
dwarf_expr_dumper = _DWARF_EXPR_DUMPER_CACHE[cache_key]
|
||||
return '(' + dwarf_expr_dumper.dump_expr(expr, cu_offset) + ')'
|
||||
|
||||
|
||||
@overload
|
||||
def describe_reg_name(regnum: int, machine_arch: str | None, default: Literal[False]) -> str | None: ...
|
||||
@overload
|
||||
def describe_reg_name(regnum: int, machine_arch: str | None = ..., default: Literal[True] = ...) -> str: ...
|
||||
def describe_reg_name(regnum: int, machine_arch: str | None = None, default: bool = True) -> str | None:
|
||||
""" Provide a textual description for a register name, given its serial
|
||||
number. The number is expected to be valid.
|
||||
"""
|
||||
if machine_arch is None:
|
||||
machine_arch = _MACHINE_ARCH
|
||||
|
||||
if machine_arch == 'x86':
|
||||
return _REG_NAMES_x86[regnum]
|
||||
elif machine_arch == 'x64':
|
||||
return _REG_NAMES_x64[regnum]
|
||||
elif machine_arch == 'AArch64':
|
||||
return _REG_NAMES_AArch64[regnum]
|
||||
elif default:
|
||||
return 'r%s' % regnum
|
||||
else:
|
||||
return None
|
||||
|
||||
def describe_form_class(form: str) -> str | None:
|
||||
"""For a given form name, determine its value class.
|
||||
|
||||
For example, given 'DW_FORM.data1' returns 'constant'.
|
||||
|
||||
For some forms, like DW_FORM.indirect and DW_FORM.sec_offset, the class is
|
||||
not hard-coded and extra information is required. For these, None is
|
||||
returned.
|
||||
"""
|
||||
return _FORM_CLASS[form]
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# The machine architecture. Set globally via set_global_machine_arch
|
||||
#
|
||||
_MACHINE_ARCH: str | None = None
|
||||
|
||||
# Implements the alternative format of readelf: lowercase hex, prefixed with 0x unless 0
|
||||
def _format_hex(n: int) -> str:
|
||||
return '0x%x' % n if n != 0 else '0'
|
||||
|
||||
def _describe_attr_ref(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return '<%s>' % _format_hex(attr.value + die.cu.cu_offset)
|
||||
|
||||
def _describe_attr_ref_sig8(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return 'signature: %s' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_value_passthrough(
|
||||
attr: AttributeValue,
|
||||
die: DIE,
|
||||
section_offset: int,
|
||||
) -> str | int:
|
||||
return attr.value
|
||||
|
||||
def _describe_attr_hex(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return '%s' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_hex_addr(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return '<%s>' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_split_64bit(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
low_word = attr.value & 0xFFFFFFFF
|
||||
high_word = (attr.value >> 32) & 0xFFFFFFFF
|
||||
return '%s %s' % (_format_hex(low_word), _format_hex(high_word))
|
||||
|
||||
def _describe_attr_strp(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return '(indirect string, offset: %s): %s' % (
|
||||
_format_hex(attr.raw_value), bytes2str(attr.value))
|
||||
|
||||
def _describe_attr_line_strp(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return '(indirect line string, offset: %s): %s' % (
|
||||
_format_hex(attr.raw_value), bytes2str(attr.value))
|
||||
|
||||
def _describe_attr_string(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return bytes2str(attr.value)
|
||||
|
||||
def _describe_attr_debool(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
""" To be consistent with readelf, generate 1 for True flags, 0 for False
|
||||
flags.
|
||||
"""
|
||||
return '1' if attr.value else '0'
|
||||
|
||||
def _describe_attr_present(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
""" Some forms may simply mean that an attribute is present,
|
||||
without providing any value.
|
||||
"""
|
||||
return '1'
|
||||
|
||||
def _describe_attr_block(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
s = '%s byte block: ' % len(attr.value)
|
||||
s += ' '.join('%x' % item for item in attr.value) + ' '
|
||||
return s
|
||||
|
||||
|
||||
_ATTR_DESCRIPTION_MAP = defaultdict(
|
||||
lambda: _describe_attr_value_passthrough, # default_factory
|
||||
|
||||
DW_FORM_ref1=_describe_attr_ref,
|
||||
DW_FORM_ref2=_describe_attr_ref,
|
||||
DW_FORM_ref4=_describe_attr_ref,
|
||||
DW_FORM_ref8=_describe_attr_split_64bit,
|
||||
DW_FORM_ref_udata=_describe_attr_ref,
|
||||
DW_FORM_ref_addr=_describe_attr_hex_addr,
|
||||
DW_FORM_data4=_describe_attr_hex,
|
||||
DW_FORM_data8=_describe_attr_hex,
|
||||
DW_FORM_addr=_describe_attr_hex,
|
||||
DW_FORM_sec_offset=_describe_attr_hex,
|
||||
DW_FORM_flag=_describe_attr_debool,
|
||||
DW_FORM_data1=_describe_attr_value_passthrough,
|
||||
DW_FORM_data2=_describe_attr_value_passthrough,
|
||||
DW_FORM_sdata=_describe_attr_value_passthrough,
|
||||
DW_FORM_udata=_describe_attr_value_passthrough,
|
||||
DW_FORM_string=_describe_attr_string,
|
||||
DW_FORM_strp=_describe_attr_strp,
|
||||
DW_FORM_line_strp=_describe_attr_line_strp,
|
||||
DW_FORM_block1=_describe_attr_block,
|
||||
DW_FORM_block2=_describe_attr_block,
|
||||
DW_FORM_block4=_describe_attr_block,
|
||||
DW_FORM_block=_describe_attr_block,
|
||||
DW_FORM_flag_present=_describe_attr_present,
|
||||
DW_FORM_exprloc=_describe_attr_block,
|
||||
DW_FORM_ref_sig8=_describe_attr_ref_sig8,
|
||||
)
|
||||
|
||||
_FORM_CLASS = dict(
|
||||
DW_FORM_addr='address',
|
||||
DW_FORM_block2='block',
|
||||
DW_FORM_block4='block',
|
||||
DW_FORM_data2='constant',
|
||||
DW_FORM_data4='constant',
|
||||
DW_FORM_data8='constant',
|
||||
DW_FORM_string='string',
|
||||
DW_FORM_block='block',
|
||||
DW_FORM_block1='block',
|
||||
DW_FORM_data1='constant',
|
||||
DW_FORM_flag='flag',
|
||||
DW_FORM_sdata='constant',
|
||||
DW_FORM_strp='string',
|
||||
DW_FORM_udata='constant',
|
||||
DW_FORM_ref_addr='reference',
|
||||
DW_FORM_ref1='reference',
|
||||
DW_FORM_ref2='reference',
|
||||
DW_FORM_ref4='reference',
|
||||
DW_FORM_ref8='reference',
|
||||
DW_FORM_ref_udata='reference',
|
||||
DW_FORM_indirect=None,
|
||||
DW_FORM_sec_offset=None,
|
||||
DW_FORM_exprloc='exprloc',
|
||||
DW_FORM_flag_present='flag',
|
||||
DW_FORM_ref_sig8='reference',
|
||||
)
|
||||
|
||||
_DESCR_DW_INL = {
|
||||
DW_INL.not_inlined: '(not inlined)',
|
||||
DW_INL.inlined: '(inlined)',
|
||||
DW_INL.declared_not_inlined: '(declared as inline but ignored)',
|
||||
DW_INL.declared_inlined: '(declared as inline and inlined)',
|
||||
}
|
||||
|
||||
_DESCR_DW_LANG = {
|
||||
DW_LANG.C89: '(ANSI C)',
|
||||
DW_LANG.C: '(non-ANSI C)',
|
||||
DW_LANG.Ada83: '(Ada)',
|
||||
DW_LANG.C_plus_plus: '(C++)',
|
||||
DW_LANG.Cobol74: '(Cobol 74)',
|
||||
DW_LANG.Cobol85: '(Cobol 85)',
|
||||
DW_LANG.Fortran77: '(FORTRAN 77)',
|
||||
DW_LANG.Fortran90: '(Fortran 90)',
|
||||
DW_LANG.Pascal83: '(ANSI Pascal)',
|
||||
DW_LANG.Modula2: '(Modula 2)',
|
||||
DW_LANG.Java: '(Java)',
|
||||
DW_LANG.C99: '(ANSI C99)',
|
||||
DW_LANG.Ada95: '(ADA 95)',
|
||||
DW_LANG.Fortran95: '(Fortran 95)',
|
||||
DW_LANG.PLI: '(PLI)',
|
||||
DW_LANG.ObjC: '(Objective C)',
|
||||
DW_LANG.ObjC_plus_plus: '(Objective C++)',
|
||||
DW_LANG.UPC: '(Unified Parallel C)',
|
||||
DW_LANG.D: '(D)',
|
||||
DW_LANG.Python: '(Python)',
|
||||
DW_LANG.OpenCL: '(OpenCL)',
|
||||
DW_LANG.Go: '(Go)',
|
||||
DW_LANG.Modula3: '(Modula 3)',
|
||||
DW_LANG.Haskell: '(Haskell)',
|
||||
DW_LANG.C_plus_plus_03: '(C++03)',
|
||||
DW_LANG.C_plus_plus_11: '(C++11)',
|
||||
DW_LANG.OCaml: '(OCaml)',
|
||||
DW_LANG.Rust: '(Rust)',
|
||||
DW_LANG.C11: '(C11)',
|
||||
DW_LANG.Swift: '(Swift)',
|
||||
DW_LANG.Julia: '(Julia)',
|
||||
DW_LANG.Dylan: '(Dylan)',
|
||||
DW_LANG.C_plus_plus_14: '(C++14)',
|
||||
DW_LANG.Fortran03: '(Fortran 03)',
|
||||
DW_LANG.Fortran08: '(Fortran 08)',
|
||||
DW_LANG.RenderScript: '(RenderScript)',
|
||||
DW_LANG.BLISS: '(Bliss)', # Not in binutils
|
||||
DW_LANG.Mips_Assembler: '(MIPS assembler)',
|
||||
DW_LANG.HP_Bliss: '(HP Bliss)',
|
||||
DW_LANG.HP_Basic91: '(HP Basic 91)',
|
||||
DW_LANG.HP_Pascal91: '(HP Pascal 91)',
|
||||
DW_LANG.HP_IMacro: '(HP IMacro)',
|
||||
DW_LANG.HP_Assembler: '(HP assembler)'
|
||||
}
|
||||
|
||||
_DESCR_DW_ATE = {
|
||||
DW_ATE.void: '(void)',
|
||||
DW_ATE.address: '(machine address)',
|
||||
DW_ATE.boolean: '(boolean)',
|
||||
DW_ATE.complex_float: '(complex float)',
|
||||
DW_ATE.float: '(float)',
|
||||
DW_ATE.signed: '(signed)',
|
||||
DW_ATE.signed_char: '(signed char)',
|
||||
DW_ATE.unsigned: '(unsigned)',
|
||||
DW_ATE.unsigned_char: '(unsigned char)',
|
||||
DW_ATE.imaginary_float: '(imaginary float)',
|
||||
DW_ATE.decimal_float: '(decimal float)',
|
||||
DW_ATE.packed_decimal: '(packed_decimal)',
|
||||
DW_ATE.numeric_string: '(numeric_string)',
|
||||
DW_ATE.edited: '(edited)',
|
||||
DW_ATE.signed_fixed: '(signed_fixed)',
|
||||
DW_ATE.unsigned_fixed: '(unsigned_fixed)',
|
||||
DW_ATE.UTF: '(unicode string)',
|
||||
DW_ATE.HP_float80: '(HP_float80)',
|
||||
DW_ATE.HP_complex_float80: '(HP_complex_float80)',
|
||||
DW_ATE.HP_float128: '(HP_float128)',
|
||||
DW_ATE.HP_complex_float128: '(HP_complex_float128)',
|
||||
DW_ATE.HP_floathpintel: '(HP_floathpintel)',
|
||||
DW_ATE.HP_imaginary_float80: '(HP_imaginary_float80)',
|
||||
DW_ATE.HP_imaginary_float128: '(HP_imaginary_float128)',
|
||||
}
|
||||
|
||||
_DESCR_DW_ACCESS = {
|
||||
DW_ACCESS.public: '(public)',
|
||||
DW_ACCESS.protected: '(protected)',
|
||||
DW_ACCESS.private: '(private)',
|
||||
}
|
||||
|
||||
_DESCR_DW_VIS = {
|
||||
DW_VIS.local: '(local)',
|
||||
DW_VIS.exported: '(exported)',
|
||||
DW_VIS.qualified: '(qualified)',
|
||||
}
|
||||
|
||||
_DESCR_DW_VIRTUALITY = {
|
||||
DW_VIRTUALITY.none: '(none)',
|
||||
DW_VIRTUALITY.virtual: '(virtual)',
|
||||
DW_VIRTUALITY.pure_virtual: '(pure virtual)',
|
||||
}
|
||||
|
||||
_DESCR_DW_ID_CASE = {
|
||||
DW_ID.case_sensitive: '(case_sensitive)',
|
||||
DW_ID.up_case: '(up_case)',
|
||||
DW_ID.down_case: '(down_case)',
|
||||
DW_ID.case_insensitive: '(case_insensitive)',
|
||||
}
|
||||
|
||||
_DESCR_DW_CC = {
|
||||
DW_CC.normal: '(normal)',
|
||||
DW_CC.program: '(program)',
|
||||
DW_CC.nocall: '(nocall)',
|
||||
DW_CC.pass_by_reference: '(pass by ref)',
|
||||
DW_CC.pass_by_valuee: '(pass by value)',
|
||||
}
|
||||
|
||||
_DESCR_DW_ORD = {
|
||||
DW_ORD.row_major: '(row major)',
|
||||
DW_ORD.col_major: '(column major)',
|
||||
}
|
||||
|
||||
_DESCR_CFI_REGISTER_RULE_TYPE = dict(
|
||||
UNDEFINED='u',
|
||||
SAME_VALUE='s',
|
||||
OFFSET='c',
|
||||
VAL_OFFSET='v',
|
||||
REGISTER='',
|
||||
EXPRESSION='exp',
|
||||
VAL_EXPRESSION='vexp',
|
||||
ARCHITECTURAL='a',
|
||||
)
|
||||
|
||||
def _make_extra_mapper(
|
||||
mapping: Mapping[_INT, str],
|
||||
default: str,
|
||||
default_interpolate_value: bool = False,
|
||||
) -> Callable[[AttributeValue, DIE, int], str]:
|
||||
""" Create a mapping function from attribute parameters to an extra
|
||||
value that should be displayed.
|
||||
"""
|
||||
|
||||
def mapper(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
if default_interpolate_value:
|
||||
d = default % attr.value
|
||||
else:
|
||||
d = default
|
||||
return mapping.get(attr.value, d)
|
||||
|
||||
return mapper
|
||||
|
||||
|
||||
def _make_extra_string(s: str = '') -> Callable[[AttributeValue, DIE, int], str]:
|
||||
""" Create an extra function that just returns a constant string.
|
||||
"""
|
||||
def extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
return s
|
||||
return extra
|
||||
|
||||
|
||||
_DWARF_EXPR_DUMPER_CACHE: dict[int, ExprDumper] = {}
|
||||
|
||||
def _location_list_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
# According to section 2.6 of the DWARF spec v3, class loclistptr means
|
||||
# a location list, and class block means a location expression.
|
||||
# DW_FORM.sec_offset is new in DWARFv4 as a section offset.
|
||||
if attr.form in ('DW_FORM_data4', 'DW_FORM_data8', 'DW_FORM_sec_offset'):
|
||||
return '(location list)'
|
||||
else:
|
||||
return describe_DWARF_expr(attr.value, die.cu.structs, die.cu.cu_offset)
|
||||
|
||||
|
||||
def _data_member_location_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
# According to section 5.5.6 of the DWARF spec v4, a data member location
|
||||
# can be an integer offset, or a location description.
|
||||
#
|
||||
if attr.form in ('DW_FORM_data1', 'DW_FORM_data2',
|
||||
'DW_FORM_data4', 'DW_FORM_data8',
|
||||
'DW_FORM_sdata', 'DW_FORM_implicit_const'):
|
||||
return '' # No extra description needed
|
||||
else:
|
||||
return describe_DWARF_expr(attr.value, die.cu.structs, die.cu.cu_offset)
|
||||
|
||||
|
||||
def _import_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
|
||||
# For DW_AT_import the value points to a DIE (that can be either in the
|
||||
# current DIE's CU or in another CU, depending on the FORM). The extra
|
||||
# information for it is the abbreviation number in this DIE and its tag.
|
||||
if attr.form == 'DW_FORM_ref_addr':
|
||||
# Absolute offset value
|
||||
ref_die_offset = section_offset + attr.value
|
||||
else:
|
||||
# Relative offset to the current DIE's CU
|
||||
ref_die_offset = attr.value + die.cu.cu_offset
|
||||
|
||||
# Now find the CU this DIE belongs to (since we have to find its abbrev
|
||||
# table). This is done by linearly scanning through all CUs, looking for
|
||||
# one spanning an address space containing the referred DIE's offset.
|
||||
for cu in die.dwarfinfo.iter_CUs():
|
||||
if cu['unit_length'] + cu.cu_offset > ref_die_offset >= cu.cu_offset:
|
||||
# Once we have the CU, we can actually parse this DIE from the
|
||||
# stream.
|
||||
with preserve_stream_pos(die.stream):
|
||||
ref_die = DIE(cu, die.stream, ref_die_offset)
|
||||
return '[Abbrev Number: %s (%s)]' % (
|
||||
ref_die.abbrev_code, ref_die.tag)
|
||||
|
||||
return '[unknown]'
|
||||
|
||||
|
||||
_EXTRA_INFO_DESCRIPTION_MAP = defaultdict(
|
||||
lambda: _make_extra_string(''), # default_factory
|
||||
|
||||
DW_AT_inline=_make_extra_mapper(
|
||||
_DESCR_DW_INL, '(Unknown inline attribute value: %x)',
|
||||
default_interpolate_value=True),
|
||||
DW_AT_language=_make_extra_mapper(
|
||||
_DESCR_DW_LANG, '(Unknown: %x)', default_interpolate_value=True),
|
||||
DW_AT_encoding=_make_extra_mapper(_DESCR_DW_ATE, '(unknown type)'),
|
||||
DW_AT_accessibility=_make_extra_mapper(
|
||||
_DESCR_DW_ACCESS, '(unknown accessibility)'),
|
||||
DW_AT_visibility=_make_extra_mapper(
|
||||
_DESCR_DW_VIS, '(unknown visibility)'),
|
||||
DW_AT_virtuality=_make_extra_mapper(
|
||||
_DESCR_DW_VIRTUALITY, '(unknown virtuality)'),
|
||||
DW_AT_identifier_case=_make_extra_mapper(
|
||||
_DESCR_DW_ID_CASE, '(unknown case)'),
|
||||
DW_AT_calling_convention=_make_extra_mapper(
|
||||
_DESCR_DW_CC, '(unknown convention)'),
|
||||
DW_AT_ordering=_make_extra_mapper(
|
||||
_DESCR_DW_ORD, '(undefined)'),
|
||||
DW_AT_frame_base=_location_list_extra,
|
||||
DW_AT_location=_location_list_extra,
|
||||
DW_AT_string_length=_location_list_extra,
|
||||
DW_AT_return_addr=_location_list_extra,
|
||||
DW_AT_data_member_location=_data_member_location_extra,
|
||||
DW_AT_vtable_elem_location=_location_list_extra,
|
||||
DW_AT_segment=_location_list_extra,
|
||||
DW_AT_static_link=_location_list_extra,
|
||||
DW_AT_use_location=_location_list_extra,
|
||||
DW_AT_allocated=_location_list_extra,
|
||||
DW_AT_associated=_location_list_extra,
|
||||
DW_AT_data_location=_location_list_extra,
|
||||
DW_AT_stride=_location_list_extra,
|
||||
DW_AT_call_value=_location_list_extra,
|
||||
DW_AT_import=_import_extra,
|
||||
DW_AT_GNU_call_site_value=_location_list_extra,
|
||||
DW_AT_GNU_call_site_data_value=_location_list_extra,
|
||||
DW_AT_GNU_call_site_target=_location_list_extra,
|
||||
DW_AT_GNU_call_site_target_clobbered=_location_list_extra,
|
||||
)
|
||||
|
||||
# 8 in a line, for easier counting
|
||||
_REG_NAMES_x86 = [
|
||||
'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi',
|
||||
'eip', 'eflags', '<none>', 'st0', 'st1', 'st2', 'st3', 'st4',
|
||||
'st5', 'st6', 'st7', '<none>', '<none>', 'xmm0', 'xmm1', 'xmm2',
|
||||
'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7', 'mm0', 'mm1', 'mm2',
|
||||
'mm3', 'mm4', 'mm5', 'mm6', 'mm7', 'fcw', 'fsw', 'mxcsr',
|
||||
'es', 'cs', 'ss', 'ds', 'fs', 'gs', '<none>', '<none>', 'tr', 'ldtr'
|
||||
]
|
||||
|
||||
_REG_NAMES_x64 = [
|
||||
'rax', 'rdx', 'rcx', 'rbx', 'rsi', 'rdi', 'rbp', 'rsp',
|
||||
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15',
|
||||
'rip', 'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6',
|
||||
'xmm7', 'xmm8', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14',
|
||||
'xmm15', 'st0', 'st1', 'st2', 'st3', 'st4', 'st5', 'st6',
|
||||
'st7', 'mm0', 'mm1', 'mm2', 'mm3', 'mm4', 'mm5', 'mm6',
|
||||
'mm7', 'rflags', 'es', 'cs', 'ss', 'ds', 'fs', 'gs',
|
||||
'<none>', '<none>', 'fs.base', 'gs.base', '<none>', '<none>', 'tr', 'ldtr',
|
||||
'mxcsr', 'fcw', 'fsw'
|
||||
]
|
||||
|
||||
# https://developer.arm.com/documentation/ihi0057/e/?lang=en#dwarf-register-names
|
||||
_REG_NAMES_AArch64 = [
|
||||
'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7',
|
||||
'x8', 'x9', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15',
|
||||
'x16', 'x17', 'x18', 'x19', 'x20', 'x21', 'x22', 'x23',
|
||||
'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30', 'sp',
|
||||
'<none>', 'ELR_mode', 'RA_SIGN_STATE', '<none>', '<none>', '<none>', '<none>', '<none>',
|
||||
'<none>', '<none>', '<none>', '<none>', '<none>', '<none>', 'VG', 'FFR',
|
||||
'p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7',
|
||||
'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15',
|
||||
'v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7',
|
||||
'v8', 'v9', 'v10', 'v11', 'v12', 'v13', 'v14', 'v15',
|
||||
'v16', 'v17', 'v18', 'v19', 'v20', 'v21', 'v22', 'v23',
|
||||
'v24', 'v25', 'v26', 'v27', 'v28', 'v29', 'v30', 'v31',
|
||||
'z0', 'z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7',
|
||||
'z8', 'z9', 'z10', 'z11', 'z12', 'z13', 'z14', 'z15',
|
||||
'z16', 'z17', 'z18', 'z19', 'z20', 'z21', 'z22', 'z23',
|
||||
'z24', 'z25', 'z26', 'z27', 'z28', 'z29', 'z30', 'z31'
|
||||
]
|
||||
|
||||
|
||||
class ExprDumper:
|
||||
""" A dumper for DWARF expressions that dumps a textual
|
||||
representation of the complete expression.
|
||||
|
||||
Usage: after creation, call dump_expr repeatedly - it's stateless.
|
||||
"""
|
||||
def __init__(self, structs: DWARFStructs) -> None:
|
||||
self.structs = structs
|
||||
self.expr_parser = DWARFExprParser(self.structs)
|
||||
self._init_lookups()
|
||||
|
||||
def dump_expr(self, expr: bytes | Iterable[int], cu_offset: int | None = None) -> str:
|
||||
""" Parse and dump a DWARF expression.
|
||||
expr should be bytes or a list of (integer) byte values.
|
||||
cu_offset is the cu_offset
|
||||
value from the CU object where the expression resides.
|
||||
Only affects a handful of GNU opcodes, if None is provided,
|
||||
that's not a crash condition, only the expression dump will
|
||||
not be consistent of that of readelf.
|
||||
|
||||
Returns a string representing the expression.
|
||||
"""
|
||||
parsed = self.expr_parser.parse_expr(bytes(expr))
|
||||
return '; '.join(
|
||||
self._dump_to_string(deo.op, deo.op_name, deo.args, cu_offset)
|
||||
for deo in parsed
|
||||
)
|
||||
|
||||
def _init_lookups(self) -> None:
|
||||
self._ops_with_decimal_arg = {
|
||||
'DW_OP_const1u', 'DW_OP_const1s', 'DW_OP_const2u', 'DW_OP_const2s',
|
||||
'DW_OP_const4u', 'DW_OP_const4s', 'DW_OP_const8u', 'DW_OP_const8s',
|
||||
'DW_OP_constu', 'DW_OP_consts', 'DW_OP_pick', 'DW_OP_plus_uconst',
|
||||
'DW_OP_bra', 'DW_OP_skip', 'DW_OP_fbreg', 'DW_OP_piece',
|
||||
'DW_OP_deref_size', 'DW_OP_xderef_size', 'DW_OP_regx'}
|
||||
|
||||
for n in range(0, 32):
|
||||
self._ops_with_decimal_arg.add('DW_OP_breg%s' % n)
|
||||
|
||||
self._ops_with_two_decimal_args = {'DW_OP_bregx'}
|
||||
|
||||
self._ops_with_hex_arg = {
|
||||
'DW_OP_addr', 'DW_OP_call2', 'DW_OP_call4', 'DW_OP_call_ref'}
|
||||
|
||||
def _dump_to_string(
|
||||
self,
|
||||
opcode: int,
|
||||
opcode_name: str,
|
||||
args: list[Any],
|
||||
cu_offset: int | None = None,
|
||||
) -> str:
|
||||
# Some GNU ops contain an offset from the current CU as an argument,
|
||||
# but readelf emits those ops with offset from the info section
|
||||
# so we need the base offset of the parent CU.
|
||||
# If omitted, arguments on some GNU opcodes will be off.
|
||||
if cu_offset is None:
|
||||
cu_offset = 0
|
||||
|
||||
if not args:
|
||||
if opcode_name.startswith('DW_OP_reg'):
|
||||
regnum = int(opcode_name[9:])
|
||||
return '%s (%s)' % (
|
||||
opcode_name,
|
||||
describe_reg_name(regnum, _MACHINE_ARCH))
|
||||
else:
|
||||
return opcode_name
|
||||
elif opcode_name in self._ops_with_decimal_arg:
|
||||
if opcode_name.startswith('DW_OP_breg'):
|
||||
regnum = int(opcode_name[10:])
|
||||
return '%s (%s): %s' % (
|
||||
opcode_name,
|
||||
describe_reg_name(regnum, _MACHINE_ARCH),
|
||||
args[0])
|
||||
elif opcode_name.endswith('regx'):
|
||||
# applies to both regx and bregx
|
||||
return '%s: %s (%s)' % (
|
||||
opcode_name,
|
||||
args[0],
|
||||
describe_reg_name(args[0], _MACHINE_ARCH))
|
||||
else:
|
||||
return '%s: %s' % (opcode_name, args[0])
|
||||
elif opcode_name in self._ops_with_hex_arg:
|
||||
return '%s: %x' % (opcode_name, args[0])
|
||||
elif opcode_name in self._ops_with_two_decimal_args:
|
||||
return '%s: %s %s' % (opcode_name, args[0], args[1])
|
||||
elif opcode_name in ('DW_OP_GNU_entry_value', 'DW_OP_entry_value'):
|
||||
return '%s: (%s)' % (opcode_name, ','.join([self._dump_to_string(deo.op, deo.op_name, deo.args, cu_offset) for deo in args[0]]))
|
||||
elif opcode_name == 'DW_OP_implicit_value':
|
||||
return "%s %s byte block: %s" % (opcode_name, len(args[0]), ''.join(["%x " % b for b in args[0]]))
|
||||
elif opcode_name == 'DW_OP_GNU_parameter_ref':
|
||||
return "%s: <0x%x>" % (opcode_name, args[0] + cu_offset)
|
||||
elif opcode_name in ('DW_OP_GNU_implicit_pointer', 'DW_OP_implicit_pointer'):
|
||||
return "%s: <0x%x> %d" % (opcode_name, args[0], args[1])
|
||||
elif opcode_name in ('DW_OP_GNU_convert', 'DW_OP_convert'):
|
||||
return "%s <0x%x>" % (opcode_name, args[0] + cu_offset)
|
||||
elif opcode_name in ('DW_OP_GNU_deref_type', 'DW_OP_deref_type'):
|
||||
return "%s: %d <0x%x>" % (opcode_name, args[0], args[1] + cu_offset)
|
||||
elif opcode_name in ('DW_OP_GNU_const_type', 'DW_OP_const_type'):
|
||||
return "%s: <0x%x> %d byte block: %s " % (opcode_name, args[0] + cu_offset, len(args[1]), ' '.join("%x" % b for b in args[1]))
|
||||
elif opcode_name in ('DW_OP_GNU_regval_type', 'DW_OP_regval_type'):
|
||||
return "%s: %d (%s) <0x%x>" % (opcode_name, args[0], describe_reg_name(args[0], _MACHINE_ARCH), args[1] + cu_offset)
|
||||
elif opcode_name == 'DW_OP_bit_piece':
|
||||
return '%s: size: %s offset: %s' % (opcode_name, args[0], args[1])
|
||||
else:
|
||||
return '<unknown %s>' % opcode_name
|
||||
@@ -0,0 +1,373 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/die.py
|
||||
#
|
||||
# DWARF Debugging Information Entry
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from ..common.exceptions import DWARFError, ELFParseError
|
||||
from ..common.utils import bytes2str, struct_parse
|
||||
from .enums import DW_FORM_raw2name
|
||||
from .dwarf_util import _resolve_via_offset_table, _get_base_offset
|
||||
from ..construct import ConstructError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from .compileunit import CompileUnit
|
||||
from .typeunit import TypeUnit
|
||||
|
||||
|
||||
# AttributeValue - describes an attribute value in the DIE:
|
||||
#
|
||||
# name:
|
||||
# The name (DW_AT_*) of this attribute
|
||||
#
|
||||
# form:
|
||||
# The DW_FORM_* name of this attribute
|
||||
#
|
||||
# value:
|
||||
# The value parsed from the section and translated accordingly to the form
|
||||
# (e.g. for a DW_FORM_strp it's the actual string taken from the string table)
|
||||
#
|
||||
# raw_value:
|
||||
# Raw value as parsed from the section - used for debugging and presentation
|
||||
# (e.g. for a DW_FORM_strp it's the raw string offset into the table)
|
||||
#
|
||||
# offset:
|
||||
# Offset of this attribute's value in the stream (absolute offset, relative
|
||||
# the beginning of the whole stream)
|
||||
#
|
||||
# indirection_length:
|
||||
# If the form of the attribute is DW_FORM_indirect, the form will contain
|
||||
# the resolved form, and this will contain the length of the indirection chain.
|
||||
# 0 means no indirection.
|
||||
class AttributeValue(NamedTuple):
|
||||
name: str
|
||||
form: str
|
||||
value: Any
|
||||
raw_value: int
|
||||
offset: int
|
||||
indirection_length: int
|
||||
|
||||
|
||||
class DIE:
|
||||
""" A DWARF debugging information entry. On creation, parses itself from
|
||||
the stream. Each DIE is held by a CU.
|
||||
|
||||
Accessible attributes:
|
||||
|
||||
tag:
|
||||
The DIE tag
|
||||
|
||||
size:
|
||||
The size this DIE occupies in the section
|
||||
|
||||
offset:
|
||||
The offset of this DIE in the stream
|
||||
|
||||
attributes:
|
||||
An ordered dictionary mapping attribute names to values. It's
|
||||
ordered to preserve the order of attributes in the section
|
||||
|
||||
has_children:
|
||||
Specifies whether this DIE has children
|
||||
|
||||
abbrev_code:
|
||||
The abbreviation code pointing to an abbreviation entry (note
|
||||
that this is for informational purposes only - this object
|
||||
interacts with its abbreviation table transparently).
|
||||
|
||||
See also the public methods.
|
||||
"""
|
||||
def __init__(self, cu: CompileUnit | TypeUnit, stream: IO[bytes], offset: int) -> None:
|
||||
""" cu:
|
||||
CompileUnit object this DIE belongs to. Used to obtain context
|
||||
information (structs, abbrev table, etc.)
|
||||
|
||||
stream, offset:
|
||||
The stream and offset into it where this DIE's data is located
|
||||
"""
|
||||
self.cu = cu
|
||||
self.dwarfinfo = self.cu.dwarfinfo # get DWARFInfo context
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
|
||||
self.attributes: dict[str, Any] = {}
|
||||
self.tag: str | int | None = None
|
||||
self.has_children: bool | None = None
|
||||
self.abbrev_code: int | None = None
|
||||
self.size = 0
|
||||
# Null DIE terminator. It can be used to obtain offset range occupied
|
||||
# by this DIE including its whole subtree.
|
||||
self._terminator: DIE | None = None
|
||||
self._parent: DIE | None = None
|
||||
|
||||
self._parse_DIE()
|
||||
|
||||
def is_null(self) -> bool:
|
||||
""" Is this a null entry?
|
||||
"""
|
||||
return self.tag is None
|
||||
|
||||
def get_DIE_from_attribute(self, name: str) -> DIE:
|
||||
""" Return the DIE referenced by the named attribute of this DIE.
|
||||
The attribute must be in the reference attribute class.
|
||||
|
||||
name:
|
||||
The name of the attribute in the reference class.
|
||||
"""
|
||||
attr = self.attributes[name]
|
||||
if attr.form in ('DW_FORM_ref1', 'DW_FORM_ref2', 'DW_FORM_ref4',
|
||||
'DW_FORM_ref8', 'DW_FORM_ref', 'DW_FORM_ref_udata'):
|
||||
refaddr = self.cu.cu_offset + attr.raw_value
|
||||
return self.cu.get_DIE_from_refaddr(refaddr)
|
||||
elif attr.form in ('DW_FORM_ref_addr'):
|
||||
return self.cu.dwarfinfo.get_DIE_from_refaddr(attr.raw_value)
|
||||
elif attr.form in ('DW_FORM_ref_sig8'):
|
||||
return self.cu.dwarfinfo.get_DIE_by_sig8(attr.raw_value)
|
||||
elif attr.form in ('DW_FORM_ref_sup4', 'DW_FORM_ref_sup8', 'DW_FORM_GNU_ref_alt'):
|
||||
if self.dwarfinfo.supplementary_dwarfinfo:
|
||||
return self.dwarfinfo.supplementary_dwarfinfo.get_DIE_from_refaddr(attr.raw_value)
|
||||
# FIXME: how to distinguish supplementary files from dwo ?
|
||||
raise NotImplementedError('%s to dwo' % attr.form)
|
||||
else:
|
||||
raise DWARFError('%s is not a reference class form attribute' % attr)
|
||||
|
||||
def get_parent(self) -> DIE | None:
|
||||
""" Return the parent DIE of this DIE, or None if the DIE has no
|
||||
parent (i.e. is a top-level DIE).
|
||||
"""
|
||||
if self._parent is None:
|
||||
self._search_ancestor_offspring()
|
||||
return self._parent
|
||||
|
||||
def get_full_path(self) -> str:
|
||||
""" Return the full path filename for the DIE.
|
||||
|
||||
The filename is the join of 'DW_AT_comp_dir' and 'DW_AT_name',
|
||||
either of which may be missing in practice. Note that its value is
|
||||
usually a string taken from the .debug_string section and the
|
||||
returned value will be a string.
|
||||
"""
|
||||
comp_dir_attr = self.attributes.get('DW_AT_comp_dir', None)
|
||||
comp_dir = bytes2str(comp_dir_attr.value) if comp_dir_attr else ''
|
||||
fname_attr = self.attributes.get('DW_AT_name', None)
|
||||
fname = bytes2str(fname_attr.value) if fname_attr else ''
|
||||
return os.path.join(comp_dir, fname)
|
||||
|
||||
def iter_children(self) -> Iterator[DIE]:
|
||||
""" Iterates all children of this DIE
|
||||
"""
|
||||
return self.cu.iter_DIE_children(self)
|
||||
|
||||
def iter_siblings(self) -> Iterator[DIE]:
|
||||
""" Yield all siblings of this DIE
|
||||
"""
|
||||
parent = self.get_parent()
|
||||
if parent:
|
||||
for sibling in parent.iter_children():
|
||||
if sibling is not self:
|
||||
yield sibling
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
# The following methods are used while creating the DIE and should not be
|
||||
# interesting to consumers
|
||||
#
|
||||
|
||||
def set_parent(self, die: DIE) -> None:
|
||||
self._parent = die
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def _search_ancestor_offspring(self) -> None:
|
||||
""" Search our ancestors identifying their offspring to find our parent.
|
||||
|
||||
DIEs are stored as a flattened tree. The top DIE is the ancestor
|
||||
of all DIEs in the unit. Each parent is guaranteed to be at
|
||||
an offset less than their children. In each generation of children
|
||||
the sibling with the closest offset not greater than our offset is
|
||||
our ancestor.
|
||||
"""
|
||||
# This code is called when get_parent notices that the _parent has
|
||||
# not been identified. To avoid execution for each sibling record all
|
||||
# the children of any parent iterated. Assuming get_parent will also be
|
||||
# called for siblings, it is more efficient if siblings references are
|
||||
# provided and no worse than a single walk if they are missing, while
|
||||
# stopping iteration early could result in O(n^2) walks.
|
||||
search: DIE = self.cu.get_top_DIE()
|
||||
while search.offset < self.offset:
|
||||
prev = search
|
||||
for child in search.iter_children():
|
||||
child.set_parent(search)
|
||||
if child.offset <= self.offset:
|
||||
prev = child
|
||||
|
||||
# We also need to check the offset of the terminator DIE
|
||||
if search.has_children and search._terminator and search._terminator.offset <= self.offset:
|
||||
prev = search._terminator
|
||||
|
||||
# If we didn't find a closer parent, give up, don't loop.
|
||||
# Either we mis-parsed an ancestor or someone created a DIE
|
||||
# by an offset that was not actually the start of a DIE.
|
||||
if prev is search:
|
||||
raise ValueError("offset %s not in CU %s DIE tree" %
|
||||
(self.offset, self.cu.cu_offset))
|
||||
|
||||
search = prev
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = 'DIE %s, size=%s, has_children=%s\n' % (
|
||||
self.tag, self.size, self.has_children)
|
||||
for attrname, attrval in self.attributes.items():
|
||||
s += ' |%-18s: %s\n' % (attrname, attrval)
|
||||
return s
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
def _parse_DIE(self) -> None:
|
||||
""" Parses the DIE info from the section, based on the abbreviation
|
||||
table of the CU
|
||||
"""
|
||||
try:
|
||||
structs = self.cu.structs
|
||||
stream = self.stream
|
||||
|
||||
# A DIE begins with the abbreviation code. Read it and use it to
|
||||
# obtain the abbrev declaration for this DIE.
|
||||
# Note: here and elsewhere, preserve_stream_pos is used on operations
|
||||
# that manipulate the stream by reading data from it.
|
||||
stream.seek(self.offset)
|
||||
self.abbrev_code = structs.the_Dwarf_uleb128.parse_stream(stream)
|
||||
assert self.abbrev_code is not None
|
||||
|
||||
# This may be a null entry
|
||||
if self.abbrev_code == 0:
|
||||
self.size = stream.tell() - self.offset
|
||||
return
|
||||
|
||||
abbrev_decl = self.cu.get_abbrev_table().get_abbrev(self.abbrev_code)
|
||||
self.tag = abbrev_decl['tag']
|
||||
self.has_children = abbrev_decl.has_children()
|
||||
|
||||
# Guided by the attributes listed in the abbreviation declaration, parse
|
||||
# values from the stream.
|
||||
for spec in abbrev_decl['attr_spec']:
|
||||
form = spec.form
|
||||
name = spec.name
|
||||
attr_offset = stream.tell()
|
||||
indirection_length = 0
|
||||
# Special case here: the attribute value is stored in the attribute
|
||||
# definition in the abbreviation spec, not in the DIE itself.
|
||||
if form == 'DW_FORM_implicit_const':
|
||||
value = spec.value
|
||||
raw_value = value
|
||||
# Another special case: the attribute value is a form code followed by the real value in that form
|
||||
elif form == 'DW_FORM_indirect':
|
||||
(form, raw_value, indirection_length) = self._resolve_indirect()
|
||||
value = self._translate_attr_value(form, raw_value)
|
||||
else:
|
||||
dw_form = structs.Dwarf_dw_form[form]
|
||||
assert dw_form is not None
|
||||
raw_value = dw_form.parse_stream(stream)
|
||||
value = self._translate_attr_value(form, raw_value)
|
||||
self.attributes[name] = AttributeValue(
|
||||
name=name,
|
||||
form=form,
|
||||
value=value,
|
||||
raw_value=raw_value,
|
||||
offset=attr_offset,
|
||||
indirection_length = indirection_length)
|
||||
|
||||
self.size = stream.tell() - self.offset
|
||||
except ConstructError as e:
|
||||
raise ELFParseError(str(e))
|
||||
|
||||
def _resolve_indirect(self) -> tuple[str, int, int]:
|
||||
# Supports arbitrary indirection nesting (the standard doesn't prohibit that).
|
||||
# Expects the stream to be at the real form.
|
||||
# Returns (form, raw_value, length).
|
||||
structs = self.cu.structs
|
||||
length = 1
|
||||
real_form_code: int = struct_parse(structs.the_Dwarf_uleb128, self.stream) # Numeric form code
|
||||
while True:
|
||||
try:
|
||||
real_form = DW_FORM_raw2name[real_form_code] # Form name or exception if bogus code
|
||||
except KeyError:
|
||||
raise DWARFError('Found DW_FORM_indirect with unknown real form 0x%x' % real_form_code)
|
||||
|
||||
dw_form = structs.Dwarf_dw_form[real_form]
|
||||
assert dw_form is not None
|
||||
raw_value: int = struct_parse(dw_form, self.stream)
|
||||
|
||||
if real_form != 'DW_FORM_indirect': # Happy path: one level of indirection
|
||||
return (real_form, raw_value, length)
|
||||
else: # Indirection cascade
|
||||
length += 1
|
||||
real_form_code = raw_value
|
||||
# And continue parsing
|
||||
# No explicit infinite loop guard because the stream will end eventually
|
||||
|
||||
def _translate_attr_value(self, form: str, raw_value: Any) -> Any:
|
||||
""" Translate a raw attr value according to the form
|
||||
"""
|
||||
# Indirect forms can only be parsed if the top DIE of this CU has already been parsed
|
||||
# and listed in the CU, since the top DIE would have to contain the DW_AT_xxx_base attributes.
|
||||
# This breaks if there is an indirect encoding in the top DIE itself before the
|
||||
# corresponding _base, and it was seen in the wild.
|
||||
# There is a hook in get_top_DIE() to resolve those lazily.
|
||||
translate_indirect = self.cu.has_top_DIE() or self.offset != self.cu.cu_die_offset
|
||||
if form == 'DW_FORM_strp':
|
||||
return self.dwarfinfo.get_string_from_table(raw_value)
|
||||
elif form == 'DW_FORM_line_strp':
|
||||
return self.dwarfinfo.get_string_from_linetable(raw_value)
|
||||
elif form in ('DW_FORM_GNU_strp_alt', 'DW_FORM_strp_sup') and self.dwarfinfo.supplementary_dwarfinfo:
|
||||
return self.dwarfinfo.supplementary_dwarfinfo.get_string_from_table(raw_value)
|
||||
elif form == 'DW_FORM_flag':
|
||||
return not raw_value == 0
|
||||
elif form == 'DW_FORM_flag_present':
|
||||
return True
|
||||
elif form in ('DW_FORM_addrx', 'DW_FORM_addrx1', 'DW_FORM_addrx2', 'DW_FORM_addrx3', 'DW_FORM_addrx4') and translate_indirect:
|
||||
return self.cu.dwarfinfo.get_addr(self.cu, raw_value)
|
||||
elif form in ('DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4') and translate_indirect:
|
||||
assert self.dwarfinfo.debug_str_offsets_sec is not None
|
||||
stream = self.dwarfinfo.debug_str_offsets_sec.stream
|
||||
base_offset = _get_base_offset(self.cu, 'DW_AT_str_offsets_base')
|
||||
offset_size = 4 if self.cu.structs.dwarf_format == 32 else 8
|
||||
str_offset = struct_parse(self.cu.structs.the_Dwarf_offset, stream, base_offset + raw_value*offset_size)
|
||||
return self.dwarfinfo.get_string_from_table(str_offset)
|
||||
elif form == 'DW_FORM_loclistx' and translate_indirect:
|
||||
assert self.dwarfinfo.debug_loclists_sec is not None
|
||||
return _resolve_via_offset_table(self.dwarfinfo.debug_loclists_sec.stream, self.cu, raw_value, 'DW_AT_loclists_base')
|
||||
elif form == 'DW_FORM_rnglistx' and translate_indirect:
|
||||
assert self.dwarfinfo.debug_rnglists_sec is not None
|
||||
return _resolve_via_offset_table(self.dwarfinfo.debug_rnglists_sec.stream, self.cu, raw_value, 'DW_AT_rnglists_base')
|
||||
return raw_value
|
||||
|
||||
def _translate_indirect_attributes(self) -> None:
|
||||
""" This is a hook to translate the DW_FORM_...x values in the top DIE
|
||||
once the top DIE is parsed to the end. They can't be translated
|
||||
while the top DIE is being parsed, because they implicitly make a
|
||||
reference to the DW_AT_xxx_base attribute in the same DIE that may
|
||||
not have been parsed yet.
|
||||
"""
|
||||
for key, attr in self.attributes.items():
|
||||
if attr.form in ('DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4',
|
||||
'DW_FORM_addrx', 'DW_FORM_addrx1', 'DW_FORM_addrx2', 'DW_FORM_addrx3', 'DW_FORM_addrx4',
|
||||
'DW_FORM_loclistx', 'DW_FORM_rnglistx'):
|
||||
# Can't change value in place, got to replace the whole attribute record
|
||||
self.attributes[key] = AttributeValue(
|
||||
name=attr.name,
|
||||
form=attr.form,
|
||||
value=self._translate_attr_value(attr.form, attr.raw_value),
|
||||
raw_value=attr.raw_value,
|
||||
offset=attr.offset,
|
||||
indirection_length=attr.indirection_length)
|
||||
@@ -0,0 +1,293 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarf_expr.py
|
||||
#
|
||||
# Decoding DWARF expressions
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..common.exceptions import DWARFError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
|
||||
from ..construct.core import Construct
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
# DWARF expression opcodes. name -> opcode mapping
|
||||
DW_OP_name2opcode: Mapping[str, int] = dict(
|
||||
DW_OP_addr=0x03,
|
||||
DW_OP_deref=0x06,
|
||||
DW_OP_const1u=0x08,
|
||||
DW_OP_const1s=0x09,
|
||||
DW_OP_const2u=0x0a,
|
||||
DW_OP_const2s=0x0b,
|
||||
DW_OP_const4u=0x0c,
|
||||
DW_OP_const4s=0x0d,
|
||||
DW_OP_const8u=0x0e,
|
||||
DW_OP_const8s=0x0f,
|
||||
DW_OP_constu=0x10,
|
||||
DW_OP_consts=0x11,
|
||||
DW_OP_dup=0x12,
|
||||
DW_OP_drop=0x13,
|
||||
DW_OP_over=0x14,
|
||||
DW_OP_pick=0x15,
|
||||
DW_OP_swap=0x16,
|
||||
DW_OP_rot=0x17,
|
||||
DW_OP_xderef=0x18,
|
||||
DW_OP_abs=0x19,
|
||||
DW_OP_and=0x1a,
|
||||
DW_OP_div=0x1b,
|
||||
DW_OP_minus=0x1c,
|
||||
DW_OP_mod=0x1d,
|
||||
DW_OP_mul=0x1e,
|
||||
DW_OP_neg=0x1f,
|
||||
DW_OP_not=0x20,
|
||||
DW_OP_or=0x21,
|
||||
DW_OP_plus=0x22,
|
||||
DW_OP_plus_uconst=0x23,
|
||||
DW_OP_shl=0x24,
|
||||
DW_OP_shr=0x25,
|
||||
DW_OP_shra=0x26,
|
||||
DW_OP_xor=0x27,
|
||||
DW_OP_bra=0x28,
|
||||
DW_OP_eq=0x29,
|
||||
DW_OP_ge=0x2a,
|
||||
DW_OP_gt=0x2b,
|
||||
DW_OP_le=0x2c,
|
||||
DW_OP_lt=0x2d,
|
||||
DW_OP_ne=0x2e,
|
||||
DW_OP_skip=0x2f,
|
||||
DW_OP_regx=0x90,
|
||||
DW_OP_fbreg=0x91,
|
||||
DW_OP_bregx=0x92,
|
||||
DW_OP_piece=0x93,
|
||||
DW_OP_deref_size=0x94,
|
||||
DW_OP_xderef_size=0x95,
|
||||
DW_OP_nop=0x96,
|
||||
DW_OP_push_object_address=0x97,
|
||||
DW_OP_call2=0x98,
|
||||
DW_OP_call4=0x99,
|
||||
DW_OP_call_ref=0x9a,
|
||||
DW_OP_form_tls_address=0x9b,
|
||||
DW_OP_call_frame_cfa=0x9c,
|
||||
DW_OP_bit_piece=0x9d,
|
||||
DW_OP_implicit_value=0x9e,
|
||||
DW_OP_stack_value=0x9f,
|
||||
DW_OP_implicit_pointer=0xa0,
|
||||
DW_OP_addrx=0xa1,
|
||||
DW_OP_constx=0xa2,
|
||||
DW_OP_entry_value=0xa3,
|
||||
DW_OP_const_type=0xa4,
|
||||
DW_OP_regval_type=0xa5,
|
||||
DW_OP_deref_type=0xa6,
|
||||
DW_OP_xderef_type=0xa7,
|
||||
DW_OP_convert=0xa8,
|
||||
DW_OP_reinterpret=0xa9,
|
||||
DW_OP_lo_user=0xe0,
|
||||
DW_OP_GNU_push_tls_address=0xe0,
|
||||
DW_OP_WASM_location=0xed,
|
||||
DW_OP_GNU_uninit=0xf0,
|
||||
DW_OP_GNU_implicit_pointer=0xf2,
|
||||
DW_OP_GNU_entry_value=0xf3,
|
||||
DW_OP_GNU_const_type=0xf4,
|
||||
DW_OP_GNU_regval_type=0xf5,
|
||||
DW_OP_GNU_deref_type=0xf6,
|
||||
DW_OP_GNU_convert=0xf7,
|
||||
DW_OP_GNU_parameter_ref=0xfa,
|
||||
DW_OP_GNU_addr_index=0xfb,
|
||||
DW_OP_GNU_const_index=0xfc,
|
||||
DW_OP_GNU_variable_value=0xfd,
|
||||
DW_OP_hi_user=0xff,
|
||||
**{f"DW_OP_lit{val}": 0x30 + val for val in range(0, 32)},
|
||||
**{f"DW_OP_reg{val}": 0x50 + val for val in range(0, 32)},
|
||||
**{f"DW_OP_breg{val}": 0x70 + val for val in range(0, 32)},
|
||||
)
|
||||
|
||||
# opcode -> name mapping
|
||||
DW_OP_opcode2name: Mapping[int, str] = {v: k for k, v in DW_OP_name2opcode.items()}
|
||||
|
||||
|
||||
# Each parsed DWARF expression is returned as this type with its numeric opcode,
|
||||
# op name (as a string) and a list of arguments.
|
||||
class DWARFExprOp(NamedTuple):
|
||||
op: int
|
||||
op_name: str
|
||||
args: list[Any]
|
||||
offset: int
|
||||
|
||||
|
||||
class DWARFExprParser:
|
||||
"""DWARF expression parser.
|
||||
|
||||
When initialized, requires structs to cache a dispatch table. After that,
|
||||
parse_expr can be called repeatedly - it's stateless.
|
||||
"""
|
||||
|
||||
def __init__(self, structs: DWARFStructs) -> None:
|
||||
self._dispatch_table = _init_dispatch_table(structs)
|
||||
|
||||
def parse_expr(self, expr: bytes | Iterable[int]) -> list[DWARFExprOp]:
|
||||
""" Parses expr (bytes or a list of integers) into a list of DWARFExprOp.
|
||||
|
||||
The list can potentially be nested.
|
||||
"""
|
||||
stream = BytesIO(bytes(expr))
|
||||
parsed: list[DWARFExprOp] = []
|
||||
|
||||
while True:
|
||||
# Get the next opcode from the stream. If nothing is left in the
|
||||
# stream, we're done.
|
||||
offset = stream.tell()
|
||||
byte = stream.read(1)
|
||||
if not byte:
|
||||
break
|
||||
|
||||
# Decode the opcode and its name.
|
||||
op = ord(byte)
|
||||
op_name = DW_OP_opcode2name.get(op, 'OP:0x%x' % op)
|
||||
|
||||
# Use dispatch table to parse args.
|
||||
arg_parser = self._dispatch_table[op]
|
||||
args = arg_parser(stream)
|
||||
|
||||
parsed.append(DWARFExprOp(op=op, op_name=op_name, args=args, offset=offset))
|
||||
|
||||
return parsed
|
||||
|
||||
|
||||
def _init_dispatch_table(structs: DWARFStructs) -> dict[int, Callable[[IO[bytes]], list[Any]]]:
|
||||
"""Creates a dispatch table for parsing args of an op.
|
||||
|
||||
Returns a dict mapping opcode to a function. The function accepts a stream
|
||||
and return a list of parsed arguments for the opcode from the stream;
|
||||
the stream is advanced by the function as needed.
|
||||
"""
|
||||
table: dict[int, Callable[[IO[bytes]], list[Any]]] = {}
|
||||
def add(opcode_name: str, func: Callable[[IO[bytes]], list[Any]]) -> None:
|
||||
table[DW_OP_name2opcode[opcode_name]] = func
|
||||
|
||||
def parse_noargs() -> Callable[[IO[bytes]], list[None]]:
|
||||
return lambda stream: []
|
||||
|
||||
def parse_op_addr() -> Callable[[IO[bytes]], list[int]]:
|
||||
return lambda stream: [struct_parse(structs.the_Dwarf_target_addr,
|
||||
stream)]
|
||||
|
||||
def parse_arg_struct(arg_struct: Construct) -> Callable[[IO[bytes]], list[Any]]:
|
||||
return lambda stream: [struct_parse(arg_struct, stream)]
|
||||
|
||||
def parse_arg_struct2(
|
||||
arg1_struct: Construct,
|
||||
arg2_struct: Construct,
|
||||
) -> Callable[[IO[bytes]], list[Any]]:
|
||||
return lambda stream: [struct_parse(arg1_struct, stream),
|
||||
struct_parse(arg2_struct, stream)]
|
||||
|
||||
# ULEB128, then an expression of that length
|
||||
def parse_nestedexpr() -> Callable[[IO[bytes]], list[list[DWARFExprOp]]]:
|
||||
|
||||
def parse(stream: IO[bytes]) -> list[list[DWARFExprOp]]:
|
||||
size: int = struct_parse(structs.the_Dwarf_uleb128, stream)
|
||||
nested_expr_blob = stream.read(size)
|
||||
return [DWARFExprParser(structs).parse_expr(nested_expr_blob)]
|
||||
return parse
|
||||
|
||||
# ULEB128, then a blob of that size
|
||||
def parse_blob() -> Callable[[IO[bytes]], list[list[int]]]:
|
||||
return lambda stream: [list(stream.read(struct_parse(structs.the_Dwarf_uleb128, stream)))]
|
||||
|
||||
# ULEB128 with datatype DIE offset, then byte, then a blob of that size
|
||||
def parse_typedblob() -> Callable[[IO[bytes]], list[int | list[int]]]:
|
||||
return lambda stream: [struct_parse(structs.the_Dwarf_uleb128, stream), list(stream.read(struct_parse(structs.the_Dwarf_uint8, stream)))]
|
||||
|
||||
# https://yurydelendik.github.io/webassembly-dwarf/
|
||||
# Byte, then variant: 0, 1, 2 => uleb128, 3 => uint32
|
||||
def parse_wasmloc() -> Callable[[IO[bytes]], list[int]]:
|
||||
|
||||
def parse(stream: IO[bytes]) -> list[int]:
|
||||
op: int = struct_parse(structs.the_Dwarf_uint8, stream)
|
||||
if 0 <= op <= 2:
|
||||
return [op, struct_parse(structs.the_Dwarf_uleb128, stream)]
|
||||
elif op == 3:
|
||||
return [op, struct_parse(structs.the_Dwarf_uint32, stream)]
|
||||
else:
|
||||
raise DWARFError("Unknown operation code in DW_OP_WASM_location: %d" % (op,))
|
||||
return parse
|
||||
|
||||
add('DW_OP_addr', parse_op_addr())
|
||||
add('DW_OP_addrx', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_const1u', parse_arg_struct(structs.the_Dwarf_uint8))
|
||||
add('DW_OP_const1s', parse_arg_struct(structs.Dwarf_int8('')))
|
||||
add('DW_OP_const2u', parse_arg_struct(structs.the_Dwarf_uint16))
|
||||
add('DW_OP_const2s', parse_arg_struct(structs.Dwarf_int16('')))
|
||||
add('DW_OP_const4u', parse_arg_struct(structs.the_Dwarf_uint32))
|
||||
add('DW_OP_const4s', parse_arg_struct(structs.Dwarf_int32('')))
|
||||
add('DW_OP_const8u', parse_arg_struct(structs.Dwarf_uint64('')))
|
||||
add('DW_OP_const8s', parse_arg_struct(structs.Dwarf_int64('')))
|
||||
add('DW_OP_constu', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_consts', parse_arg_struct(structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_pick', parse_arg_struct(structs.the_Dwarf_uint8))
|
||||
add('DW_OP_plus_uconst', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_bra', parse_arg_struct(structs.Dwarf_int16('')))
|
||||
add('DW_OP_skip', parse_arg_struct(structs.Dwarf_int16('')))
|
||||
|
||||
for opname in [ 'DW_OP_deref', 'DW_OP_dup', 'DW_OP_drop', 'DW_OP_over',
|
||||
'DW_OP_swap', 'DW_OP_swap', 'DW_OP_rot', 'DW_OP_xderef',
|
||||
'DW_OP_abs', 'DW_OP_and', 'DW_OP_div', 'DW_OP_minus',
|
||||
'DW_OP_mod', 'DW_OP_mul', 'DW_OP_neg', 'DW_OP_not',
|
||||
'DW_OP_or', 'DW_OP_plus', 'DW_OP_shl', 'DW_OP_shr',
|
||||
'DW_OP_shra', 'DW_OP_xor', 'DW_OP_eq', 'DW_OP_ge',
|
||||
'DW_OP_gt', 'DW_OP_le', 'DW_OP_lt', 'DW_OP_ne', 'DW_OP_nop',
|
||||
'DW_OP_push_object_address', 'DW_OP_form_tls_address',
|
||||
'DW_OP_call_frame_cfa', 'DW_OP_stack_value',
|
||||
'DW_OP_GNU_push_tls_address', 'DW_OP_GNU_uninit']:
|
||||
add(opname, parse_noargs())
|
||||
|
||||
for n in range(0, 32):
|
||||
add('DW_OP_lit%s' % n, parse_noargs())
|
||||
add('DW_OP_reg%s' % n, parse_noargs())
|
||||
add('DW_OP_breg%s' % n, parse_arg_struct(structs.the_Dwarf_sleb128))
|
||||
|
||||
for opname in [ 'DW_OP_regx', 'DW_OP_piece', 'DW_OP_convert', 'DW_OP_GNU_convert',
|
||||
'DW_OP_GNU_addr_index', 'DW_OP_GNU_const_index', 'DW_OP_GNU_variable_value']:
|
||||
add(opname, parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
|
||||
add('DW_OP_fbreg', parse_arg_struct(structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_bregx', parse_arg_struct2(structs.the_Dwarf_uleb128,
|
||||
structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_bit_piece', parse_arg_struct2(structs.the_Dwarf_uleb128,
|
||||
structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_deref_size', parse_arg_struct(structs.Dwarf_int8('')))
|
||||
add('DW_OP_xderef_size', parse_arg_struct(structs.Dwarf_int8('')))
|
||||
add('DW_OP_call2', parse_arg_struct(structs.the_Dwarf_uint16))
|
||||
add('DW_OP_call4', parse_arg_struct(structs.the_Dwarf_uint32))
|
||||
add('DW_OP_call_ref', parse_arg_struct(structs.the_Dwarf_offset))
|
||||
add('DW_OP_implicit_value', parse_blob())
|
||||
add('DW_OP_entry_value', parse_nestedexpr())
|
||||
add('DW_OP_const_type', parse_typedblob())
|
||||
add('DW_OP_regval_type', parse_arg_struct2(structs.the_Dwarf_uleb128,
|
||||
structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_deref_type', parse_arg_struct2(structs.the_Dwarf_uint8,
|
||||
structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_implicit_pointer', parse_arg_struct2(structs.the_Dwarf_offset,
|
||||
structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_GNU_entry_value', parse_nestedexpr())
|
||||
add('DW_OP_GNU_const_type', parse_typedblob())
|
||||
add('DW_OP_GNU_regval_type', parse_arg_struct2(structs.the_Dwarf_uleb128,
|
||||
structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_GNU_deref_type', parse_arg_struct2(structs.the_Dwarf_uint8,
|
||||
structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_GNU_implicit_pointer', parse_arg_struct2(structs.the_Dwarf_offset,
|
||||
structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_GNU_parameter_ref', parse_arg_struct(structs.the_Dwarf_offset))
|
||||
add('DW_OP_WASM_location', parse_wasmloc())
|
||||
|
||||
return table
|
||||
@@ -0,0 +1,95 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarf_utils.py
|
||||
#
|
||||
# Minor, shared DWARF helpers
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import binascii
|
||||
from typing import IO, TYPE_CHECKING, Any
|
||||
|
||||
from ..construct.macros import Array
|
||||
from ..common.exceptions import DWARFError
|
||||
from ..common.utils import preserve_stream_pos, struct_parse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct import Struct
|
||||
from .compileunit import CompileUnit
|
||||
from .structs import DWARFStructs
|
||||
from .typeunit import TypeUnit
|
||||
|
||||
|
||||
def _get_base_offset(cu: CompileUnit | TypeUnit, base_attribute_name: str) -> int:
|
||||
"""Retrieves a required, base offset-type atribute
|
||||
from the top DIE in the CU. Applies to several indirectly
|
||||
encoded objects - range lists, location lists, strings, addresses.
|
||||
"""
|
||||
cu_top_die = cu.get_top_DIE()
|
||||
if base_attribute_name not in cu_top_die.attributes:
|
||||
raise DWARFError("The CU at offset 0x%x needs %s" % (cu.cu_offset, base_attribute_name))
|
||||
return cu_top_die.attributes[base_attribute_name].value
|
||||
|
||||
def _resolve_via_offset_table(
|
||||
stream: IO[bytes],
|
||||
cu: CompileUnit | TypeUnit,
|
||||
index: int,
|
||||
base_attribute_name: str,
|
||||
) -> int:
|
||||
"""Given an index in the offset table and directions where to find it,
|
||||
retrieves an offset. Works for loclists, rnglists.
|
||||
|
||||
The DWARF offset bitness of the CU block in the section matches that
|
||||
of the CU record in dwarf_info. See DWARFv5 standard, section 7.4.
|
||||
|
||||
This is used for translating DW_FORM_loclistx, DW_FORM_rnglistx
|
||||
via the offset table in the respective section.
|
||||
"""
|
||||
base_offset = _get_base_offset(cu, base_attribute_name)
|
||||
# That's offset (within the rnglists/loclists/str_offsets section) of
|
||||
# the offset table for this CU's block in that section, which in turn is indexed by the index.
|
||||
|
||||
offset_size = 4 if cu.structs.dwarf_format == 32 else 8
|
||||
with preserve_stream_pos(stream):
|
||||
return base_offset + struct_parse(cu.structs.the_Dwarf_offset, stream, base_offset + index*offset_size)
|
||||
|
||||
def _iter_CUs_in_section(
|
||||
stream: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
parser: Struct,
|
||||
) -> Iterator[Any]:
|
||||
"""Iterates through the list of CU sections in loclists or rangelists. Almost identical structures there.
|
||||
|
||||
get_parser is a lambda that takes structs, returns the parser
|
||||
"""
|
||||
stream.seek(0, os.SEEK_END)
|
||||
endpos = stream.tell()
|
||||
stream.seek(0, os.SEEK_SET)
|
||||
|
||||
offset = 0
|
||||
while offset < endpos:
|
||||
header = struct_parse(parser, stream, offset)
|
||||
if header.offset_count > 0:
|
||||
offset_parser = structs.Dwarf_uint64 if header.is64 else structs.Dwarf_uint32
|
||||
header['offsets'] = struct_parse(Array(header.offset_count, offset_parser('')), stream)
|
||||
else:
|
||||
header['offsets'] = False
|
||||
yield header
|
||||
offset = header.offset_after_length + header.unit_length
|
||||
|
||||
def _file_crc32(file: IO[bytes]) -> int:
|
||||
""" Provided a readable binary stream, reads the stream to the end
|
||||
and computes the CRC32 checksum of its contents,
|
||||
with the initial value of 0.
|
||||
"""
|
||||
d = file.read(4096)
|
||||
checksum = 0
|
||||
while d:
|
||||
checksum = binascii.crc32(d, checksum)
|
||||
d = file.read(4096)
|
||||
return checksum
|
||||
@@ -0,0 +1,762 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarfinfo.py
|
||||
#
|
||||
# DWARFInfo - Main class for accessing DWARF debug information
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from bisect import bisect_right
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, NamedTuple
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from ..common.exceptions import DWARFError
|
||||
from ..common.utils import (struct_parse, dwarf_assert,
|
||||
parse_cstring_from_stream)
|
||||
from .structs import DWARFStructs
|
||||
from .compileunit import CompileUnit
|
||||
from .typeunit import TypeUnit
|
||||
from .abbrevtable import AbbrevTable
|
||||
from .lineprogram import LineProgram
|
||||
from .callframe import CallFrameInfo
|
||||
from .locationlists import LocationLists, LocationListsPair
|
||||
from .ranges import RangeLists, RangeListsPair
|
||||
from .aranges import ARanges
|
||||
from .namelut import NameLUT
|
||||
from .dwarf_util import _get_base_offset
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
|
||||
from ..construct.lib.container import ListContainer
|
||||
from .callframe import ZERO, CFIEntry
|
||||
from .die import DIE
|
||||
from .namelut import NameLUTEntry
|
||||
|
||||
|
||||
# Describes a debug section
|
||||
#
|
||||
# stream: a stream object containing the data of this section
|
||||
# name: section name in the container file
|
||||
# global_offset: the global offset of the section in its container file
|
||||
# size: the size of the section's data, in bytes
|
||||
# address: the virtual address for the section's data
|
||||
#
|
||||
# 'name' and 'global_offset' are for descriptional purposes only and
|
||||
# aren't strictly required for the DWARF parsing to work. 'address' is required
|
||||
# to properly decode the special '.eh_frame' format.
|
||||
#
|
||||
class DebugSectionDescriptor(NamedTuple):
|
||||
stream: IO[bytes]
|
||||
name: str
|
||||
global_offset: int | None
|
||||
size: int
|
||||
address: int
|
||||
|
||||
|
||||
# Some configuration parameters for the DWARF reader. This exists to allow
|
||||
# DWARFInfo to be independent from any specific file format/container.
|
||||
#
|
||||
# little_endian:
|
||||
# boolean flag specifying whether the data in the file is little endian
|
||||
#
|
||||
# machine_arch:
|
||||
# Machine architecture as a string. For example 'x86' or 'x64'
|
||||
#
|
||||
# default_address_size:
|
||||
# The default address size for the container file (sizeof pointer, in bytes)
|
||||
#
|
||||
class DwarfConfig(NamedTuple):
|
||||
little_endian: bool
|
||||
machine_arch: str
|
||||
default_address_size: int
|
||||
|
||||
|
||||
class DWARFInfo:
|
||||
""" Acts also as a "context" to other major objects, bridging between
|
||||
various parts of the debug information.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
config: DwarfConfig,
|
||||
debug_info_sec: DebugSectionDescriptor | None,
|
||||
debug_aranges_sec: DebugSectionDescriptor | None,
|
||||
debug_abbrev_sec: DebugSectionDescriptor | None,
|
||||
debug_frame_sec: DebugSectionDescriptor | None,
|
||||
eh_frame_sec: DebugSectionDescriptor | None,
|
||||
debug_str_sec: DebugSectionDescriptor | None,
|
||||
debug_loc_sec: DebugSectionDescriptor | None,
|
||||
debug_ranges_sec: DebugSectionDescriptor | None,
|
||||
debug_line_sec: DebugSectionDescriptor | None,
|
||||
debug_pubtypes_sec: DebugSectionDescriptor | None,
|
||||
debug_pubnames_sec: DebugSectionDescriptor | None,
|
||||
debug_addr_sec: DebugSectionDescriptor | None,
|
||||
debug_str_offsets_sec: DebugSectionDescriptor | None,
|
||||
debug_line_str_sec: DebugSectionDescriptor | None,
|
||||
debug_loclists_sec: DebugSectionDescriptor | None,
|
||||
debug_rnglists_sec: DebugSectionDescriptor | None,
|
||||
debug_sup_sec: DebugSectionDescriptor | None,
|
||||
gnu_debugaltlink_sec: DebugSectionDescriptor | None,
|
||||
debug_types_sec: DebugSectionDescriptor | None,
|
||||
) -> None:
|
||||
""" config:
|
||||
A DwarfConfig object
|
||||
|
||||
debug_*_sec:
|
||||
DebugSectionDescriptor for a section. Pass None for sections
|
||||
that don't exist. These arguments are best given with
|
||||
keyword syntax.
|
||||
"""
|
||||
self.config = config
|
||||
self.debug_info_sec = debug_info_sec
|
||||
self.debug_aranges_sec = debug_aranges_sec
|
||||
self.debug_abbrev_sec = debug_abbrev_sec
|
||||
self.debug_frame_sec = debug_frame_sec
|
||||
self.eh_frame_sec = eh_frame_sec
|
||||
self.debug_str_sec = debug_str_sec
|
||||
self.debug_loc_sec = debug_loc_sec
|
||||
self.debug_ranges_sec = debug_ranges_sec
|
||||
self.debug_line_sec = debug_line_sec
|
||||
self.debug_addr_sec = debug_addr_sec
|
||||
self.debug_str_offsets_sec = debug_str_offsets_sec
|
||||
self.debug_line_str_sec = debug_line_str_sec
|
||||
self.debug_pubtypes_sec = debug_pubtypes_sec
|
||||
self.debug_pubnames_sec = debug_pubnames_sec
|
||||
self.debug_loclists_sec = debug_loclists_sec
|
||||
self.debug_rnglists_sec = debug_rnglists_sec
|
||||
self.debug_sup_sec = debug_sup_sec
|
||||
self.gnu_debugaltlink_sec = gnu_debugaltlink_sec
|
||||
self.debug_types_sec = debug_types_sec
|
||||
|
||||
# Sets the supplementary_dwarfinfo to None. Client code can set this
|
||||
# to something else, typically a DWARFInfo file read from an ELFFile
|
||||
# which path is stored in the debug_sup_sec or gnu_debugaltlink_sec.
|
||||
self.supplementary_dwarfinfo: DWARFInfo | None = None
|
||||
|
||||
# This is the DWARFStructs the context uses, so it doesn't depend on
|
||||
# DWARF format and address_size (these are determined per CU) - set them
|
||||
# to default values.
|
||||
self.structs = DWARFStructs(
|
||||
little_endian=self.config.little_endian,
|
||||
dwarf_format=32,
|
||||
address_size=self.config.default_address_size)
|
||||
|
||||
# Cache for abbrev tables: a dict keyed by offset
|
||||
self._abbrevtable_cache: dict[int, AbbrevTable] = {}
|
||||
# Cache for program lines tables: a dict keyed by offset
|
||||
self._linetable_cache: dict[int, LineProgram] = {}
|
||||
|
||||
# Cache of compile units and map of their offsets for bisect lookup.
|
||||
# Access with .iter_CUs(), .get_CU_containing(), and/or .get_CU_at().
|
||||
self._cu_cache: list[CompileUnit] = []
|
||||
self._cu_offsets_map: list[int] = []
|
||||
|
||||
@property
|
||||
def has_debug_info(self) -> bool:
|
||||
""" Return whether this contains debug information.
|
||||
|
||||
It can be not the case when the ELF only contains .eh_frame, which is
|
||||
encoded DWARF but not actually for debugging.
|
||||
"""
|
||||
return bool(self.debug_info_sec)
|
||||
|
||||
def has_debug_types(self) -> bool:
|
||||
""" Return whether this contains debug types information.
|
||||
"""
|
||||
return bool(self.debug_types_sec)
|
||||
|
||||
def get_DIE_from_lut_entry(self, lut_entry: NameLUTEntry) -> DIE:
|
||||
""" Get the DIE from the pubnames or putbtypes lookup table entry.
|
||||
|
||||
lut_entry:
|
||||
A NameLUTEntry object from a NameLUT instance (see
|
||||
.get_pubmames and .get_pubtypes methods).
|
||||
"""
|
||||
cu = self.get_CU_at(lut_entry.cu_ofs)
|
||||
return self.get_DIE_from_refaddr(lut_entry.die_ofs, cu)
|
||||
|
||||
def get_DIE_from_refaddr(self, refaddr: int, cu: CompileUnit | None = None) -> DIE:
|
||||
""" Given a .debug_info section offset of a DIE, return the DIE.
|
||||
|
||||
refaddr:
|
||||
The refaddr may come from a DW_FORM_ref_addr attribute.
|
||||
|
||||
cu:
|
||||
The compile unit object, if known. If None a search
|
||||
from the closest offset less than refaddr will be performed.
|
||||
"""
|
||||
if cu is None:
|
||||
cu = self.get_CU_containing(refaddr)
|
||||
return cu.get_DIE_from_refaddr(refaddr)
|
||||
|
||||
def get_DIE_by_sig8(self, sig8: int) -> DIE:
|
||||
""" Find and return a DIE referenced by its type signature.
|
||||
sig8:
|
||||
The 8 byte signature (as a 64-bit unsigned integer)
|
||||
Returns the DIE with the given type signature by searching
|
||||
for the Type Unit with the matching signature then finding
|
||||
the DIE at the offset given by the type_die field in the
|
||||
Type Unit header.
|
||||
Signatures are an 64-bit unsigned integers computed by the
|
||||
DWARF producer as specified in the DWARF standard. Each
|
||||
Type Unit contains one signature and the offset to the
|
||||
corresponding DW_AT_type DIE in its unit header.
|
||||
Describing a type can generate several DIEs. By moving
|
||||
a DIE and its related DIEs to a Type Unit and generating
|
||||
a hash of the DIEs and attributes in a flattened form
|
||||
multiple Compile Units in a linked object can reference
|
||||
the same DIE in the overall DWARF structure.
|
||||
In DWARF v4 type units are identified by their appearance in the
|
||||
.debug_types section.
|
||||
"""
|
||||
tu = self._type_units_by_sig.get(sig8)
|
||||
if tu is None:
|
||||
raise KeyError("Signature %016x not found in .debug_types" % sig8)
|
||||
return tu._get_cached_DIE(tu.tu_offset + tu['type_offset'])
|
||||
|
||||
def get_CU_containing(self, refaddr: int) -> CompileUnit:
|
||||
""" Find the CU that includes the given reference address in the
|
||||
.debug_info section.
|
||||
|
||||
refaddr:
|
||||
Either a refaddr of a DIE (possibly from a DW_FORM_ref_addr
|
||||
attribute) or the section offset of a CU (possibly from an
|
||||
aranges table).
|
||||
|
||||
This function will parse and cache CUs until the search criteria
|
||||
is met, starting from the closest known offset lessthan or equal
|
||||
to the given address.
|
||||
"""
|
||||
dwarf_assert(
|
||||
self.has_debug_info,
|
||||
'CU lookup but no debug info section')
|
||||
assert self.debug_info_sec is not None
|
||||
dwarf_assert(
|
||||
0 <= refaddr < self.debug_info_sec.size,
|
||||
"refaddr %s beyond .debug_info size" % refaddr)
|
||||
|
||||
# The CU containing the DIE we desire will be to the right of the
|
||||
# DIE insert point. If we have a CU address, then it will be a
|
||||
# match but the right insert minus one will still be the item.
|
||||
# The first CU starts at offset 0, so start there if cache is empty.
|
||||
i = bisect_right(self._cu_offsets_map, refaddr)
|
||||
start = self._cu_offsets_map[i - 1] if i > 0 else 0
|
||||
|
||||
# parse CUs until we find one containing the desired address
|
||||
for cu in self._parse_CUs_iter(start):
|
||||
if cu.cu_offset <= refaddr < cu.cu_offset + cu.size:
|
||||
return cu
|
||||
|
||||
raise ValueError("CU for reference address %s not found" % refaddr)
|
||||
|
||||
def get_CU_at(self, offset: int) -> CompileUnit:
|
||||
""" Given a CU header offset, return the parsed CU.
|
||||
|
||||
offset:
|
||||
The offset may be from an accelerated access table such as
|
||||
the public names, public types, address range table, or
|
||||
prior use.
|
||||
|
||||
This function will directly parse the CU doing no validation of
|
||||
the offset beyond checking the size of the .debug_info section.
|
||||
"""
|
||||
dwarf_assert(
|
||||
self.has_debug_info,
|
||||
'CU lookup but no debug info section')
|
||||
assert self.debug_info_sec is not None
|
||||
dwarf_assert(
|
||||
0 <= offset < self.debug_info_sec.size,
|
||||
"offset %s beyond .debug_info size" % offset)
|
||||
|
||||
return self._cached_CU_at_offset(offset)
|
||||
|
||||
def get_TU_by_sig8(self, sig8: int) -> TypeUnit:
|
||||
""" Find and return a Type Unit referenced by its signature
|
||||
|
||||
sig8:
|
||||
The 8 byte unique signature (as a 64-bit unsigned integer)
|
||||
|
||||
Returns the TU with the given type signature by parsing the
|
||||
.debug_types section.
|
||||
|
||||
"""
|
||||
tu = self._type_units_by_sig.get(sig8)
|
||||
if tu is None:
|
||||
raise KeyError("Signature %016x not found in .debug_types" % sig8)
|
||||
return tu
|
||||
|
||||
def iter_CUs(self) -> Iterator[CompileUnit]:
|
||||
""" Yield all the compile units (CompileUnit objects) in the debug info
|
||||
"""
|
||||
return self._parse_CUs_iter()
|
||||
|
||||
def iter_TUs(self) -> Iterator[TypeUnit]:
|
||||
"""Yield all the type units (TypeUnit objects) in the debug_types
|
||||
"""
|
||||
return self._parse_TUs_iter()
|
||||
|
||||
def get_abbrev_table(self, offset: int) -> AbbrevTable:
|
||||
""" Get an AbbrevTable from the given offset in the debug_abbrev
|
||||
section.
|
||||
|
||||
The only verification done on the offset is that it's within the
|
||||
bounds of the section (if not, an exception is raised).
|
||||
It is the caller's responsibility to make sure the offset actually
|
||||
points to a valid abbreviation table.
|
||||
|
||||
AbbrevTable objects are cached internally (two calls for the same
|
||||
offset will return the same object).
|
||||
"""
|
||||
assert self.debug_abbrev_sec is not None
|
||||
dwarf_assert(
|
||||
offset < self.debug_abbrev_sec.size,
|
||||
"Offset '0x%x' to abbrev table out of section bounds" % offset)
|
||||
if offset not in self._abbrevtable_cache:
|
||||
self._abbrevtable_cache[offset] = AbbrevTable(
|
||||
structs=self.structs,
|
||||
stream=self.debug_abbrev_sec.stream,
|
||||
offset=offset)
|
||||
return self._abbrevtable_cache[offset]
|
||||
|
||||
def get_string_from_table(self, offset: int) -> bytes | None:
|
||||
""" Obtain a string from the string table section, given an offset
|
||||
relative to the section.
|
||||
"""
|
||||
assert self.debug_str_sec is not None
|
||||
return parse_cstring_from_stream(self.debug_str_sec.stream, offset)
|
||||
|
||||
def get_string_from_linetable(self, offset: int) -> bytes | None:
|
||||
""" Obtain a string from the string table section, given an offset
|
||||
relative to the section.
|
||||
"""
|
||||
assert self.debug_line_str_sec is not None
|
||||
return parse_cstring_from_stream(self.debug_line_str_sec.stream, offset)
|
||||
|
||||
def line_program_for_CU(self, CU: CompileUnit) -> LineProgram | None:
|
||||
""" Given a CU object, fetch the line program it points to from the
|
||||
.debug_line section.
|
||||
If the CU doesn't point to a line program, return None.
|
||||
|
||||
Note about directory and file names. They are returned as two collections
|
||||
in the lineprogram object's header - include_directory and file_entry.
|
||||
|
||||
In DWARFv5, they have introduced a different, extensible format for those
|
||||
collections. So in a lineprogram v5+, there are two more collections in
|
||||
the header - directories and file_names. Those might contain extra DWARFv5
|
||||
information that is not exposed in include_directory and file_entry.
|
||||
"""
|
||||
# The line program is pointed to by the DW_AT_stmt_list attribute of
|
||||
# the top DIE of a CU.
|
||||
top_DIE = CU.get_top_DIE()
|
||||
if 'DW_AT_stmt_list' in top_DIE.attributes:
|
||||
return self._parse_line_program_at_offset(
|
||||
top_DIE.attributes['DW_AT_stmt_list'].value, CU.structs)
|
||||
else:
|
||||
return None
|
||||
|
||||
def has_CFI(self) -> bool:
|
||||
""" Does this dwarf info have a dwarf_frame CFI section?
|
||||
"""
|
||||
return self.debug_frame_sec is not None
|
||||
|
||||
def CFI_entries(self) -> list[CFIEntry | ZERO]:
|
||||
""" Get a list of dwarf_frame CFI entries from the .debug_frame section.
|
||||
"""
|
||||
assert self.debug_frame_sec is not None
|
||||
cfi = CallFrameInfo(
|
||||
stream=self.debug_frame_sec.stream,
|
||||
size=self.debug_frame_sec.size,
|
||||
address=self.debug_frame_sec.address,
|
||||
base_structs=self.structs)
|
||||
return cfi.get_entries()
|
||||
|
||||
def has_EH_CFI(self) -> bool:
|
||||
""" Does this dwarf info have a eh_frame CFI section?
|
||||
"""
|
||||
return self.eh_frame_sec is not None
|
||||
|
||||
def EH_CFI_entries(self) -> list[CFIEntry | ZERO]:
|
||||
""" Get a list of eh_frame CFI entries from the .eh_frame section.
|
||||
"""
|
||||
assert self.eh_frame_sec is not None
|
||||
cfi = CallFrameInfo(
|
||||
stream=self.eh_frame_sec.stream,
|
||||
size=self.eh_frame_sec.size,
|
||||
address=self.eh_frame_sec.address,
|
||||
base_structs=self.structs,
|
||||
for_eh_frame=True)
|
||||
return cfi.get_entries()
|
||||
|
||||
def get_pubtypes(self) -> NameLUT | None:
|
||||
"""
|
||||
Returns a NameLUT object that contains information read from the
|
||||
.debug_pubtypes section in the ELF file.
|
||||
|
||||
NameLUT is essentially a dictionary containing the CU/DIE offsets of
|
||||
each symbol. See the NameLUT doc string for more details.
|
||||
"""
|
||||
|
||||
if self.debug_pubtypes_sec:
|
||||
return NameLUT(self.debug_pubtypes_sec.stream,
|
||||
self.debug_pubtypes_sec.size,
|
||||
self.structs)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_pubnames(self) -> NameLUT | None:
|
||||
"""
|
||||
Returns a NameLUT object that contains information read from the
|
||||
.debug_pubnames section in the ELF file.
|
||||
|
||||
NameLUT is essentially a dictionary containing the CU/DIE offsets of
|
||||
each symbol. See the NameLUT doc string for more details.
|
||||
"""
|
||||
|
||||
if self.debug_pubnames_sec:
|
||||
return NameLUT(self.debug_pubnames_sec.stream,
|
||||
self.debug_pubnames_sec.size,
|
||||
self.structs)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_aranges(self) -> ARanges | None:
|
||||
""" Get an ARanges object representing the .debug_aranges section of
|
||||
the DWARF data, or None if the section doesn't exist
|
||||
"""
|
||||
if self.debug_aranges_sec:
|
||||
return ARanges(self.debug_aranges_sec.stream,
|
||||
self.debug_aranges_sec.size,
|
||||
self.structs)
|
||||
else:
|
||||
return None
|
||||
|
||||
def location_lists(self) -> LocationLists | LocationListsPair | None:
|
||||
""" Get a LocationLists object representing the .debug_loc/debug_loclists section of
|
||||
the DWARF data, or None if this section doesn't exist.
|
||||
|
||||
If both sections exist, it returns a LocationListsPair.
|
||||
"""
|
||||
if self.debug_loclists_sec and self.debug_loc_sec is None:
|
||||
return LocationLists(self.debug_loclists_sec.stream, self.structs, 5, self)
|
||||
elif self.debug_loc_sec and self.debug_loclists_sec is None:
|
||||
return LocationLists(self.debug_loc_sec.stream, self.structs, 4, self)
|
||||
elif self.debug_loc_sec and self.debug_loclists_sec:
|
||||
return LocationListsPair(self.debug_loc_sec.stream, self.debug_loclists_sec.stream, self.structs, self)
|
||||
else:
|
||||
return None
|
||||
|
||||
def range_lists(self) -> RangeLists | RangeListsPair | None:
|
||||
""" Get a RangeLists object representing the .debug_ranges/.debug_rnglists section of
|
||||
the DWARF data, or None if this section doesn't exist.
|
||||
|
||||
If both sections exist, it returns a RangeListsPair.
|
||||
"""
|
||||
if self.debug_rnglists_sec and self.debug_ranges_sec is None:
|
||||
return RangeLists(self.debug_rnglists_sec.stream, self.structs, 5, self)
|
||||
elif self.debug_ranges_sec and self.debug_rnglists_sec is None:
|
||||
return RangeLists(self.debug_ranges_sec.stream, self.structs, 4, self)
|
||||
elif self.debug_ranges_sec and self.debug_rnglists_sec:
|
||||
return RangeListsPair(self.debug_ranges_sec.stream, self.debug_rnglists_sec.stream, self.structs, self)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_addr(self, cu: CompileUnit | TypeUnit, addr_index: int) -> int:
|
||||
"""Provided a CU and an index, retrieves an address from the debug_addr section
|
||||
"""
|
||||
if not self.debug_addr_sec:
|
||||
raise DWARFError('The file does not contain a debug_addr section for indirect address access')
|
||||
# Selectors are not supported, but no assert on that. TODO?
|
||||
cu_addr_base = _get_base_offset(cu, 'DW_AT_addr_base')
|
||||
return struct_parse(cu.structs.the_Dwarf_target_addr, self.debug_addr_sec.stream, cu_addr_base + addr_index*cu.header.address_size)
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def _parse_CUs_iter(self, offset: int = 0) -> Iterator[CompileUnit]:
|
||||
""" Iterate CU objects in order of appearance in the debug_info section.
|
||||
|
||||
offset:
|
||||
The offset of the first CU to yield. Additional iterations
|
||||
will return the sequential unit objects.
|
||||
|
||||
See .iter_CUs(), .get_CU_containing(), and .get_CU_at().
|
||||
"""
|
||||
if self.debug_info_sec is None:
|
||||
return
|
||||
|
||||
while offset < self.debug_info_sec.size:
|
||||
cu = self._cached_CU_at_offset(offset)
|
||||
# Compute the offset of the next CU in the section. The unit_length
|
||||
# field of the CU header contains its size not including the length
|
||||
# field itself.
|
||||
offset = (offset +
|
||||
cu['unit_length'] +
|
||||
cu.structs.initial_length_field_size())
|
||||
yield cu
|
||||
|
||||
def _parse_TUs_iter(self, offset: int = 0) -> Iterator[TypeUnit]:
|
||||
""" Iterate Type Unit objects in order of appearance in the debug_types section.
|
||||
|
||||
offset:
|
||||
The offset of the first TU to yield. Additional iterations
|
||||
will return the sequential unit objects.
|
||||
|
||||
See .iter_TUs().
|
||||
"""
|
||||
if self.debug_types_sec is None:
|
||||
return
|
||||
|
||||
while offset < self.debug_types_sec.size:
|
||||
tu = self._parse_TU_at_offset(offset)
|
||||
# Compute the offset of the next TU in the section. The unit_length
|
||||
# field of the TU header contains its size not including the length
|
||||
# field itself.
|
||||
offset = (offset +
|
||||
tu['unit_length'] +
|
||||
tu.structs.initial_length_field_size())
|
||||
|
||||
yield tu
|
||||
|
||||
@cached_property
|
||||
def _type_units_by_sig(self) -> dict[int, TypeUnit]:
|
||||
""" Check if the .debug_types section is previously parsed. If not,
|
||||
parse all TUs and store them in an ordered dict using their unique
|
||||
64-bit signature as the key.
|
||||
|
||||
See .get_TU_by_sig8().
|
||||
"""
|
||||
if self.debug_types_sec is None:
|
||||
return {}
|
||||
|
||||
# Parse all the Type Units in the types section for access by sig8
|
||||
units = {}
|
||||
offset = 0
|
||||
while offset < self.debug_types_sec.size:
|
||||
tu = self._parse_TU_at_offset(offset)
|
||||
# Compute the offset of the next TU in the section. The unit_length
|
||||
# field of the TU header contains its size not including the length
|
||||
# field itself.
|
||||
offset += tu['unit_length'] + tu.structs.initial_length_field_size()
|
||||
units[tu['signature']] = tu
|
||||
|
||||
return units
|
||||
|
||||
def _cached_CU_at_offset(self, offset: int) -> CompileUnit:
|
||||
""" Return the CU with unit header at the given offset into the
|
||||
debug_info section from the cache. If not present, the unit is
|
||||
header is parsed and the object is installed in the cache.
|
||||
|
||||
offset:
|
||||
The offset of the unit header in the .debug_info section
|
||||
to of the unit to fetch from the cache.
|
||||
|
||||
See get_CU_at().
|
||||
"""
|
||||
# Find the insert point for the requested offset. With bisect_right,
|
||||
# if this entry is present in the cache it will be the prior entry.
|
||||
i = bisect_right(self._cu_offsets_map, offset)
|
||||
if i >= 1 and offset == self._cu_offsets_map[i - 1]:
|
||||
return self._cu_cache[i - 1]
|
||||
|
||||
# Parse the CU and insert the offset and object into the cache.
|
||||
# The ._cu_offsets_map[] contains just the numeric offsets for the
|
||||
# bisect_right search while the parallel indexed ._cu_cache[] holds
|
||||
# the object references.
|
||||
cu = self._parse_CU_at_offset(offset)
|
||||
self._cu_offsets_map.insert(i, offset)
|
||||
self._cu_cache.insert(i, cu)
|
||||
return cu
|
||||
|
||||
def _parse_CU_at_offset(self, offset: int) -> CompileUnit:
|
||||
""" Parse and return a CU at the given offset in the debug_info stream.
|
||||
"""
|
||||
# Section 7.4 (32-bit and 64-bit DWARF Formats) of the DWARF spec v3
|
||||
# states that the first 32-bit word of the CU header determines
|
||||
# whether the CU is represented with 32-bit or 64-bit DWARF format.
|
||||
#
|
||||
# So we peek at the first word in the CU header to determine its
|
||||
# dwarf format. Based on it, we then create a new DWARFStructs
|
||||
# instance suitable for this CU and use it to parse the rest.
|
||||
#
|
||||
assert self.debug_info_sec is not None
|
||||
initial_length = struct_parse(
|
||||
self.structs.the_Dwarf_uint32, self.debug_info_sec.stream, offset)
|
||||
dwarf_format = 64 if initial_length == 0xFFFFFFFF else 32
|
||||
|
||||
|
||||
# Temporary structs for parsing the header
|
||||
# The structs for the rest of the CU depend on the header data.
|
||||
#
|
||||
cu_structs = DWARFStructs(
|
||||
little_endian=self.config.little_endian,
|
||||
dwarf_format=dwarf_format,
|
||||
address_size=4,
|
||||
dwarf_version=2)
|
||||
|
||||
cu_header = struct_parse(
|
||||
cu_structs.Dwarf_CU_header, self.debug_info_sec.stream, offset)
|
||||
|
||||
# structs for the rest of the CU, taking into account bitness and DWARF version
|
||||
cu_structs = DWARFStructs(
|
||||
little_endian=self.config.little_endian,
|
||||
dwarf_format=dwarf_format,
|
||||
address_size=cu_header['address_size'],
|
||||
dwarf_version=cu_header['version'])
|
||||
|
||||
cu_die_offset = self.debug_info_sec.stream.tell()
|
||||
dwarf_assert(
|
||||
self._is_supported_version(cu_header['version']),
|
||||
"Expected supported DWARF version. Got '%s'" % cu_header['version'])
|
||||
return CompileUnit(
|
||||
header=cu_header,
|
||||
dwarfinfo=self,
|
||||
structs=cu_structs,
|
||||
cu_offset=offset,
|
||||
cu_die_offset=cu_die_offset)
|
||||
|
||||
def _parse_TU_at_offset(self, offset: int) -> TypeUnit:
|
||||
""" Parse and return a Type Unit (TU) at the given offset in the debug_types stream.
|
||||
"""
|
||||
# Section 7.4 (32-bit and 64-bit DWARF Formats) of the DWARF spec v4
|
||||
# states that the first 32-bit word of the TU header determines
|
||||
# whether the TU is represented with 32-bit or 64-bit DWARF format.
|
||||
#
|
||||
# So we peek at the first word in the TU header to determine its
|
||||
# dwarf format. Based on it, we then create a new DWARFStructs
|
||||
# instance suitable for this TU and use it to parse the rest.
|
||||
#
|
||||
assert self.debug_types_sec is not None
|
||||
initial_length = struct_parse(
|
||||
self.structs.the_Dwarf_uint32, self.debug_types_sec.stream, offset)
|
||||
dwarf_format = 64 if initial_length == 0xFFFFFFFF else 32
|
||||
|
||||
# Temporary structs for parsing the header
|
||||
# The structs for the rest of the TU depend on the header data.
|
||||
#
|
||||
tu_structs = DWARFStructs(
|
||||
little_endian=self.config.little_endian,
|
||||
dwarf_format=dwarf_format,
|
||||
address_size=4,
|
||||
dwarf_version=2)
|
||||
|
||||
tu_header = struct_parse(
|
||||
tu_structs.Dwarf_TU_header, self.debug_types_sec.stream, offset)
|
||||
|
||||
# structs for the rest of the TU, taking into account bit-width and DWARF version
|
||||
tu_structs = DWARFStructs(
|
||||
little_endian=self.config.little_endian,
|
||||
dwarf_format=dwarf_format,
|
||||
address_size=tu_header['address_size'],
|
||||
dwarf_version=tu_header['version'])
|
||||
|
||||
tu_die_offset = self.debug_types_sec.stream.tell()
|
||||
dwarf_assert(
|
||||
self._is_supported_version(tu_header['version']),
|
||||
"Expected supported DWARF version. Got '%s'" % tu_header['version'])
|
||||
return TypeUnit(
|
||||
header=tu_header,
|
||||
dwarfinfo=self,
|
||||
structs=tu_structs,
|
||||
tu_offset=offset,
|
||||
tu_die_offset=tu_die_offset)
|
||||
|
||||
def _is_supported_version(self, version: int) -> bool:
|
||||
""" DWARF version supported by this parser
|
||||
"""
|
||||
return 2 <= version <= 5
|
||||
|
||||
def _parse_line_program_at_offset(self, offset: int, structs: DWARFStructs) -> LineProgram:
|
||||
""" Given an offset to the .debug_line section, parse the line program
|
||||
starting at this offset in the section and return it.
|
||||
structs is the DWARFStructs object used to do this parsing.
|
||||
"""
|
||||
|
||||
if offset in self._linetable_cache:
|
||||
return self._linetable_cache[offset]
|
||||
|
||||
assert self.debug_line_sec is not None
|
||||
lineprog_header = struct_parse(
|
||||
structs.Dwarf_lineprog_header,
|
||||
self.debug_line_sec.stream,
|
||||
offset)
|
||||
|
||||
# DWARF5: resolve names
|
||||
def resolve_strings(
|
||||
lineprog_header: Container,
|
||||
format_field: str,
|
||||
data_field: str,
|
||||
) -> None:
|
||||
if lineprog_header.get(format_field, False):
|
||||
data = lineprog_header[data_field]
|
||||
for field in lineprog_header[format_field]:
|
||||
|
||||
def replace_value(
|
||||
data: ListContainer,
|
||||
content_type: str,
|
||||
replacer: Callable[[int], bytes | None],
|
||||
) -> None:
|
||||
for entry in data:
|
||||
entry[content_type] = replacer(entry[content_type])
|
||||
|
||||
if field.form == 'DW_FORM_line_strp':
|
||||
replace_value(data, field.content_type, self.get_string_from_linetable)
|
||||
elif field.form == 'DW_FORM_strp':
|
||||
replace_value(data, field.content_type, self.get_string_from_table)
|
||||
elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_GNU_strp_alt'):
|
||||
if self.supplementary_dwarfinfo:
|
||||
replace_value(data, field.content_type, self.supplementary_dwarfinfo.get_string_from_table)
|
||||
else:
|
||||
replace_value(data, field.content_type, lambda x: str(x).encode())
|
||||
elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4'):
|
||||
raise NotImplementedError()
|
||||
|
||||
resolve_strings(lineprog_header, 'directory_entry_format', 'directories')
|
||||
resolve_strings(lineprog_header, 'file_name_entry_format', 'file_names')
|
||||
|
||||
# DWARF5: provide compatible file/directory name arrays for legacy lineprogram consumers
|
||||
if lineprog_header.get('directories', False):
|
||||
lineprog_header.include_directory = tuple(d.DW_LNCT_path for d in lineprog_header.directories)
|
||||
if lineprog_header.get('file_names', False):
|
||||
lineprog_header.file_entry = tuple(
|
||||
Container(**{
|
||||
'name':e.get('DW_LNCT_path'),
|
||||
'dir_index': e.get('DW_LNCT_directory_index'),
|
||||
'mtime': e.get('DW_LNCT_timestamp'),
|
||||
'length': e.get('DW_LNCT_size')})
|
||||
for e in lineprog_header.file_names)
|
||||
|
||||
# Calculate the offset to the next line program (see DWARF 6.2.4)
|
||||
end_offset = ( offset + lineprog_header['unit_length'] +
|
||||
structs.initial_length_field_size())
|
||||
|
||||
lineprogram = LineProgram(
|
||||
header=lineprog_header,
|
||||
stream=self.debug_line_sec.stream,
|
||||
structs=structs,
|
||||
program_start_offset=self.debug_line_sec.stream.tell(),
|
||||
program_end_offset=end_offset)
|
||||
|
||||
self._linetable_cache[offset] = lineprogram
|
||||
return lineprogram
|
||||
|
||||
def parse_debugsupinfo(self) -> bytes | None:
|
||||
"""
|
||||
Extract a filename from .debug_sup, .gnu_debualtlink sections.
|
||||
"""
|
||||
if self.debug_sup_sec is not None:
|
||||
self.debug_sup_sec.stream.seek(0)
|
||||
suplink = self.structs.Dwarf_debugsup.parse_stream(self.debug_sup_sec.stream)
|
||||
if suplink.is_supplementary == 0:
|
||||
return suplink.sup_filename
|
||||
if self.gnu_debugaltlink_sec is not None:
|
||||
self.gnu_debugaltlink_sec.stream.seek(0)
|
||||
suplink = self.structs.Dwarf_debugaltlink.parse_stream(self.gnu_debugaltlink_sec.stream)
|
||||
return suplink.sup_filename
|
||||
# The section .gnu_debuglink with similarly looking contents
|
||||
# has a different meaning - it doesn't point at supplementary DWARF,
|
||||
# which is meant to be referenced from primary DWARF,
|
||||
# it points at DWARF proper.
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,600 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/enums.py
|
||||
#
|
||||
# Mappings of enum names to values
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..construct import Pass
|
||||
|
||||
|
||||
ENUM_DW_TAG = dict(
|
||||
DW_TAG_null = 0x00,
|
||||
DW_TAG_array_type = 0x01,
|
||||
DW_TAG_class_type = 0x02,
|
||||
DW_TAG_entry_point = 0x03,
|
||||
DW_TAG_enumeration_type = 0x04,
|
||||
DW_TAG_formal_parameter = 0x05,
|
||||
DW_TAG_global_subroutine = 0x06,
|
||||
DW_TAG_global_variable = 0x07,
|
||||
DW_TAG_imported_declaration = 0x08,
|
||||
DW_TAG_label = 0x0a,
|
||||
DW_TAG_lexical_block = 0x0b,
|
||||
DW_TAG_local_variable = 0x0c,
|
||||
DW_TAG_member = 0x0d,
|
||||
DW_TAG_pointer_type = 0x0f,
|
||||
DW_TAG_reference_type = 0x10,
|
||||
DW_TAG_compile_unit = 0x11,
|
||||
DW_TAG_string_type = 0x12,
|
||||
DW_TAG_structure_type = 0x13,
|
||||
DW_TAG_subroutine = 0x14,
|
||||
DW_TAG_subroutine_type = 0x15,
|
||||
DW_TAG_typedef = 0x16,
|
||||
DW_TAG_union_type = 0x17,
|
||||
DW_TAG_unspecified_parameters = 0x18,
|
||||
DW_TAG_variant = 0x19,
|
||||
DW_TAG_common_block = 0x1a,
|
||||
DW_TAG_common_inclusion = 0x1b,
|
||||
DW_TAG_inheritance = 0x1c,
|
||||
DW_TAG_inlined_subroutine = 0x1d,
|
||||
DW_TAG_module = 0x1e,
|
||||
DW_TAG_ptr_to_member_type = 0x1f,
|
||||
DW_TAG_set_type = 0x20,
|
||||
DW_TAG_subrange_type = 0x21,
|
||||
DW_TAG_with_stmt = 0x22,
|
||||
DW_TAG_access_declaration = 0x23,
|
||||
DW_TAG_base_type = 0x24,
|
||||
DW_TAG_catch_block = 0x25,
|
||||
DW_TAG_const_type = 0x26,
|
||||
DW_TAG_constant = 0x27,
|
||||
DW_TAG_enumerator = 0x28,
|
||||
DW_TAG_file_type = 0x29,
|
||||
DW_TAG_friend = 0x2a,
|
||||
DW_TAG_namelist = 0x2b,
|
||||
DW_TAG_namelist_item = 0x2c,
|
||||
DW_TAG_namelist_items = 0x2c,
|
||||
DW_TAG_packed_type = 0x2d,
|
||||
DW_TAG_subprogram = 0x2e,
|
||||
|
||||
# The DWARF standard defines these as _parameter, not _param, but we
|
||||
# maintain compatibility with readelf.
|
||||
DW_TAG_template_type_param = 0x2f,
|
||||
DW_TAG_template_value_param = 0x30,
|
||||
|
||||
DW_TAG_thrown_type = 0x31,
|
||||
DW_TAG_try_block = 0x32,
|
||||
DW_TAG_variant_part = 0x33,
|
||||
DW_TAG_variable = 0x34,
|
||||
DW_TAG_volatile_type = 0x35,
|
||||
DW_TAG_dwarf_procedure = 0x36,
|
||||
DW_TAG_restrict_type = 0x37,
|
||||
DW_TAG_interface_type = 0x38,
|
||||
DW_TAG_namespace = 0x39,
|
||||
DW_TAG_imported_module = 0x3a,
|
||||
DW_TAG_unspecified_type = 0x3b,
|
||||
DW_TAG_partial_unit = 0x3c,
|
||||
DW_TAG_imported_unit = 0x3d,
|
||||
DW_TAG_mutable_type = 0x3e,
|
||||
DW_TAG_condition = 0x3f,
|
||||
DW_TAG_shared_type = 0x40,
|
||||
DW_TAG_type_unit = 0x41,
|
||||
DW_TAG_rvalue_reference_type = 0x42,
|
||||
DW_TAG_template_alias = 0x43,
|
||||
DW_TAG_coarray_type = 0x44,
|
||||
DW_TAG_generic_subrange = 0x45,
|
||||
DW_TAG_dynamic_type = 0x46,
|
||||
DW_TAG_atomic_type = 0x47,
|
||||
DW_TAG_call_site = 0x48,
|
||||
DW_TAG_call_site_parameter = 0x49,
|
||||
DW_TAG_skeleton_unit = 0x4a,
|
||||
DW_TAG_immutable_type = 0x4b,
|
||||
|
||||
# Tags between 0x4080 and 0xffff are user-defined.
|
||||
# different implementations may overlap?
|
||||
|
||||
DW_TAG_lo_user = 0x4080,
|
||||
DW_TAG_GNU_template_template_param = 0x4106,
|
||||
DW_TAG_GNU_template_parameter_pack = 0x4107,
|
||||
DW_TAG_GNU_formal_parameter_pack = 0x4108,
|
||||
DW_TAG_GNU_call_site = 0x4109,
|
||||
DW_TAG_GNU_call_site_parameter = 0x410a,
|
||||
|
||||
DW_TAG_APPLE_property = 0x4200,
|
||||
|
||||
DW_TAG_hi_user = 0xffff,
|
||||
|
||||
_default_ = Pass,
|
||||
)
|
||||
|
||||
|
||||
ENUM_DW_CHILDREN = dict(
|
||||
DW_CHILDREN_no = 0x00,
|
||||
DW_CHILDREN_yes = 0x01,
|
||||
)
|
||||
|
||||
|
||||
ENUM_DW_AT = dict(
|
||||
DW_AT_null = 0x00,
|
||||
DW_AT_sibling = 0x01,
|
||||
DW_AT_location = 0x02,
|
||||
DW_AT_name = 0x03,
|
||||
DW_AT_fund_type = 0x05,
|
||||
DW_AT_mod_fund_type = 0x06,
|
||||
DW_AT_user_def_type = 0x07,
|
||||
DW_AT_mod_u_d_type = 0x08,
|
||||
DW_AT_ordering = 0x09,
|
||||
DW_AT_subscr_data = 0x0a,
|
||||
DW_AT_byte_size = 0x0b,
|
||||
DW_AT_bit_offset = 0x0c,
|
||||
DW_AT_bit_size = 0x0d,
|
||||
DW_AT_element_list = 0x0f,
|
||||
DW_AT_stmt_list = 0x10,
|
||||
DW_AT_low_pc = 0x11,
|
||||
DW_AT_high_pc = 0x12,
|
||||
DW_AT_language = 0x13,
|
||||
DW_AT_member = 0x14,
|
||||
DW_AT_discr = 0x15,
|
||||
DW_AT_discr_value = 0x16,
|
||||
DW_AT_visibility = 0x17,
|
||||
DW_AT_import = 0x18,
|
||||
DW_AT_string_length = 0x19,
|
||||
DW_AT_common_reference = 0x1a,
|
||||
DW_AT_comp_dir = 0x1b,
|
||||
DW_AT_const_value = 0x1c,
|
||||
DW_AT_containing_type = 0x1d,
|
||||
DW_AT_default_value = 0x1e,
|
||||
DW_AT_friends = 0x1f,
|
||||
DW_AT_inline = 0x20,
|
||||
DW_AT_is_optional = 0x21,
|
||||
DW_AT_lower_bound = 0x22,
|
||||
DW_AT_program = 0x23,
|
||||
DW_AT_private = 0x24,
|
||||
DW_AT_producer = 0x25,
|
||||
DW_AT_protected = 0x26,
|
||||
DW_AT_prototyped = 0x27,
|
||||
DW_AT_public = 0x28,
|
||||
DW_AT_pure_virtual = 0x29,
|
||||
DW_AT_return_addr = 0x2a,
|
||||
# In DWARFv1, DW_AT_specification was at 0x2b, moved to 0x47 in v2
|
||||
DW_AT_start_scope = 0x2c,
|
||||
DW_AT_bit_stride = 0x2e,
|
||||
DW_AT_stride_size = 0x2e,
|
||||
DW_AT_upper_bound = 0x2f,
|
||||
DW_AT_virtual = 0x30,
|
||||
DW_AT_abstract_origin = 0x31,
|
||||
DW_AT_accessibility = 0x32,
|
||||
DW_AT_address_class = 0x33,
|
||||
DW_AT_artificial = 0x34,
|
||||
DW_AT_base_types = 0x35,
|
||||
DW_AT_calling_convention = 0x36,
|
||||
DW_AT_count = 0x37,
|
||||
DW_AT_data_member_location = 0x38,
|
||||
DW_AT_decl_column = 0x39,
|
||||
DW_AT_decl_file = 0x3a,
|
||||
DW_AT_decl_line = 0x3b,
|
||||
DW_AT_declaration = 0x3c,
|
||||
DW_AT_discr_list = 0x3d,
|
||||
DW_AT_encoding = 0x3e,
|
||||
DW_AT_external = 0x3f,
|
||||
DW_AT_frame_base = 0x40,
|
||||
DW_AT_friend = 0x41,
|
||||
DW_AT_identifier_case = 0x42,
|
||||
DW_AT_macro_info = 0x43,
|
||||
DW_AT_namelist_item = 0x44,
|
||||
DW_AT_priority = 0x45,
|
||||
DW_AT_segment = 0x46,
|
||||
DW_AT_specification = 0x47,
|
||||
DW_AT_static_link = 0x48,
|
||||
DW_AT_type = 0x49,
|
||||
DW_AT_use_location = 0x4a,
|
||||
DW_AT_variable_parameter = 0x4b,
|
||||
DW_AT_virtuality = 0x4c,
|
||||
DW_AT_vtable_elem_location = 0x4d,
|
||||
DW_AT_allocated = 0x4e,
|
||||
DW_AT_associated = 0x4f,
|
||||
DW_AT_data_location = 0x50,
|
||||
DW_AT_byte_stride = 0x51,
|
||||
DW_AT_stride = 0x51,
|
||||
DW_AT_entry_pc = 0x52,
|
||||
DW_AT_use_UTF8 = 0x53,
|
||||
DW_AT_extension = 0x54,
|
||||
DW_AT_ranges = 0x55,
|
||||
DW_AT_trampoline = 0x56,
|
||||
DW_AT_call_column = 0x57,
|
||||
DW_AT_call_file = 0x58,
|
||||
DW_AT_call_line = 0x59,
|
||||
DW_AT_description = 0x5a,
|
||||
DW_AT_binary_scale = 0x5b,
|
||||
DW_AT_decimal_scale = 0x5c,
|
||||
DW_AT_small = 0x5d,
|
||||
DW_AT_decimal_sign = 0x5e,
|
||||
DW_AT_digit_count = 0x5f,
|
||||
DW_AT_picture_string = 0x60,
|
||||
DW_AT_mutable = 0x61,
|
||||
DW_AT_threads_scaled = 0x62,
|
||||
DW_AT_explicit = 0x63,
|
||||
DW_AT_object_pointer = 0x64,
|
||||
DW_AT_endianity = 0x65,
|
||||
DW_AT_elemental = 0x66,
|
||||
DW_AT_pure = 0x67,
|
||||
DW_AT_recursive = 0x68,
|
||||
DW_AT_signature = 0x69,
|
||||
DW_AT_main_subprogram = 0x6a,
|
||||
DW_AT_data_bit_offset = 0x6b,
|
||||
DW_AT_const_expr = 0x6c,
|
||||
DW_AT_enum_class = 0x6d,
|
||||
DW_AT_linkage_name = 0x6e,
|
||||
DW_AT_string_length_bit_size = 0x6f,
|
||||
DW_AT_string_length_byte_size = 0x70,
|
||||
DW_AT_rank = 0x71,
|
||||
DW_AT_str_offsets_base = 0x72,
|
||||
DW_AT_addr_base = 0x73,
|
||||
DW_AT_rnglists_base = 0x74,
|
||||
DW_AT_dwo_name = 0x76,
|
||||
DW_AT_reference = 0x77,
|
||||
DW_AT_rvalue_reference = 0x78,
|
||||
DW_AT_macros = 0x79,
|
||||
DW_AT_call_all_calls = 0x7a,
|
||||
DW_AT_call_all_source_calls = 0x7b,
|
||||
DW_AT_call_all_tail_calls = 0x7c,
|
||||
DW_AT_call_return_pc = 0x7d,
|
||||
DW_AT_call_value = 0x7e,
|
||||
DW_AT_call_origin = 0x7f,
|
||||
DW_AT_call_parameter = 0x80,
|
||||
DW_AT_call_pc = 0x81,
|
||||
DW_AT_call_tail_call = 0x82,
|
||||
DW_AT_call_target = 0x83,
|
||||
DW_AT_call_target_clobbered = 0x84,
|
||||
DW_AT_call_data_location = 0x85,
|
||||
DW_AT_call_data_value = 0x86,
|
||||
DW_AT_noreturn = 0x87,
|
||||
DW_AT_alignment = 0x88,
|
||||
DW_AT_export_symbols = 0x89,
|
||||
DW_AT_deleted = 0x8a,
|
||||
DW_AT_defaulted = 0x8b,
|
||||
DW_AT_loclists_base = 0x8c,
|
||||
|
||||
DW_AT_MIPS_fde = 0x2001,
|
||||
DW_AT_MIPS_loop_begin = 0x2002,
|
||||
DW_AT_MIPS_tail_loop_begin = 0x2003,
|
||||
DW_AT_MIPS_epilog_begin = 0x2004,
|
||||
DW_AT_MIPS_loop_unroll_factor = 0x2005,
|
||||
DW_AT_MIPS_software_pipeline_depth = 0x2006,
|
||||
DW_AT_MIPS_linkage_name = 0x2007,
|
||||
DW_AT_MIPS_stride = 0x2008,
|
||||
DW_AT_MIPS_abstract_name = 0x2009,
|
||||
DW_AT_MIPS_clone_origin = 0x200a,
|
||||
DW_AT_MIPS_has_inlines = 0x200b,
|
||||
DW_AT_MIPS_stride_byte = 0x200c,
|
||||
DW_AT_MIPS_stride_elem = 0x200d,
|
||||
DW_AT_MIPS_ptr_dopetype = 0x200e,
|
||||
DW_AT_MIPS_allocatable_dopetype = 0x200f,
|
||||
DW_AT_MIPS_assumed_shape_dopetype = 0x2010,
|
||||
DW_AT_MIPS_assumed_size = 0x2011,
|
||||
|
||||
DW_AT_HP_opt_level = 0x2014,
|
||||
|
||||
DW_AT_sf_names = 0x2101,
|
||||
DW_AT_src_info = 0x2102,
|
||||
DW_AT_mac_info = 0x2103,
|
||||
DW_AT_src_coords = 0x2104,
|
||||
DW_AT_body_begin = 0x2105,
|
||||
DW_AT_body_end = 0x2106,
|
||||
DW_AT_GNU_vector = 0x2107,
|
||||
DW_AT_GNU_template_name = 0x2110,
|
||||
DW_AT_GNU_odr_signature = 0x210f,
|
||||
|
||||
DW_AT_GNU_call_site_value = 0x2111,
|
||||
DW_AT_GNU_call_site_data_value = 0x2112,
|
||||
DW_AT_GNU_call_site_target = 0x2113,
|
||||
DW_AT_GNU_call_site_target_clobbered = 0x2114,
|
||||
DW_AT_GNU_tail_call = 0x2115,
|
||||
DW_AT_GNU_all_tail_call_sites = 0x2116,
|
||||
DW_AT_GNU_all_call_sites = 0x2117,
|
||||
DW_AT_GNU_all_source_call_sites = 0x2118,
|
||||
DW_AT_GNU_macros = 0x2119,
|
||||
DW_AT_GNU_deleted = 0x211a,
|
||||
DW_AT_GNU_dwo_name = 0x2130,
|
||||
DW_AT_GNU_dwo_id = 0x2131,
|
||||
DW_AT_GNU_ranges_base = 0x2132,
|
||||
DW_AT_GNU_addr_base = 0x2133,
|
||||
DW_AT_GNU_pubnames = 0x2134,
|
||||
DW_AT_GNU_pubtypes = 0x2135,
|
||||
DW_AT_GNU_discriminator = 0x2136,
|
||||
DW_AT_GNU_locviews = 0x2137,
|
||||
DW_AT_GNU_entry_view = 0x2138,
|
||||
|
||||
DW_AT_LLVM_include_path = 0x3e00,
|
||||
DW_AT_LLVM_config_macros = 0x3e01,
|
||||
DW_AT_LLVM_isysroot = 0x3e02, # sysroot elsewhere
|
||||
DW_AT_LLVM_tag_offset = 0x3e03,
|
||||
DW_AT_LLVM_apinotes = 0x3e07,
|
||||
|
||||
DW_AT_APPLE_optimized = 0x3fe1,
|
||||
DW_AT_APPLE_flags = 0x3fe2,
|
||||
DW_AT_APPLE_isa = 0x3fe3,
|
||||
DW_AT_APPLE_block = 0x3fe4,
|
||||
DW_AT_APPLE_major_runtime_vers = 0x3fe5,
|
||||
DW_AT_APPLE_runtime_class = 0x3fe6,
|
||||
DW_AT_APPLE_omit_frame_ptr = 0x3fe7,
|
||||
DW_AT_APPLE_property_name = 0x3fe8,
|
||||
DW_AT_APPLE_property_getter = 0x3fe9,
|
||||
DW_AT_APPLE_property_setter = 0x3fea,
|
||||
DW_AT_APPLE_property_attribute = 0x3feb,
|
||||
DW_AT_APPLE_objc_complete_type = 0x3fec,
|
||||
DW_AT_APPLE_property = 0x3fed,
|
||||
DW_AT_APPLE_objc_direct = 0x3fee,
|
||||
DW_AT_APPLE_sdk = 0x3fef,
|
||||
|
||||
_default_ = Pass,
|
||||
)
|
||||
|
||||
|
||||
ENUM_DW_FORM = dict(
|
||||
DW_FORM_null = 0x00,
|
||||
DW_FORM_addr = 0x01,
|
||||
DW_FORM_ref = 0x02,
|
||||
DW_FORM_block2 = 0x03,
|
||||
DW_FORM_block4 = 0x04,
|
||||
DW_FORM_data2 = 0x05,
|
||||
DW_FORM_data4 = 0x06,
|
||||
DW_FORM_data8 = 0x07,
|
||||
DW_FORM_string = 0x08,
|
||||
DW_FORM_block = 0x09,
|
||||
DW_FORM_block1 = 0x0a,
|
||||
DW_FORM_data1 = 0x0b,
|
||||
DW_FORM_flag = 0x0c,
|
||||
DW_FORM_sdata = 0x0d,
|
||||
DW_FORM_strp = 0x0e,
|
||||
DW_FORM_udata = 0x0f,
|
||||
DW_FORM_ref_addr = 0x10,
|
||||
DW_FORM_ref1 = 0x11,
|
||||
DW_FORM_ref2 = 0x12,
|
||||
DW_FORM_ref4 = 0x13,
|
||||
DW_FORM_ref8 = 0x14,
|
||||
DW_FORM_ref_udata = 0x15,
|
||||
DW_FORM_indirect = 0x16,
|
||||
DW_FORM_sec_offset = 0x17,
|
||||
DW_FORM_exprloc = 0x18,
|
||||
DW_FORM_flag_present = 0x19,
|
||||
DW_FORM_strx = 0x1a,
|
||||
DW_FORM_addrx = 0x1b,
|
||||
DW_FORM_ref_sup4 = 0x1c,
|
||||
DW_FORM_strp_sup = 0x1d,
|
||||
DW_FORM_data16 = 0x1e,
|
||||
DW_FORM_line_strp = 0x1f,
|
||||
DW_FORM_ref_sig8 = 0x20,
|
||||
DW_FORM_implicit_const = 0x21,
|
||||
DW_FORM_loclistx = 0x22,
|
||||
DW_FORM_rnglistx = 0x23,
|
||||
DW_FORM_ref_sup8 = 0x24,
|
||||
DW_FORM_strx1 = 0x25,
|
||||
DW_FORM_strx2 = 0x26,
|
||||
DW_FORM_strx3 = 0x27,
|
||||
DW_FORM_strx4 = 0x28,
|
||||
DW_FORM_addrx1 = 0x29,
|
||||
DW_FORM_addrx2 = 0x2a,
|
||||
DW_FORM_addrx3 = 0x2b,
|
||||
DW_FORM_addrx4 = 0x2c,
|
||||
|
||||
DW_FORM_GNU_addr_index = 0x1f01,
|
||||
DW_FORM_GNU_str_index = 0x1f02,
|
||||
DW_FORM_GNU_ref_alt = 0x1f20,
|
||||
DW_FORM_GNU_strp_alt = 0x1f21,
|
||||
_default_ = Pass,
|
||||
)
|
||||
|
||||
# Inverse mapping for ENUM_DW_FORM
|
||||
DW_FORM_raw2name = dict((v, k) for k, v in ENUM_DW_FORM.items())
|
||||
|
||||
# See http://www.airs.com/blog/archives/460
|
||||
DW_EH_encoding_flags = dict(
|
||||
DW_EH_PE_absptr = 0x00,
|
||||
DW_EH_PE_uleb128 = 0x01,
|
||||
DW_EH_PE_udata2 = 0x02,
|
||||
DW_EH_PE_udata4 = 0x03,
|
||||
DW_EH_PE_udata8 = 0x04,
|
||||
|
||||
DW_EH_PE_signed = 0x08,
|
||||
DW_EH_PE_sleb128 = 0x09,
|
||||
DW_EH_PE_sdata2 = 0x0a,
|
||||
DW_EH_PE_sdata4 = 0x0b,
|
||||
DW_EH_PE_sdata8 = 0x0c,
|
||||
|
||||
DW_EH_PE_pcrel = 0x10,
|
||||
DW_EH_PE_textrel = 0x20,
|
||||
DW_EH_PE_datarel = 0x30,
|
||||
DW_EH_PE_funcrel = 0x40,
|
||||
DW_EH_PE_aligned = 0x50,
|
||||
DW_EH_PE_indirect = 0x80,
|
||||
|
||||
DW_EH_PE_omit = 0xff,
|
||||
)
|
||||
|
||||
ENUM_DW_LNCT = dict(
|
||||
DW_LNCT_path = 0x1,
|
||||
DW_LNCT_directory_index = 0x2,
|
||||
DW_LNCT_timestamp = 0x3,
|
||||
DW_LNCT_size = 0x4,
|
||||
DW_LNCT_MD5 = 0x5,
|
||||
DW_LNCT_lo_user = 0x2000,
|
||||
DW_LNCT_LLVM_source = 0x2001,
|
||||
DW_LNCT_LLVM_is_MD5 = 0x2002,
|
||||
DW_LNCT_hi_user = 0x3fff
|
||||
)
|
||||
|
||||
ENUM_DW_UT = dict(
|
||||
DW_UT_compile = 0x01,
|
||||
DW_UT_type = 0x02,
|
||||
DW_UT_partial = 0x03,
|
||||
DW_UT_skeleton = 0x04,
|
||||
DW_UT_split_compile = 0x05,
|
||||
DW_UT_split_type = 0x06,
|
||||
DW_UT_lo_user = 0x80,
|
||||
DW_UT_hi_user = 0xff
|
||||
)
|
||||
|
||||
ENUM_DW_LLE = dict(
|
||||
DW_LLE_end_of_list = 0x00,
|
||||
DW_LLE_base_addressx = 0x01,
|
||||
DW_LLE_startx_endx = 0x02,
|
||||
DW_LLE_startx_length = 0x03,
|
||||
DW_LLE_offset_pair = 0x04,
|
||||
DW_LLE_default_location = 0x05,
|
||||
DW_LLE_base_address = 0x06,
|
||||
DW_LLE_start_end = 0x07,
|
||||
DW_LLE_start_length = 0x08
|
||||
)
|
||||
|
||||
ENUM_DW_RLE = dict(
|
||||
DW_RLE_end_of_list = 0x00,
|
||||
DW_RLE_base_addressx = 0x01,
|
||||
DW_RLE_startx_endx = 0x02,
|
||||
DW_RLE_startx_length = 0x03,
|
||||
DW_RLE_offset_pair = 0x04,
|
||||
DW_RLE_base_address = 0x05,
|
||||
DW_RLE_start_end = 0x06,
|
||||
DW_RLE_start_length = 0x07
|
||||
)
|
||||
|
||||
# See https://dwarfstd.org/languages.html
|
||||
ENUM_DW_LANG = dict(
|
||||
# DWARF v5 and earlier
|
||||
DW_LANG_C89 = 0x0001,
|
||||
DW_LANG_C = 0x0002,
|
||||
DW_LANG_Ada83 = 0x0003,
|
||||
DW_LANG_C_plus_plus = 0x0004,
|
||||
DW_LANG_Cobol74 = 0x0005,
|
||||
DW_LANG_Cobol85 = 0x0006,
|
||||
DW_LANG_Fortran77 = 0x0007,
|
||||
DW_LANG_Fortran90 = 0x0008,
|
||||
DW_LANG_Pascal83 = 0x0009,
|
||||
DW_LANG_Modula2 = 0x000a,
|
||||
DW_LANG_Java = 0x000b,
|
||||
DW_LANG_C99 = 0x000c,
|
||||
DW_LANG_Ada95 = 0x000d,
|
||||
DW_LANG_Fortran95 = 0x000e,
|
||||
DW_LANG_PLI = 0x000f,
|
||||
DW_LANG_ObjC = 0x0010,
|
||||
DW_LANG_ObjC_plus_plus = 0x0011,
|
||||
DW_LANG_UPC = 0x0012,
|
||||
DW_LANG_D = 0x0013,
|
||||
DW_LANG_Python = 0x0014,
|
||||
DW_LANG_OpenCL = 0x0015,
|
||||
DW_LANG_Go = 0x0016,
|
||||
DW_LANG_Modula3 = 0x0017,
|
||||
DW_LANG_Haskell = 0x0018,
|
||||
DW_LANG_C_plus_plus_03 = 0x0019,
|
||||
DW_LANG_C_plus_plus_11 = 0x001a,
|
||||
DW_LANG_OCaml = 0x001b,
|
||||
DW_LANG_Rust = 0x001c,
|
||||
DW_LANG_C11 = 0x001d,
|
||||
DW_LANG_Swift = 0x001e,
|
||||
DW_LANG_Julia = 0x001f,
|
||||
DW_LANG_Dylan = 0x0020,
|
||||
DW_LANG_C_plus_plus_14 = 0x0021,
|
||||
DW_LANG_Fortran03 = 0x0022,
|
||||
DW_LANG_Fortran08 = 0x0023,
|
||||
DW_LANG_RenderScript = 0x0024,
|
||||
DW_LANG_BLISS = 0x0025,
|
||||
# After DWARF v5
|
||||
DW_LANG_Kotlin = 0x0026,
|
||||
DW_LANG_Zig = 0x0027,
|
||||
DW_LANG_Crystal = 0x0028,
|
||||
DW_LANG_C_plus_plus_17 = 0x002a,
|
||||
DW_LANG_C_plus_plus_20 = 0x002b,
|
||||
DW_LANG_C17 = 0x002c,
|
||||
DW_LANG_Fortran18 = 0x002d,
|
||||
DW_LANG_Ada2005 = 0x002e,
|
||||
DW_LANG_Ada2012 = 0x002f,
|
||||
DW_LANG_HIP = 0x0030,
|
||||
DW_LANG_Assembly = 0x0031,
|
||||
DW_LANG_C_sharp = 0x0032,
|
||||
DW_LANG_Mojo = 0x0033,
|
||||
DW_LANG_GLSL = 0x0034,
|
||||
DW_LANG_GLSL_ES = 0x0035,
|
||||
DW_LANG_HLSL = 0x0036,
|
||||
DW_LANG_OpenCL_CPP = 0x0037,
|
||||
DW_LANG_CPP_for_OpenCL = 0x0038,
|
||||
DW_LANG_SYCL = 0x0039,
|
||||
DW_LANG_C_plus_plus_23 = 0x003a,
|
||||
DW_LANG_Odin = 0x003b,
|
||||
DW_LANG_P4 = 0x003c,
|
||||
DW_LANG_Metal = 0x003d,
|
||||
DW_LANG_C23 = 0x003e,
|
||||
DW_LANG_Fortran23 = 0x003f,
|
||||
DW_LANG_Ruby = 0x0040,
|
||||
DW_LANG_Move = 0x0041,
|
||||
DW_LANG_Hylo = 0x0042,
|
||||
DW_LANG_V = 0x0043,
|
||||
DW_LANG_Algol68 = 0x0044,
|
||||
DW_LANG_Nim = 0x0045,
|
||||
DW_LANG_Erlang = 0x0046,
|
||||
DW_LANG_Elixir = 0x0047,
|
||||
DW_LANG_Gleam = 0x0048,
|
||||
# Vendor extensions range
|
||||
DW_LANG_lo_user = 0x8000,
|
||||
DW_LANG_hi_user = 0xffff,
|
||||
# Note: in absense of official names, the naming of the following
|
||||
# extensions follows what's done in the include/dwarf2.h file in the GCC
|
||||
# repo.
|
||||
# Mips extensions
|
||||
DW_LANG_Mips_Assembler = 0x8001,
|
||||
# UPC (Unified Parallel C) extensions
|
||||
DW_LANG_Upc = 0x8765, # Replaced by DW_LANG_UPC
|
||||
# HP extensions
|
||||
DW_LANG_HP_Bliss = 0x8003,
|
||||
DW_LANG_HP_Basic91 = 0x8004,
|
||||
DW_LANG_HP_Pascal91 = 0x8005,
|
||||
DW_LANG_HP_IMacro = 0x8006,
|
||||
DW_LANG_HP_Assembler = 0x8007,
|
||||
# Rust extensions
|
||||
DW_LANG_Rust_old = 0x9000 # Replaced by DW_LANG_Rust
|
||||
)
|
||||
|
||||
ENUM_DW_ATE = dict(
|
||||
DW_ATE_address = 0x01,
|
||||
DW_ATE_boolean = 0x02,
|
||||
DW_ATE_complex_float = 0x03,
|
||||
DW_ATE_float = 0x04,
|
||||
DW_ATE_signed = 0x05,
|
||||
DW_ATE_signed_char = 0x06,
|
||||
DW_ATE_unsigned = 0x07,
|
||||
DW_ATE_unsigned_char = 0x08,
|
||||
DW_ATE_imaginary_float = 0x09,
|
||||
DW_ATE_packed_decimal = 0x0a,
|
||||
DW_ATE_numeric_string = 0x0b,
|
||||
DW_ATE_edited = 0x0c,
|
||||
DW_ATE_signed_fixed = 0x0d,
|
||||
DW_ATE_unsigned_fixed = 0x0e,
|
||||
DW_ATE_decimal_float = 0x0f,
|
||||
DW_ATE_UTF = 0x10,
|
||||
DW_ATE_UCS = 0x11,
|
||||
DW_ATE_ASCII = 0x12,
|
||||
DW_ATE_lo_user = 0x80,
|
||||
DW_ATE_hi_user = 0xff
|
||||
)
|
||||
|
||||
ENUM_DW_ACCESS = dict(
|
||||
DW_ACCESS_public = 0x01,
|
||||
DW_ACCESS_protected = 0x02,
|
||||
DW_ACCESS_private = 0x03
|
||||
)
|
||||
|
||||
ENUM_DW_INL = dict(
|
||||
DW_INL_not_inlined = 0x00,
|
||||
DW_INL_inlined = 0x01,
|
||||
DW_INL_declared_not_inlined = 0x02,
|
||||
DW_INL_declared_inlined = 0x03
|
||||
)
|
||||
|
||||
ENUM_DW_CC = dict(
|
||||
DW_CC_normal = 0x01,
|
||||
DW_CC_program = 0x02,
|
||||
DW_CC_nocall = 0x03,
|
||||
DW_CC_pass_by_reference = 0x04,
|
||||
DW_CC_pass_by_value = 0x05,
|
||||
DW_CC_lo_user = 0x40,
|
||||
DW_CC_hi_user = 0xff
|
||||
)
|
||||
@@ -0,0 +1,280 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/lineprogram.py
|
||||
#
|
||||
# DWARF line number program
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import copy
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from ..common.utils import struct_parse, dwarf_assert
|
||||
from .constants import DW_LNE, DW_LNS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..construct.lib.container import Container
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
# LineProgramEntry - an entry in the line program.
|
||||
# A line program is a sequence of encoded entries. Some of these entries add a
|
||||
# new LineState (mapping between line and address), and some don't.
|
||||
#
|
||||
# command:
|
||||
# The command/opcode - always numeric. For standard commands - it's the opcode
|
||||
# that can be matched with one of the DW_LNS constants. For extended commands
|
||||
# it's the extended opcode that can be matched with one of the DW_LNE
|
||||
# constants. For special commands, it's the opcode itself.
|
||||
#
|
||||
# args:
|
||||
# A list of decoded arguments of the command.
|
||||
#
|
||||
# is_extended:
|
||||
# Since extended commands are encoded by a zero followed by an extended
|
||||
# opcode, and these extended opcodes overlap with other opcodes, this
|
||||
# flag is needed to mark that the command has an extended opcode.
|
||||
#
|
||||
# state:
|
||||
# For commands that add a new state, it's the relevant LineState object.
|
||||
# For commands that don't add a new state, it's None.
|
||||
#
|
||||
class LineProgramEntry(NamedTuple):
|
||||
command: int
|
||||
is_extended: bool
|
||||
args: list[int]
|
||||
state: LineState | None
|
||||
|
||||
|
||||
class LineState:
|
||||
""" Represents a line program state (or a "row" in the matrix
|
||||
describing debug location information for addresses).
|
||||
The instance variables of this class are the "state machine registers"
|
||||
described in section 6.2.2 of DWARFv3
|
||||
"""
|
||||
def __init__(self, default_is_stmt: int) -> None:
|
||||
self.address = 0
|
||||
self.file = 1
|
||||
self.line = 1
|
||||
self.column = 0
|
||||
self.op_index = 0
|
||||
self.is_stmt = default_is_stmt
|
||||
self.basic_block = False
|
||||
self.end_sequence = False
|
||||
self.prologue_end = False
|
||||
self.epilogue_begin = False
|
||||
self.isa = 0
|
||||
self.discriminator = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '\n'.join((
|
||||
'<LineState %x:' % id(self),
|
||||
' address = 0x%x' % self.address,
|
||||
*(
|
||||
' %s = %s' % (attr, getattr(self, attr))
|
||||
for attr in ('file', 'line', 'column', 'is_stmt', 'basic_block',
|
||||
'end_sequence', 'prologue_end', 'epilogue_begin', 'isa',
|
||||
'discriminator')
|
||||
),
|
||||
'>',
|
||||
))
|
||||
|
||||
|
||||
class LineProgram:
|
||||
""" Builds a "line table", which is essentially the matrix described
|
||||
in section 6.2 of DWARFv3. It's a list of LineState objects,
|
||||
sorted by increasing address, so it can be used to obtain the
|
||||
state information for each address.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
stream: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
program_start_offset: int,
|
||||
program_end_offset: int,
|
||||
) -> None:
|
||||
"""
|
||||
header:
|
||||
The header of this line program. Note: LineProgram may modify
|
||||
its header by appending file entries if DW_LNE.define_file
|
||||
instructions are encountered.
|
||||
|
||||
stream:
|
||||
The stream this program can be read from.
|
||||
|
||||
structs:
|
||||
A DWARFStructs instance suitable for this line program
|
||||
|
||||
program_{start|end}_offset:
|
||||
Offset in the debug_line section stream where this program
|
||||
starts (the actual program, after the header), and where it
|
||||
ends.
|
||||
The actual range includes start but not end: [start, end - 1]
|
||||
"""
|
||||
self.stream = stream
|
||||
self.header = header
|
||||
self.structs = structs
|
||||
self.program_start_offset = program_start_offset
|
||||
self.program_end_offset = program_end_offset
|
||||
|
||||
def get_entries(self) -> list[LineProgramEntry]:
|
||||
""" Get the decoded entries for this line program. Return a list of
|
||||
LineProgramEntry objects.
|
||||
Note that this contains more information than absolutely required
|
||||
for the line table. The line table can be easily extracted from
|
||||
the list of entries by looking only at entries with non-None
|
||||
state. The extra information is mainly for the purposes of display
|
||||
with readelf and debugging.
|
||||
"""
|
||||
return self._decode_line_program
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
@cached_property
|
||||
def _decode_line_program(self) -> list[LineProgramEntry]:
|
||||
entries = []
|
||||
state = LineState(self.header['default_is_stmt'])
|
||||
|
||||
def add_entry_new_state(cmd: int, args: list[int], is_extended: bool = False) -> None:
|
||||
# Add an entry that sets a new state.
|
||||
# After adding, clear some state registers.
|
||||
entries.append(LineProgramEntry(
|
||||
cmd, is_extended, args, copy.copy(state)))
|
||||
state.discriminator = 0
|
||||
state.basic_block = False
|
||||
state.prologue_end = False
|
||||
state.epilogue_begin = False
|
||||
|
||||
def add_entry_old_state(cmd: int, args: list[int], is_extended: bool = False) -> None:
|
||||
# Add an entry that doesn't visibly set a new state
|
||||
entries.append(LineProgramEntry(cmd, is_extended, args, None))
|
||||
|
||||
offset = self.program_start_offset
|
||||
while offset < self.program_end_offset:
|
||||
opcode: int = struct_parse(
|
||||
self.structs.the_Dwarf_uint8,
|
||||
self.stream,
|
||||
offset)
|
||||
|
||||
# As an exercise in avoiding premature optimization, if...elif
|
||||
# chains are used here for standard and extended opcodes instead
|
||||
# of dispatch tables. This keeps the code much cleaner. Besides,
|
||||
# the majority of instructions in a typical program are special
|
||||
# opcodes anyway.
|
||||
if opcode >= self.header['opcode_base']:
|
||||
# Special opcode (follow the recipe in 6.2.5.1)
|
||||
maximum_operations_per_instruction: int = self['maximum_operations_per_instruction']
|
||||
adjusted_opcode: int = opcode - self['opcode_base']
|
||||
operation_advance: int = adjusted_opcode // self['line_range']
|
||||
address_addend: int = (
|
||||
self['minimum_instruction_length'] *
|
||||
((state.op_index + operation_advance) //
|
||||
maximum_operations_per_instruction))
|
||||
state.address += address_addend
|
||||
state.op_index = (state.op_index + operation_advance) % maximum_operations_per_instruction
|
||||
line_addend: int = self['line_base'] + (adjusted_opcode % self['line_range'])
|
||||
state.line += line_addend
|
||||
add_entry_new_state(
|
||||
opcode, [line_addend, address_addend, state.op_index])
|
||||
elif opcode == 0:
|
||||
# Extended opcode: start with a zero byte, followed by
|
||||
# instruction size and the instruction itself.
|
||||
inst_len: int = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
ex_opcode: int = struct_parse(self.structs.the_Dwarf_uint8,
|
||||
self.stream)
|
||||
|
||||
if ex_opcode == DW_LNE.end_sequence:
|
||||
state.end_sequence = True
|
||||
state.is_stmt = 0
|
||||
add_entry_new_state(ex_opcode, [], is_extended=True)
|
||||
# reset state
|
||||
state = LineState(self.header['default_is_stmt'])
|
||||
elif ex_opcode == DW_LNE.set_address:
|
||||
operand: int = struct_parse(self.structs.the_Dwarf_target_addr,
|
||||
self.stream)
|
||||
state.address = operand
|
||||
add_entry_old_state(ex_opcode, [operand], is_extended=True)
|
||||
elif ex_opcode == DW_LNE.define_file:
|
||||
operand = struct_parse(
|
||||
self.structs.Dwarf_lineprog_file_entry, self.stream)
|
||||
self['file_entry'].append(operand)
|
||||
add_entry_old_state(ex_opcode, [operand], is_extended=True)
|
||||
elif ex_opcode == DW_LNE.set_discriminator:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
state.discriminator = operand
|
||||
else:
|
||||
# Unknown, but need to roll forward the stream because the
|
||||
# length is specified. Seek forward inst_len - 1 because
|
||||
# we've already read the extended opcode, which takes part
|
||||
# in the length.
|
||||
self.stream.seek(inst_len - 1, os.SEEK_CUR)
|
||||
else: # 0 < opcode < opcode_base
|
||||
# Standard opcode
|
||||
if opcode == DW_LNS.copy:
|
||||
add_entry_new_state(opcode, [])
|
||||
elif opcode == DW_LNS.advance_pc:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
address_addend = (
|
||||
operand * self.header['minimum_instruction_length'])
|
||||
state.address += address_addend
|
||||
add_entry_old_state(opcode, [address_addend])
|
||||
elif opcode == DW_LNS.advance_line:
|
||||
operand = struct_parse(self.structs.the_Dwarf_sleb128,
|
||||
self.stream)
|
||||
state.line += operand
|
||||
elif opcode == DW_LNS.set_file:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
state.file = operand
|
||||
add_entry_old_state(opcode, [operand])
|
||||
elif opcode == DW_LNS.set_column:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
state.column = operand
|
||||
add_entry_old_state(opcode, [operand])
|
||||
elif opcode == DW_LNS.negate_stmt:
|
||||
state.is_stmt = not state.is_stmt
|
||||
add_entry_old_state(opcode, [])
|
||||
elif opcode == DW_LNS.set_basic_block:
|
||||
state.basic_block = True
|
||||
add_entry_old_state(opcode, [])
|
||||
elif opcode == DW_LNS.const_add_pc:
|
||||
adjusted_opcode = 255 - self['opcode_base']
|
||||
address_addend = ((adjusted_opcode // self['line_range']) *
|
||||
self['minimum_instruction_length'])
|
||||
state.address += address_addend
|
||||
add_entry_old_state(opcode, [address_addend])
|
||||
elif opcode == DW_LNS.fixed_advance_pc:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uint16,
|
||||
self.stream)
|
||||
state.address += operand
|
||||
add_entry_old_state(opcode, [operand])
|
||||
elif opcode == DW_LNS.set_prologue_end:
|
||||
state.prologue_end = True
|
||||
add_entry_old_state(opcode, [])
|
||||
elif opcode == DW_LNS.set_epilogue_begin:
|
||||
state.epilogue_begin = True
|
||||
add_entry_old_state(opcode, [])
|
||||
elif opcode == DW_LNS.set_isa:
|
||||
operand = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
state.isa = operand
|
||||
add_entry_old_state(opcode, [operand])
|
||||
else:
|
||||
dwarf_assert(False, 'Invalid standard line program opcode: %s' % (
|
||||
opcode,))
|
||||
offset = self.stream.tell()
|
||||
return entries
|
||||
@@ -0,0 +1,422 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/locationlists.py
|
||||
#
|
||||
# DWARF location lists section decoding (.debug_loc)
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import IO, TYPE_CHECKING, NamedTuple
|
||||
|
||||
from ..common.exceptions import DWARFError
|
||||
from ..common.utils import struct_parse
|
||||
from .dwarf_util import _iter_CUs_in_section
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .compileunit import CompileUnit
|
||||
from .die import DIE, AttributeValue
|
||||
from .dwarfinfo import DWARFInfo
|
||||
from .structs import DWARFStructs
|
||||
from .typeunit import TypeUnit
|
||||
|
||||
|
||||
class LocationExpr(NamedTuple):
|
||||
loc_expr: list[int]
|
||||
|
||||
|
||||
class LocationEntry(NamedTuple):
|
||||
entry_offset: int
|
||||
entry_length: int
|
||||
begin_offset: int
|
||||
end_offset: int
|
||||
loc_expr: list[int]
|
||||
is_absolute: bool
|
||||
|
||||
|
||||
class BaseAddressEntry(NamedTuple):
|
||||
entry_offset: int
|
||||
entry_length: int
|
||||
base_address: int
|
||||
|
||||
|
||||
class LocationViewPair(NamedTuple):
|
||||
entry_offset: int
|
||||
begin: int
|
||||
end: int
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_Location = LocationExpr | LocationEntry | BaseAddressEntry | LocationViewPair
|
||||
|
||||
|
||||
def _translate_startx_length(e: Container, cu: CompileUnit | TypeUnit) -> LocationEntry:
|
||||
start_offset: int = cu.dwarfinfo.get_addr(cu, e.start_index)
|
||||
return LocationEntry(e.entry_offset, e.entry_length, start_offset, start_offset + e.length, e.loc_expr, True)
|
||||
|
||||
# Maps parsed entries to the tuples above; LocationViewPair is mapped elsewhere
|
||||
entry_translate: dict[str, Callable[[Container, CompileUnit | TypeUnit], _Location]] = {
|
||||
'DW_LLE_base_address' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.entry_length, e.address),
|
||||
'DW_LLE_offset_pair' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, e.loc_expr, False),
|
||||
'DW_LLE_start_length' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, e.loc_expr, True),
|
||||
'DW_LLE_start_end' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, e.loc_expr, True),
|
||||
'DW_LLE_default_location': lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, -1, -1, e.loc_expr, True),
|
||||
'DW_LLE_base_addressx' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.index)), # type: ignore[has-type]
|
||||
'DW_LLE_startx_endx' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.start_index), cu.dwarfinfo.get_addr(cu, e.end_index), e.loc_expr, True), # type: ignore[has-type]
|
||||
'DW_LLE_startx_length' : _translate_startx_length
|
||||
}
|
||||
|
||||
class LocationListsPair:
|
||||
"""For those binaries that contain both a debug_loc and a debug_loclists section,
|
||||
it holds a LocationLists object for both and forwards API calls to the right one.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
streamv4: IO[bytes],
|
||||
streamv5: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
dwarfinfo: DWARFInfo | None = None,
|
||||
) -> None:
|
||||
self._loc = LocationLists(streamv4, structs, 4, dwarfinfo)
|
||||
self._loclists = LocationLists(streamv5, structs, 5, dwarfinfo)
|
||||
|
||||
def get_location_list_at_offset(self, offset: int, die: DIE | None = None) -> list[_Location]:
|
||||
"""See LocationLists.get_location_list_at_offset().
|
||||
"""
|
||||
if die is None:
|
||||
raise DWARFError("For this binary, \"die\" needs to be provided")
|
||||
section = self._loclists if die.cu.header.version >= 5 else self._loc
|
||||
return section.get_location_list_at_offset(offset, die)
|
||||
|
||||
def iter_location_lists(self) -> Iterator[BaseAddressEntry | LocationEntry]:
|
||||
"""Tricky proposition, since the structure of loc and loclists
|
||||
is not identical. A realistic readelf implementation needs to be aware of both
|
||||
"""
|
||||
raise DWARFError("Iterating through two sections is not supported")
|
||||
|
||||
def iter_CUs(self) -> Iterator[CompileUnit]:
|
||||
"""See LocationLists.iter_CUs()
|
||||
|
||||
There are no CUs in DWARFv4 sections.
|
||||
"""
|
||||
raise DWARFError("Iterating through two sections is not supported")
|
||||
|
||||
class LocationLists:
|
||||
""" A single location list is a Python list consisting of LocationEntry or
|
||||
BaseAddressEntry objects.
|
||||
|
||||
Starting with DWARF5, it may also contain LocationViewPair, but only
|
||||
if scanning the section, never when requested for a DIE attribute.
|
||||
|
||||
The default location entries are returned as LocationEntry with
|
||||
begin_offset == end_offset == -1
|
||||
|
||||
Version determines whether the executable contains a debug_loc
|
||||
section, or a DWARFv5 style debug_loclists one. Only the 4/5
|
||||
distinction matters.
|
||||
|
||||
Dwarfinfo is only needed for DWARFv5 location entry encodings
|
||||
that contain references to other sections (e. g. DW_LLE_startx_endx),
|
||||
and only for location list enumeration.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
version: int = 4,
|
||||
dwarfinfo: DWARFInfo | None = None,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.structs = structs
|
||||
self.dwarfinfo = dwarfinfo
|
||||
self.version = version
|
||||
self._max_addr: int = 2 ** (self.structs.address_size * 8) - 1
|
||||
|
||||
def get_location_list_at_offset(self, offset: int, die: DIE | None = None) -> list[_Location]:
|
||||
""" Get a location list at the given offset in the section.
|
||||
Passing the die is only neccessary in DWARF5+, for decoding
|
||||
location entry encodings that contain references to other sections.
|
||||
"""
|
||||
self.stream.seek(offset, os.SEEK_SET)
|
||||
if self.version >= 5:
|
||||
if die is None:
|
||||
raise DWARFError("For this binary, \"die\" needs to be provided")
|
||||
return self._parse_location_list_from_stream_v5(die.cu)
|
||||
return self._parse_location_list_from_stream()
|
||||
|
||||
def iter_location_lists(self) -> Iterator[list[_Location]]:
|
||||
""" Iterates through location lists and view pairs. Returns lists of
|
||||
LocationEntry, BaseAddressEntry, and LocationViewPair objects.
|
||||
"""
|
||||
# The location lists section was never meant for sequential access.
|
||||
# Location lists are referenced by DIE attributes by offset or by index.
|
||||
|
||||
# As of DWARFv5, it may contain, in addition to proper location lists,
|
||||
# location list view pairs, which are referenced by the nonstandard DW_AT_GNU_locviews
|
||||
# attribute. A set of locview pairs (which is a couple of ULEB128 values) may preceed
|
||||
# a location list; the former is referenced by the DW_AT_GNU_locviews attribute, the
|
||||
# latter - by DW_AT_location (in the same DIE). Binutils' readelf dumps those.
|
||||
# There is a view pair for each location-type entry in the list.
|
||||
#
|
||||
# Also, the section may contain gaps.
|
||||
#
|
||||
# Taking a cue from binutils, we would have to scan this section while looking at
|
||||
# what's in DIEs.
|
||||
ver5 = self.version >= 5
|
||||
stream = self.stream
|
||||
stream.seek(0, os.SEEK_END)
|
||||
endpos = stream.tell()
|
||||
|
||||
stream.seek(0, os.SEEK_SET)
|
||||
|
||||
# Need to provide support for DW_AT_GNU_locviews. They are interspersed in
|
||||
# the locations section, no way to tell where short of checking all DIEs
|
||||
all_offsets = set() # Set of offsets where either a locview pair set can be found, or a view-less loclist
|
||||
locviews = dict() # Map of locview offset to the respective loclist offset
|
||||
cu_map = dict() # Map of loclist offsets to CUs
|
||||
assert self.dwarfinfo is not None
|
||||
for cu in self.dwarfinfo.iter_CUs():
|
||||
cu_ver: int = cu['version']
|
||||
if (cu_ver >= 5) == ver5:
|
||||
for die in cu.iter_DIEs():
|
||||
# A combination of location and locviews means there is a location list
|
||||
# preceed by several locview pairs
|
||||
if 'DW_AT_GNU_locviews' in die.attributes:
|
||||
assert('DW_AT_location' in die.attributes and
|
||||
LocationParser._attribute_has_loc_list(die.attributes['DW_AT_location'], cu_ver))
|
||||
views_offset: int = die.attributes['DW_AT_GNU_locviews'].value
|
||||
list_offset: int = die.attributes['DW_AT_location'].value
|
||||
locviews[views_offset] = list_offset
|
||||
cu_map[list_offset] = cu
|
||||
all_offsets.add(views_offset)
|
||||
|
||||
# Scan other attributes for location lists
|
||||
for key in die.attributes:
|
||||
attr = die.attributes[key]
|
||||
if ((key != 'DW_AT_location' or 'DW_AT_GNU_locviews' not in die.attributes) and
|
||||
LocationParser.attribute_has_location(attr, cu_ver) and
|
||||
LocationParser._attribute_has_loc_list(attr, cu_ver)):
|
||||
list_offset = attr.value
|
||||
all_offsets.add(list_offset)
|
||||
cu_map[list_offset] = cu
|
||||
sorted_offsets = sorted(all_offsets)
|
||||
|
||||
if ver5:
|
||||
# Loclists section is organized as an array of CUs, each length prefixed.
|
||||
# We don't assume that the CUs go in the same order as the ones in info.
|
||||
offset_index = 0
|
||||
while stream.tell() < endpos:
|
||||
# We are at the start of the CU block in the loclists now
|
||||
cu_header = struct_parse(self.structs.Dwarf_loclists_CU_header, stream)
|
||||
assert(cu_header.version == 5)
|
||||
|
||||
# GNU binutils supports two traversal modes: by offsets in CU header, and sequential.
|
||||
# We don't have a binary for the former yet. On an off chance that we one day might,
|
||||
# let's parse the header anyway.
|
||||
|
||||
cu_end_offset: int = cu_header.offset_after_length + cu_header.unit_length
|
||||
# Unit_length includes the header but doesn't include the length
|
||||
|
||||
while stream.tell() < cu_end_offset:
|
||||
# Skip the gap to the next object
|
||||
next_offset = sorted_offsets[offset_index]
|
||||
if next_offset == stream.tell(): # At an object, either a loc list or a loc view pair
|
||||
locview_pairs = self._parse_locview_pairs(locviews)
|
||||
entries = self._parse_location_list_from_stream_v5(cu_map[stream.tell()])
|
||||
yield locview_pairs + entries
|
||||
offset_index += 1
|
||||
else: # We are at a gap - skip the gap to the next object or to the next CU
|
||||
if next_offset > cu_end_offset: # Gap at the CU end - the next object is in the next CU
|
||||
next_offset = cu_end_offset # And implicitly quit the loop within the CU
|
||||
stream.seek(next_offset, os.SEEK_SET)
|
||||
else:
|
||||
for offset in sorted_offsets:
|
||||
list_offset = locviews.get(offset, offset)
|
||||
if cu_map[list_offset].header.version < 5:
|
||||
stream.seek(offset, os.SEEK_SET)
|
||||
locview_pairs = self._parse_locview_pairs(locviews)
|
||||
entries = self._parse_location_list_from_stream()
|
||||
yield locview_pairs + entries
|
||||
|
||||
def iter_CUs(self) -> Iterator[CompileUnit]:
|
||||
"""For DWARF5 returns an array of objects, where each one has an array of offsets
|
||||
"""
|
||||
if self.version < 5:
|
||||
raise DWARFError("CU iteration in loclists is not supported with DWARF<5")
|
||||
|
||||
assert self.dwarfinfo is not None
|
||||
structs = next(self.dwarfinfo.iter_CUs()).structs # Just pick one
|
||||
return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_loclists_CU_header)
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def _parse_location_list_from_stream(self) -> list[_Location]:
|
||||
lst: list[_Location] = []
|
||||
while True:
|
||||
entry_offset = self.stream.tell()
|
||||
begin_offset: int = struct_parse(
|
||||
self.structs.the_Dwarf_target_addr, self.stream)
|
||||
end_offset: int = struct_parse(
|
||||
self.structs.the_Dwarf_target_addr, self.stream)
|
||||
if begin_offset == 0 and end_offset == 0:
|
||||
# End of list - we're done.
|
||||
break
|
||||
elif begin_offset == self._max_addr:
|
||||
# Base address selection entry
|
||||
entry_length = self.stream.tell() - entry_offset
|
||||
lst.append(BaseAddressEntry(entry_offset=entry_offset, entry_length=entry_length, base_address=end_offset))
|
||||
else:
|
||||
# Location list entry
|
||||
expr_len: int = struct_parse(
|
||||
self.structs.the_Dwarf_uint16, self.stream)
|
||||
loc_expr: list[int] = [struct_parse(self.structs.the_Dwarf_uint8,
|
||||
self.stream)
|
||||
for i in range(expr_len)]
|
||||
entry_length = self.stream.tell() - entry_offset
|
||||
lst.append(LocationEntry(
|
||||
entry_offset=entry_offset,
|
||||
entry_length=entry_length,
|
||||
begin_offset=begin_offset,
|
||||
end_offset=end_offset,
|
||||
loc_expr=loc_expr,
|
||||
is_absolute = False))
|
||||
return lst
|
||||
|
||||
def _parse_location_list_from_stream_v5(
|
||||
self,
|
||||
cu: CompileUnit | TypeUnit | None = None,
|
||||
) -> list[_Location]:
|
||||
""" Returns an array with BaseAddressEntry and LocationEntry.
|
||||
No terminator entries.
|
||||
|
||||
The cu argument is necessary if the section is a
|
||||
DWARFv5 debug_loclists one, and the target loclist
|
||||
contains indirect encodings.
|
||||
"""
|
||||
return [entry_translate[entry.entry_type](entry, cu) # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
for entry
|
||||
in struct_parse(self.structs.Dwarf_loclists_entries, self.stream)]
|
||||
|
||||
# From V5 style entries to a LocationEntry/BaseAddressEntry
|
||||
def _translate_entry_v5(self, entry: Container, die: DIE) -> _Location:
|
||||
off: int = entry.entry_offset
|
||||
len: int = entry.entry_end_offset - off
|
||||
type: str = entry.entry_type
|
||||
if type == 'DW_LLE_base_address':
|
||||
return BaseAddressEntry(off, len, entry.address)
|
||||
elif type == 'DW_LLE_offset_pair':
|
||||
return LocationEntry(off, len, entry.start_offset, entry.end_offset, entry.loc_expr, False)
|
||||
elif type == 'DW_LLE_start_length':
|
||||
return LocationEntry(off, len, entry.start_address, entry.start_address + entry.length, entry.loc_expr, True)
|
||||
elif type == 'DW_LLE_start_end': # No test for this yet, but the format seems straightforward
|
||||
return LocationEntry(off, len, entry.start_address, entry.end_address, entry.loc_expr, True)
|
||||
elif type == 'DW_LLE_default_location': # No test for this either, and this is new in the API
|
||||
return LocationEntry(off, len, -1, -1, entry.loc_expr, True)
|
||||
elif type in ('DW_LLE_base_addressx', 'DW_LLE_startx_endx', 'DW_LLE_startx_length'):
|
||||
# We don't have sample binaries for those LLEs. Their proper parsing would
|
||||
# require knowing the CU context (so that indices can be resolved to code offsets)
|
||||
raise NotImplementedError("Location list entry type %s is not supported yet" % (type,))
|
||||
else:
|
||||
raise DWARFError(False, "Unknown DW_LLE code: %s" % (type,))
|
||||
|
||||
# Locviews is the dict, mapping locview offsets to corresponding loclist offsets
|
||||
def _parse_locview_pairs(self, locviews: Mapping[int, int]) -> list[LocationViewPair]:
|
||||
stream = self.stream
|
||||
list_offset: int | None = locviews.get(stream.tell(), None)
|
||||
pairs: list[LocationViewPair] = []
|
||||
if list_offset is not None:
|
||||
while stream.tell() < list_offset:
|
||||
pair = struct_parse(self.structs.Dwarf_locview_pair, stream)
|
||||
pairs.append(LocationViewPair(pair.entry_offset, pair.begin, pair.end))
|
||||
assert(stream.tell() == list_offset)
|
||||
return pairs
|
||||
|
||||
class LocationParser:
|
||||
""" A parser for location information in DIEs.
|
||||
Handles both location information contained within the attribute
|
||||
itself (represented as a LocationExpr object) and references to
|
||||
location lists in the .debug_loc section (represented as a
|
||||
list).
|
||||
"""
|
||||
def __init__(self, location_lists: LocationLists | LocationListsPair | None) -> None:
|
||||
self.location_lists = location_lists
|
||||
|
||||
@staticmethod
|
||||
def attribute_has_location(attr: AttributeValue, dwarf_version: int) -> bool:
|
||||
""" Checks if a DIE attribute contains location information.
|
||||
"""
|
||||
return (LocationParser._attribute_is_loclistptr_class(attr) and
|
||||
(LocationParser._attribute_has_loc_expr(attr, dwarf_version) or
|
||||
LocationParser._attribute_has_loc_list(attr, dwarf_version)))
|
||||
|
||||
def parse_from_attribute(
|
||||
self,
|
||||
attr: AttributeValue,
|
||||
dwarf_version: int,
|
||||
die: DIE | None = None,
|
||||
) -> LocationExpr | list[_Location]:
|
||||
""" Parses a DIE attribute and returns either a LocationExpr or
|
||||
a list.
|
||||
"""
|
||||
if self.attribute_has_location(attr, dwarf_version):
|
||||
if self._attribute_has_loc_expr(attr, dwarf_version):
|
||||
return LocationExpr(attr.value)
|
||||
elif self._attribute_has_loc_list(attr, dwarf_version):
|
||||
assert self.location_lists is not None
|
||||
return self.location_lists.get_location_list_at_offset(
|
||||
attr.value, die)
|
||||
# We don't yet know if the DIE context will be needed.
|
||||
# We might get it without a full tree traversal using
|
||||
# attr.offset as a key, but we assume a good DWARF5
|
||||
# aware consumer would pass a DIE along.
|
||||
raise ValueError("Attribute does not have location information")
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
@staticmethod
|
||||
def _attribute_has_loc_expr(attr: AttributeValue, dwarf_version: int) -> bool:
|
||||
return ((dwarf_version < 4 and attr.form.startswith('DW_FORM_block') and
|
||||
not attr.name == 'DW_AT_const_value') or
|
||||
attr.form == 'DW_FORM_exprloc')
|
||||
|
||||
@staticmethod
|
||||
def _attribute_has_loc_list(attr: AttributeValue, dwarf_version: int) -> bool:
|
||||
return (((dwarf_version < 4 and
|
||||
attr.form in ('DW_FORM_data1', 'DW_FORM_data2', 'DW_FORM_data4', 'DW_FORM_data8') and
|
||||
not attr.name == 'DW_AT_const_value') or
|
||||
attr.form in ('DW_FORM_sec_offset', 'DW_FORM_loclistx')) and
|
||||
not LocationParser._attribute_is_constant(attr, dwarf_version))
|
||||
|
||||
# Starting with DWARF3, DW_AT_data_member_location may contain an integer offset
|
||||
# instead of a location expression. Need to prevent false positives on attribute_has_location().
|
||||
# As for DW_AT_upper_bound/DW_AT_count, we've seen it in form DW_FORM_locexpr in a V5 binary. usually it's a constant,
|
||||
# but the constant sholdn't be misinterpreted as a loclist pointer.
|
||||
@staticmethod
|
||||
def _attribute_is_constant(attr: AttributeValue, dwarf_version: int) -> bool:
|
||||
return (((dwarf_version >= 3 and attr.name == 'DW_AT_data_member_location') or
|
||||
(attr.name in ('DW_AT_upper_bound', 'DW_AT_count'))) and
|
||||
attr.form in ('DW_FORM_data1', 'DW_FORM_data2', 'DW_FORM_data4', 'DW_FORM_data8', 'DW_FORM_sdata', 'DW_FORM_udata'))
|
||||
|
||||
@staticmethod
|
||||
def _attribute_is_loclistptr_class(attr: AttributeValue) -> bool:
|
||||
return (attr.name in ( 'DW_AT_location', 'DW_AT_string_length',
|
||||
'DW_AT_const_value', 'DW_AT_return_addr',
|
||||
'DW_AT_data_member_location',
|
||||
'DW_AT_frame_base', 'DW_AT_segment',
|
||||
'DW_AT_static_link', 'DW_AT_use_location',
|
||||
'DW_AT_vtable_elem_location',
|
||||
'DW_AT_call_value',
|
||||
'DW_AT_GNU_call_site_value',
|
||||
'DW_AT_GNU_call_site_target',
|
||||
'DW_AT_GNU_call_site_data_value',
|
||||
'DW_AT_call_target',
|
||||
'DW_AT_call_target_clobbered',
|
||||
'DW_AT_call_data_location',
|
||||
'DW_AT_call_data_value',
|
||||
'DW_AT_upper_bound',
|
||||
'DW_AT_count'))
|
||||
@@ -0,0 +1,201 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/namelut.py
|
||||
#
|
||||
# DWARF pubtypes/pubnames section decoding (.debug_pubtypes, .debug_pubnames)
|
||||
#
|
||||
# Vijay Ramasami (rvijayc@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, NamedTuple, TypeVar, overload
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..construct import CString, Struct, If
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import ItemsView, Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .structs import DWARFStructs
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class NameLUTEntry(NamedTuple):
|
||||
cu_ofs: int
|
||||
die_ofs: int
|
||||
|
||||
|
||||
class NameLUT(Mapping[str, NameLUTEntry]):
|
||||
"""
|
||||
A "Name LUT" holds any of the tables specified by .debug_pubtypes or
|
||||
.debug_pubnames sections. This is basically a dictionary where the key is
|
||||
the symbol name (either a public variable, function or a type), and the
|
||||
value is the tuple (cu_offset, die_offset) corresponding to the variable.
|
||||
The die_offset is an absolute offset (meaning, it can be used to search the
|
||||
CU by iterating until a match is obtained).
|
||||
|
||||
An ordered dictionary is used to preserve the CU order (i.e, items are
|
||||
stored on a per-CU basis (as it was originally in the .debug_* section).
|
||||
|
||||
Usage:
|
||||
|
||||
The NameLUT walks and talks like a dictionary and hence it can be used as
|
||||
such. Some examples below:
|
||||
|
||||
# get the pubnames (a NameLUT from DWARF info).
|
||||
pubnames = dwarf_info.get_pubnames()
|
||||
|
||||
# lookup a variable.
|
||||
entry1 = pubnames["var_name1"]
|
||||
entry2 = pubnames.get("var_name2", default=<default_var>)
|
||||
print(entry2.cu_ofs)
|
||||
...
|
||||
|
||||
# iterate over items.
|
||||
for (name, entry) in pubnames.items():
|
||||
# do stuff with name, entry.cu_ofs, entry.die_ofs
|
||||
|
||||
# iterate over items on a per-CU basis.
|
||||
import itertools
|
||||
for cu_ofs, item_list in itertools.groupby(pubnames.items(),
|
||||
key = lambda x: x[1].cu_ofs):
|
||||
# items are now grouped by cu_ofs.
|
||||
# item_list is an iterator yeilding NameLUTEntry'ies belonging
|
||||
# to cu_ofs.
|
||||
# We can parse the CU at cu_offset and use the parsed CU results
|
||||
# to parse the pubname DIEs in the CU listed by item_list.
|
||||
for item in item_list:
|
||||
# work with item which is part of the CU with cu_ofs.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, stream: IO[bytes], size: int, structs: DWARFStructs) -> None:
|
||||
self._stream = stream
|
||||
self._size = size
|
||||
self._structs = structs
|
||||
|
||||
def get_entries(self) -> dict[str, NameLUTEntry]:
|
||||
"""
|
||||
Returns the parsed NameLUT entries. The returned object is a dictionary
|
||||
with the symbol name as the key and NameLUTEntry(cu_ofs, die_ofs) as
|
||||
the value.
|
||||
|
||||
This is useful when dealing with very large ELF files with millions of
|
||||
entries. The returned entries can be pickled to a file and restored by
|
||||
calling set_entries on subsequent loads.
|
||||
"""
|
||||
return self._entries
|
||||
|
||||
def set_entries(self, entries: dict[str, NameLUTEntry], cu_headers: list[Container]) -> None:
|
||||
"""
|
||||
Set the NameLUT entries from an external source. The input is a
|
||||
dictionary with the symbol name as the key and NameLUTEntry(cu_ofs,
|
||||
die_ofs) as the value.
|
||||
|
||||
This option is useful when dealing with very large ELF files with
|
||||
millions of entries. The entries can be parsed once and pickled to a
|
||||
file and can be restored via this function on subsequent loads.
|
||||
"""
|
||||
self._entries = entries
|
||||
self._cu_headers = cu_headers
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of entries in the NameLUT.
|
||||
"""
|
||||
return len(self._entries)
|
||||
|
||||
def __getitem__(self, name: str) -> NameLUTEntry:
|
||||
"""
|
||||
Returns a namedtuple - NameLUTEntry(cu_ofs, die_ofs) - that corresponds
|
||||
to the given symbol name.
|
||||
"""
|
||||
return self._entries[name]
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
"""
|
||||
Returns an iterator to the NameLUT dictionary.
|
||||
"""
|
||||
return iter(self._entries)
|
||||
|
||||
def items(self) -> ItemsView[str, NameLUTEntry]:
|
||||
"""
|
||||
Returns the NameLUT dictionary items.
|
||||
"""
|
||||
return self._entries.items()
|
||||
|
||||
@overload
|
||||
def get(self, name: str) -> NameLUTEntry | None: ...
|
||||
@overload
|
||||
def get(self, name: str, default: NameLUTEntry | _T = ...) -> NameLUTEntry | _T: ...
|
||||
def get(self, name: str, default: NameLUTEntry | _T | None = None) -> NameLUTEntry | _T | None:
|
||||
"""
|
||||
Returns NameLUTEntry(cu_ofs, die_ofs) for the provided symbol name or
|
||||
None if the symbol does not exist in the corresponding section.
|
||||
"""
|
||||
return self._entries.get(name, default)
|
||||
|
||||
def get_cu_headers(self) -> list[Container]:
|
||||
"""
|
||||
Returns all CU headers. Mainly required for readelf.
|
||||
"""
|
||||
return self._cu_headers
|
||||
|
||||
@cached_property
|
||||
def _entries(self) -> dict[str, NameLUTEntry]:
|
||||
return self.__entries[0]
|
||||
|
||||
@cached_property
|
||||
def _cu_headers(self) -> list[Container]:
|
||||
return self.__entries[1]
|
||||
|
||||
@cached_property
|
||||
def __entries(self) -> tuple[dict[str, NameLUTEntry], list[Container]]:
|
||||
"""
|
||||
Parse the (name, cu_ofs, die_ofs) information from this section.
|
||||
"""
|
||||
self._stream.seek(0)
|
||||
entries: dict[str, NameLUTEntry] = {}
|
||||
cu_headers: list[Container] = []
|
||||
offset = 0
|
||||
# According to 6.1.1. of DWARFv4, each set of names is terminated by
|
||||
# an offset field containing zero (and no following string). Because
|
||||
# of sequential parsing, every next entry may be that terminator.
|
||||
# So, field "name" is conditional.
|
||||
entry_struct = Struct("Dwarf_offset_name_pair",
|
||||
self._structs.Dwarf_offset('die_ofs'),
|
||||
If(lambda ctx: ctx['die_ofs'], CString('name')))
|
||||
|
||||
# each run of this loop will fetch one CU worth of entries.
|
||||
while offset < self._size:
|
||||
|
||||
# read the header for this CU.
|
||||
namelut_hdr = struct_parse(self._structs.Dwarf_nameLUT_header,
|
||||
self._stream, offset)
|
||||
cu_headers.append(namelut_hdr)
|
||||
# compute the next offset.
|
||||
offset = (offset + namelut_hdr.unit_length +
|
||||
self._structs.initial_length_field_size())
|
||||
|
||||
# before inner loop, latch data that will be used in the inner
|
||||
# loop to avoid attribute access and other computation.
|
||||
hdr_cu_ofs = namelut_hdr.debug_info_offset
|
||||
|
||||
# while die_ofs of the entry is non-zero (which indicates the end) ...
|
||||
while True:
|
||||
entry = struct_parse(entry_struct, self._stream)
|
||||
|
||||
# if it is zero, this is the terminating record.
|
||||
if entry.die_ofs == 0:
|
||||
break
|
||||
# add this entry to the look-up dictionary.
|
||||
entries[entry.name.decode('utf-8')] = NameLUTEntry(
|
||||
cu_ofs = hdr_cu_ofs,
|
||||
die_ofs = hdr_cu_ofs + entry.die_ofs)
|
||||
|
||||
# return the entries parsed so far.
|
||||
return (entries, cu_headers)
|
||||
@@ -0,0 +1,252 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/ranges.py
|
||||
#
|
||||
# DWARF ranges section decoding (.debug_ranges)
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import IO, TYPE_CHECKING, NamedTuple, NoReturn
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..common.exceptions import DWARFError
|
||||
from .dwarf_util import _iter_CUs_in_section
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .compileunit import CompileUnit
|
||||
from .dwarfinfo import DWARFInfo
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
class RangeEntry(NamedTuple):
|
||||
entry_offset: int
|
||||
entry_length: int
|
||||
begin_offset: int
|
||||
end_offset: int
|
||||
is_absolute: bool
|
||||
|
||||
|
||||
class BaseAddressEntry(NamedTuple):
|
||||
entry_offset: int
|
||||
base_address: int
|
||||
|
||||
# If we ever see a list with a base entry at the end, there will be an error that entry_length is not a field.
|
||||
|
||||
def _translate_startx_length(e: Container, cu: CompileUnit) -> RangeEntry:
|
||||
start_offset = cu.dwarfinfo.get_addr(cu, e.start_index)
|
||||
return RangeEntry(e.entry_offset, e.entry_length, start_offset, start_offset + e.length, True)
|
||||
|
||||
# Maps parsed entry types to RangeEntry/BaseAddressEntry objects
|
||||
entry_translate: dict[str, Callable[[Container, CompileUnit], RangeEntry | BaseAddressEntry]] = {
|
||||
'DW_RLE_base_address' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.address),
|
||||
'DW_RLE_offset_pair' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, False),
|
||||
'DW_RLE_start_end' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, True),
|
||||
'DW_RLE_start_length' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, True),
|
||||
'DW_RLE_base_addressx': lambda e, cu: BaseAddressEntry(e.entry_offset, cu.dwarfinfo.get_addr(cu, e.index)), # type: ignore[has-type]
|
||||
'DW_RLE_startx_endx' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.start_index), cu.dwarfinfo.get_addr(cu, e.end_index), True), # type: ignore[has-type]
|
||||
'DW_RLE_startx_length': _translate_startx_length
|
||||
}
|
||||
|
||||
class RangeListsPair:
|
||||
"""For those binaries that contain both a debug_ranges and a debug_rnglists section,
|
||||
it holds a RangeLists object for both and forwards API calls to the right one based
|
||||
on the CU version.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
streamv4: IO[bytes],
|
||||
streamv5: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
dwarfinfo: DWARFInfo | None = None,
|
||||
) -> None:
|
||||
self._ranges = RangeLists(streamv4, structs, 4, dwarfinfo)
|
||||
self._rnglists = RangeLists(streamv5, structs, 5, dwarfinfo)
|
||||
|
||||
def get_range_list_at_offset(
|
||||
self,
|
||||
offset: int,
|
||||
cu: CompileUnit | None = None,
|
||||
) -> list[RangeEntry | BaseAddressEntry]:
|
||||
"""Forwards the call to either v4 section or v5 one,
|
||||
depending on DWARF version in the CU.
|
||||
"""
|
||||
if cu is None:
|
||||
raise DWARFError("For this binary, \"cu\" needs to be provided")
|
||||
section = self._rnglists if cu.header.version >= 5 else self._ranges
|
||||
return section.get_range_list_at_offset(offset, cu)
|
||||
|
||||
def get_range_list_at_offset_ex(self, offset: int) -> Container:
|
||||
"""Gets an untranslated v5 rangelist from the v5 section.
|
||||
"""
|
||||
return self._rnglists.get_range_list_at_offset_ex(offset)
|
||||
|
||||
def iter_range_lists(self) -> NoReturn:
|
||||
"""Tricky proposition, since the structure of ranges and rnglists
|
||||
is not identical. A realistic readelf implementation needs to be aware of both.
|
||||
"""
|
||||
raise DWARFError("Iterating through two sections is not supported")
|
||||
|
||||
def iter_CUs(self) -> Iterator[CompileUnit]:
|
||||
"""See RangeLists.iter_CUs()
|
||||
|
||||
CU structure is only present in DWARFv5 rnglists sections. A well written
|
||||
section dumper should check if one is present.
|
||||
"""
|
||||
return self._rnglists.iter_CUs()
|
||||
|
||||
def iter_CU_range_lists_ex(self, cu: Container) -> Iterator[CompileUnit]:
|
||||
"""See RangeLists.iter_CU_range_lists_ex()
|
||||
|
||||
CU structure is only present in DWARFv5 rnglists sections. A well written
|
||||
section dumper should check if one is present.
|
||||
"""
|
||||
return self._rnglists.iter_CU_range_lists_ex(cu)
|
||||
|
||||
def translate_v5_entry(
|
||||
self,
|
||||
entry: Container,
|
||||
cu: CompileUnit,
|
||||
) -> RangeEntry | BaseAddressEntry:
|
||||
"""Forwards a V5 entry translation request to the V5 section
|
||||
"""
|
||||
return self._rnglists.translate_v5_entry(entry, cu)
|
||||
|
||||
class RangeLists:
|
||||
""" A single range list is a Python list consisting of RangeEntry or
|
||||
BaseAddressEntry objects.
|
||||
|
||||
Since v0.29, two new parameters - version and dwarfinfo
|
||||
|
||||
version is used to distinguish DWARFv5 rnglists section from
|
||||
the DWARF<=4 ranges section. Only the 4/5 distinction matters.
|
||||
|
||||
The dwarfinfo is needed for enumeration, because enumeration
|
||||
requires scanning the DIEs, because ranges may overlap, even on DWARF<=4
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
structs: DWARFStructs,
|
||||
version: int,
|
||||
dwarfinfo: DWARFInfo | None,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.structs = structs
|
||||
self._max_addr = 2 ** (self.structs.address_size * 8) - 1
|
||||
self.version = version
|
||||
self._dwarfinfo = dwarfinfo
|
||||
|
||||
def get_range_list_at_offset(
|
||||
self,
|
||||
offset: int,
|
||||
cu: CompileUnit | None = None,
|
||||
) -> list[RangeEntry | BaseAddressEntry]:
|
||||
""" Get a range list at the given offset in the section.
|
||||
|
||||
The cu argument is necessary if the ranges section is a
|
||||
DWARFv5 debug_rnglists one, and the target rangelist
|
||||
contains indirect encodings
|
||||
"""
|
||||
self.stream.seek(offset, os.SEEK_SET)
|
||||
return self._parse_range_list_from_stream(cu)
|
||||
|
||||
def get_range_list_at_offset_ex(self, offset: int) -> Container:
|
||||
"""Get a DWARF v5 range list, addresses and offsets unresolved,
|
||||
at the given offset in the section
|
||||
"""
|
||||
return struct_parse(self.structs.Dwarf_rnglists_entries, self.stream, offset)
|
||||
|
||||
def iter_range_lists(self) -> Iterator[list[RangeEntry | BaseAddressEntry]]:
|
||||
""" Yields all range lists found in the section according to readelf rules.
|
||||
Scans the DIEs for rangelist offsets, then pulls those.
|
||||
Returned rangelists are always translated into lists of BaseAddressEntry/RangeEntry objects.
|
||||
"""
|
||||
# Rangelists can overlap. That is, one DIE points at the rangelist beginning, and another
|
||||
# points at the middle of the same. Therefore, enumerating them is not a well defined
|
||||
# operation - do you count those as two different (but overlapping) ones, or as a single one?
|
||||
# For debugging utility, you want two. That's what readelf does. For faithfully
|
||||
# representing the section contents, you want one.
|
||||
# That was the behaviour of pyelftools 0.28 and below - calling
|
||||
# parse until the stream end. Leaving aside the question of correctless,
|
||||
# that's uncompatible with readelf.
|
||||
|
||||
ver5 = self.version >= 5
|
||||
# This maps list offset to CU
|
||||
cu_map = {die.attributes['DW_AT_ranges'].value : cu
|
||||
for cu in self._dwarfinfo.iter_CUs() # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
|
||||
for die in cu.iter_DIEs()
|
||||
if 'DW_AT_ranges' in die.attributes and (cu['version'] >= 5) == ver5}
|
||||
all_offsets = list(cu_map.keys())
|
||||
all_offsets.sort()
|
||||
|
||||
for offset in all_offsets:
|
||||
yield self.get_range_list_at_offset(offset, cu_map[offset])
|
||||
|
||||
def iter_CUs(self) -> Iterator[CompileUnit]:
|
||||
"""For DWARF5 returns an array of objects, where each one has an array of offsets
|
||||
"""
|
||||
if self.version < 5:
|
||||
raise DWARFError("CU iteration in rnglists is not supported with DWARF<5")
|
||||
|
||||
assert self._dwarfinfo is not None
|
||||
structs = next(self._dwarfinfo.iter_CUs()).structs # Just pick one
|
||||
return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_rnglists_CU_header)
|
||||
|
||||
def iter_CU_range_lists_ex(self, cu: Container) -> Iterator[CompileUnit]:
|
||||
"""For DWARF5, returns untranslated rangelists in the CU, where CU comes from iter_CUs above
|
||||
"""
|
||||
stream = self.stream
|
||||
stream.seek(cu.offset_table_offset + (64 if cu.is64 else 32) * cu.offset_count)
|
||||
while stream.tell() < cu.offset_after_length + cu.unit_length:
|
||||
yield struct_parse(self.structs.Dwarf_rnglists_entries, stream)
|
||||
|
||||
def translate_v5_entry(
|
||||
self,
|
||||
entry: Container,
|
||||
cu: CompileUnit,
|
||||
) -> RangeEntry | BaseAddressEntry:
|
||||
"""Translates entries in a DWARFv5 rangelist from raw parsed format to
|
||||
a list of BaseAddressEntry/RangeEntry, using the CU
|
||||
"""
|
||||
return entry_translate[entry.entry_type](entry, cu)
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def _parse_range_list_from_stream(
|
||||
self,
|
||||
cu: CompileUnit | None,
|
||||
) -> list[RangeEntry | BaseAddressEntry]:
|
||||
if self.version >= 5:
|
||||
assert cu is not None
|
||||
return list(entry_translate[entry.entry_type](entry, cu)
|
||||
for entry
|
||||
in struct_parse(self.structs.Dwarf_rnglists_entries, self.stream))
|
||||
else:
|
||||
lst: list[RangeEntry | BaseAddressEntry] = []
|
||||
while True:
|
||||
entry_offset = self.stream.tell()
|
||||
begin_offset = struct_parse(
|
||||
self.structs.the_Dwarf_target_addr, self.stream)
|
||||
end_offset = struct_parse(
|
||||
self.structs.the_Dwarf_target_addr, self.stream)
|
||||
if begin_offset == 0 and end_offset == 0:
|
||||
# End of list - we're done.
|
||||
break
|
||||
elif begin_offset == self._max_addr:
|
||||
# Base address selection entry
|
||||
lst.append(BaseAddressEntry(entry_offset=entry_offset, base_address=end_offset))
|
||||
else:
|
||||
# Range entry
|
||||
lst.append(RangeEntry(
|
||||
entry_offset=entry_offset,
|
||||
entry_length=self.stream.tell() - entry_offset,
|
||||
begin_offset=begin_offset,
|
||||
end_offset=end_offset,
|
||||
is_absolute=False))
|
||||
return lst
|
||||
@@ -0,0 +1,603 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/structs.py
|
||||
#
|
||||
# Encapsulation of Construct structs for parsing DWARF, adjusted for correct
|
||||
# endianness and word-size.
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO, TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
import elftools.dwarf.enums as e
|
||||
from ..construct import (
|
||||
UBInt8, UBInt16, UBInt32, UBInt64, ULInt8, ULInt16, ULInt32, ULInt64,
|
||||
SBInt8, SBInt16, SBInt32, SBInt64, SLInt8, SLInt16, SLInt32, SLInt64,
|
||||
Adapter, Struct, ConstructError, If, Enum, Array, PrefixedArray,
|
||||
CString, Embed, StaticField, IfThenElse, Construct, Rename, String, Switch, Value
|
||||
)
|
||||
from ..common.construct_utils import (RepeatUntilExcluding, ULEB128, SLEB128,
|
||||
StreamOffset, ULInt24, UBInt24)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
from ..construct.adapters import LengthValueAdapter
|
||||
from ..construct.lib.container import Container
|
||||
|
||||
|
||||
class DWARFStructs:
|
||||
""" Exposes Construct structs suitable for parsing information from DWARF
|
||||
sections. Each compile unit in DWARF info can have its own structs
|
||||
object. Keep in mind that these structs have to be given a name (by
|
||||
calling them with a name) before being used for parsing (like other
|
||||
Construct structs). Those that should be used without a name are marked
|
||||
by (+).
|
||||
|
||||
Accessible attributes (mostly as described in chapter 7 of the DWARF
|
||||
spec v3):
|
||||
|
||||
Dwarf_[u]int{8,16,32,64):
|
||||
Data chunks of the common sizes
|
||||
|
||||
Dwarf_offset:
|
||||
32-bit or 64-bit word, depending on dwarf_format
|
||||
|
||||
Dwarf_length:
|
||||
32-bit or 64-bit word, depending on dwarf_format
|
||||
|
||||
Dwarf_target_addr:
|
||||
32-bit or 64-bit word, depending on address size
|
||||
|
||||
Dwarf_initial_length:
|
||||
"Initial length field" encoding
|
||||
section 7.4
|
||||
|
||||
Dwarf_{u,s}leb128:
|
||||
ULEB128 and SLEB128 variable-length encoding
|
||||
|
||||
Dwarf_CU_header (+):
|
||||
Compilation unit header
|
||||
|
||||
Dwarf_TU_header (+):
|
||||
Type unit header
|
||||
|
||||
Dwarf_abbrev_declaration (+):
|
||||
Abbreviation table declaration - doesn't include the initial
|
||||
code, only the contents.
|
||||
|
||||
Dwarf_dw_form (+):
|
||||
A dictionary mapping 'DW_FORM_*' keys into construct Structs
|
||||
that parse such forms. These Structs have already been given
|
||||
dummy names.
|
||||
|
||||
Dwarf_lineprog_header (+):
|
||||
Line program header
|
||||
|
||||
Dwarf_lineprog_file_entry (+):
|
||||
A single file entry in a line program header or instruction
|
||||
|
||||
Dwarf_CIE_header (+):
|
||||
A call-frame CIE
|
||||
|
||||
Dwarf_FDE_header (+):
|
||||
A call-frame FDE
|
||||
|
||||
See also the documentation of public methods.
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# type hints for dynamically defined class variables
|
||||
little_endian: bool
|
||||
dwarf_format: int
|
||||
address_size: int
|
||||
dwarf_version: int
|
||||
|
||||
# Cache for structs instances based on creation parameters. Structs
|
||||
# initialization is expensive and we don't won't to repeat it
|
||||
# unnecessarily.
|
||||
_structs_cache: ClassVar[dict[tuple[bool, int, int, int], Self]] = {}
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
little_endian: bool,
|
||||
dwarf_format: int,
|
||||
address_size: int,
|
||||
dwarf_version: int = 2,
|
||||
) -> Self:
|
||||
""" dwarf_version:
|
||||
Numeric DWARF version
|
||||
|
||||
little_endian:
|
||||
True if the file is little endian, False if big
|
||||
|
||||
dwarf_format:
|
||||
DWARF Format: 32 or 64-bit (see spec section 7.4)
|
||||
|
||||
address_size:
|
||||
Target machine address size, in bytes (4 or 8). (See spec
|
||||
section 7.5.1)
|
||||
"""
|
||||
key = (little_endian, dwarf_format, address_size, dwarf_version)
|
||||
|
||||
if key in cls._structs_cache:
|
||||
return cls._structs_cache[key]
|
||||
|
||||
self = super().__new__(cls)
|
||||
assert dwarf_format == 32 or dwarf_format == 64
|
||||
assert address_size == 8 or address_size == 4, str(address_size)
|
||||
self.little_endian = little_endian
|
||||
self.dwarf_format = dwarf_format
|
||||
self.address_size = address_size
|
||||
self.dwarf_version = dwarf_version
|
||||
self._create_structs()
|
||||
cls._structs_cache[key] = self
|
||||
return self
|
||||
|
||||
def initial_length_field_size(self) -> int:
|
||||
""" Size of an initial length field.
|
||||
"""
|
||||
return 4 if self.dwarf_format == 32 else 12
|
||||
|
||||
def _create_structs(self) -> None:
|
||||
if self.little_endian:
|
||||
self.Dwarf_uint8 = ULInt8
|
||||
self.Dwarf_uint16 = ULInt16
|
||||
self.Dwarf_uint24: type[ULInt24 | UBInt24] = ULInt24
|
||||
self.Dwarf_uint32 = ULInt32
|
||||
self.Dwarf_uint64 = ULInt64
|
||||
self.Dwarf_offset = ULInt32 if self.dwarf_format == 32 else ULInt64
|
||||
self.Dwarf_length = ULInt32 if self.dwarf_format == 32 else ULInt64
|
||||
self.Dwarf_target_addr = (
|
||||
ULInt32 if self.address_size == 4 else ULInt64)
|
||||
self.Dwarf_int8 = SLInt8
|
||||
self.Dwarf_int16 = SLInt16
|
||||
self.Dwarf_int32 = SLInt32
|
||||
self.Dwarf_int64 = SLInt64
|
||||
else:
|
||||
self.Dwarf_uint8 = UBInt8
|
||||
self.Dwarf_uint16 = UBInt16
|
||||
self.Dwarf_uint24 = UBInt24
|
||||
self.Dwarf_uint32 = UBInt32
|
||||
self.Dwarf_uint64 = UBInt64
|
||||
self.Dwarf_offset = UBInt32 if self.dwarf_format == 32 else UBInt64
|
||||
self.Dwarf_length = UBInt32 if self.dwarf_format == 32 else UBInt64
|
||||
self.Dwarf_target_addr = (
|
||||
UBInt32 if self.address_size == 4 else UBInt64)
|
||||
self.Dwarf_int8 = SBInt8
|
||||
self.Dwarf_int16 = SBInt16
|
||||
self.Dwarf_int32 = SBInt32
|
||||
self.Dwarf_int64 = SBInt64
|
||||
|
||||
# Only instantiate those parsers that are used standalone,
|
||||
# as opposed to dispatch tables (e. g. forms, opcodes).
|
||||
# In dispatch tables, they are instantiated already.
|
||||
# LEB128 parsers are instantiated too, elsewhere.
|
||||
self.the_Dwarf_offset = self.Dwarf_offset('')
|
||||
self.the_Dwarf_target_addr = self.Dwarf_target_addr('')
|
||||
self.the_Dwarf_uint32 = self.Dwarf_uint32('')
|
||||
self.the_Dwarf_uint16 = self.Dwarf_uint16('')
|
||||
self.the_Dwarf_uint8 = self.Dwarf_uint8('')
|
||||
|
||||
self._create_initial_length()
|
||||
self._create_leb128()
|
||||
self._create_cu_header()
|
||||
self._create_tu_header()
|
||||
self._create_abbrev_declaration()
|
||||
self._create_dw_form()
|
||||
self._create_lineprog_header()
|
||||
self._create_callframe_entry_headers()
|
||||
self._create_aranges_header()
|
||||
self._create_nameLUT_header()
|
||||
self._create_string_offsets_table_header()
|
||||
self._create_address_table_header()
|
||||
self._create_loclists_parsers()
|
||||
self._create_rnglists_parsers()
|
||||
|
||||
self._create_debugsup()
|
||||
self._create_gnu_debugaltlink()
|
||||
|
||||
def _create_initial_length(self) -> None:
|
||||
|
||||
def _InitialLength(name: str) -> _InitialLengthAdapter:
|
||||
# Adapts a Struct that parses forward a full initial length field.
|
||||
# Only if the first word is the continuation value, the second
|
||||
# word is parsed from the stream.
|
||||
return _InitialLengthAdapter(
|
||||
Struct(name,
|
||||
self.Dwarf_uint32('first'),
|
||||
If(lambda ctx: ctx.first == 0xFFFFFFFF,
|
||||
self.Dwarf_uint64('second'),
|
||||
elsevalue=None)))
|
||||
|
||||
self.Dwarf_initial_length = _InitialLength
|
||||
|
||||
def _create_leb128(self) -> None:
|
||||
self.Dwarf_uleb128 = ULEB128
|
||||
self.Dwarf_sleb128 = SLEB128
|
||||
self.the_Dwarf_uleb128 = self.Dwarf_uleb128('')
|
||||
self.the_Dwarf_sleb128 = self.Dwarf_sleb128('')
|
||||
|
||||
def _create_cu_header(self) -> None:
|
||||
dwarfv4_CU_header = Struct('',
|
||||
self.Dwarf_offset('debug_abbrev_offset'),
|
||||
self.Dwarf_uint8('address_size')
|
||||
)
|
||||
# DWARFv5 reverses the order of address_size and debug_abbrev_offset.
|
||||
# DWARFv5 7.5.1.1
|
||||
dwarfv5_CP_CU_header = Struct('',
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_offset('debug_abbrev_offset')
|
||||
)
|
||||
# DWARFv5 7.5.1.2
|
||||
dwarfv5_SS_CU_header = Struct('',
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_offset('debug_abbrev_offset'),
|
||||
self.Dwarf_uint64('dwo_id')
|
||||
)
|
||||
# DWARFv5 7.5.1.3
|
||||
dwarfv5_TS_CU_header = Struct('',
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_offset('debug_abbrev_offset'),
|
||||
self.Dwarf_uint64('type_signature'),
|
||||
self.Dwarf_offset('type_offset')
|
||||
)
|
||||
dwarfv5_CU_header = Struct('',
|
||||
Enum(self.Dwarf_uint8('unit_type'), **e.ENUM_DW_UT),
|
||||
Embed(Switch('', lambda ctx: ctx.unit_type,
|
||||
{
|
||||
'DW_UT_compile' : dwarfv5_CP_CU_header,
|
||||
'DW_UT_partial' : dwarfv5_CP_CU_header,
|
||||
'DW_UT_skeleton' : dwarfv5_SS_CU_header,
|
||||
'DW_UT_split_compile' : dwarfv5_SS_CU_header,
|
||||
'DW_UT_type' : dwarfv5_TS_CU_header,
|
||||
'DW_UT_split_type' : dwarfv5_TS_CU_header,
|
||||
})))
|
||||
self.Dwarf_CU_header = Struct('Dwarf_CU_header',
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
IfThenElse('', lambda ctx: ctx['version'] >= 5,
|
||||
Embed(dwarfv5_CU_header),
|
||||
Embed(dwarfv4_CU_header),
|
||||
))
|
||||
|
||||
def _create_tu_header(self) -> None:
|
||||
self.Dwarf_TU_header = Struct('Dwarf_TU_header',
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_offset('debug_abbrev_offset'),
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_uint64('signature'),
|
||||
self.Dwarf_offset('type_offset'))
|
||||
|
||||
def _create_abbrev_declaration(self) -> None:
|
||||
self.Dwarf_abbrev_declaration = Struct('Dwarf_abbrev_entry',
|
||||
Enum(self.Dwarf_uleb128('tag'), **e.ENUM_DW_TAG),
|
||||
Enum(self.Dwarf_uint8('children_flag'), **e.ENUM_DW_CHILDREN),
|
||||
RepeatUntilExcluding(
|
||||
lambda obj, ctx:
|
||||
obj.name == 'DW_AT_null' and obj.form == 'DW_FORM_null',
|
||||
Struct('attr_spec',
|
||||
Enum(self.Dwarf_uleb128('name'), **e.ENUM_DW_AT),
|
||||
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM),
|
||||
If(lambda ctx: ctx['form'] == 'DW_FORM_implicit_const',
|
||||
self.Dwarf_sleb128('value')))))
|
||||
|
||||
def _create_debugsup(self) -> None:
|
||||
# We don't care about checksums, for now.
|
||||
self.Dwarf_debugsup = Struct('Elf_debugsup',
|
||||
self.Dwarf_int16('version'),
|
||||
self.Dwarf_uint8('is_supplementary'),
|
||||
CString('sup_filename'))
|
||||
|
||||
def _create_gnu_debugaltlink(self) -> None:
|
||||
self.Dwarf_debugaltlink = Struct('Elf_debugaltlink',
|
||||
CString("sup_filename"),
|
||||
String("sup_checksum", length=20))
|
||||
|
||||
def _create_dw_form(self) -> None:
|
||||
self.Dwarf_dw_form = dict(
|
||||
DW_FORM_addr=self.the_Dwarf_target_addr,
|
||||
DW_FORM_addrx=self.the_Dwarf_uleb128,
|
||||
DW_FORM_addrx1=self.the_Dwarf_uint8,
|
||||
DW_FORM_addrx2=self.the_Dwarf_uint16,
|
||||
DW_FORM_addrx3=self.Dwarf_uint24(''),
|
||||
DW_FORM_addrx4=self.the_Dwarf_uint32,
|
||||
|
||||
DW_FORM_block1=self._make_block_struct(self.Dwarf_uint8),
|
||||
DW_FORM_block2=self._make_block_struct(self.Dwarf_uint16),
|
||||
DW_FORM_block4=self._make_block_struct(self.Dwarf_uint32),
|
||||
DW_FORM_block=self._make_block_struct(self.Dwarf_uleb128),
|
||||
|
||||
# All DW_FORM_data<n> forms are assumed to be unsigned
|
||||
DW_FORM_data1=self.the_Dwarf_uint8,
|
||||
DW_FORM_data2=self.the_Dwarf_uint16,
|
||||
DW_FORM_data4=self.the_Dwarf_uint32,
|
||||
DW_FORM_data8=self.Dwarf_uint64(''),
|
||||
DW_FORM_data16=Array(16, self.the_Dwarf_uint8), # Used for hashes and such, not for integers
|
||||
DW_FORM_sdata=self.the_Dwarf_sleb128,
|
||||
DW_FORM_udata=self.the_Dwarf_uleb128,
|
||||
|
||||
DW_FORM_string=CString(''),
|
||||
DW_FORM_strp=self.the_Dwarf_offset,
|
||||
DW_FORM_strp_sup=self.the_Dwarf_offset,
|
||||
DW_FORM_line_strp=self.the_Dwarf_offset,
|
||||
DW_FORM_strx=self.the_Dwarf_uleb128,
|
||||
DW_FORM_strx1=self.the_Dwarf_uint8,
|
||||
DW_FORM_strx2=self.the_Dwarf_uint16,
|
||||
DW_FORM_strx3=self.Dwarf_uint24(''),
|
||||
DW_FORM_strx4=self.Dwarf_uint64(''),
|
||||
DW_FORM_flag=self.the_Dwarf_uint8,
|
||||
|
||||
DW_FORM_ref=self.the_Dwarf_uint32,
|
||||
DW_FORM_ref1=self.the_Dwarf_uint8,
|
||||
DW_FORM_ref2=self.the_Dwarf_uint16,
|
||||
DW_FORM_ref4=self.the_Dwarf_uint32,
|
||||
DW_FORM_ref_sup4=self.the_Dwarf_uint32,
|
||||
DW_FORM_ref8=self.Dwarf_uint64(''),
|
||||
DW_FORM_ref_sup8=self.Dwarf_uint64(''),
|
||||
DW_FORM_ref_udata=self.the_Dwarf_uleb128,
|
||||
DW_FORM_ref_addr=self.the_Dwarf_target_addr if self.dwarf_version == 2 else self.the_Dwarf_offset,
|
||||
|
||||
DW_FORM_indirect=self.the_Dwarf_uleb128,
|
||||
|
||||
# Treated separatedly while parsing, but here so that all forms resolve
|
||||
DW_FORM_implicit_const=None,
|
||||
|
||||
# New forms in DWARFv4
|
||||
DW_FORM_flag_present = StaticField('', 0),
|
||||
DW_FORM_sec_offset = self.the_Dwarf_offset,
|
||||
DW_FORM_exprloc = self._make_block_struct(self.Dwarf_uleb128),
|
||||
DW_FORM_ref_sig8 = self.Dwarf_uint64(''),
|
||||
|
||||
DW_FORM_GNU_strp_alt=self.the_Dwarf_offset,
|
||||
DW_FORM_GNU_ref_alt=self.the_Dwarf_offset,
|
||||
DW_AT_GNU_all_call_sites=self.the_Dwarf_uleb128,
|
||||
|
||||
# New forms in DWARFv5
|
||||
DW_FORM_loclistx=self.the_Dwarf_uleb128,
|
||||
DW_FORM_rnglistx=self.the_Dwarf_uleb128
|
||||
)
|
||||
|
||||
def _create_aranges_header(self) -> None:
|
||||
self.Dwarf_aranges_header = Struct("Dwarf_aranges_header",
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_offset('debug_info_offset'), # a little tbd
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_uint8('segment_size')
|
||||
)
|
||||
|
||||
def _create_nameLUT_header(self) -> None:
|
||||
self.Dwarf_nameLUT_header = Struct("Dwarf_nameLUT_header",
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_offset('debug_info_offset'),
|
||||
self.Dwarf_length('debug_info_length')
|
||||
)
|
||||
|
||||
def _create_string_offsets_table_header(self) -> None:
|
||||
self.Dwarf_string_offsets_table_header = Struct(
|
||||
"Dwarf_string_offets_table_header",
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_uint16('padding'),
|
||||
)
|
||||
|
||||
def _create_address_table_header(self) -> None:
|
||||
self.Dwarf_address_table_header = Struct("Dwarf_address_table_header",
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_uint8('segment_selector_size'),
|
||||
)
|
||||
|
||||
def _create_lineprog_header(self) -> None:
|
||||
# A file entry is terminated by a NULL byte, so we don't want to parse
|
||||
# past it. Therefore an If is used.
|
||||
self.Dwarf_lineprog_file_entry = Struct('file_entry',
|
||||
CString('name'),
|
||||
If(lambda ctx: bool(ctx.name),
|
||||
Embed(Struct('',
|
||||
self.Dwarf_uleb128('dir_index'),
|
||||
self.Dwarf_uleb128('mtime'),
|
||||
self.Dwarf_uleb128('length')))))
|
||||
|
||||
class FormattedEntry(Construct):
|
||||
# Generates a parser based on a previously parsed piece,
|
||||
# similar to deprecared Dynamic.
|
||||
# Strings are resolved later, since it potentially requires
|
||||
# looking at another section.
|
||||
def __init__(self, name: str, structs: DWARFStructs, format_field: str) -> None:
|
||||
Construct.__init__(self, name)
|
||||
self.structs = structs
|
||||
self.format_field = format_field
|
||||
|
||||
def _parse(self, stream: IO[bytes], context: Container) -> Any:
|
||||
# Somewhat tricky technique here, explicitly writing back to the context
|
||||
if self.format_field + "_parser" in context:
|
||||
parser = context[self.format_field + "_parser"]
|
||||
else:
|
||||
fields = tuple(
|
||||
Rename(f.content_type, self.structs.Dwarf_dw_form[f.form]) # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
for f in context[self.format_field])
|
||||
parser = Struct('formatted_entry', *fields)
|
||||
context[self.format_field + "_parser"] = parser
|
||||
return parser._parse(stream, context)
|
||||
|
||||
ver5 = lambda ctx: ctx.version >= 5
|
||||
|
||||
self.Dwarf_lineprog_header = Struct('Dwarf_lineprog_header',
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
If(ver5,
|
||||
self.Dwarf_uint8("address_size"),
|
||||
None),
|
||||
If(ver5,
|
||||
self.Dwarf_uint8("segment_selector_size"),
|
||||
None),
|
||||
self.Dwarf_offset('header_length'),
|
||||
self.Dwarf_uint8('minimum_instruction_length'),
|
||||
If(lambda ctx: ctx.version >= 4,
|
||||
self.Dwarf_uint8("maximum_operations_per_instruction"),
|
||||
1),
|
||||
self.Dwarf_uint8('default_is_stmt'),
|
||||
self.Dwarf_int8('line_base'),
|
||||
self.Dwarf_uint8('line_range'),
|
||||
self.Dwarf_uint8('opcode_base'),
|
||||
Array(lambda ctx: ctx.opcode_base - 1,
|
||||
self.Dwarf_uint8('standard_opcode_lengths')),
|
||||
If(ver5,
|
||||
PrefixedArray(
|
||||
Struct('directory_entry_format',
|
||||
Enum(self.Dwarf_uleb128('content_type'), **e.ENUM_DW_LNCT),
|
||||
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM)),
|
||||
self.Dwarf_uint8("directory_entry_format_count"))),
|
||||
If(ver5, # Name deliberately doesn't match the legacy object, since the format can't be made compatible
|
||||
PrefixedArray(
|
||||
FormattedEntry('directories', self, "directory_entry_format"),
|
||||
self.Dwarf_uleb128('directories_count'))),
|
||||
If(ver5,
|
||||
PrefixedArray(
|
||||
Struct('file_name_entry_format',
|
||||
Enum(self.Dwarf_uleb128('content_type'), **e.ENUM_DW_LNCT),
|
||||
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM)),
|
||||
self.Dwarf_uint8("file_name_entry_format_count"))),
|
||||
If(ver5,
|
||||
PrefixedArray(
|
||||
FormattedEntry('file_names', self, "file_name_entry_format"),
|
||||
self.Dwarf_uleb128('file_names_count'))),
|
||||
# Legacy directories/files - DWARF < 5 only
|
||||
If(lambda ctx: ctx.version < 5,
|
||||
RepeatUntilExcluding(
|
||||
lambda obj, ctx: obj == b'',
|
||||
CString('include_directory'))),
|
||||
If(lambda ctx: ctx.version < 5,
|
||||
RepeatUntilExcluding(
|
||||
lambda obj, ctx: not obj.name,
|
||||
self.Dwarf_lineprog_file_entry)) # array name is file_entry
|
||||
)
|
||||
|
||||
def _create_callframe_entry_headers(self) -> None:
|
||||
self.Dwarf_CIE_header = Struct('Dwarf_CIE_header',
|
||||
self.Dwarf_initial_length('length'),
|
||||
self.Dwarf_offset('CIE_id'),
|
||||
self.Dwarf_uint8('version'),
|
||||
CString('augmentation'),
|
||||
If(lambda ctx: ctx.version >= 4, self.Dwarf_uint8('address_size')),
|
||||
If(lambda ctx: ctx.version >= 4, self.Dwarf_uint8('segment_size')),
|
||||
self.Dwarf_uleb128('code_alignment_factor'),
|
||||
self.Dwarf_sleb128('data_alignment_factor'),
|
||||
IfThenElse('return_address_register', lambda ctx: ctx.version > 1,
|
||||
self.Dwarf_uleb128(''),
|
||||
self.Dwarf_uint8('')))
|
||||
self.EH_CIE_header = self.Dwarf_CIE_header
|
||||
|
||||
# The CIE header was modified in DWARFv4, but the
|
||||
# CIE header version is driven by the version # in the header
|
||||
# itself, independent of the DWARF version
|
||||
# in the CUs.
|
||||
|
||||
self.Dwarf_FDE_header = Struct('Dwarf_FDE_header',
|
||||
self.Dwarf_initial_length('length'),
|
||||
self.Dwarf_offset('CIE_pointer'),
|
||||
self.Dwarf_target_addr('initial_location'),
|
||||
self.Dwarf_target_addr('address_range'))
|
||||
|
||||
def _make_block_struct(self, length_field: Callable[[str], Construct]) -> LengthValueAdapter:
|
||||
""" Create a struct for DW_FORM_block<size>
|
||||
"""
|
||||
return PrefixedArray(
|
||||
subcon=self.Dwarf_uint8('elem'),
|
||||
length_field=length_field(''))
|
||||
|
||||
def _create_loclists_parsers(self) -> None:
|
||||
""" Create a struct for debug_loclists CU header, DWARFv5, 7,29
|
||||
"""
|
||||
self.Dwarf_loclists_CU_header = Struct('Dwarf_loclists_CU_header',
|
||||
StreamOffset('cu_offset'),
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
Value('is64', lambda ctx: ctx.is64),
|
||||
StreamOffset('offset_after_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_uint8('segment_selector_size'),
|
||||
self.Dwarf_uint32('offset_count'),
|
||||
StreamOffset('offset_table_offset'))
|
||||
|
||||
cld = self.Dwarf_loclists_counted_location_description = PrefixedArray(self.Dwarf_uint8('loc_expr'), self.the_Dwarf_uleb128)
|
||||
|
||||
self.Dwarf_loclists_entries = RepeatUntilExcluding(
|
||||
lambda obj, ctx: obj.entry_type == 'DW_LLE_end_of_list',
|
||||
Struct('entry',
|
||||
StreamOffset('entry_offset'),
|
||||
Enum(self.Dwarf_uint8('entry_type'), **e.ENUM_DW_LLE),
|
||||
Embed(Switch('', lambda ctx: ctx.entry_type,
|
||||
{
|
||||
'DW_LLE_end_of_list' : Struct('end_of_list'),
|
||||
'DW_LLE_base_addressx' : Struct('base_addressx', self.Dwarf_uleb128('index')),
|
||||
'DW_LLE_startx_endx' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('end_index'), cld),
|
||||
'DW_LLE_startx_length' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('length'), cld),
|
||||
'DW_LLE_offset_pair' : Struct('startx_endx', self.Dwarf_uleb128('start_offset'), self.Dwarf_uleb128('end_offset'), cld),
|
||||
'DW_LLE_default_location' : Struct('default_location', cld),
|
||||
'DW_LLE_base_address' : Struct('base_address', self.Dwarf_target_addr('address')),
|
||||
'DW_LLE_start_end' : Struct('start_end', self.Dwarf_target_addr('start_address'), self.Dwarf_target_addr('end_address'), cld),
|
||||
'DW_LLE_start_length' : Struct('start_length', self.Dwarf_target_addr('start_address'), self.Dwarf_uleb128('length'), cld),
|
||||
})),
|
||||
StreamOffset('entry_end_offset'),
|
||||
Value('entry_length', lambda ctx: ctx.entry_end_offset - ctx.entry_offset)))
|
||||
|
||||
self.Dwarf_locview_pair = Struct('locview_pair',
|
||||
StreamOffset('entry_offset'), self.Dwarf_uleb128('begin'), self.Dwarf_uleb128('end'))
|
||||
|
||||
def _create_rnglists_parsers(self) -> None:
|
||||
self.Dwarf_rnglists_CU_header = Struct('Dwarf_rnglists_CU_header',
|
||||
StreamOffset('cu_offset'),
|
||||
self.Dwarf_initial_length('unit_length'),
|
||||
Value('is64', lambda ctx: ctx.is64),
|
||||
StreamOffset('offset_after_length'),
|
||||
self.Dwarf_uint16('version'),
|
||||
self.Dwarf_uint8('address_size'),
|
||||
self.Dwarf_uint8('segment_selector_size'),
|
||||
self.Dwarf_uint32('offset_count'),
|
||||
StreamOffset('offset_table_offset'))
|
||||
|
||||
self.Dwarf_rnglists_entries = RepeatUntilExcluding(
|
||||
lambda obj, ctx: obj.entry_type == 'DW_RLE_end_of_list',
|
||||
Struct('entry',
|
||||
StreamOffset('entry_offset'),
|
||||
Enum(self.Dwarf_uint8('entry_type'), **e.ENUM_DW_RLE),
|
||||
Embed(Switch('', lambda ctx: ctx.entry_type,
|
||||
{
|
||||
'DW_RLE_end_of_list' : Struct('end_of_list'),
|
||||
'DW_RLE_base_addressx' : Struct('base_addressx', self.Dwarf_uleb128('index')),
|
||||
'DW_RLE_startx_endx' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('end_index')),
|
||||
'DW_RLE_startx_length' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('length')),
|
||||
'DW_RLE_offset_pair' : Struct('startx_endx', self.Dwarf_uleb128('start_offset'), self.Dwarf_uleb128('end_offset')),
|
||||
'DW_RLE_base_address' : Struct('base_address', self.Dwarf_target_addr('address')),
|
||||
'DW_RLE_start_end' : Struct('start_end', self.Dwarf_target_addr('start_address'), self.Dwarf_target_addr('end_address')),
|
||||
'DW_RLE_start_length' : Struct('start_length', self.Dwarf_target_addr('start_address'), self.Dwarf_uleb128('length'))
|
||||
})),
|
||||
StreamOffset('entry_end_offset'),
|
||||
Value('entry_length', lambda ctx: ctx.entry_end_offset - ctx.entry_offset)))
|
||||
|
||||
|
||||
class _InitialLengthAdapter(Adapter):
|
||||
""" A standard Construct adapter that expects a sub-construct
|
||||
as a struct with one or two values (first, second).
|
||||
"""
|
||||
def _decode(self, obj: Container, context: Container) -> int:
|
||||
if obj.first < 0xFFFFFF00:
|
||||
context['is64'] = False
|
||||
return obj.first
|
||||
else:
|
||||
if obj.first == 0xFFFFFFFF:
|
||||
context['is64'] = True
|
||||
return obj.second
|
||||
else:
|
||||
raise ConstructError("Failed decoding initial length for %X" % (
|
||||
obj.first))
|
||||
@@ -0,0 +1,278 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/typeunit.py
|
||||
#
|
||||
# DWARF type unit
|
||||
#
|
||||
# Dinkar Khandalekar (contact@dinkar.dev)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from bisect import bisect_right
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .die import DIE
|
||||
from ..common.utils import dwarf_assert
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .abbrevtable import AbbrevTable
|
||||
from .dwarfinfo import DWARFInfo
|
||||
from .structs import DWARFStructs
|
||||
|
||||
|
||||
class TypeUnit:
|
||||
""" A DWARF type unit (TU).
|
||||
|
||||
A type unit contains type definition entries that can be used to
|
||||
reference to type definition for debugging information entries in
|
||||
other compilation units and type units. Each type unit must be uniquely
|
||||
identified by a 64-bit signature. (DWARFv4 section 3.1.3)
|
||||
|
||||
Type units are stored in the .debug_types section. This section was
|
||||
introduced by the DWARFv4 standard (and removed in the DWARFv5 standard;
|
||||
the underlying type units were relocated to the .debug_info
|
||||
section - DWARFv5 section 1.4)
|
||||
|
||||
Serves as a container and context to DIEs that describe type definitions
|
||||
referenced from compilation units and other type units.
|
||||
|
||||
TU header entries can be accessed as dict keys from this object, i.e.
|
||||
tu = TypeUnit(...)
|
||||
tu['version'] # version field of the TU header
|
||||
|
||||
To get the top-level DIE describing the type unit, call the
|
||||
get_top_DIE method.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
dwarfinfo: DWARFInfo,
|
||||
structs: DWARFStructs,
|
||||
tu_offset: int,
|
||||
tu_die_offset: int,
|
||||
) -> None:
|
||||
""" header:
|
||||
TU header for this type unit
|
||||
|
||||
dwarfinfo:
|
||||
The DWARFInfo context object which created this one
|
||||
|
||||
structs:
|
||||
A DWARFStructs instance suitable for this type unit
|
||||
|
||||
tu_offset:
|
||||
Offset in the stream to the beginning of this TU (its header)
|
||||
|
||||
tu_die_offset:
|
||||
Offset in the stream of the top DIE of this TU
|
||||
"""
|
||||
self.dwarfinfo = dwarfinfo
|
||||
self.header = header
|
||||
self.structs = structs
|
||||
self.tu_offset = tu_offset
|
||||
self.tu_die_offset = tu_die_offset
|
||||
|
||||
# A list of DIEs belonging to this TU.
|
||||
# This list is lazily constructed as DIEs are iterated over.
|
||||
self._dielist: list[DIE] = []
|
||||
# A list of file offsets, corresponding (by index) to the DIEs
|
||||
# in `self._dielist`. This list exists separately from
|
||||
# `self._dielist` to make it binary searchable, enabling the
|
||||
# DIE population strategy used in `iter_DIE_children`.
|
||||
# Like `self._dielist`, this list is lazily constructed
|
||||
# as DIEs are iterated over.
|
||||
self._diemap: list[int] = []
|
||||
|
||||
@property
|
||||
def cu_offset(self) -> int:
|
||||
"""Simulates the cu_offset attribute required by the DIE by returning the tu_offset instead
|
||||
"""
|
||||
return self.tu_offset
|
||||
|
||||
@property
|
||||
def cu_die_offset(self) -> int:
|
||||
"""Simulates the cu_die_offset attribute required by the DIE by returning the tu_offset instead
|
||||
"""
|
||||
return self.tu_die_offset
|
||||
|
||||
def dwarf_format(self) -> int:
|
||||
""" Get the DWARF format (32 or 64) for this TU
|
||||
"""
|
||||
return self.structs.dwarf_format
|
||||
|
||||
def get_abbrev_table(self) -> AbbrevTable:
|
||||
""" Get the abbreviation table (AbbrevTable object) for this TU
|
||||
"""
|
||||
return self._abbrev_table
|
||||
|
||||
@cached_property
|
||||
def _abbrev_table(self) -> AbbrevTable:
|
||||
return self.dwarfinfo.get_abbrev_table(self['debug_abbrev_offset'])
|
||||
|
||||
def get_top_DIE(self) -> DIE:
|
||||
""" Get the top DIE (which is DW_TAG_type_unit entry) of this TU
|
||||
"""
|
||||
|
||||
# Note that a top DIE always has minimal offset and is therefore
|
||||
# at the beginning of our lists, so no bisect is required.
|
||||
if self._diemap:
|
||||
return self._dielist[0]
|
||||
|
||||
assert self.dwarfinfo.debug_types_sec is not None
|
||||
top = DIE(
|
||||
cu=self,
|
||||
stream=self.dwarfinfo.debug_types_sec.stream,
|
||||
offset=self.tu_die_offset)
|
||||
|
||||
self._dielist.insert(0, top)
|
||||
self._diemap.insert(0, self.tu_die_offset)
|
||||
|
||||
top._translate_indirect_attributes() # Can't translate indirect attributes until the top DIE has been parsed to the end
|
||||
|
||||
return top
|
||||
|
||||
def has_top_DIE(self) -> bool:
|
||||
""" Returns whether the top DIE in this TU has already been parsed and cached.
|
||||
No parsing on demand!
|
||||
"""
|
||||
return bool(self._diemap)
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return self['unit_length'] + self.structs.initial_length_field_size()
|
||||
|
||||
def iter_DIEs(self) -> Iterator[DIE]:
|
||||
""" Iterate over all the DIEs in the TU, in order of their appearance.
|
||||
Note that null DIEs will also be returned.
|
||||
"""
|
||||
return self._iter_DIE_subtree(self.get_top_DIE())
|
||||
|
||||
def iter_DIE_children(self, die: DIE) -> Iterator[DIE]:
|
||||
""" Given a DIE, yields either its children, without null DIE list
|
||||
terminator, or nothing, if that DIE has no children.
|
||||
|
||||
The null DIE terminator is saved in that DIE when iteration ended.
|
||||
"""
|
||||
if not die.has_children:
|
||||
return
|
||||
|
||||
# `cur_offset` tracks the stream offset of the next DIE to yield
|
||||
# as we iterate over our children,
|
||||
cur_offset = die.offset + die.size
|
||||
|
||||
while True:
|
||||
child = self._get_cached_DIE(cur_offset)
|
||||
|
||||
child.set_parent(die)
|
||||
|
||||
if child.is_null():
|
||||
die._terminator = child
|
||||
return
|
||||
|
||||
yield child
|
||||
|
||||
if not child.has_children:
|
||||
cur_offset += child.size
|
||||
elif "DW_AT_sibling" in child.attributes:
|
||||
sibling = child.attributes["DW_AT_sibling"]
|
||||
if sibling.form in ('DW_FORM_ref1', 'DW_FORM_ref2',
|
||||
'DW_FORM_ref4', 'DW_FORM_ref8',
|
||||
'DW_FORM_ref', 'DW_FORM_ref_udata'):
|
||||
cur_offset = sibling.value + self.tu_offset
|
||||
elif sibling.form == 'DW_FORM_ref_addr':
|
||||
cur_offset = sibling.value
|
||||
else:
|
||||
raise NotImplementedError('sibling in form %s' % sibling.form)
|
||||
else:
|
||||
# If no DW_AT_sibling attribute is provided by the producer
|
||||
# then the whole child subtree must be parsed to find its next
|
||||
# sibling. There is one zero byte representing null DIE
|
||||
# terminating children list. It is used to locate child subtree
|
||||
# bounds.
|
||||
|
||||
# If children are not parsed yet, this instruction will manage
|
||||
# to recursive call of this function which will result in
|
||||
# setting of `_terminator` attribute of the `child`.
|
||||
if child._terminator is None:
|
||||
for _ in self.iter_DIE_children(child):
|
||||
pass
|
||||
assert child._terminator is not None
|
||||
|
||||
cur_offset = child._terminator.offset + child._terminator.size
|
||||
|
||||
def get_DIE_from_refaddr(self, refaddr: int) -> DIE:
|
||||
""" Obtain a DIE contained in this CU from a reference.
|
||||
refaddr:
|
||||
The offset into the .debug_info section, which must be
|
||||
contained in this CU or a DWARFError will be raised.
|
||||
When using a reference class attribute with a form that is
|
||||
relative to the compile unit, add unit add the compile unit's
|
||||
.cu_addr before calling this function.
|
||||
"""
|
||||
# All DIEs are after the cu header and within the unit
|
||||
dwarf_assert(
|
||||
self.cu_die_offset <= refaddr < self.cu_offset + self.size,
|
||||
'refaddr %s not in DIE range of CU %s' % (refaddr, self.cu_offset))
|
||||
|
||||
return self._get_cached_DIE(refaddr)
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _iter_DIE_subtree(self, die: DIE) -> Iterator[DIE]:
|
||||
""" Given a DIE, this yields it with its subtree including null DIEs
|
||||
(child list terminators).
|
||||
"""
|
||||
# If the die is an imported unit, replace it with what it refers to if
|
||||
# we can
|
||||
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
|
||||
die = die.get_DIE_from_attribute('DW_AT_import')
|
||||
yield die
|
||||
if die.has_children:
|
||||
for c in die.iter_children():
|
||||
yield from die.cu._iter_DIE_subtree(c)
|
||||
assert die._terminator is not None
|
||||
yield die._terminator
|
||||
|
||||
def _get_cached_DIE(self, offset: int) -> DIE:
|
||||
""" Given a DIE offset, look it up in the cache. If not present,
|
||||
parse the DIE and insert it into the cache.
|
||||
|
||||
offset:
|
||||
The offset of the DIE in the debug_types section to retrieve.
|
||||
|
||||
The stream reference is copied from the top DIE. The top die will
|
||||
also be parsed and cached if needed.
|
||||
|
||||
See also get_DIE_from_refaddr(self, refaddr).
|
||||
"""
|
||||
# The top die must be in the cache if any DIE is in the cache.
|
||||
# The stream is the same for all DIEs in this TU, so populate
|
||||
# the top DIE and obtain a reference to its stream.
|
||||
top_die_stream = self.get_top_DIE().stream
|
||||
|
||||
# `offset` is the offset in the stream of the DIE we want to return.
|
||||
# The map is maintined as a parallel array to the list. We call
|
||||
# bisect each time to ensure new DIEs are inserted in the correct
|
||||
# order within both `self._dielist` and `self._diemap`.
|
||||
i = bisect_right(self._diemap, offset)
|
||||
|
||||
# Note that `self._diemap` cannot be empty because a the top DIE
|
||||
# was inserted by the call to .get_top_DIE(). Also it has the minimal
|
||||
# offset, so the bisect_right insert point will always be at least 1.
|
||||
if offset == self._diemap[i - 1]:
|
||||
die = self._dielist[i - 1]
|
||||
else:
|
||||
die = DIE(cu=self, stream=top_die_stream, offset=offset)
|
||||
self._dielist.insert(i, die)
|
||||
self._diemap.insert(i, offset)
|
||||
|
||||
return die
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
EHABI_INDEX_ENTRY_SIZE: int = 8
|
||||
@@ -0,0 +1,289 @@
|
||||
# -------------------------------------------------------------------------------
|
||||
# elftools: ehabi/decoder.py
|
||||
#
|
||||
# Decode ARM exception handler bytecode.
|
||||
#
|
||||
# LeadroyaL (leadroyal@qq.com)
|
||||
# This code is in the public domain
|
||||
# -------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, NamedTuple
|
||||
|
||||
|
||||
class EHABIBytecodeDecoder:
|
||||
""" Decoder of a sequence of ARM exception handler abi bytecode.
|
||||
|
||||
Reference:
|
||||
https://github.com/llvm/llvm-project/blob/master/llvm/tools/llvm-readobj/ARMEHABIPrinter.h
|
||||
https://developer.arm.com/documentation/ihi0038/b/
|
||||
|
||||
Accessible attributes:
|
||||
|
||||
mnemonic_array:
|
||||
MnemonicItem array.
|
||||
|
||||
Parameters:
|
||||
|
||||
bytecode_array:
|
||||
Integer array, raw data of bytecode.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, bytecode_array: list[int]) -> None:
|
||||
self._bytecode_array = bytecode_array
|
||||
self._index: int = 0
|
||||
self.mnemonic_array: list[MnemonicItem] | None = None
|
||||
self._decode()
|
||||
|
||||
def _decode(self) -> None:
|
||||
""" Decode bytecode array, put result into mnemonic_array.
|
||||
"""
|
||||
self._index = 0
|
||||
self.mnemonic_array = []
|
||||
while self._index < len(self._bytecode_array):
|
||||
for mask, value, handler in self.ring:
|
||||
if (self._bytecode_array[self._index] & mask) == value:
|
||||
start_idx = self._index
|
||||
mnemonic = handler(self)
|
||||
end_idx = self._index
|
||||
self.mnemonic_array.append(
|
||||
MnemonicItem(self._bytecode_array[start_idx: end_idx], mnemonic))
|
||||
break
|
||||
|
||||
def _decode_00xxxxxx(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; vsp = vsp + %u\n", Opcode,
|
||||
# ((Opcode & 0x3f) << 2) + 4);
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'vsp = vsp + %u' % (((opcode & 0x3f) << 2) + 4)
|
||||
|
||||
def _decode_01xxxxxx(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; vsp = vsp - %u\n", Opcode,
|
||||
# ((Opcode & 0x3f) << 2) + 4);
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'vsp = vsp - %u' % (((opcode & 0x3f) << 2) + 4)
|
||||
|
||||
gpr_register_names = ("r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
||||
"r8", "r9", "r10", "fp", "ip", "sp", "lr", "pc")
|
||||
|
||||
def _calculate_range(self, start: int, count: int) -> int:
|
||||
return ((1 << (count + 1)) - 1) << start
|
||||
|
||||
def _printGPR(self, gpr_mask: int) -> str:
|
||||
hits = [self.gpr_register_names[i] for i in range(32) if gpr_mask & (1 << i) != 0]
|
||||
return '{%s}' % ', '.join(hits)
|
||||
|
||||
def _print_registers(self, vfp_mask: int, prefix: str) -> str:
|
||||
hits = [prefix + str(i) for i in range(32) if vfp_mask & (1 << i) != 0]
|
||||
return '{%s}' % ', '.join(hits)
|
||||
|
||||
def _decode_1000iiii_iiiiiiii(self) -> str:
|
||||
op0 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
# uint16_t GPRMask = (Opcode1 << 4) | ((Opcode0 & 0x0f) << 12);
|
||||
# SW.startLine()
|
||||
# << format("0x%02X 0x%02X ; %s",
|
||||
# Opcode0, Opcode1, GPRMask ? "pop " : "refuse to unwind");
|
||||
# if (GPRMask)
|
||||
# PrintGPR(GPRMask);
|
||||
gpr_mask = (op1 << 4) | ((op0 & 0x0f) << 12)
|
||||
if gpr_mask == 0:
|
||||
return 'refuse to unwind'
|
||||
else:
|
||||
return 'pop %s' % self._printGPR(gpr_mask)
|
||||
|
||||
def _decode_10011101(self) -> str:
|
||||
self._index += 1
|
||||
return 'reserved (ARM MOVrr)'
|
||||
|
||||
def _decode_10011111(self) -> str:
|
||||
self._index += 1
|
||||
return 'reserved (WiMMX MOVrr)'
|
||||
|
||||
def _decode_1001nnnn(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; vsp = r%u\n", Opcode, (Opcode & 0x0f));
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'vsp = r%u' % (opcode & 0x0f)
|
||||
|
||||
def _decode_10100nnn(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; pop ", Opcode);
|
||||
# PrintGPR((((1 << ((Opcode & 0x7) + 1)) - 1) << 4));
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'pop %s' % self._printGPR(self._calculate_range(4, opcode & 0x07))
|
||||
|
||||
def _decode_10101nnn(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; pop ", Opcode);
|
||||
# PrintGPR((((1 << ((Opcode & 0x7) + 1)) - 1) << 4) | (1 << 14));
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'pop %s' % self._printGPR(self._calculate_range(4, opcode & 0x07) | (1 << 14))
|
||||
|
||||
def _decode_10110000(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; finish\n", Opcode);
|
||||
self._index += 1
|
||||
return 'finish'
|
||||
|
||||
def _decode_10110001_0000iiii(self) -> str:
|
||||
# SW.startLine()
|
||||
# << format("0x%02X 0x%02X ; %s", Opcode0, Opcode1,
|
||||
# ((Opcode1 & 0xf0) || Opcode1 == 0x00) ? "spare" : "pop ");
|
||||
# if (((Opcode1 & 0xf0) == 0x00) && Opcode1)
|
||||
# PrintGPR((Opcode1 & 0x0f));
|
||||
self._index += 1 # skip constant byte
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
if (op1 & 0xf0) != 0 or op1 == 0x00:
|
||||
return 'spare'
|
||||
else:
|
||||
return 'pop %s' % self._printGPR(op1 & 0x0f)
|
||||
|
||||
def _decode_10110010_uleb128(self) -> str:
|
||||
# SmallVector<uint8_t, 4> ULEB;
|
||||
# do { ULEB.push_back(Opcodes[OI ^ 3]); } while (Opcodes[OI++ ^ 3] & 0x80);
|
||||
# uint64_t Value = 0;
|
||||
# for (unsigned BI = 0, BE = ULEB.size(); BI != BE; ++BI)
|
||||
# Value = Value | ((ULEB[BI] & 0x7f) << (7 * BI));
|
||||
# OS << format("; vsp = vsp + %" PRIu64 "\n", 0x204 + (Value << 2));
|
||||
self._index += 1 # skip constant byte
|
||||
uleb_buffer = [self._bytecode_array[self._index]]
|
||||
self._index += 1
|
||||
while self._bytecode_array[self._index] & 0x80 == 0:
|
||||
uleb_buffer.append(self._bytecode_array[self._index])
|
||||
self._index += 1
|
||||
value = 0
|
||||
for b in reversed(uleb_buffer):
|
||||
value = (value << 7) + (b & 0x7F)
|
||||
return 'vsp = vsp + %u' % (0x204 + (value << 2))
|
||||
|
||||
def _decode_10110011_sssscccc(self) -> str:
|
||||
# these two decoders are equal
|
||||
return self._decode_11001001_sssscccc()
|
||||
|
||||
def _decode_101101nn(self) -> str:
|
||||
return self._spare()
|
||||
|
||||
def _decode_10111nnn(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; pop ", Opcode);
|
||||
# PrintRegisters((((1 << ((Opcode & 0x07) + 1)) - 1) << 8), "d");
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'pop %s' % self._print_registers(self._calculate_range(8, opcode & 0x07), "d")
|
||||
|
||||
def _decode_11000110_sssscccc(self) -> str:
|
||||
# SW.startLine() << format("0x%02X 0x%02X ; pop ", Opcode0, Opcode1);
|
||||
# uint8_t Start = ((Opcode1 & 0xf0) >> 4);
|
||||
# uint8_t Count = ((Opcode1 & 0x0f) >> 0);
|
||||
# PrintRegisters((((1 << (Count + 1)) - 1) << Start), "wR");
|
||||
self._index += 1 # skip constant byte
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
start = ((op1 & 0xf0) >> 4)
|
||||
count = ((op1 & 0x0f) >> 0)
|
||||
return 'pop %s' % self._print_registers(self._calculate_range(start, count), "wR")
|
||||
|
||||
def _decode_11000111_0000iiii(self) -> str:
|
||||
# SW.startLine()
|
||||
# << format("0x%02X 0x%02X ; %s", Opcode0, Opcode1,
|
||||
# ((Opcode1 & 0xf0) || Opcode1 == 0x00) ? "spare" : "pop ");
|
||||
# if ((Opcode1 & 0xf0) == 0x00 && Opcode1)
|
||||
# PrintRegisters(Opcode1 & 0x0f, "wCGR");
|
||||
self._index += 1 # skip constant byte
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
if (op1 & 0xf0) != 0 or op1 == 0x00:
|
||||
return 'spare'
|
||||
else:
|
||||
return 'pop %s' % self._print_registers(op1 & 0x0f, "wCGR")
|
||||
|
||||
def _decode_11001000_sssscccc(self) -> str:
|
||||
# SW.startLine() << format("0x%02X 0x%02X ; pop ", Opcode0, Opcode1);
|
||||
# uint8_t Start = 16 + ((Opcode1 & 0xf0) >> 4);
|
||||
# uint8_t Count = ((Opcode1 & 0x0f) >> 0);
|
||||
# PrintRegisters((((1 << (Count + 1)) - 1) << Start), "d");
|
||||
self._index += 1 # skip constant byte
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
start = 16 + ((op1 & 0xf0) >> 4)
|
||||
count = ((op1 & 0x0f) >> 0)
|
||||
return 'pop %s' % self._print_registers(self._calculate_range(start, count), "d")
|
||||
|
||||
def _decode_11001001_sssscccc(self) -> str:
|
||||
# SW.startLine() << format("0x%02X 0x%02X ; pop ", Opcode0, Opcode1);
|
||||
# uint8_t Start = ((Opcode1 & 0xf0) >> 4);
|
||||
# uint8_t Count = ((Opcode1 & 0x0f) >> 0);
|
||||
# PrintRegisters((((1 << (Count + 1)) - 1) << Start), "d");
|
||||
self._index += 1 # skip constant byte
|
||||
op1 = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
start = ((op1 & 0xf0) >> 4)
|
||||
count = ((op1 & 0x0f) >> 0)
|
||||
return 'pop %s' % self._print_registers(self._calculate_range(start, count), "d")
|
||||
|
||||
def _decode_11001yyy(self) -> str:
|
||||
return self._spare()
|
||||
|
||||
def _decode_11000nnn(self) -> str:
|
||||
# SW.startLine() << format("0x%02X ; pop ", Opcode);
|
||||
# PrintRegisters((((1 << ((Opcode & 0x07) + 1)) - 1) << 10), "wR");
|
||||
opcode = self._bytecode_array[self._index]
|
||||
self._index += 1
|
||||
return 'pop %s' % self._print_registers(self._calculate_range(10, opcode & 0x07), "wR")
|
||||
|
||||
def _decode_11010nnn(self) -> str:
|
||||
# these two decoders are equal
|
||||
return self._decode_10111nnn()
|
||||
|
||||
def _decode_11xxxyyy(self) -> str:
|
||||
return self._spare()
|
||||
|
||||
def _spare(self) -> str:
|
||||
self._index += 1
|
||||
return 'spare'
|
||||
|
||||
class _DECODE_RECIPE_TYPE(NamedTuple):
|
||||
mask: int
|
||||
value: int
|
||||
handler: Callable[[EHABIBytecodeDecoder], str]
|
||||
|
||||
ring = (
|
||||
_DECODE_RECIPE_TYPE(mask=0xc0, value=0x00, handler=_decode_00xxxxxx),
|
||||
_DECODE_RECIPE_TYPE(mask=0xc0, value=0x40, handler=_decode_01xxxxxx),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf0, value=0x80, handler=_decode_1000iiii_iiiiiiii),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0x9d, handler=_decode_10011101),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0x9f, handler=_decode_10011111),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf0, value=0x90, handler=_decode_1001nnnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf8, value=0xa0, handler=_decode_10100nnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf8, value=0xa8, handler=_decode_10101nnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xb0, handler=_decode_10110000),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xb1, handler=_decode_10110001_0000iiii),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xb2, handler=_decode_10110010_uleb128),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xb3, handler=_decode_10110011_sssscccc),
|
||||
_DECODE_RECIPE_TYPE(mask=0xfc, value=0xb4, handler=_decode_101101nn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf8, value=0xb8, handler=_decode_10111nnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xc6, handler=_decode_11000110_sssscccc),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xc7, handler=_decode_11000111_0000iiii),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xc8, handler=_decode_11001000_sssscccc),
|
||||
_DECODE_RECIPE_TYPE(mask=0xff, value=0xc9, handler=_decode_11001001_sssscccc),
|
||||
_DECODE_RECIPE_TYPE(mask=0xc8, value=0xc8, handler=_decode_11001yyy),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf8, value=0xc0, handler=_decode_11000nnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xf8, value=0xd0, handler=_decode_11010nnn),
|
||||
_DECODE_RECIPE_TYPE(mask=0xc0, value=0xc0, handler=_decode_11xxxyyy),
|
||||
)
|
||||
|
||||
|
||||
class MnemonicItem:
|
||||
""" Single mnemonic item.
|
||||
"""
|
||||
|
||||
def __init__(self, bytecode: list[int], mnemonic: str) -> None:
|
||||
self.bytecode = bytecode
|
||||
self.mnemonic = mnemonic
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '%s ; %s' % (' '.join(['0x%02x' % x for x in self.bytecode]), self.mnemonic)
|
||||
@@ -0,0 +1,232 @@
|
||||
# -------------------------------------------------------------------------------
|
||||
# elftools: ehabi/ehabiinfo.py
|
||||
#
|
||||
# Decoder for ARM exception handler bytecode.
|
||||
#
|
||||
# LeadroyaL (leadroyal@qq.com)
|
||||
# This code is in the public domain
|
||||
# -------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
|
||||
from .decoder import EHABIBytecodeDecoder
|
||||
from .constants import EHABI_INDEX_ENTRY_SIZE
|
||||
from .structs import EHABIStructs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..elf.sections import Section
|
||||
from .decoder import MnemonicItem
|
||||
|
||||
|
||||
class EHABIInfo:
|
||||
""" ARM exception handler abi information class.
|
||||
|
||||
Parameters:
|
||||
|
||||
arm_idx_section:
|
||||
elf.sections.Section object, section which type is SHT_ARM_EXIDX.
|
||||
|
||||
little_endian:
|
||||
bool, endianness of elf file.
|
||||
"""
|
||||
|
||||
def __init__(self, arm_idx_section: Section, little_endian: bool) -> None:
|
||||
self._arm_idx_section = arm_idx_section
|
||||
self._struct = EHABIStructs(little_endian)
|
||||
|
||||
def section_name(self) -> str:
|
||||
return self._arm_idx_section.name
|
||||
|
||||
def section_offset(self) -> int:
|
||||
return self._arm_idx_section['sh_offset']
|
||||
|
||||
def num_entry(self) -> int:
|
||||
""" Number of exception handler entry in the section.
|
||||
"""
|
||||
return self._num_entry
|
||||
|
||||
@cached_property
|
||||
def _num_entry(self) -> int:
|
||||
return self._arm_idx_section['sh_size'] // EHABI_INDEX_ENTRY_SIZE
|
||||
|
||||
def get_entry(self, n: int) -> EHABIEntry:
|
||||
""" Get the exception handler entry at index #n. (EHABIEntry object or a subclass)
|
||||
"""
|
||||
if n >= self.num_entry():
|
||||
raise IndexError('Invalid entry %d/%d' % (n, self.num_entry()))
|
||||
eh_index_entry_offset = self.section_offset() + n * EHABI_INDEX_ENTRY_SIZE
|
||||
eh_index_data = struct_parse(self._struct.EH_index_struct, self._arm_idx_section.stream, eh_index_entry_offset)
|
||||
word0, word1 = eh_index_data['word0'], eh_index_data['word1']
|
||||
|
||||
if word0 & 0x80000000 != 0:
|
||||
return CorruptEHABIEntry('Corrupt ARM exception handler table entry: %x' % n)
|
||||
|
||||
function_offset = arm_expand_prel31(word0, self.section_offset() + n * EHABI_INDEX_ENTRY_SIZE)
|
||||
|
||||
if word1 == 1:
|
||||
# 0x1 means cannot unwind
|
||||
return CannotUnwindEHABIEntry(function_offset)
|
||||
elif word1 & 0x80000000 == 0:
|
||||
# highest bit is zero, point to .ARM.extab data
|
||||
eh_table_offset = arm_expand_prel31(word1, self.section_offset() + n * EHABI_INDEX_ENTRY_SIZE + 4)
|
||||
eh_index_data = struct_parse(self._struct.EH_table_struct, self._arm_idx_section.stream, eh_table_offset)
|
||||
word0 = eh_index_data['word0']
|
||||
if word0 & 0x80000000 == 0:
|
||||
# highest bit is one, generic model
|
||||
return GenericEHABIEntry(function_offset, arm_expand_prel31(word0, eh_table_offset))
|
||||
else:
|
||||
# highest bit is one, arm compact model
|
||||
# highest half must be 0b1000 for compact model
|
||||
if word0 & 0x70000000 != 0:
|
||||
return CorruptEHABIEntry('Corrupt ARM compact model table entry: %x' % n)
|
||||
per_index = (word0 >> 24) & 0x7f
|
||||
if per_index == 0:
|
||||
# arm compact model 0
|
||||
opcode = [(word0 & 0xFF0000) >> 16, (word0 & 0xFF00) >> 8, word0 & 0xFF]
|
||||
return EHABIEntry(function_offset, per_index, opcode)
|
||||
elif per_index == 1 or per_index == 2:
|
||||
# arm compact model 1/2
|
||||
more_word = (word0 >> 16) & 0xff
|
||||
opcode = [(word0 >> 8) & 0xff, (word0 >> 0) & 0xff]
|
||||
self._arm_idx_section.stream.seek(eh_table_offset + 4)
|
||||
for i in range(more_word):
|
||||
r = struct_parse(self._struct.EH_table_struct, self._arm_idx_section.stream)['word0']
|
||||
opcode.append((r >> 24) & 0xFF)
|
||||
opcode.append((r >> 16) & 0xFF)
|
||||
opcode.append((r >> 8) & 0xFF)
|
||||
opcode.append((r >> 0) & 0xFF)
|
||||
return EHABIEntry(function_offset, per_index, opcode, eh_table_offset=eh_table_offset)
|
||||
else:
|
||||
return CorruptEHABIEntry('Unknown ARM compact model %d at table entry: %x' % (per_index, n))
|
||||
else:
|
||||
# highest bit is one, compact model must be 0
|
||||
if word1 & 0x7f000000 != 0:
|
||||
return CorruptEHABIEntry('Corrupt ARM compact model table entry: %x' % n)
|
||||
opcode = [(word1 & 0xFF0000) >> 16, (word1 & 0xFF00) >> 8, word1 & 0xFF]
|
||||
return EHABIEntry(function_offset, 0, opcode)
|
||||
|
||||
|
||||
class EHABIEntry:
|
||||
""" Exception handler abi entry.
|
||||
|
||||
Accessible attributes:
|
||||
|
||||
function_offset:
|
||||
Integer.
|
||||
None if corrupt. (Reference: CorruptEHABIEntry)
|
||||
|
||||
personality:
|
||||
Integer.
|
||||
None if corrupt or unwindable. (Reference: CorruptEHABIEntry, CannotUnwindEHABIEntry)
|
||||
0/1/2 for ARM personality compact format.
|
||||
Others for generic personality.
|
||||
|
||||
bytecode_array:
|
||||
Integer array.
|
||||
None if corrupt or unwindable or generic personality.
|
||||
(Reference: CorruptEHABIEntry, CannotUnwindEHABIEntry, GenericEHABIEntry)
|
||||
|
||||
eh_table_offset:
|
||||
Integer.
|
||||
Only entries who point to .ARM.extab contains this field, otherwise return None.
|
||||
|
||||
unwindable:
|
||||
bool. Whether this function is unwindable.
|
||||
|
||||
corrupt:
|
||||
bool. Whether this entry is corrupt.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
function_offset: int | None,
|
||||
personality: int | None,
|
||||
bytecode_array: list[int] | None,
|
||||
eh_table_offset: int | None = None,
|
||||
unwindable: bool = True,
|
||||
corrupt: bool = False,
|
||||
) -> None:
|
||||
self.function_offset = function_offset
|
||||
self.personality = personality
|
||||
self.bytecode_array = bytecode_array
|
||||
self.eh_table_offset = eh_table_offset
|
||||
self.unwindable = unwindable
|
||||
self.corrupt = corrupt
|
||||
|
||||
def mnmemonic_array(self) -> list[MnemonicItem] | None:
|
||||
if self.bytecode_array:
|
||||
return EHABIBytecodeDecoder(self.bytecode_array).mnemonic_array
|
||||
else:
|
||||
return None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
fo = self.function_offset
|
||||
to = self.eh_table_offset
|
||||
return (
|
||||
"<EHABIEntry"
|
||||
f" function_offset={'' if fo is None else '{fo:#x}'}"
|
||||
f", personaality={self.personality}"
|
||||
f"{', eh_table_offset={to:#x}' if to else ''}"
|
||||
f", bytecode={self.bytecode_array}"
|
||||
">"
|
||||
)
|
||||
|
||||
|
||||
class CorruptEHABIEntry(EHABIEntry):
|
||||
""" This entry is corrupt. Attribute #corrupt will be True.
|
||||
"""
|
||||
|
||||
def __init__(self, reason: str) -> None:
|
||||
super().__init__(function_offset=None, personality=None, bytecode_array=None,
|
||||
corrupt=True)
|
||||
self.reason = reason
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<CorruptEHABIEntry reason=%s>" % self.reason
|
||||
|
||||
|
||||
class CannotUnwindEHABIEntry(EHABIEntry):
|
||||
""" This function cannot be unwind. Attribute #unwindable will be False.
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
function_offset: int # instead of `int|None` to save `is None` checks everywhere
|
||||
|
||||
def __init__(self, function_offset: int) -> None:
|
||||
super().__init__(function_offset, personality=None, bytecode_array=None,
|
||||
unwindable=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<CannotUnwindEHABIEntry function_offset=0x%x>" % self.function_offset
|
||||
|
||||
|
||||
class GenericEHABIEntry(EHABIEntry):
|
||||
""" This entry is generic model rather than ARM compact model.Attribute #bytecode_array will be None.
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
function_offset: int # instead of `int|None` to save `is None` checks everywhere
|
||||
personality: int
|
||||
|
||||
def __init__(self, function_offset: int, personality: int) -> None:
|
||||
super().__init__(function_offset, personality, bytecode_array=None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<GenericEHABIEntry function_offset=0x%x, personality=0x%x>" % (self.function_offset, self.personality)
|
||||
|
||||
|
||||
def arm_expand_prel31(address: int, place: int) -> int:
|
||||
"""
|
||||
address: uint32
|
||||
place: uint32
|
||||
return: uint64
|
||||
"""
|
||||
location = address & 0x7fffffff
|
||||
if location & 0x04000000:
|
||||
location |= 0xffffffff80000000
|
||||
return location + place & 0xffffffffffffffff
|
||||
@@ -0,0 +1,47 @@
|
||||
# -------------------------------------------------------------------------------
|
||||
# elftools: ehabi/structs.py
|
||||
#
|
||||
# Encapsulation of Construct structs for parsing an EHABI, adjusted for
|
||||
# correct endianness and word-size.
|
||||
#
|
||||
# LeadroyaL (leadroyal@qq.com)
|
||||
# This code is in the public domain
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
from ..construct import UBInt32, ULInt32, Struct
|
||||
|
||||
|
||||
class EHABIStructs:
|
||||
""" Accessible attributes:
|
||||
|
||||
EH_index_struct:
|
||||
Struct of item in section .ARM.exidx.
|
||||
|
||||
EH_table_struct:
|
||||
Struct of item in section .ARM.extab.
|
||||
"""
|
||||
|
||||
def __init__(self, little_endian: bool) -> None:
|
||||
self._little_endian = little_endian
|
||||
self._create_structs()
|
||||
|
||||
def _create_structs(self) -> None:
|
||||
if self._little_endian:
|
||||
self.EHABI_uint32 = ULInt32
|
||||
else:
|
||||
self.EHABI_uint32 = UBInt32
|
||||
self._create_exception_handler_index()
|
||||
self._create_exception_handler_table()
|
||||
|
||||
def _create_exception_handler_index(self) -> None:
|
||||
self.EH_index_struct = Struct(
|
||||
'EH_index',
|
||||
self.EHABI_uint32('word0'),
|
||||
self.EHABI_uint32('word1')
|
||||
)
|
||||
|
||||
def _create_exception_handler_table(self) -> None:
|
||||
self.EH_table_struct = Struct(
|
||||
'EH_table',
|
||||
self.EHABI_uint32('word0'),
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,176 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/constants.py
|
||||
#
|
||||
# Constants and flags, placed into classes for namespacing
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class E_FLAGS:
|
||||
""" Flag values for the e_flags field of the ELF header
|
||||
"""
|
||||
EF_ARM_EABIMASK=0xFF000000
|
||||
EF_ARM_EABI_VER1=0x01000000
|
||||
EF_ARM_EABI_VER2=0x02000000
|
||||
EF_ARM_EABI_VER3=0x03000000
|
||||
EF_ARM_EABI_VER4=0x04000000
|
||||
EF_ARM_EABI_VER5=0x05000000
|
||||
EF_ARM_GCCMASK=0x00400FFF
|
||||
EF_ARM_RELEXEC=0x01
|
||||
EF_ARM_HASENTRY=0x02
|
||||
EF_ARM_SYMSARESORTED=0x04
|
||||
EF_ARM_DYNSYMSUSESEGIDX=0x8
|
||||
EF_ARM_MAPSYMSFIRST=0x10
|
||||
EF_ARM_LE8=0x00400000
|
||||
EF_ARM_BE8=0x00800000
|
||||
EF_ARM_ABI_FLOAT_SOFT=0x00000200
|
||||
EF_ARM_ABI_FLOAT_HARD=0x00000400
|
||||
|
||||
EF_PPC64_ABI_V0=0
|
||||
EF_PPC64_ABI_V1=1
|
||||
EF_PPC64_ABI_V2=2
|
||||
|
||||
EF_MIPS_NOREORDER=1
|
||||
EF_MIPS_PIC=2
|
||||
EF_MIPS_CPIC=4
|
||||
EF_MIPS_XGOT=8
|
||||
EF_MIPS_64BIT_WHIRL=16
|
||||
EF_MIPS_ABI2=32
|
||||
EF_MIPS_ABI_ON32=64
|
||||
EF_MIPS_32BITMODE = 256
|
||||
EF_MIPS_NAN2008=1024
|
||||
EF_MIPS_ARCH=0xf0000000
|
||||
EF_MIPS_ARCH_1=0x00000000
|
||||
EF_MIPS_ARCH_2=0x10000000
|
||||
EF_MIPS_ARCH_3=0x20000000
|
||||
EF_MIPS_ARCH_4=0x30000000
|
||||
EF_MIPS_ARCH_5=0x40000000
|
||||
EF_MIPS_ARCH_32=0x50000000
|
||||
EF_MIPS_ARCH_64=0x60000000
|
||||
EF_MIPS_ARCH_32R2=0x70000000
|
||||
EF_MIPS_ARCH_64R2=0x80000000
|
||||
|
||||
EF_RISCV_RVC=0x00000001
|
||||
EF_RISCV_FLOAT_ABI=0x00000006
|
||||
EF_RISCV_FLOAT_ABI_SOFT=0x00000000
|
||||
EF_RISCV_FLOAT_ABI_SINGLE=0x00000002
|
||||
EF_RISCV_FLOAT_ABI_DOUBLE=0x00000004
|
||||
EF_RISCV_FLOAT_ABI_QUAD=0x00000006
|
||||
EF_RISCV_RVE=0x00000008
|
||||
EF_RISCV_TSO=0x00000010
|
||||
|
||||
EF_LOONGARCH_OBJABI_MASK=0x000000C0
|
||||
EF_LOONGARCH_OBJABI_V0=0x00000000
|
||||
EF_LOONGARCH_OBJABI_V1=0x00000040
|
||||
EF_LOONGARCH_ABI_MODIFIER_MASK=0x00000007
|
||||
EF_LOONGARCH_ABI_SOFT_FLOAT=0x00000001
|
||||
EF_LOONGARCH_ABI_SINGLE_FLOAT=0x00000002
|
||||
EF_LOONGARCH_ABI_DOUBLE_FLOAT=0x00000003
|
||||
# The names in the glibc elf.h say "LARCH" instead of "LOONGARCH",
|
||||
# provide these names for users' convenience.
|
||||
EF_LARCH_OBJABI_MASK = EF_LOONGARCH_OBJABI_MASK
|
||||
EF_LARCH_OBJABI_V0 = EF_LOONGARCH_OBJABI_V0
|
||||
EF_LARCH_OBJABI_V1 = EF_LOONGARCH_OBJABI_V1
|
||||
EF_LARCH_ABI_MODIFIER_MASK = EF_LOONGARCH_ABI_MODIFIER_MASK
|
||||
EF_LARCH_ABI_SOFT_FLOAT = EF_LOONGARCH_ABI_SOFT_FLOAT
|
||||
EF_LARCH_ABI_SINGLE_FLOAT = EF_LOONGARCH_ABI_SINGLE_FLOAT
|
||||
EF_LARCH_ABI_DOUBLE_FLOAT = EF_LOONGARCH_ABI_DOUBLE_FLOAT
|
||||
|
||||
class E_FLAGS_MASKS:
|
||||
"""Masks to be used for convenience when working with E_FLAGS
|
||||
|
||||
This is a simplified approach that is also used by GNU binutils
|
||||
readelf
|
||||
"""
|
||||
EFM_MIPS_ABI = 0x0000F000
|
||||
EFM_MIPS_ABI_O32 = 0x00001000
|
||||
EFM_MIPS_ABI_O64 = 0x00002000
|
||||
EFM_MIPS_ABI_EABI32 = 0x00003000
|
||||
EFM_MIPS_ABI_EABI64 = 0x00004000
|
||||
|
||||
|
||||
class SHN_INDICES:
|
||||
""" Special section indices
|
||||
"""
|
||||
SHN_UNDEF=0
|
||||
SHN_LORESERVE=0xff00
|
||||
SHN_LOPROC=0xff00
|
||||
SHN_HIPROC=0xff1f
|
||||
SHN_ABS=0xfff1
|
||||
SHN_COMMON=0xfff2
|
||||
SHN_HIRESERVE=0xffff
|
||||
SHN_XINDEX=0xffff
|
||||
|
||||
|
||||
class SH_FLAGS:
|
||||
""" Flag values for the sh_flags field of section headers
|
||||
"""
|
||||
SHF_WRITE=0x1
|
||||
SHF_ALLOC=0x2
|
||||
SHF_EXECINSTR=0x4
|
||||
SHF_MERGE=0x10
|
||||
SHF_STRINGS=0x20
|
||||
SHF_INFO_LINK=0x40
|
||||
SHF_LINK_ORDER=0x80
|
||||
SHF_OS_NONCONFORMING=0x100
|
||||
SHF_GROUP=0x200
|
||||
SHF_TLS=0x400
|
||||
SHF_COMPRESSED=0x800
|
||||
SHF_MASKOS=0x0ff00000
|
||||
SHF_EXCLUDE=0x80000000
|
||||
SHF_MASKPROC=0xf0000000
|
||||
|
||||
|
||||
class RH_FLAGS:
|
||||
""" Flag values for the DT_MIPS_FLAGS dynamic table entries
|
||||
"""
|
||||
RHF_NONE=0x00000000
|
||||
RHF_QUICKSTART=0x00000001
|
||||
RHF_NOTPOT=0x00000002
|
||||
RHF_NO_LIBRARY_REPLACEMENT=0x00000004
|
||||
RHF_NO_MOVE=0x00000008
|
||||
RHF_SGI_ONLY=0x00000010
|
||||
RHF_GUARANTEE_INIT=0x00000020
|
||||
RHF_DELTA_C_PLUS_PLUS=0x00000040
|
||||
RHF_GUARANTEE_START_INIT=0x00000080
|
||||
RHF_PIXIE=0x00000100
|
||||
RHF_DEFAULT_DELAY_LOAD=0x00000200
|
||||
RHF_REQUICKSTART=0x00000400
|
||||
RHF_REQUICKSTARTED=0x00000800
|
||||
RHF_CORD=0x00001000
|
||||
RHF_NO_UNRES_UNDEF=0x00002000
|
||||
RHF_RLD_ORDER_SAFE=0x00004000
|
||||
|
||||
|
||||
class P_FLAGS:
|
||||
""" Flag values for the p_flags field of program headers
|
||||
"""
|
||||
PF_X=0x1
|
||||
PF_W=0x2
|
||||
PF_R=0x4
|
||||
PF_MASKOS=0x00FF0000
|
||||
PF_MASKPROC=0xFF000000
|
||||
|
||||
|
||||
# symbol info flags for entries
|
||||
# in the .SUNW_syminfo section
|
||||
class SUNW_SYMINFO_FLAGS:
|
||||
""" Flags for the si_flags field of entries
|
||||
in the .SUNW_syminfo section
|
||||
"""
|
||||
SYMINFO_FLG_DIRECT=0x1
|
||||
SYMINFO_FLG_FILTER=0x2
|
||||
SYMINFO_FLG_COPY=0x4
|
||||
SYMINFO_FLG_LAZYLOAD=0x8
|
||||
SYMINFO_FLG_DIRECTBIND=0x10
|
||||
SYMINFO_FLG_NOEXTDIRECT=0x20
|
||||
SYMINFO_FLG_AUXILIARY=0x40
|
||||
SYMINFO_FLG_INTERPOSE=0x80
|
||||
SYMINFO_FLG_CAP=0x100
|
||||
SYMINFO_FLG_DEFERRED=0x200
|
||||
|
||||
class VER_FLAGS:
|
||||
VER_FLG_BASE=0x1
|
||||
VER_FLG_WEAK=0x2
|
||||
VER_FLG_INFO=0x4
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/dynamic.py
|
||||
#
|
||||
# ELF Dynamic Tags
|
||||
#
|
||||
# Mike Frysinger (vapier@gentoo.org)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, Protocol, TypedDict, cast, runtime_checkable
|
||||
|
||||
from ..common.exceptions import ELFError
|
||||
from ..common.utils import elf_assert, struct_parse, parse_cstring_from_stream
|
||||
from .enums import ENUM_D_TAG
|
||||
from .hash import ELFHashTable, GNUHashTable
|
||||
from .relocation import RelocationTable, RelrRelocationTable
|
||||
from .sections import Section, Symbol
|
||||
from .segments import Segment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
|
||||
|
||||
class RelocationTables(TypedDict, total=False):
|
||||
REL: RelocationTable
|
||||
RELA: RelocationTable
|
||||
RELR: RelrRelocationTable
|
||||
JMPREL: RelocationTable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _StringTable(Protocol):
|
||||
"""Common base-class of elftools.elf.dynamic._DynamicStringTable and
|
||||
elftools.elf.section.StringTableSection to be consumed by
|
||||
DynamicTag|Dynamic.
|
||||
Requires @runtime_checkable as `assert isinstance(…, _StringTable)` is
|
||||
used."""
|
||||
def get_string(self, offset: int, /) -> str: ...
|
||||
|
||||
|
||||
class _DynamicStringTable:
|
||||
""" Bare string table based on values found via ELF dynamic tags and
|
||||
loadable segments only. Good enough for get_string() only.
|
||||
"""
|
||||
def __init__(self, stream: IO[bytes], table_offset: int) -> None:
|
||||
self._stream = stream
|
||||
self._table_offset = table_offset
|
||||
|
||||
def get_string(self, offset: int) -> str:
|
||||
""" Get the string stored at the given offset in this string table.
|
||||
"""
|
||||
s = parse_cstring_from_stream(self._stream, self._table_offset + offset)
|
||||
return s.decode('utf-8') if s else ''
|
||||
|
||||
|
||||
class DynamicTag:
|
||||
""" Dynamic Tag object - representing a single dynamic tag entry from a
|
||||
dynamic section.
|
||||
|
||||
Allows dictionary-like access to the dynamic structure. For special
|
||||
tags (those listed in the _HANDLED_TAGS set below), creates additional
|
||||
attributes for convenience. For example, .soname will contain the actual
|
||||
value of DT_SONAME (fetched from the dynamic symbol table).
|
||||
"""
|
||||
_HANDLED_TAGS = frozenset(
|
||||
['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME',
|
||||
'DT_SUNW_FILTER'])
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entry: Container,
|
||||
stringtable: _StringTable | None,
|
||||
) -> None:
|
||||
if stringtable is None:
|
||||
raise ELFError('Creating DynamicTag without string table')
|
||||
self.entry = entry
|
||||
if entry.d_tag in self._HANDLED_TAGS:
|
||||
setattr(self, entry.d_tag[3:].lower(),
|
||||
stringtable.get_string(self.entry.d_val))
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<DynamicTag (%s): %r>' % (self.entry.d_tag, self.entry)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.entry.d_tag in self._HANDLED_TAGS:
|
||||
s = '"%s"' % getattr(self, self.entry.d_tag[3:].lower())
|
||||
else:
|
||||
s = '%#x' % self.entry.d_ptr
|
||||
return '<DynamicTag (%s) %s>' % (self.entry.d_tag, s)
|
||||
|
||||
|
||||
class Dynamic:
|
||||
""" Shared functionality between dynamic sections and segments.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
elffile: ELFFile,
|
||||
stringtable: _StringTable | Section | None,
|
||||
position: int,
|
||||
empty: bool,
|
||||
) -> None:
|
||||
"""
|
||||
stream:
|
||||
The file-like object from which to load data
|
||||
|
||||
elffile:
|
||||
The parent elffile object
|
||||
|
||||
stringtable:
|
||||
A stringtable reference to use for parsing string references in
|
||||
entries
|
||||
|
||||
position:
|
||||
The file offset of the dynamic segment/section
|
||||
|
||||
empty:
|
||||
Whether this is a degenerate case with zero entries. Normally, every
|
||||
dynamic table will have at least one entry, the DT_NULL terminator.
|
||||
"""
|
||||
self.elffile = elffile
|
||||
self.elfstructs = elffile.structs
|
||||
self._stream = stream
|
||||
self._num_tags = -1 if not empty else 0
|
||||
self._offset = position
|
||||
self._tagsize = self.elfstructs.Elf_Dyn.sizeof()
|
||||
self._empty = empty
|
||||
|
||||
# Do not access this directly yourself; use _get_stringtable() instead.
|
||||
self._stringtable: _StringTable | Section | None = stringtable
|
||||
|
||||
def get_table_offset(self, tag_name: str) -> tuple[int | None, int | None]:
|
||||
""" Return the virtual address and file offset of a dynamic table.
|
||||
"""
|
||||
try:
|
||||
ptr: int = next(
|
||||
tag['d_ptr']
|
||||
for tag in self._iter_tags(type=tag_name)
|
||||
)
|
||||
except StopIteration:
|
||||
return (None, None)
|
||||
|
||||
# If we found a virtual address, locate the offset in the file
|
||||
# by using the program headers.
|
||||
offset = next(self.elffile.address_offsets(ptr), None)
|
||||
|
||||
return ptr, offset
|
||||
|
||||
def _get_stringtable(self) -> _StringTable:
|
||||
""" Return a string table for looking up dynamic tag related strings.
|
||||
|
||||
This won't be a "full" string table object, but will at least
|
||||
support the get_string() function.
|
||||
"""
|
||||
if self._stringtable:
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
# If the ELF has stripped its section table (which is unusual, but
|
||||
# perfectly valid), we need to use the dynamic tags to locate the
|
||||
# dynamic string table.
|
||||
_, table_offset = self.get_table_offset('DT_STRTAB')
|
||||
if table_offset is not None:
|
||||
self._stringtable = _DynamicStringTable(self._stream, table_offset)
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
# That didn't work for some reason. Let's use the section header
|
||||
# even though this ELF is super weird.
|
||||
self._stringtable = self.elffile.get_section_by_name('.dynstr')
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
def _iter_tags(self, type: str | None = None) -> Iterator[Container]:
|
||||
""" Yield all raw tags (limit to |type| if specified)
|
||||
"""
|
||||
if self._empty:
|
||||
return
|
||||
for n in itertools.count():
|
||||
tag = self._get_tag(n)
|
||||
if type is None or tag['d_tag'] == type:
|
||||
yield tag
|
||||
if tag['d_tag'] == 'DT_NULL':
|
||||
break
|
||||
|
||||
def iter_tags(self, type: str | None = None) -> Iterator[DynamicTag]:
|
||||
""" Yield all tags (limit to |type| if specified)
|
||||
"""
|
||||
for tag in self._iter_tags(type=type):
|
||||
yield DynamicTag(tag, self._get_stringtable())
|
||||
|
||||
def _get_tag(self, n: int) -> Container:
|
||||
""" Get the raw tag at index #n from the file
|
||||
"""
|
||||
if self._num_tags != -1 and n >= self._num_tags:
|
||||
raise IndexError(n)
|
||||
offset = self._offset + n * self._tagsize
|
||||
return struct_parse(
|
||||
self.elfstructs.Elf_Dyn,
|
||||
self._stream,
|
||||
stream_pos=offset)
|
||||
|
||||
def get_tag(self, n: int) -> DynamicTag:
|
||||
""" Get the tag at index #n from the file (DynamicTag object)
|
||||
"""
|
||||
return DynamicTag(self._get_tag(n), self._get_stringtable())
|
||||
|
||||
def num_tags(self) -> int | None:
|
||||
""" Number of dynamic tags in the file, including the DT_NULL tag
|
||||
"""
|
||||
if self._num_tags != -1:
|
||||
return self._num_tags
|
||||
|
||||
for n in itertools.count():
|
||||
tag = self.get_tag(n)
|
||||
if tag.entry.d_tag == 'DT_NULL':
|
||||
self._num_tags = n + 1
|
||||
return self._num_tags
|
||||
|
||||
return None
|
||||
|
||||
def get_relocation_tables(self) -> RelocationTables:
|
||||
""" Load all available relocation tables from DYNAMIC tags.
|
||||
|
||||
Returns a dictionary mapping found table types (REL, RELA,
|
||||
RELR, JMPREL) to RelocationTable objects.
|
||||
"""
|
||||
|
||||
result: RelocationTables = {}
|
||||
|
||||
if list(self.iter_tags('DT_REL')):
|
||||
result['REL'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_REL')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELSZ'))['d_val'], False)
|
||||
|
||||
relentsz = next(self.iter_tags('DT_RELENT'))['d_val']
|
||||
elf_assert(result['REL'].entry_size == relentsz,
|
||||
'Expected DT_RELENT to be %s' % relentsz)
|
||||
|
||||
if list(self.iter_tags('DT_RELA')):
|
||||
result['RELA'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_RELA')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELASZ'))['d_val'], True)
|
||||
|
||||
relentsz = next(self.iter_tags('DT_RELAENT'))['d_val']
|
||||
elf_assert(result['RELA'].entry_size == relentsz,
|
||||
'Expected DT_RELAENT to be %s' % relentsz)
|
||||
|
||||
if list(self.iter_tags('DT_RELR')):
|
||||
result['RELR'] = RelrRelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_RELR')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELRSZ'))['d_val'],
|
||||
next(self.iter_tags('DT_RELRENT'))['d_val'])
|
||||
|
||||
if list(self.iter_tags('DT_JMPREL')):
|
||||
result['JMPREL'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_JMPREL')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_PLTRELSZ'))['d_val'],
|
||||
next(self.iter_tags('DT_PLTREL'))['d_val'] == ENUM_D_TAG['DT_RELA'])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class DynamicSection(Section, Dynamic):
|
||||
""" ELF dynamic table section. Knows how to process the list of tags.
|
||||
"""
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
stringtable = elffile.get_section(header['sh_link'], ('SHT_STRTAB', 'SHT_NOBITS', 'SHT_NULL'))
|
||||
Dynamic.__init__(self, self.stream, self.elffile, stringtable,
|
||||
self['sh_offset'], self['sh_type'] == 'SHT_NOBITS')
|
||||
|
||||
|
||||
class DynamicSegment(Segment, Dynamic):
|
||||
""" ELF dynamic table segment. Knows how to process the list of tags.
|
||||
"""
|
||||
def __init__(self, header: Container, stream: IO[bytes], elffile: ELFFile) -> None:
|
||||
# The string table section to be used to resolve string names in
|
||||
# the dynamic tag array is the one pointed at by the sh_link field
|
||||
# of the dynamic section header.
|
||||
# So we must look for the dynamic section contained in the dynamic
|
||||
# segment, we do so by searching for the dynamic section whose content
|
||||
# is located at the same offset as the dynamic segment
|
||||
stringtable = next(
|
||||
(
|
||||
elffile.get_section(section['sh_link'])
|
||||
for section in elffile.iter_sections()
|
||||
if isinstance(section, DynamicSection) and section['sh_offset'] == header['p_offset']
|
||||
),
|
||||
None,
|
||||
)
|
||||
Segment.__init__(self, header, stream)
|
||||
Dynamic.__init__(self, stream, elffile, stringtable, self['p_offset'],
|
||||
self['p_filesz'] == 0)
|
||||
self._symbol_size = self.elfstructs.Elf_Sym.sizeof()
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table recovered from DT_SYMTAB
|
||||
"""
|
||||
return self._num_symbols
|
||||
|
||||
@cached_property
|
||||
def _num_symbols(self) -> int:
|
||||
# Check if a DT_GNU_HASH tag exists and recover the number of symbols
|
||||
# from the corresponding hash table
|
||||
_, gnu_hash_offset = self.get_table_offset('DT_GNU_HASH')
|
||||
if gnu_hash_offset is not None:
|
||||
gnu_hash_section = GNUHashTable(self.elffile, gnu_hash_offset, self)
|
||||
return gnu_hash_section.get_number_of_symbols()
|
||||
|
||||
# If DT_GNU_HASH did not exist, maybe we can use DT_HASH
|
||||
_, hash_offset = self.get_table_offset('DT_HASH')
|
||||
if hash_offset is not None:
|
||||
# Get the hash table from the DT_HASH offset
|
||||
hash_section = ELFHashTable(self.elffile, hash_offset, None, self)
|
||||
return hash_section.get_number_of_symbols()
|
||||
|
||||
# Find closest higher pointer than tab_ptr. We'll use that to mark
|
||||
# the end of the symbol table.
|
||||
tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
|
||||
if tab_ptr is None or tab_offset is None:
|
||||
raise ELFError('Segment does not contain DT_SYMTAB.')
|
||||
|
||||
nearest_ptr: int | None = None
|
||||
for tag in self.iter_tags():
|
||||
tag_ptr = tag['d_ptr']
|
||||
if tag['d_tag'] == 'DT_SYMENT':
|
||||
if self._symbol_size != tag['d_val']:
|
||||
# DT_SYMENT is the size of one symbol entry. It must be
|
||||
# the same as returned by Elf_Sym.sizeof.
|
||||
raise ELFError('DT_SYMENT (%d) != Elf_Sym (%d).' %
|
||||
(tag['d_val'], self._symbol_size))
|
||||
if (tag_ptr > tab_ptr and
|
||||
(nearest_ptr is None or nearest_ptr > tag_ptr)):
|
||||
nearest_ptr = tag_ptr
|
||||
|
||||
if nearest_ptr is not None:
|
||||
return (nearest_ptr - tab_ptr) // self._symbol_size
|
||||
|
||||
# Use the end of last segment that contains DT_SYMTAB (or ends on it)
|
||||
for segment in self.elffile.iter_segments(type='PT_LOAD'):
|
||||
start = segment['p_vaddr']
|
||||
end = start + segment['p_filesz']
|
||||
if start <= tab_ptr <= end:
|
||||
nearest_ptr = end
|
||||
|
||||
if nearest_ptr is not None:
|
||||
return (nearest_ptr - tab_ptr) // self._symbol_size
|
||||
|
||||
raise ELFError('Cannot determine the end of DT_SYMTAB.')
|
||||
|
||||
def get_symbol(self, index: int) -> Symbol:
|
||||
""" Get the symbol at index #index from the table (Symbol object)
|
||||
"""
|
||||
tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
|
||||
if tab_ptr is None or tab_offset is None:
|
||||
raise ELFError('Segment does not contain DT_SYMTAB.')
|
||||
|
||||
symbol = struct_parse(
|
||||
self.elfstructs.Elf_Sym,
|
||||
self._stream,
|
||||
stream_pos=tab_offset + index * self._symbol_size)
|
||||
|
||||
string_table = self._get_stringtable()
|
||||
symbol_name = string_table.get_string(symbol["st_name"])
|
||||
|
||||
return Symbol(symbol, symbol_name)
|
||||
|
||||
def get_symbol_by_name(self, name: str) -> list[Symbol] | None:
|
||||
""" Get a symbol(s) by name. Return None if no symbol by the given name
|
||||
exists.
|
||||
"""
|
||||
symnums = self._symbol_name_map.get(name)
|
||||
return [self.get_symbol(i) for i in symnums] if symnums else None
|
||||
|
||||
@cached_property
|
||||
def _symbol_name_map(self) -> dict[str, list[int]]:
|
||||
smap = defaultdict(list)
|
||||
for i, sym in enumerate(self.iter_symbols()):
|
||||
smap[sym.name].append(i)
|
||||
return smap
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all symbols in this dynamic segment. The symbols are usually
|
||||
the same as returned by SymbolTableSection.iter_symbols. However,
|
||||
in stripped binaries, SymbolTableSection might have been removed.
|
||||
This method reads from the mandatory dynamic tag DT_SYMTAB.
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
@@ -0,0 +1,982 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/elffile.py
|
||||
#
|
||||
# ELFFile - main class for accessing ELF files
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
from functools import cached_property
|
||||
from io import BytesIO
|
||||
from typing import IO, TYPE_CHECKING, Any
|
||||
|
||||
from ..common.exceptions import ELFError, ELFParseError
|
||||
from ..common.utils import struct_parse, elf_assert
|
||||
from .structs import ELFStructs
|
||||
from .sections import (
|
||||
Section, StringTableSection, SymbolTableSection,
|
||||
SymbolTableIndexSection, SUNWSyminfoTableSection, NullSection,
|
||||
NoteSection, StabSection, ARMAttributesSection, RISCVAttributesSection)
|
||||
from .dynamic import DynamicSection, DynamicSegment
|
||||
from .relocation import (RelocationSection, RelocationHandler,
|
||||
RelrRelocationSection)
|
||||
from .gnuversions import (
|
||||
GNUVerNeedSection, GNUVerDefSection,
|
||||
GNUVerSymSection)
|
||||
from .segments import Segment, InterpSegment, NoteSegment
|
||||
from ..dwarf.dwarfinfo import DWARFInfo, DebugSectionDescriptor, DwarfConfig
|
||||
from ..ehabi.ehabiinfo import EHABIInfo
|
||||
from .hash import ELFHashSection, GNUHashSection
|
||||
from .constants import SHN_INDICES
|
||||
from ..dwarf.dwarf_util import _file_crc32
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
from collections.abc import Container as TContainer
|
||||
from types import TracebackType
|
||||
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
|
||||
|
||||
class ELFFile:
|
||||
""" Creation: the constructor accepts a stream (file-like object) with the
|
||||
contents of an ELF file.
|
||||
|
||||
Optionally, a stream_loader function can be passed as the second
|
||||
argument. This stream_loader function takes a relative string path to
|
||||
load a supplementary object file, and returns a stream suitable for
|
||||
creating a new ELFFile. Currently, the only such relative file path is
|
||||
obtained from the supplementary object files.
|
||||
|
||||
Accessible attributes:
|
||||
|
||||
stream:
|
||||
The stream holding the data of the file - must be a binary
|
||||
stream (bytes, not string).
|
||||
|
||||
elfclass:
|
||||
32 or 64 - specifies the word size of the target machine
|
||||
|
||||
little_endian:
|
||||
boolean - specifies the target machine's endianness
|
||||
|
||||
elftype:
|
||||
string or int, either known value of E_TYPE enum defining ELF
|
||||
type (e.g. executable, dynamic library or core dump) or integral
|
||||
unparsed value
|
||||
|
||||
header:
|
||||
the complete ELF file header
|
||||
|
||||
e_ident_raw:
|
||||
the raw e_ident field of the header
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
stream_loader: Callable[[str], IO[bytes]] | None = None,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.stream.seek(0, io.SEEK_END)
|
||||
self.stream_len = self.stream.tell()
|
||||
|
||||
self._identify_file()
|
||||
self.structs = ELFStructs(
|
||||
little_endian=self.little_endian,
|
||||
elfclass=self.elfclass)
|
||||
|
||||
self.structs.create_basic_structs()
|
||||
self.header = self._parse_elf_header()
|
||||
self.structs.create_advanced_structs(
|
||||
self['e_type'],
|
||||
self['e_machine'],
|
||||
self['e_ident']['EI_OSABI'])
|
||||
self.stream.seek(0)
|
||||
self.e_ident_raw = self.stream.read(16)
|
||||
|
||||
self.stream_loader = stream_loader
|
||||
|
||||
@classmethod
|
||||
def load_from_path(cls, path: str | bytes) -> ELFFile:
|
||||
"""Takes a local filesystem path accepted by open(), and returns an
|
||||
ELFFile from it, setting up a stream_loader that resolves linked files
|
||||
using normalized string paths relative to the original file.
|
||||
"""
|
||||
stream = open(path, 'rb')
|
||||
return ELFFile(stream, ELFFile.make_relative_loader(os.fsdecode(path)))
|
||||
|
||||
@staticmethod
|
||||
def make_relative_loader(base_path: str) -> Callable[[str], IO[bytes]]:
|
||||
""" Return a function that takes a potentially relative path,
|
||||
resolves it against base_path (str), and opens a file at that.
|
||||
|
||||
ELFFile uses functions like that for resolving DWARF links. The
|
||||
raw bytes parsed from ELF metadata are decoded before calling this
|
||||
loader.
|
||||
"""
|
||||
if not isinstance(base_path, str):
|
||||
raise TypeError('base_path must be str')
|
||||
base_directory = os.path.realpath(os.path.dirname(base_path))
|
||||
|
||||
def loader(rel_path: str) -> IO[bytes]:
|
||||
if not isinstance(rel_path, str):
|
||||
raise TypeError('rel_path must be str')
|
||||
|
||||
if os.path.isabs(rel_path):
|
||||
raise ELFError('External DWARF path must be relative to the ELF file directory.')
|
||||
|
||||
rel_path = os.path.realpath(os.path.join(base_directory, rel_path))
|
||||
# Resolve ".." segments and symlinks before checking that the final
|
||||
# target still lives under the ELF file's directory.
|
||||
if os.path.commonpath([base_directory, rel_path]) != base_directory:
|
||||
raise ELFError('External DWARF path escapes the ELF file directory.')
|
||||
|
||||
return open(rel_path, 'rb')
|
||||
return loader
|
||||
|
||||
def num_sections(self) -> int:
|
||||
""" Number of sections in the file
|
||||
"""
|
||||
if self['e_shoff'] == 0:
|
||||
return 0
|
||||
# From the ELF ABI documentation at
|
||||
# https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html:
|
||||
# "e_shnum normally tells how many entries the section header table
|
||||
# contains. [...] If the number of sections is greater than or equal to
|
||||
# SHN_LORESERVE (0xff00), e_shnum has the value SHN_UNDEF (0) and the
|
||||
# actual number of section header table entries is contained in the
|
||||
# sh_size field of the section header at index 0 (otherwise, the sh_size
|
||||
# member of the initial entry contains 0)."
|
||||
if self['e_shnum'] == 0:
|
||||
section_header = self._get_section_header(0)
|
||||
return section_header['sh_size']
|
||||
return self['e_shnum']
|
||||
|
||||
def get_section(self, n: int, type: TContainer[str] | None = None) -> Section:
|
||||
""" Get the section at index #n from the file (Section object or a
|
||||
subclass)
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if type and section_header.sh_type not in type:
|
||||
raise ELFError("Unexpected section type %s, expected %s" % (section_header['sh_type'], type))
|
||||
return self._make_section(section_header)
|
||||
|
||||
def _get_linked_symtab_section(self, n: int) -> SymbolTableSection:
|
||||
""" Get the section at index #n from the file, throws
|
||||
if it's not a SYMTAB/DYNTAB.
|
||||
Used for resolving section links with target type validation.
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if section_header['sh_type'] not in ('SHT_SYMTAB', 'SHT_DYNSYM'):
|
||||
raise ELFError("Section points at section %d of type %s, expected SHT_SYMTAB/SHT_DYNSYM" % (n, section_header['sh_type']))
|
||||
section = self._make_section(section_header)
|
||||
assert isinstance(section, SymbolTableSection)
|
||||
return section
|
||||
|
||||
def _get_linked_strtab_section(self, n: int) -> StringTableSection:
|
||||
""" Get the section at index #n from the file, throws
|
||||
if it's not a STRTAB.
|
||||
Used for resolving section links with target type validation.
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if section_header['sh_type'] != 'SHT_STRTAB':
|
||||
raise ELFError("SHT_SYMTAB section points at section %d of type %s, expected SHT_STRTAB" % (n, section_header['sh_type']))
|
||||
section = self._make_section(section_header)
|
||||
assert isinstance(section, StringTableSection)
|
||||
return section
|
||||
|
||||
def get_section_by_name(self, name: str) -> Section | None:
|
||||
""" Get a section from the file, by name. Return None if no such
|
||||
section exists.
|
||||
"""
|
||||
secnum = self._section_name_map.get(name, None)
|
||||
return None if secnum is None else self.get_section(secnum)
|
||||
|
||||
def get_section_index(self, section_name: str) -> int | None:
|
||||
""" Gets the index of the section by name. Return None if no such
|
||||
section name exists.
|
||||
"""
|
||||
return self._section_name_map.get(section_name, None)
|
||||
|
||||
def has_section(self, section_name: str) -> bool:
|
||||
""" Section existence check by name, without the overhead of parsing if found.
|
||||
"""
|
||||
return section_name in self._section_name_map
|
||||
|
||||
def iter_sections(self, type: str | None = None) -> Iterator[Section]:
|
||||
""" Yield all the sections in the file. If the optional |type|
|
||||
parameter is passed, this method will only yield sections of the
|
||||
given type. The parameter value must be a string containing the
|
||||
name of the type as defined in the ELF specification, e.g.
|
||||
'SHT_SYMTAB'.
|
||||
"""
|
||||
for i in range(self.num_sections()):
|
||||
section = self.get_section(i)
|
||||
if type is None or section['sh_type'] == type:
|
||||
yield section
|
||||
|
||||
def num_segments(self) -> int:
|
||||
""" Number of segments in the file
|
||||
"""
|
||||
# From: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
|
||||
# Section: 4.1.2 Number of Program Headers
|
||||
# If the number of program headers is greater than or equal to
|
||||
# PN_XNUM (0xffff), this member has the value PN_XNUM
|
||||
# (0xffff). The actual number of program header table entries
|
||||
# is contained in the sh_info field of the section header at
|
||||
# index 0.
|
||||
if self['e_phnum'] < 0xffff:
|
||||
return self['e_phnum']
|
||||
else:
|
||||
return self.get_section(0)['sh_info']
|
||||
|
||||
def get_segment(self, n: int) -> Segment:
|
||||
""" Get the segment at index #n from the file (Segment object)
|
||||
"""
|
||||
segment_header = self._get_segment_header(n)
|
||||
return self._make_segment(segment_header)
|
||||
|
||||
def iter_segments(self, type: str | None = None) -> Iterator[Segment]:
|
||||
""" Yield all the segments in the file. If the optional |type|
|
||||
parameter is passed, this method will only yield segments of the
|
||||
given type. The parameter value must be a string containing the
|
||||
name of the type as defined in the ELF specification, e.g.
|
||||
'PT_LOAD'.
|
||||
"""
|
||||
for i in range(self.num_segments()):
|
||||
segment = self.get_segment(i)
|
||||
if type is None or segment['p_type'] == type:
|
||||
yield segment
|
||||
|
||||
def address_offsets(self, start: int, size: int = 1) -> Iterator[int]:
|
||||
""" Yield a file offset for each ELF segment containing a memory region.
|
||||
|
||||
A memory region is defined by the range [start...start+size). The
|
||||
offset of the region is yielded.
|
||||
"""
|
||||
end = start + size
|
||||
# consider LOAD only to prevent same address being yielded twice
|
||||
for seg in self.iter_segments(type='PT_LOAD'):
|
||||
if (start >= seg['p_vaddr'] and
|
||||
end <= seg['p_vaddr'] + seg['p_filesz']):
|
||||
yield start - seg['p_vaddr'] + seg['p_offset']
|
||||
|
||||
def has_dwarf_info(self, strict: bool = False) -> bool:
|
||||
""" Check whether this file appears to have debugging information.
|
||||
We assume that if it has the .debug_info or .zdebug_info section, it
|
||||
has all the other required sections as well.
|
||||
|
||||
Unless you pass strict=True, the presence of .eh_frame section,
|
||||
which is DWARF adjacent but hardly DWARF proper, will count as debug info.
|
||||
Stripped files contain .eh_frame but none of the .[z]debug_xxx sections.
|
||||
"""
|
||||
return (self.has_section('.debug_info') or
|
||||
self.has_section('.zdebug_info') or
|
||||
(not strict and self.has_section('.eh_frame')))
|
||||
|
||||
def get_dwarf_info(
|
||||
self,
|
||||
relocate_dwarf_sections: bool = True,
|
||||
follow_links: bool = True,
|
||||
) -> DWARFInfo:
|
||||
""" Return a DWARFInfo object representing the debugging information in
|
||||
this file.
|
||||
|
||||
If relocate_dwarf_sections is True, relocations for DWARF sections
|
||||
are looked up and applied.
|
||||
|
||||
If follow_links is True, we will try to load the external and/or supplementary
|
||||
object file (if any), and use it to resolve references and imports.
|
||||
"""
|
||||
# Expect that has_dwarf_info() was called, so at least .debug_info is
|
||||
# present.
|
||||
# Sections that aren't found will be passed as None to DWARFInfo.
|
||||
|
||||
# TODO: support linking by build ID
|
||||
# https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html
|
||||
|
||||
# A file may contain a debug link but not be stripped, so check for debug_info just in case
|
||||
debuglink_section = self.get_section_by_name('.gnu_debuglink')
|
||||
if debuglink_section and not self.has_dwarf_info(True) and follow_links and self.stream_loader:
|
||||
debuglink = struct_parse(self.structs.Gnu_debuglink, debuglink_section.stream, debuglink_section.header.sh_offset)
|
||||
with self.stream_loader(os.fsdecode(debuglink.filename)) as ext_file:
|
||||
# Validate checksum...
|
||||
if _file_crc32(ext_file) != debuglink.checksum:
|
||||
raise ELFError('The linked DWARF file does not match the checksum in the link.')
|
||||
ext_file.seek(0, os.SEEK_SET)
|
||||
ext_elffile = ELFFile(ext_file, self.stream_loader)
|
||||
# Inheriting the stream loader like that might be wrong if the supplementary DWARF link in the other file
|
||||
# is relative to the other file's directory as opposed to this file's directory.
|
||||
return ext_elffile.get_dwarf_info(relocate_dwarf_sections=relocate_dwarf_sections, follow_links=True)
|
||||
|
||||
section_names = ['.debug_info', '.debug_aranges', '.debug_abbrev',
|
||||
'.debug_str', '.debug_line', '.debug_frame',
|
||||
'.debug_loc', '.debug_ranges', '.debug_pubtypes',
|
||||
'.debug_pubnames', '.debug_addr',
|
||||
'.debug_str_offsets', '.debug_line_str',
|
||||
'.debug_loclists', '.debug_rnglists',
|
||||
'.debug_sup', '.gnu_debugaltlink', '.debug_types',
|
||||
]
|
||||
|
||||
compressed = self.has_section('.zdebug_info')
|
||||
if compressed:
|
||||
section_names = [f'.z{s[1:]}' for s in section_names]
|
||||
|
||||
# As it is loaded in the process image, .eh_frame cannot be compressed
|
||||
section_names.append('.eh_frame')
|
||||
|
||||
(debug_info_sec_name, debug_aranges_sec_name, debug_abbrev_sec_name,
|
||||
debug_str_sec_name, debug_line_sec_name, debug_frame_sec_name,
|
||||
debug_loc_sec_name, debug_ranges_sec_name, debug_pubtypes_name,
|
||||
debug_pubnames_name, debug_addr_name, debug_str_offsets_name,
|
||||
debug_line_str_name, debug_loclists_sec_name, debug_rnglists_sec_name,
|
||||
debug_sup_name, gnu_debugaltlink_name, debug_types_sec_name,
|
||||
eh_frame_sec_name) = section_names
|
||||
|
||||
debug_sections: dict[str, DebugSectionDescriptor | None] = {}
|
||||
for secname in section_names:
|
||||
section = self.get_section_by_name(secname)
|
||||
if section is None:
|
||||
debug_sections[secname] = None
|
||||
else:
|
||||
dwarf_section = self._read_dwarf_section(
|
||||
section,
|
||||
relocate_dwarf_sections)
|
||||
if compressed and secname.startswith('.z'):
|
||||
dwarf_section = self._decompress_dwarf_section(dwarf_section)
|
||||
debug_sections[secname] = dwarf_section
|
||||
|
||||
# Lookup if we have any of the .gnu_debugaltlink (GNU proprietary
|
||||
# implementation) or .debug_sup sections, referencing a supplementary
|
||||
# DWARF file
|
||||
|
||||
dwarfinfo = DWARFInfo(
|
||||
config=DwarfConfig(
|
||||
little_endian=self.little_endian,
|
||||
default_address_size=self.elfclass // 8,
|
||||
machine_arch=self.get_machine_arch()),
|
||||
debug_info_sec=debug_sections[debug_info_sec_name],
|
||||
debug_aranges_sec=debug_sections[debug_aranges_sec_name],
|
||||
debug_abbrev_sec=debug_sections[debug_abbrev_sec_name],
|
||||
debug_frame_sec=debug_sections[debug_frame_sec_name],
|
||||
eh_frame_sec=debug_sections[eh_frame_sec_name],
|
||||
debug_str_sec=debug_sections[debug_str_sec_name],
|
||||
debug_loc_sec=debug_sections[debug_loc_sec_name],
|
||||
debug_ranges_sec=debug_sections[debug_ranges_sec_name],
|
||||
debug_line_sec=debug_sections[debug_line_sec_name],
|
||||
debug_pubtypes_sec=debug_sections[debug_pubtypes_name],
|
||||
debug_pubnames_sec=debug_sections[debug_pubnames_name],
|
||||
debug_addr_sec=debug_sections[debug_addr_name],
|
||||
debug_str_offsets_sec=debug_sections[debug_str_offsets_name],
|
||||
debug_line_str_sec=debug_sections[debug_line_str_name],
|
||||
debug_loclists_sec=debug_sections[debug_loclists_sec_name],
|
||||
debug_rnglists_sec=debug_sections[debug_rnglists_sec_name],
|
||||
debug_sup_sec=debug_sections[debug_sup_name],
|
||||
gnu_debugaltlink_sec=debug_sections[gnu_debugaltlink_name],
|
||||
debug_types_sec=debug_sections[debug_types_sec_name]
|
||||
)
|
||||
if follow_links:
|
||||
dwarfinfo.supplementary_dwarfinfo = self.get_supplementary_dwarfinfo(dwarfinfo)
|
||||
return dwarfinfo
|
||||
|
||||
def has_dwarf_link(self) -> bool:
|
||||
""" Whether the binary's debug info is in an
|
||||
external file. Use get_dwarf_link to retrieve the path to it.
|
||||
"""
|
||||
return self.has_section('.gnu_debuglink')
|
||||
|
||||
def get_dwarf_link(self) -> Container | None:
|
||||
""" Read the .gnu_debuglink section, return an object with filename (as bytes) and checksum (as number) in it.
|
||||
"""
|
||||
section = self.get_section_by_name('.gnu_debuglink')
|
||||
return struct_parse(self.structs.Gnu_debuglink, section.stream, section.header.sh_offset) if section else None
|
||||
|
||||
def get_supplementary_dwarfinfo(self, dwarfinfo: DWARFInfo) -> DWARFInfo | None:
|
||||
"""
|
||||
Read supplementary dwarfinfo, from either the standared .debug_sup
|
||||
section, the GNU proprietary .gnu_debugaltlink, or .gnu_debuglink.
|
||||
"""
|
||||
supfilepath = dwarfinfo.parse_debugsupinfo()
|
||||
if supfilepath is not None and self.stream_loader is not None:
|
||||
stream = self.stream_loader(os.fsdecode(supfilepath))
|
||||
supelffile = ELFFile(stream)
|
||||
dwarf_info = supelffile.get_dwarf_info()
|
||||
stream.close()
|
||||
return dwarf_info
|
||||
return None
|
||||
|
||||
|
||||
def has_ehabi_info(self) -> bool:
|
||||
""" Check whether this file appears to have arm exception handler index table.
|
||||
"""
|
||||
return any(self.iter_sections(type='SHT_ARM_EXIDX'))
|
||||
|
||||
def get_ehabi_infos(self) -> list[EHABIInfo] | None:
|
||||
""" Generally, shared library and executable contain 1 .ARM.exidx section.
|
||||
Object file contains many .ARM.exidx sections.
|
||||
So we must traverse every section and filter sections whose type is SHT_ARM_EXIDX.
|
||||
"""
|
||||
if self['e_type'] == 'ET_REL':
|
||||
# TODO: support relocatable file
|
||||
assert False, "Current version of pyelftools doesn't support relocatable file."
|
||||
_ret = [
|
||||
EHABIInfo(section, self.little_endian)
|
||||
for section in self.iter_sections(type='SHT_ARM_EXIDX')
|
||||
]
|
||||
return _ret if _ret else None
|
||||
|
||||
def get_machine_arch(self) -> str:
|
||||
""" Return the machine architecture, as detected from the ELF header.
|
||||
"""
|
||||
architectures = {
|
||||
'EM_M32' : 'AT&T WE 32100',
|
||||
'EM_SPARC' : 'SPARC',
|
||||
'EM_386' : 'x86',
|
||||
'EM_68K' : 'Motorola 68000',
|
||||
'EM_88K' : 'Motorola 88000',
|
||||
'EM_IAMCU' : 'Intel MCU',
|
||||
'EM_860' : 'Intel 80860',
|
||||
'EM_MIPS' : 'MIPS',
|
||||
'EM_S370' : 'IBM System/370',
|
||||
'EM_MIPS_RS3_LE' : 'MIPS RS3000 Little-endian',
|
||||
'EM_PARISC' : 'Hewlett-Packard PA-RISC',
|
||||
'EM_VPP500' : 'Fujitsu VPP500',
|
||||
'EM_SPARC32PLUS' : 'Enhanced SPARC',
|
||||
'EM_960' : 'Intel 80960',
|
||||
'EM_PPC' : 'PowerPC',
|
||||
'EM_PPC64' : '64-bit PowerPC',
|
||||
'EM_S390' : 'IBM S/390',
|
||||
'EM_SPU' : 'IBM SPU/SPC',
|
||||
'EM_V800' : 'NEC V800',
|
||||
'EM_FR20' : 'Fujitsu FR20',
|
||||
'EM_RH32' : 'TRW RH-32',
|
||||
'EM_RCE' : 'Motorola RCE',
|
||||
'EM_ARM' : 'ARM',
|
||||
'EM_ALPHA' : 'Digital Alpha',
|
||||
'EM_SH' : 'Hitachi SH',
|
||||
'EM_SPARCV9' : 'SPARC Version 9',
|
||||
'EM_TRICORE' : 'Siemens TriCore embedded processor',
|
||||
'EM_ARC' : 'Argonaut RISC Core, Argonaut Technologies Inc.',
|
||||
'EM_H8_300' : 'Hitachi H8/300',
|
||||
'EM_H8_300H' : 'Hitachi H8/300H',
|
||||
'EM_H8S' : 'Hitachi H8S',
|
||||
'EM_H8_500' : 'Hitachi H8/500',
|
||||
'EM_IA_64' : 'Intel IA-64',
|
||||
'EM_MIPS_X' : 'MIPS-X',
|
||||
'EM_COLDFIRE' : 'Motorola ColdFire',
|
||||
'EM_68HC12' : 'Motorola M68HC12',
|
||||
'EM_MMA' : 'Fujitsu MMA',
|
||||
'EM_PCP' : 'Siemens PCP',
|
||||
'EM_NCPU' : 'Sony nCPU',
|
||||
'EM_NDR1' : 'Denso NDR1',
|
||||
'EM_STARCORE' : 'Motorola Star*Core',
|
||||
'EM_ME16' : 'Toyota ME16',
|
||||
'EM_ST100' : 'STMicroelectronics ST100',
|
||||
'EM_TINYJ' : 'Advanced Logic TinyJ',
|
||||
'EM_X86_64' : 'x64',
|
||||
'EM_PDSP' : 'Sony DSP',
|
||||
'EM_PDP10' : 'Digital Equipment PDP-10',
|
||||
'EM_PDP11' : 'Digital Equipment PDP-11',
|
||||
'EM_FX66' : 'Siemens FX66',
|
||||
'EM_ST9PLUS' : 'STMicroelectronics ST9+ 8/16 bit',
|
||||
'EM_ST7' : 'STMicroelectronics ST7 8-bit',
|
||||
'EM_68HC16' : 'Motorola MC68HC16',
|
||||
'EM_68HC11' : 'Motorola MC68HC11',
|
||||
'EM_68HC08' : 'Motorola MC68HC08',
|
||||
'EM_68HC05' : 'Motorola MC68HC05',
|
||||
'EM_SVX' : 'Silicon Graphics SVx',
|
||||
'EM_ST19' : 'STMicroelectronics ST19 8-bit',
|
||||
'EM_VAX' : 'Digital VAX',
|
||||
'EM_CRIS' : 'Axis Communications 32-bit',
|
||||
'EM_JAVELIN' : 'Infineon Technologies 32-bit',
|
||||
'EM_FIREPATH' : 'Element 14 64-bit DSP',
|
||||
'EM_ZSP' : 'LSI Logic 16-bit DSP',
|
||||
'EM_MMIX' : 'Donald Knuth\'s educational 64-bit',
|
||||
'EM_HUANY' : 'Harvard University machine-independent object files',
|
||||
'EM_PRISM' : 'SiTera Prism',
|
||||
'EM_AVR' : 'Atmel AVR 8-bit',
|
||||
'EM_FR30' : 'Fujitsu FR30',
|
||||
'EM_D10V' : 'Mitsubishi D10V',
|
||||
'EM_D30V' : 'Mitsubishi D30V',
|
||||
'EM_V850' : 'NEC v850',
|
||||
'EM_M32R' : 'Mitsubishi M32R',
|
||||
'EM_MN10300' : 'Matsushita MN10300',
|
||||
'EM_MN10200' : 'Matsushita MN10200',
|
||||
'EM_PJ' : 'picoJava',
|
||||
'EM_OPENRISC' : 'OpenRISC 32-bit',
|
||||
'EM_ARC_COMPACT' : 'ARC International ARCompact',
|
||||
'EM_XTENSA' : 'Tensilica Xtensa',
|
||||
'EM_VIDEOCORE' : 'Alphamosaic VideoCore',
|
||||
'EM_TMM_GPP' : 'Thompson Multimedia',
|
||||
'EM_NS32K' : 'National Semiconductor 32000 series',
|
||||
'EM_TPC' : 'Tenor Network TPC',
|
||||
'EM_SNP1K' : 'Trebia SNP 1000',
|
||||
'EM_ST200' : 'STMicroelectronics ST200',
|
||||
'EM_IP2K' : 'Ubicom IP2xxx',
|
||||
'EM_MAX' : 'MAX',
|
||||
'EM_CR' : 'National Semiconductor CompactRISC',
|
||||
'EM_F2MC16' : 'Fujitsu F2MC16',
|
||||
'EM_MSP430' : 'Texas Instruments msp430',
|
||||
'EM_BLACKFIN' : 'Analog Devices Blackfin',
|
||||
'EM_SE_C33' : 'Seiko Epson S1C33',
|
||||
'EM_SEP' : 'Sharp',
|
||||
'EM_ARCA' : 'Arca RISC',
|
||||
'EM_UNICORE' : 'PKU-Unity MPRC',
|
||||
'EM_EXCESS' : 'eXcess',
|
||||
'EM_DXP' : 'Icera Semiconductor Deep Execution Processor',
|
||||
'EM_ALTERA_NIOS2' : 'Altera Nios II',
|
||||
'EM_CRX' : 'National Semiconductor CompactRISC CRX',
|
||||
'EM_XGATE' : 'Motorola XGATE',
|
||||
'EM_C166' : 'Infineon C16x/XC16x',
|
||||
'EM_M16C' : 'Renesas M16C',
|
||||
'EM_DSPIC30F' : 'Microchip Technology dsPIC30F',
|
||||
'EM_CE' : 'Freescale Communication Engine RISC core',
|
||||
'EM_M32C' : 'Renesas M32C',
|
||||
'EM_TSK3000' : 'Altium TSK3000',
|
||||
'EM_RS08' : 'Freescale RS08',
|
||||
'EM_SHARC' : 'Analog Devices SHARC',
|
||||
'EM_ECOG2' : 'Cyan Technology eCOG2',
|
||||
'EM_SCORE7' : 'Sunplus S+core7 RISC',
|
||||
'EM_DSP24' : 'New Japan Radio (NJR) 24-bit DSP',
|
||||
'EM_VIDEOCORE3' : 'Broadcom VideoCore III',
|
||||
'EM_LATTICEMICO32' : 'Lattice FPGA RISC',
|
||||
'EM_SE_C17' : 'Seiko Epson C17',
|
||||
'EM_TI_C6000' : 'TI TMS320C6000',
|
||||
'EM_TI_C2000' : 'TI TMS320C2000',
|
||||
'EM_TI_C5500' : 'TI TMS320C55x',
|
||||
'EM_TI_ARP32' : 'TI Application Specific RISC, 32bit',
|
||||
'EM_TI_PRU' : 'TI Programmable Realtime Unit',
|
||||
'EM_MMDSP_PLUS' : 'STMicroelectronics 64bit VLIW',
|
||||
'EM_CYPRESS_M8C' : 'Cypress M8C',
|
||||
'EM_R32C' : 'Renesas R32C',
|
||||
'EM_TRIMEDIA' : 'NXP Semiconductors TriMedia',
|
||||
'EM_QDSP6' : 'QUALCOMM DSP6',
|
||||
'EM_8051' : 'Intel 8051',
|
||||
'EM_STXP7X' : 'STMicroelectronics STxP7x',
|
||||
'EM_NDS32' : 'Andes Technology RISC',
|
||||
'EM_ECOG1' : 'Cyan Technology eCOG1X',
|
||||
'EM_ECOG1X' : 'Cyan Technology eCOG1X',
|
||||
'EM_MAXQ30' : 'Dallas Semiconductor MAXQ30',
|
||||
'EM_XIMO16' : 'New Japan Radio (NJR) 16-bit',
|
||||
'EM_MANIK' : 'M2000 Reconfigurable RISC',
|
||||
'EM_CRAYNV2' : 'Cray Inc. NV2',
|
||||
'EM_RX' : 'Renesas RX',
|
||||
'EM_METAG' : 'Imagination Technologies META',
|
||||
'EM_MCST_ELBRUS' : 'MCST Elbrus',
|
||||
'EM_ECOG16' : 'Cyan Technology eCOG16',
|
||||
'EM_CR16' : 'National Semiconductor CompactRISC CR16 16-bit',
|
||||
'EM_ETPU' : 'Freescale',
|
||||
'EM_SLE9X' : 'Infineon Technologies SLE9X',
|
||||
'EM_L10M' : 'Intel L10M',
|
||||
'EM_K10M' : 'Intel K10M',
|
||||
'EM_AARCH64' : 'AArch64',
|
||||
'EM_AVR32' : 'Atmel 32-bit',
|
||||
'EM_STM8' : 'STMicroeletronics STM8 8-bit',
|
||||
'EM_TILE64' : 'Tilera TILE64',
|
||||
'EM_TILEPRO' : 'Tilera TILEPro',
|
||||
'EM_MICROBLAZE' : 'Xilinx MicroBlaze 32-bit RISC',
|
||||
'EM_CUDA' : 'NVIDIA CUDA',
|
||||
'EM_TILEGX' : 'Tilera TILE-Gx',
|
||||
'EM_CLOUDSHIELD' : 'CloudShield',
|
||||
'EM_COREA_1ST' : 'KIPO-KAIST Core-A 1st generation',
|
||||
'EM_COREA_2ND' : 'KIPO-KAIST Core-A 2nd generation',
|
||||
'EM_ARC_COMPACT2' : 'Synopsys ARCompact V2',
|
||||
'EM_OPEN8' : 'Open8 8-bit RISC',
|
||||
'EM_RL78' : 'Renesas RL78',
|
||||
'EM_VIDEOCORE5' : 'Broadcom VideoCore V',
|
||||
'EM_78KOR' : 'Renesas 78KOR',
|
||||
'EM_56800EX' : 'Freescale 56800EX',
|
||||
'EM_BA1' : 'Beyond BA1',
|
||||
'EM_BA2' : 'Beyond BA2',
|
||||
'EM_XCORE' : 'XMOS xCORE',
|
||||
'EM_MCHP_PIC' : 'Microchip 8-bit PIC',
|
||||
'EM_INTEL205' : 'Reserved by Intel',
|
||||
'EM_INTEL206' : 'Reserved by Intel',
|
||||
'EM_INTEL207' : 'Reserved by Intel',
|
||||
'EM_INTEL208' : 'Reserved by Intel',
|
||||
'EM_INTEL209' : 'Reserved by Intel',
|
||||
'EM_KM32' : 'KM211 KM32 32-bit',
|
||||
'EM_KMX32' : 'KM211 KMX32 32-bit',
|
||||
'EM_KMX16' : 'KM211 KMX16 16-bit',
|
||||
'EM_KMX8' : 'KM211 KMX8 8-bit',
|
||||
'EM_KVARC' : 'KM211 KVARC',
|
||||
'EM_CDP' : 'Paneve CDP',
|
||||
'EM_COGE' : 'Cognitive',
|
||||
'EM_COOL' : 'Bluechip Systems CoolEngine',
|
||||
'EM_NORC' : 'Nanoradio Optimized RISC',
|
||||
'EM_CSR_KALIMBA' : 'CSR Kalimba',
|
||||
'EM_Z80' : 'Zilog Z80',
|
||||
'EM_VISIUM' : 'VISIUMcore',
|
||||
'EM_FT32' : 'FTDI Chip FT32 32-bit RISC',
|
||||
'EM_MOXIE' : 'Moxie',
|
||||
'EM_AMDGPU' : 'AMD GPU',
|
||||
'EM_RISCV' : 'RISC-V',
|
||||
'EM_BPF' : 'Linux BPF - in-kernel virtual machine',
|
||||
'EM_CSKY' : 'C-SKY',
|
||||
'EM_LOONGARCH' : 'LoongArch',
|
||||
'EM_FRV' : 'Fujitsu FR-V'
|
||||
}
|
||||
|
||||
return architectures.get(self['e_machine'], '<unknown>')
|
||||
|
||||
def get_shstrndx(self) -> int:
|
||||
""" Find the string table section index for the section header table
|
||||
"""
|
||||
# From https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html:
|
||||
# If the section name string table section index is greater than or
|
||||
# equal to SHN_LORESERVE (0xff00), this member has the value SHN_XINDEX
|
||||
# (0xffff) and the actual index of the section name string table section
|
||||
# is contained in the sh_link field of the section header at index 0.
|
||||
if self['e_shstrndx'] != SHN_INDICES.SHN_XINDEX:
|
||||
return self['e_shstrndx']
|
||||
else:
|
||||
section_header = self._get_section_header(0)
|
||||
return section_header['sh_link']
|
||||
|
||||
#-------------------------------- PRIVATE --------------------------------#
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _identify_file(self) -> None:
|
||||
""" Verify the ELF file and identify its class and endianness.
|
||||
"""
|
||||
# Note: this code reads the stream directly, without using ELFStructs,
|
||||
# since we don't yet know its exact format. ELF was designed to be
|
||||
# read like this - its e_ident field is word-size and endian agnostic.
|
||||
self.stream.seek(0)
|
||||
magic = self.stream.read(4)
|
||||
elf_assert(magic == b'\x7fELF', 'Magic number does not match')
|
||||
|
||||
ei_class = self.stream.read(1)
|
||||
if ei_class == b'\x01':
|
||||
self.elfclass = 32
|
||||
elif ei_class == b'\x02':
|
||||
self.elfclass = 64
|
||||
else:
|
||||
raise ELFError('Invalid EI_CLASS %s' % repr(ei_class))
|
||||
|
||||
ei_data = self.stream.read(1)
|
||||
if ei_data == b'\x01':
|
||||
self.little_endian = True
|
||||
elif ei_data == b'\x02':
|
||||
self.little_endian = False
|
||||
else:
|
||||
raise ELFError('Invalid EI_DATA %s' % repr(ei_data))
|
||||
|
||||
def _section_offset(self, n: int) -> int:
|
||||
""" Compute the offset of section #n in the file
|
||||
"""
|
||||
shentsize = self['e_shentsize']
|
||||
if self['e_shoff'] > 0 and shentsize < self.structs.Elf_Shdr.sizeof():
|
||||
raise ELFError('Too small e_shentsize: %s' % shentsize)
|
||||
return self['e_shoff'] + n * shentsize
|
||||
|
||||
def _segment_offset(self, n: int) -> int:
|
||||
""" Compute the offset of segment #n in the file
|
||||
"""
|
||||
phentsize = self['e_phentsize']
|
||||
if self['e_phoff'] > 0 and phentsize < self.structs.Elf_Phdr.sizeof():
|
||||
raise ELFError('Too small e_phentsize: %s' % phentsize)
|
||||
return self['e_phoff'] + n * phentsize
|
||||
|
||||
def _make_segment(self, segment_header: Container) -> Segment:
|
||||
""" Create a Segment object of the appropriate type
|
||||
"""
|
||||
segtype = segment_header['p_type']
|
||||
if segtype == 'PT_INTERP':
|
||||
return InterpSegment(segment_header, self.stream)
|
||||
elif segtype == 'PT_DYNAMIC':
|
||||
return DynamicSegment(segment_header, self.stream, self)
|
||||
elif segtype == 'PT_NOTE':
|
||||
return NoteSegment(segment_header, self.stream, self)
|
||||
else:
|
||||
return Segment(segment_header, self.stream)
|
||||
|
||||
def _get_section_header(self, n: int) -> Container:
|
||||
""" Find the header of section #n, parse it and return the struct
|
||||
"""
|
||||
|
||||
stream_pos = self._section_offset(n)
|
||||
if stream_pos > self.stream_len:
|
||||
msg = f"Reading section {n} at offset {stream_pos} past EOF {self.stream_len}"
|
||||
raise ELFParseError(msg)
|
||||
|
||||
return struct_parse(
|
||||
self.structs.Elf_Shdr,
|
||||
self.stream,
|
||||
stream_pos=stream_pos)
|
||||
|
||||
def _get_section_name(self, section_header: Container) -> str:
|
||||
""" Given a section header, find this section's name in the file's
|
||||
string table
|
||||
"""
|
||||
name_offset = section_header['sh_name']
|
||||
return self._section_header_stringtable.get_string(name_offset)
|
||||
|
||||
def _make_section(self, section_header: Container) -> Section:
|
||||
""" Create a section object of the appropriate type
|
||||
"""
|
||||
name = self._get_section_name(section_header)
|
||||
sectype = section_header['sh_type']
|
||||
|
||||
if sectype == 'SHT_STRTAB':
|
||||
return StringTableSection(section_header, name, self)
|
||||
elif sectype == 'SHT_NULL':
|
||||
return NullSection(section_header, name, self)
|
||||
elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM', 'SHT_SUNW_LDYNSYM'):
|
||||
return self._make_symbol_table_section(section_header, name)
|
||||
elif sectype == 'SHT_SYMTAB_SHNDX':
|
||||
return self._make_symbol_table_index_section(section_header, name)
|
||||
elif sectype == 'SHT_SUNW_syminfo':
|
||||
return self._make_sunwsyminfo_table_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_verneed':
|
||||
return self._make_gnu_verneed_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_verdef':
|
||||
return self._make_gnu_verdef_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_versym':
|
||||
return self._make_gnu_versym_section(section_header, name)
|
||||
elif sectype in ('SHT_REL', 'SHT_RELA'):
|
||||
return RelocationSection(section_header, name, self)
|
||||
elif sectype == 'SHT_DYNAMIC':
|
||||
return DynamicSection(section_header, name, self)
|
||||
elif sectype == 'SHT_NOTE':
|
||||
return NoteSection(section_header, name, self)
|
||||
elif sectype == 'SHT_PROGBITS' and name == '.stab':
|
||||
return StabSection(section_header, name, self)
|
||||
elif sectype == 'SHT_ARM_ATTRIBUTES':
|
||||
return ARMAttributesSection(section_header, name, self)
|
||||
elif sectype == 'SHT_RISCV_ATTRIBUTES':
|
||||
return RISCVAttributesSection(section_header, name, self)
|
||||
elif sectype == 'SHT_HASH':
|
||||
return self._make_elf_hash_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_HASH':
|
||||
return self._make_gnu_hash_section(section_header, name)
|
||||
elif sectype == 'SHT_RELR':
|
||||
return RelrRelocationSection(section_header, name, self)
|
||||
else:
|
||||
return Section(section_header, name, self)
|
||||
|
||||
@cached_property
|
||||
def _section_name_map(self) -> dict[str, int]:
|
||||
return {
|
||||
sec.name: i
|
||||
for i, sec in enumerate(self.iter_sections())
|
||||
}
|
||||
|
||||
def _make_symbol_table_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SymbolTableSection:
|
||||
""" Create a SymbolTableSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return SymbolTableSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_symbol_table_index_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SymbolTableIndexSection:
|
||||
""" Create a SymbolTableIndexSection object
|
||||
"""
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
return SymbolTableIndexSection(
|
||||
section_header, name, elffile=self,
|
||||
symboltable=linked_symtab_index)
|
||||
|
||||
def _make_sunwsyminfo_table_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SUNWSyminfoTableSection:
|
||||
""" Create a SUNWSyminfoTableSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_symtab_section(linked_strtab_index)
|
||||
return SUNWSyminfoTableSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
symboltable=strtab_section)
|
||||
|
||||
def _make_gnu_verneed_section(self, section_header: Container, name: str) -> GNUVerNeedSection:
|
||||
""" Create a GNUVerNeedSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return GNUVerNeedSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_gnu_verdef_section(self, section_header: Container, name: str) -> GNUVerDefSection:
|
||||
""" Create a GNUVerDefSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return GNUVerDefSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_gnu_versym_section(self, section_header: Container, name: str) -> GNUVerSymSection:
|
||||
""" Create a GNUVerSymSection
|
||||
"""
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return GNUVerSymSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
symboltable=symtab_section)
|
||||
|
||||
def _make_elf_hash_section(self, section_header: Container, name: str) -> ELFHashSection:
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return ELFHashSection(
|
||||
section_header, name, self, symtab_section
|
||||
)
|
||||
|
||||
def _make_gnu_hash_section(self, section_header: Container, name: str) -> GNUHashSection:
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return GNUHashSection(
|
||||
section_header, name, self, symtab_section
|
||||
)
|
||||
|
||||
def _get_segment_header(self, n: int) -> Container: # Elf_Phdr:
|
||||
""" Find the header of segment #n, parse it and return the struct
|
||||
"""
|
||||
return struct_parse(
|
||||
self.structs.Elf_Phdr,
|
||||
self.stream,
|
||||
stream_pos=self._segment_offset(n))
|
||||
|
||||
@cached_property
|
||||
def _section_header_stringtable(self) -> StringTableSection:
|
||||
""" Get the string table section corresponding to the section header
|
||||
table.
|
||||
"""
|
||||
stringtable_section_num = self.get_shstrndx()
|
||||
|
||||
try:
|
||||
stringtable_section_header = self._get_section_header(stringtable_section_num)
|
||||
except ELFParseError as ex:
|
||||
raise ELFParseError("String Table not found") from ex
|
||||
|
||||
return StringTableSection(
|
||||
header=stringtable_section_header,
|
||||
name='',
|
||||
elffile=self)
|
||||
|
||||
def _parse_elf_header(self) -> Container:
|
||||
""" Parses the ELF file header and assigns the result to attributes
|
||||
of this object.
|
||||
"""
|
||||
return struct_parse(self.structs.Elf_Ehdr, self.stream, stream_pos=0)
|
||||
|
||||
def _read_dwarf_section(
|
||||
self,
|
||||
section: Section,
|
||||
relocate_dwarf_sections: bool,
|
||||
) -> DebugSectionDescriptor:
|
||||
""" Read the contents of a DWARF section from the stream and return a
|
||||
DebugSectionDescriptor. Apply relocations if asked to.
|
||||
"""
|
||||
phantom_bytes = self.has_phantom_bytes()
|
||||
# The section data is read into a new stream, for processing
|
||||
section_stream = BytesIO()
|
||||
section_data = section.data()
|
||||
section_stream.write(section_data[::2] if phantom_bytes else section_data)
|
||||
|
||||
if relocate_dwarf_sections:
|
||||
reloc_handler = RelocationHandler(self)
|
||||
reloc_section = reloc_handler.find_relocations_for_section(section)
|
||||
if reloc_section is not None:
|
||||
if phantom_bytes:
|
||||
# No guidance how should the relocation work - before or after the odd byte skip
|
||||
raise ELFParseError("This binary has relocations in the DWARF sections, currently not supported.")
|
||||
else:
|
||||
reloc_handler.apply_section_relocations(
|
||||
section_stream, reloc_section)
|
||||
|
||||
return DebugSectionDescriptor(
|
||||
stream=section_stream,
|
||||
name=section.name,
|
||||
global_offset=section['sh_offset'],
|
||||
size=section.data_size//2 if phantom_bytes else section.data_size,
|
||||
address=section['sh_addr'])
|
||||
|
||||
@staticmethod
|
||||
def _decompress_dwarf_section(section: DebugSectionDescriptor) -> DebugSectionDescriptor:
|
||||
""" Returns the uncompressed contents of the provided DWARF section.
|
||||
"""
|
||||
# TODO: support other compression formats from readelf.c
|
||||
assert section.size > 12, 'Unsupported compression format.'
|
||||
|
||||
section.stream.seek(0)
|
||||
# According to readelf.c the content should contain "ZLIB"
|
||||
# followed by the uncompressed section size - 8 bytes in
|
||||
# big-endian order
|
||||
compression_type = section.stream.read(4)
|
||||
assert compression_type == b'ZLIB', \
|
||||
'Invalid compression type: %r' % (compression_type)
|
||||
|
||||
uncompressed_size = struct.unpack('>Q', section.stream.read(8))[0]
|
||||
|
||||
decompressor = zlib.decompressobj()
|
||||
uncompressed_stream = BytesIO()
|
||||
while True:
|
||||
chunk = section.stream.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
uncompressed_stream.write(decompressor.decompress(chunk))
|
||||
uncompressed_stream.write(decompressor.flush())
|
||||
|
||||
uncompressed_stream.seek(0, io.SEEK_END)
|
||||
size = uncompressed_stream.tell()
|
||||
assert uncompressed_size == size, \
|
||||
'Wrong uncompressed size: expected %r, but got %r' % (
|
||||
uncompressed_size, size,
|
||||
)
|
||||
|
||||
return section._replace(stream=uncompressed_stream, size=size)
|
||||
|
||||
def close(self) -> None:
|
||||
self.stream.close()
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
type: type[BaseException] | None,
|
||||
value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
self.close()
|
||||
|
||||
def has_phantom_bytes(self) -> bool:
|
||||
"""The XC16 compiler for the PIC microcontrollers emits DWARF where all odd bytes in all DWARF sections
|
||||
are to be discarded ("phantom").
|
||||
|
||||
We don't know where does the phantom byte discarding fit into the usual chain of section content transforms.
|
||||
There are no XC16/PIC binaries in the corpus with relocations against DWARF, and the DWARF section compression
|
||||
seems to be unsupported by XC16.
|
||||
"""
|
||||
# Vendor flag EF_PIC30_NO_PHANTOM_BYTE=0x80000000: clear means phantom bytes are present
|
||||
return self['e_machine'] == 'EM_DSPIC30F' and (self['e_flags'] & 0x80000000) == 0
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# elftools: elf/gnuversions.py
|
||||
#
|
||||
# ELF sections
|
||||
#
|
||||
# Yann Rouillard (yann@pleiades.fr.eu.org)
|
||||
# This code is in the public domain
|
||||
#------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..common.utils import struct_parse, elf_assert
|
||||
from .sections import Section, Symbol
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.core import Struct
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
from .sections import StringTableSection, SymbolTableSection
|
||||
|
||||
|
||||
class Version:
|
||||
""" Version object - representing a version definition or dependency
|
||||
entry from a "Version Needed" or a "Version Dependency" table section.
|
||||
|
||||
This kind of entry contains a pointer to an array of auxiliary entries
|
||||
that store the information about version names or dependencies.
|
||||
These entries are not stored in this object and should be accessed
|
||||
through the appropriate method of a section object which will return
|
||||
an iterator of VersionAuxiliary objects.
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to
|
||||
verdef/verneed entry
|
||||
"""
|
||||
def __init__(self, entry: Container, name: str | None = None) -> None:
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entry
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class VersionAuxiliary:
|
||||
""" Version Auxiliary object - representing an auxiliary entry of a version
|
||||
definition or dependency entry
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to the
|
||||
verdaux/vernaux entry
|
||||
"""
|
||||
def __init__(self, entry: Container, name: str) -> None:
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class GNUVersionSection(Section):
|
||||
""" Common ancestor class for ELF SUNW|GNU Version Needed/Dependency
|
||||
sections class which contains shareable code
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
field_prefix: str,
|
||||
version_struct: Struct,
|
||||
version_auxiliaries_struct: Struct,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.stringtable = stringtable
|
||||
self.field_prefix = field_prefix
|
||||
self.version_struct = version_struct
|
||||
self.version_auxiliaries_struct = version_auxiliaries_struct
|
||||
|
||||
def num_versions(self) -> int:
|
||||
""" Number of version entries in the section
|
||||
"""
|
||||
return self['sh_info']
|
||||
|
||||
def _field_name(self, name: str, auxiliary: bool = False) -> str:
|
||||
""" Return the real field's name of version or a version auxiliary
|
||||
entry
|
||||
"""
|
||||
middle = 'a_' if auxiliary else '_'
|
||||
return self.field_prefix + middle + name
|
||||
|
||||
def _iter_version_auxiliaries(
|
||||
self,
|
||||
entry_offset: int,
|
||||
count: int,
|
||||
) -> Iterator[VersionAuxiliary]:
|
||||
""" Yield all auxiliary entries of a version entry
|
||||
"""
|
||||
name_field = self._field_name('name', auxiliary=True)
|
||||
next_field = self._field_name('next', auxiliary=True)
|
||||
|
||||
for _ in range(count):
|
||||
entry = struct_parse(
|
||||
self.version_auxiliaries_struct,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
|
||||
name = self.stringtable.get_string(entry[name_field])
|
||||
version_aux = VersionAuxiliary(entry, name)
|
||||
yield version_aux
|
||||
|
||||
entry_offset += entry[next_field]
|
||||
|
||||
def iter_versions(self) -> Iterator[tuple[Version, Iterator[VersionAuxiliary]]]:
|
||||
""" Yield all the version entries in the section
|
||||
Each time it returns the main version structure
|
||||
and an iterator to walk through its auxiliaries entries
|
||||
"""
|
||||
aux_field = self._field_name('aux')
|
||||
count_field = self._field_name('cnt')
|
||||
next_field = self._field_name('next')
|
||||
|
||||
entry_offset = self['sh_offset']
|
||||
for _ in range(self.num_versions()):
|
||||
entry = struct_parse(
|
||||
self.version_struct,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
|
||||
elf_assert(entry[count_field] > 0,
|
||||
'Expected number of version auxiliary entries (%s) to be > 0'
|
||||
'for the following version entry: %s' % (
|
||||
count_field, str(entry)))
|
||||
|
||||
version = Version(entry)
|
||||
aux_entries_offset = entry_offset + entry[aux_field]
|
||||
version_auxiliaries_iter = self._iter_version_auxiliaries(
|
||||
aux_entries_offset, entry[count_field])
|
||||
|
||||
yield version, version_auxiliaries_iter
|
||||
|
||||
entry_offset += entry[next_field]
|
||||
|
||||
|
||||
class GNUVerNeedSection(GNUVersionSection):
|
||||
""" ELF SUNW or GNU Version Needed table section.
|
||||
Has an associated StringTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
header, name, elffile, stringtable, 'vn',
|
||||
elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux)
|
||||
|
||||
def has_indexes(self) -> bool:
|
||||
""" Return True if at least one version definition entry has an index
|
||||
that is stored in the vna_other field.
|
||||
This information is used for symbol versioning
|
||||
"""
|
||||
return self._has_indexes
|
||||
|
||||
@cached_property
|
||||
def _has_indexes(self) -> bool:
|
||||
return any(
|
||||
vernaux['vna_other']
|
||||
for _, vernaux_iter in self.iter_versions()
|
||||
for vernaux in vernaux_iter
|
||||
)
|
||||
|
||||
def iter_versions(self) -> Iterator[tuple[Version, Iterator[VersionAuxiliary]]]:
|
||||
for verneed, vernaux in super().iter_versions():
|
||||
verneed.name = self.stringtable.get_string(verneed['vn_file'])
|
||||
yield verneed, vernaux
|
||||
|
||||
def get_version(self, index: int) -> tuple[Version, VersionAuxiliary] | None:
|
||||
""" Get the version information located at index #n in the table
|
||||
Return boths the verneed structure and the vernaux structure
|
||||
that contains the name of the version
|
||||
"""
|
||||
for verneed, vernaux_iter in self.iter_versions():
|
||||
for vernaux in vernaux_iter:
|
||||
if vernaux['vna_other'] == index:
|
||||
return verneed, vernaux
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class GNUVerDefSection(GNUVersionSection):
|
||||
""" ELF SUNW or GNU Version Definition table section.
|
||||
Has an associated StringTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
header, name, elffile, stringtable, 'vd',
|
||||
elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux)
|
||||
|
||||
def get_version(self, index: int) -> tuple[Version, Iterator[VersionAuxiliary]] | None:
|
||||
""" Get the version information located at index #n in the table
|
||||
Return boths the verdef structure and an iterator to retrieve
|
||||
both the version names and dependencies in the form of
|
||||
verdaux entries
|
||||
"""
|
||||
for verdef, verdaux_iter in self.iter_versions():
|
||||
if verdef['vd_ndx'] == index:
|
||||
return verdef, verdaux_iter
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class GNUVerSymSection(Section):
|
||||
""" ELF SUNW or GNU Versym table section.
|
||||
Has an associated SymbolTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: SymbolTableSection,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize']
|
||||
|
||||
def get_symbol(self, n: int) -> Symbol:
|
||||
""" Get the symbol at index #n from the table (Symbol object)
|
||||
It begins at 1 and not 0 since the first entry is used to
|
||||
store the current version of the syminfo table
|
||||
"""
|
||||
# Grab the symbol's entry from the stream
|
||||
entry_offset = self['sh_offset'] + n * self['sh_entsize']
|
||||
entry = struct_parse(
|
||||
self.structs.Elf_Versym,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
# Find the symbol name in the associated symbol table
|
||||
name = self.symboltable.get_symbol(n).name
|
||||
return Symbol(entry, name)
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
@@ -0,0 +1,238 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/hash.py
|
||||
#
|
||||
# ELF hash table sections
|
||||
#
|
||||
# Andreas Ziegler (andreas.ziegler@fau.de)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..construct.lib.container import Container
|
||||
from .sections import Section
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .elffile import ELFFile
|
||||
from .sections import Symbol
|
||||
|
||||
|
||||
class _SymbolTable(Protocol):
|
||||
"""Common base-class of elftools.elf.sections.SymbolTableSection and
|
||||
elftools.elf.dynamic.DynamicSegment to be consumed by
|
||||
(ELF|GNU)Hash(Section|Table)."""
|
||||
def get_symbol(self, index: int, /) -> Symbol | None: ...
|
||||
|
||||
|
||||
class ELFHashTable:
|
||||
""" Representation of an ELF hash table to find symbols in the
|
||||
symbol table - useful for super-stripped binaries without section
|
||||
headers where only the start of the symbol table is known from the
|
||||
dynamic segment. The layout and contents are nicely described at
|
||||
https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/.
|
||||
|
||||
The symboltable argument needs to implement a get_symbol() method -
|
||||
in a regular ELF file, this will be the linked symbol table section
|
||||
as indicated by the sh_link attribute. For super-stripped binaries,
|
||||
one should use the DynamicSegment object as the symboltable as it
|
||||
supports symbol lookup without access to a symbol table section.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
elffile: ELFFile,
|
||||
start_offset: int,
|
||||
size: int | None,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
elffile (ELFFile): The ELF file.
|
||||
start_offset: The offset of the start of the symbol table in the ELF file.
|
||||
size: Size of the table in bytes (can be None if unknown).
|
||||
symboltable: A symbol table with a get_symbol() method to do symbol lookup.
|
||||
"""
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
if size == 0: # size may also be None if its unknown
|
||||
self.params = Container(**{
|
||||
'nbuckets': 0,
|
||||
'nchains': 0,
|
||||
'buckets': [],
|
||||
'chains': [],
|
||||
})
|
||||
else:
|
||||
self.params = struct_parse(self.elffile.structs.Elf_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
|
||||
def get_number_of_symbols(self) -> int:
|
||||
""" Get the number of symbols from the hash table parameters.
|
||||
"""
|
||||
return self.params['nchains']
|
||||
|
||||
def get_symbol(self, name: str) -> Symbol | None:
|
||||
""" Look up a symbol from this hash table with the given name.
|
||||
"""
|
||||
if self.params['nbuckets'] == 0:
|
||||
return None
|
||||
hval = self.elf_hash(name) % self.params['nbuckets']
|
||||
symndx = self.params['buckets'][hval]
|
||||
while symndx != 0:
|
||||
sym = self._symboltable.get_symbol(symndx)
|
||||
if sym and sym.name == name:
|
||||
return sym
|
||||
symndx = self.params['chains'][symndx]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def elf_hash(name: bytes | str) -> int:
|
||||
""" Compute the hash value for a given symbol name.
|
||||
"""
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode('utf-8')
|
||||
h = 0
|
||||
x = 0
|
||||
for c in bytearray(name):
|
||||
h = (h << 4) + c
|
||||
x = h & 0xF0000000
|
||||
if x != 0:
|
||||
h ^= (x >> 24)
|
||||
h &= ~x
|
||||
return h
|
||||
|
||||
|
||||
class ELFHashSection(Section, ELFHashTable):
|
||||
""" Section representation of an ELF hash table. In regular ELF files, this
|
||||
allows us to use the common functions defined on Section objects when
|
||||
dealing with the hash table.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
ELFHashTable.__init__(self, elffile, self['sh_offset'], self['sh_size'], symboltable)
|
||||
|
||||
|
||||
class GNUHashTable:
|
||||
""" Representation of a GNU hash table to find symbols in the
|
||||
symbol table - useful for super-stripped binaries without section
|
||||
headers where only the start of the symbol table is known from the
|
||||
dynamic segment. The layout and contents are nicely described at
|
||||
https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/.
|
||||
|
||||
The symboltable argument needs to implement a get_symbol() method -
|
||||
in a regular ELF file, this will be the linked symbol table section
|
||||
as indicated by the sh_link attribute. For super-stripped binaries,
|
||||
one should use the DynamicSegment object as the symboltable as it
|
||||
supports symbol lookup without access to a symbol table section.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
elffile: ELFFile,
|
||||
start_offset: int,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
self.params: Container = struct_parse(self.elffile.structs.Gnu_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
# Element sizes in the hash table
|
||||
self._wordsize: int = self.elffile.structs.Elf_word('').sizeof()
|
||||
self._xwordsize: int = self.elffile.structs.Elf_xword('').sizeof()
|
||||
self._chain_pos: int = start_offset + 4 * self._wordsize + \
|
||||
self.params['bloom_size'] * self._xwordsize + \
|
||||
self.params['nbuckets'] * self._wordsize
|
||||
|
||||
def get_number_of_symbols(self) -> int:
|
||||
""" Get the number of symbols in the hash table by finding the bucket
|
||||
with the highest symbol index and walking to the end of its chain.
|
||||
"""
|
||||
# Find highest index in buckets array
|
||||
max_idx = max(self.params['buckets'])
|
||||
if max_idx < self.params['symoffset']:
|
||||
return self.params['symoffset']
|
||||
|
||||
# Position the stream at the start of the corresponding chain
|
||||
max_chain_pos = self._chain_pos + \
|
||||
(max_idx - self.params['symoffset']) * self._wordsize
|
||||
self.elffile.stream.seek(max_chain_pos)
|
||||
hash_format = '<I' if self.elffile.little_endian else '>I'
|
||||
|
||||
# Walk the chain to its end (lowest bit is set)
|
||||
while True:
|
||||
cur_hash = struct.unpack(hash_format, self.elffile.stream.read(self._wordsize))[0]
|
||||
if cur_hash & 1:
|
||||
return max_idx + 1
|
||||
|
||||
max_idx += 1
|
||||
|
||||
def _matches_bloom(self, H1: int) -> bool:
|
||||
""" Helper function to check if the given hash could be in the hash
|
||||
table by testing it against the bloom filter.
|
||||
"""
|
||||
arch_bits = self.elffile.elfclass
|
||||
H2 = H1 >> self.params['bloom_shift']
|
||||
word_idx = int(H1 / arch_bits) % self.params['bloom_size']
|
||||
BITMASK = (1 << (H1 % arch_bits)) | (1 << (H2 % arch_bits))
|
||||
return (self.params['bloom'][word_idx] & BITMASK) == BITMASK
|
||||
|
||||
def get_symbol(self, name: str) -> Symbol | None:
|
||||
""" Look up a symbol from this hash table with the given name.
|
||||
"""
|
||||
namehash = self.gnu_hash(name)
|
||||
if not self._matches_bloom(namehash):
|
||||
return None
|
||||
|
||||
symidx = self.params['buckets'][namehash % self.params['nbuckets']]
|
||||
if symidx < self.params['symoffset']:
|
||||
return None
|
||||
|
||||
self.elffile.stream.seek(self._chain_pos + (symidx - self.params['symoffset']) * self._wordsize)
|
||||
hash_format = '<I' if self.elffile.little_endian else '>I'
|
||||
while True:
|
||||
cur_hash = struct.unpack(hash_format, self.elffile.stream.read(self._wordsize))[0]
|
||||
if cur_hash | 1 == namehash | 1:
|
||||
symbol = self._symboltable.get_symbol(symidx)
|
||||
if symbol and name == symbol.name:
|
||||
return symbol
|
||||
|
||||
if cur_hash & 1:
|
||||
break
|
||||
symidx += 1
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def gnu_hash(key: bytes | str) -> int:
|
||||
""" Compute the GNU-style hash value for a given symbol name.
|
||||
"""
|
||||
if not isinstance(key, bytes):
|
||||
key = key.encode('utf-8')
|
||||
h = 5381
|
||||
for c in bytearray(key):
|
||||
h = h * 33 + c
|
||||
return h & 0xFFFFFFFF
|
||||
|
||||
|
||||
class GNUHashSection(Section, GNUHashTable):
|
||||
""" Section representation of a GNU hash table. In regular ELF files, this
|
||||
allows us to use the common functions defined on Section objects when
|
||||
dealing with the hash table.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
GNUHashTable.__init__(self, elffile, self['sh_offset'], symboltable)
|
||||
@@ -0,0 +1,80 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/notes.py
|
||||
#
|
||||
# ELF notes
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..common.utils import struct_parse, roundup, bytes2str
|
||||
from ..construct import CString
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
|
||||
|
||||
def iter_notes(elffile: ELFFile, offset: int, size: int) -> Iterator[Container]:
|
||||
""" Yield all the notes in a section or segment.
|
||||
"""
|
||||
end = offset + size
|
||||
nhdr_size = elffile.structs.Elf_Nhdr.sizeof()
|
||||
# Note: a note's name and data are 4-byte aligned, but it's possible there's
|
||||
# additional padding at the end to satisfy the alignment requirement of the segment.
|
||||
while offset + nhdr_size < end:
|
||||
note: Container = struct_parse(
|
||||
elffile.structs.Elf_Nhdr,
|
||||
elffile.stream,
|
||||
stream_pos=offset)
|
||||
note['n_offset'] = offset
|
||||
offset += nhdr_size
|
||||
elffile.stream.seek(offset)
|
||||
if note['n_namesz']:
|
||||
# n_namesz is 4-byte aligned.
|
||||
disk_namesz: int = roundup(note['n_namesz'], 2)
|
||||
note['n_name'] = bytes2str(
|
||||
CString('').parse(elffile.stream.read(disk_namesz)))
|
||||
offset += disk_namesz
|
||||
else:
|
||||
note['n_name'] = None
|
||||
|
||||
desc_data: bytes = elffile.stream.read(note['n_descsz'])
|
||||
note['n_descdata'] = desc_data
|
||||
if note['n_type'] == 'NT_GNU_ABI_TAG' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_abi,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_GNU_BUILD_ID' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = bytes(desc_data).hex()
|
||||
elif note['n_type'] == 'NT_GNU_GOLD_VERSION' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = bytes2str(desc_data)
|
||||
elif note['n_type'] == 'NT_PRPSINFO':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_Prpsinfo,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_FILE':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_Nt_File,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_GNU_PROPERTY_TYPE_0' and note['n_name'] == 'GNU':
|
||||
off = offset
|
||||
props: list[Container] = []
|
||||
# n_descsz contains the size of the note "descriptor" (the data payload),
|
||||
# excluding padding. See "Note Section" in https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
current_note_end: int = offset + note['n_descsz']
|
||||
while off < current_note_end:
|
||||
p: Container = struct_parse(elffile.structs.Elf_Prop, elffile.stream, off)
|
||||
off += roundup(p.pr_datasz + 8, 2 if elffile.elfclass == 32 else 3)
|
||||
props.append(p)
|
||||
note['n_desc'] = props
|
||||
else:
|
||||
note['n_desc'] = desc_data
|
||||
offset += roundup(note['n_descsz'], 2)
|
||||
note['n_size'] = offset - note['n_offset']
|
||||
yield note
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user