build: Initialize Python virtual environment and install project dependencies.
Some checks failed
WLED CI / wled_build (push) Has been cancelled
Some checks failed
WLED CI / wled_build (push) Has been cancelled
This commit is contained in:
7
.venv/lib/python3.11/site-packages/elftools/__init__.py
Normal file
7
.venv/lib/python3.11/site-packages/elftools/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
__version__ = '0.32'
|
||||
@@ -0,0 +1,134 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: common/construct_utils.py
|
||||
#
|
||||
# Some complementary construct utilities
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from struct import Struct
|
||||
from ..construct import (
|
||||
Subconstruct, ConstructError, ArrayError, Adapter, Field, RepeatUntil,
|
||||
Rename, SizeofError, Construct, StaticField, FieldError
|
||||
)
|
||||
|
||||
|
||||
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, subcon):
|
||||
Subconstruct.__init__(self, subcon)
|
||||
self.predicate = predicate
|
||||
self._clear_flag(self.FLAG_COPY_CONTEXT)
|
||||
self._set_flag(self.FLAG_DYNAMIC)
|
||||
def _parse(self, stream, context):
|
||||
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, stream, context):
|
||||
raise NotImplementedError('no building')
|
||||
def _sizeof(self, context):
|
||||
raise SizeofError("can't calculate size")
|
||||
|
||||
class ULEB128(Construct):
|
||||
"""A construct based parser for ULEB128 encoding.
|
||||
|
||||
Incompatible with Python 2 - assumes that the return of read()
|
||||
is an indexed collection of numbers.
|
||||
"""
|
||||
def _parse(self, stream, context):
|
||||
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(Construct):
|
||||
"""A construct based parser for SLEB128 encoding.
|
||||
|
||||
Incompatible with Python 2 - assumes that the return of read()
|
||||
is an indexed collection of numbers.
|
||||
"""
|
||||
def _parse(self, stream, context):
|
||||
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(Construct):
|
||||
"""
|
||||
Captures the current stream offset
|
||||
|
||||
Parameters:
|
||||
* name - the name of the value
|
||||
|
||||
Example:
|
||||
StreamOffset("item_offset")
|
||||
"""
|
||||
__slots__ = []
|
||||
def __init__(self, name):
|
||||
Construct.__init__(self, name)
|
||||
self._set_flag(self.FLAG_DYNAMIC)
|
||||
def _parse(self, stream, context):
|
||||
return stream.tell()
|
||||
def _build(self, obj, stream, context):
|
||||
context[self.name] = stream.tell()
|
||||
def _sizeof(self, context):
|
||||
return 0
|
||||
|
||||
_UBInt24_packer = Struct(">BH")
|
||||
_ULInt24_packer = Struct("<HB")
|
||||
|
||||
class UBInt24(StaticField):
|
||||
"""unsigned, big endian 24-bit integer"""
|
||||
def __init__(self, name):
|
||||
StaticField.__init__(self, name, 3)
|
||||
|
||||
def _parse(self, stream, context):
|
||||
(h, l) = _UBInt24_packer.unpack(StaticField._parse(self, stream, context))
|
||||
return l | (h << 16)
|
||||
|
||||
def _build(self, obj, stream, context):
|
||||
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):
|
||||
StaticField.__init__(self, name, 3)
|
||||
|
||||
def _parse(self, stream, context):
|
||||
(l, h) = _ULInt24_packer.unpack(StaticField._parse(self, stream, context))
|
||||
return l | (h << 16)
|
||||
|
||||
def _build(self, obj, stream, context):
|
||||
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
|
||||
143
.venv/lib/python3.11/site-packages/elftools/common/utils.py
Normal file
143
.venv/lib/python3.11/site-packages/elftools/common/utils.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: common/utils.py
|
||||
#
|
||||
# Miscellaneous utilities for elftools
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from contextlib import contextmanager
|
||||
from .exceptions import ELFParseError, ELFError, DWARFError
|
||||
from ..construct import ConstructError, ULInt8
|
||||
import os
|
||||
|
||||
|
||||
def merge_dicts(*dicts):
|
||||
"Given any number of dicts, merges them into a new one."""
|
||||
result = {}
|
||||
for d in dicts:
|
||||
result.update(d)
|
||||
return result
|
||||
|
||||
def bytes2str(b):
|
||||
"""Decode a bytes object into a string."""
|
||||
return b.decode('latin-1')
|
||||
|
||||
def bytelist2string(bytelist):
|
||||
""" Convert a list of byte values (e.g. [0x10 0x20 0x00]) to a bytes object
|
||||
(e.g. b'\x10\x20\x00').
|
||||
"""
|
||||
return b''.join(bytes((b,)) for b in bytelist)
|
||||
|
||||
|
||||
def struct_parse(struct, stream, stream_pos=None):
|
||||
""" 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, stream_pos=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 = []
|
||||
found = False
|
||||
while True:
|
||||
chunk = stream.read(CHUNKSIZE)
|
||||
end_index = chunk.find(b'\x00')
|
||||
if end_index >= 0:
|
||||
chunks.append(chunk[:end_index])
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
chunks.append(chunk)
|
||||
if len(chunk) < CHUNKSIZE:
|
||||
break
|
||||
return b''.join(chunks) if found else None
|
||||
|
||||
|
||||
def elf_assert(cond, msg=''):
|
||||
""" Assert that cond is True, otherwise raise ELFError(msg)
|
||||
"""
|
||||
_assert_with_exception(cond, msg, ELFError)
|
||||
|
||||
|
||||
def dwarf_assert(cond, msg=''):
|
||||
""" Assert that cond is True, otherwise raise DWARFError(msg)
|
||||
"""
|
||||
_assert_with_exception(cond, msg, DWARFError)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def preserve_stream_pos(stream):
|
||||
""" 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, bits):
|
||||
""" 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 read_blob(stream, length):
|
||||
"""Read length bytes from stream, return a list of ints
|
||||
"""
|
||||
return [struct_parse(ULInt8(''), stream) for i in range(length)]
|
||||
|
||||
def save_dwarf_section(section, filename):
|
||||
"""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
|
||||
pos = stream.tell()
|
||||
stream.seek(0, os.SEEK_SET)
|
||||
section.stream.seek(0)
|
||||
with open(filename, 'wb') as file:
|
||||
data = stream.read(section.size)
|
||||
file.write(data)
|
||||
stream.seek(pos, os.SEEK_SET)
|
||||
|
||||
def iterbytes(b):
|
||||
"""Return an iterator over the elements of a bytes object.
|
||||
|
||||
For example, for b'abc' yields b'a', b'b' and then b'c'.
|
||||
"""
|
||||
for i in range(len(b)):
|
||||
yield b[i:i+1]
|
||||
|
||||
def bytes2hex(b, sep=''):
|
||||
if not sep:
|
||||
return b.hex()
|
||||
return sep.join(map('{:02x}'.format, b))
|
||||
|
||||
#------------------------- PRIVATE -------------------------
|
||||
|
||||
def _assert_with_exception(cond, msg, exception_type):
|
||||
if not cond:
|
||||
raise exception_type(msg)
|
||||
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
#### ####
|
||||
## #### ## ## #### ###### ##### ## ## #### ###### ## ##
|
||||
## ## ## ### ## ## ## ## ## ## ## ## ## #### ##
|
||||
## ## ## ###### ### ## ##### ## ## ## ## ##
|
||||
## ## ## ## ### ## ## ## ## ## ## ## ## ##
|
||||
#### #### ## ## #### ## ## ## ##### #### ## ######
|
||||
|
||||
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("\\x01\\x02\\x03")
|
||||
Container(a = 1, b = 515)
|
||||
>>> print s.parse("\\x01\\x02\\x03")
|
||||
Container:
|
||||
a = 1
|
||||
b = 515
|
||||
>>> s.build(Container(a = 1, b = 0x0203))
|
||||
"\\x01\\x02\\x03"
|
||||
"""
|
||||
|
||||
from .core import *
|
||||
from .adapters import *
|
||||
from .macros import *
|
||||
from .debug import Probe, Debugger
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# Metadata
|
||||
#===============================================================================
|
||||
__author__ = "tomer filiba (tomerfiliba [at] gmail.com)"
|
||||
__maintainer__ = "Corbin Simpson <MostAwesomeDude@gmail.com>"
|
||||
__version__ = "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, warnings
|
||||
|
||||
def deprecated(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
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",
|
||||
]
|
||||
@@ -0,0 +1,470 @@
|
||||
from .core import Adapter, AdaptationError, Pass
|
||||
from .lib import int_to_bin, bin_to_int, swap_bytes
|
||||
from .lib import FlagsContainer, HexString
|
||||
from .lib.py3compat import BytesIO, decodebytes
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# 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, width, swapped = False, signed = False,
|
||||
bytesize = 8):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.width = width
|
||||
self.swapped = swapped
|
||||
self.signed = signed
|
||||
self.bytesize = bytesize
|
||||
def _encode(self, obj, context):
|
||||
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, context):
|
||||
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, decoding, encoding,
|
||||
decdefault = NotImplemented, encdefault = NotImplemented):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.decoding = decoding
|
||||
self.encoding = encoding
|
||||
self.decdefault = decdefault
|
||||
self.encdefault = encdefault
|
||||
def _encode(self, obj, context):
|
||||
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, context):
|
||||
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, flags):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.flags = flags
|
||||
def _encode(self, obj, context):
|
||||
flags = 0
|
||||
for name, value in self.flags.items():
|
||||
if getattr(obj, name, False):
|
||||
flags |= value
|
||||
return flags
|
||||
def _decode(self, obj, context):
|
||||
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, encoding = None):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.encoding = encoding
|
||||
def _encode(self, obj, context):
|
||||
if self.encoding:
|
||||
obj = obj.encode(self.encoding)
|
||||
return obj
|
||||
def _decode(self, obj, context):
|
||||
if self.encoding:
|
||||
obj = 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, padchar = b"\x00", paddir = "right",
|
||||
trimdir = "right"):
|
||||
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, context):
|
||||
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, context):
|
||||
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, context):
|
||||
return (len(obj), obj)
|
||||
def _decode(self, obj, context):
|
||||
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, terminators = b"\x00", encoding = None):
|
||||
StringAdapter.__init__(self, subcon, encoding = encoding)
|
||||
self.terminators = terminators
|
||||
def _encode(self, obj, context):
|
||||
return StringAdapter._encode(self, obj, context) + self.terminators[0:1]
|
||||
def _decode(self, obj, context):
|
||||
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, inner_subcon):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.inner_subcon = inner_subcon
|
||||
def _decode(self, obj, context):
|
||||
return self.inner_subcon._parse(BytesIO(obj), context)
|
||||
def _encode(self, obj, context):
|
||||
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, encoder, decoder):
|
||||
Adapter.__init__(self, subcon)
|
||||
self._encode = encoder
|
||||
self._decode = decoder
|
||||
|
||||
class HexDumpAdapter(Adapter):
|
||||
"""
|
||||
Adapter for hex-dumping strings. It returns a HexString, which is a string
|
||||
"""
|
||||
__slots__ = ["linesize"]
|
||||
def __init__(self, subcon, linesize = 16):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.linesize = linesize
|
||||
def _encode(self, obj, context):
|
||||
return obj
|
||||
def _decode(self, obj, context):
|
||||
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, value):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.value = value
|
||||
def _encode(self, obj, context):
|
||||
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, context):
|
||||
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, start, stop = None):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
def _encode(self, obj, context):
|
||||
if self.start is None:
|
||||
return obj
|
||||
return [None] * self.start + obj
|
||||
def _decode(self, obj, context):
|
||||
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, index):
|
||||
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, context):
|
||||
return [None] * self.index + [obj]
|
||||
def _decode(self, obj, context):
|
||||
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, pattern = b"\x00", strict = False):
|
||||
Adapter.__init__(self, subcon)
|
||||
self.pattern = pattern
|
||||
self.strict = strict
|
||||
def _encode(self, obj, context):
|
||||
return self._sizeof(context) * self.pattern
|
||||
def _decode(self, obj, context):
|
||||
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, context):
|
||||
if not self._validate(obj, context):
|
||||
raise ValidationError("invalid object", obj)
|
||||
return obj
|
||||
def _encode(self, obj, context):
|
||||
return self._decode(obj, context)
|
||||
def _validate(self, obj, context):
|
||||
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
|
||||
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x05")
|
||||
5
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.ValidationError: ('invalid object', 8)
|
||||
>>>
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).build(5)
|
||||
'\\x05'
|
||||
>>> OneOf(UBInt8("foo"), [4,5,6,7]).build(9)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.ValidationError: ('invalid object', 9)
|
||||
"""
|
||||
__slots__ = ["valids"]
|
||||
def __init__(self, subcon, valids):
|
||||
Validator.__init__(self, subcon)
|
||||
self.valids = valids
|
||||
def _validate(self, obj, context):
|
||||
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
|
||||
|
||||
>>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x08")
|
||||
8
|
||||
>>> NoneOf(UBInt8("foo"), [4,5,6,7]).parse("\\x06")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.ValidationError: ('invalid object', 6)
|
||||
"""
|
||||
__slots__ = ["invalids"]
|
||||
def __init__(self, subcon, invalids):
|
||||
Validator.__init__(self, subcon)
|
||||
self.invalids = invalids
|
||||
def _validate(self, obj, context):
|
||||
return obj not in self.invalids
|
||||
1326
.venv/lib/python3.11/site-packages/elftools/construct/core.py
Normal file
1326
.venv/lib/python3.11/site-packages/elftools/construct/core.py
Normal file
File diff suppressed because it is too large
Load Diff
133
.venv/lib/python3.11/site-packages/elftools/construct/debug.py
Normal file
133
.venv/lib/python3.11/site-packages/elftools/construct/debug.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Debugging utilities for constructs
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import traceback
|
||||
import pdb
|
||||
import inspect
|
||||
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 = None, show_stream = True,
|
||||
show_context = True, show_stack = True,
|
||||
stream_lookahead = 100):
|
||||
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):
|
||||
return "%s(%r)" % (self.__class__.__name__, self.printname)
|
||||
def _parse(self, stream, context):
|
||||
self.printout(stream, context)
|
||||
def _build(self, obj, stream, context):
|
||||
self.printout(stream, context)
|
||||
def _sizeof(self, context):
|
||||
return 0
|
||||
|
||||
def printout(self, stream, context):
|
||||
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)
|
||||
print
|
||||
|
||||
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, context):
|
||||
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, stream, context):
|
||||
try:
|
||||
self.subcon._build(obj, stream, context)
|
||||
except Exception:
|
||||
self.handle_exc()
|
||||
def handle_exc(self, msg = 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
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
from .py3compat import int2byte
|
||||
|
||||
|
||||
def int_to_bin(number, width=32):
|
||||
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 = {
|
||||
0: 0,
|
||||
1: 1,
|
||||
48: 0, # '0'
|
||||
49: 1, # '1'
|
||||
|
||||
# The following are for Python 2, in which iteration over a bytes object
|
||||
# yields single-character bytes and not integers.
|
||||
'\x00': 0,
|
||||
'\x01': 1,
|
||||
'0': 0,
|
||||
'1': 1,
|
||||
}
|
||||
|
||||
def bin_to_int(bits, signed=False):
|
||||
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
|
||||
ptr = 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, bytesize=8):
|
||||
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 = int2byte(i)
|
||||
bin = int_to_bin(i, 8)
|
||||
# Populate with for both keys i and ch, to support Python 2 & 3
|
||||
_char_to_bin[ch] = bin
|
||||
_char_to_bin[i] = bin
|
||||
_bin_to_char[bin] = ch
|
||||
|
||||
|
||||
def encode_bin(data):
|
||||
"""
|
||||
Create a binary representation of the given b'' object. Assume 8-bit
|
||||
ASCII. Example:
|
||||
|
||||
>>> encode_bin('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):
|
||||
"""
|
||||
Locical 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,77 @@
|
||||
from .binary import encode_bin, decode_bin
|
||||
|
||||
class BitStreamReader(object):
|
||||
|
||||
__slots__ = ["substream", "buffer", "total_size"]
|
||||
|
||||
def __init__(self, substream):
|
||||
self.substream = substream
|
||||
self.total_size = 0
|
||||
self.buffer = ""
|
||||
|
||||
def close(self):
|
||||
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):
|
||||
return self.substream.tell()
|
||||
|
||||
def seek(self, pos, whence = 0):
|
||||
self.buffer = ""
|
||||
self.total_size = 0
|
||||
self.substream.seek(pos, whence)
|
||||
|
||||
def read(self, count):
|
||||
if count < 0:
|
||||
raise ValueError("count cannot be negative")
|
||||
|
||||
l = len(self.buffer)
|
||||
if count == 0:
|
||||
data = ""
|
||||
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(object):
|
||||
|
||||
__slots__ = ["substream", "buffer", "pos"]
|
||||
|
||||
def __init__(self, substream):
|
||||
self.substream = substream
|
||||
self.buffer = []
|
||||
self.pos = 0
|
||||
|
||||
def close(self):
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
bytes = decode_bin("".join(self.buffer))
|
||||
self.substream.write(bytes)
|
||||
self.buffer = []
|
||||
self.pos = 0
|
||||
|
||||
def tell(self):
|
||||
return self.substream.tell() + self.pos // 8
|
||||
|
||||
def seek(self, pos, whence = 0):
|
||||
self.flush()
|
||||
self.substream.seek(pos, whence)
|
||||
|
||||
def write(self, data):
|
||||
if not data:
|
||||
return
|
||||
if type(data) is not str:
|
||||
raise TypeError("data must be a string, not %r" % (type(data),))
|
||||
self.buffer.append(data)
|
||||
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
Various containers.
|
||||
"""
|
||||
|
||||
from pprint import pformat
|
||||
from .py3compat import MutableMapping
|
||||
|
||||
def recursion_lock(retval, lock_name = "__recursion_lock__"):
|
||||
def decorator(func):
|
||||
def wrapper(self, *args, **kw):
|
||||
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)
|
||||
wrapper.__name__ = func.__name__
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
class Container(MutableMapping):
|
||||
"""
|
||||
A generic container of attributes.
|
||||
|
||||
Containers are the common way to express parsed data.
|
||||
"""
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.__dict__ = kw
|
||||
|
||||
# The core dictionary interface.
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self.__dict__[name]
|
||||
|
||||
def __delitem__(self, name):
|
||||
del self.__dict__[name]
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self.__dict__[name] = value
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__.keys())
|
||||
|
||||
# Extended dictionary interface.
|
||||
|
||||
def update(self, other):
|
||||
self.__dict__.update(other)
|
||||
|
||||
__update__ = update
|
||||
|
||||
def __contains__(self, value):
|
||||
return value in self.__dict__
|
||||
|
||||
# Rich comparisons.
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.__dict__ == other.__dict__
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
# Copy interface.
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(**self.__dict__)
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
# Iterator interface.
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__dict__)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self.__dict__))
|
||||
|
||||
def __str__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, str(self.__dict__))
|
||||
|
||||
class FlagsContainer(Container):
|
||||
"""
|
||||
A container providing pretty-printing for flags.
|
||||
|
||||
Only set flags are displayed.
|
||||
"""
|
||||
|
||||
@recursion_lock("<...>")
|
||||
def __str__(self):
|
||||
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):
|
||||
"""
|
||||
A container for lists.
|
||||
"""
|
||||
|
||||
__slots__ = ["__recursion_lock__"]
|
||||
|
||||
@recursion_lock("[...]")
|
||||
def __str__(self):
|
||||
return pformat(self)
|
||||
|
||||
class LazyContainer(object):
|
||||
|
||||
__slots__ = ["subcon", "stream", "pos", "context", "_value"]
|
||||
|
||||
def __init__(self, subcon, stream, pos, context):
|
||||
self.subcon = subcon
|
||||
self.stream = stream
|
||||
self.pos = pos
|
||||
self.context = context
|
||||
self._value = NotImplemented
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self._value == other._value
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __str__(self):
|
||||
return self.__pretty_str__()
|
||||
|
||||
def __pretty_str__(self, nesting = 1, indentation = " "):
|
||||
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):
|
||||
self.stream.seek(self.pos)
|
||||
return self.subcon._parse(self.stream, self.context)
|
||||
|
||||
def dispose(self):
|
||||
self.subcon = None
|
||||
self.stream = None
|
||||
self.context = None
|
||||
self.pos = None
|
||||
|
||||
def _get_value(self):
|
||||
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,43 @@
|
||||
from .py3compat import byte2int, int2byte, bytes2str
|
||||
|
||||
|
||||
# Map an integer in the inclusive range 0-255 to its string byte representation
|
||||
_printable = dict((i, ".") for i in range(256))
|
||||
_printable.update((i, bytes2str(int2byte(i))) for i in range(32, 128))
|
||||
|
||||
|
||||
def hexdump(data, linesize):
|
||||
"""
|
||||
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 = " ".join('%02x' % byte2int(b) for b in line)
|
||||
rawtext = "".join(_printable[byte2int(b)] for b in line)
|
||||
prettylines.append(fmt % (i, str(hextext), str(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, linesize = 16):
|
||||
self.linesize = linesize
|
||||
|
||||
def __new__(cls, data, *args, **kwargs):
|
||||
return bytes.__new__(cls, data)
|
||||
|
||||
def __str__(self):
|
||||
if not self:
|
||||
return "''"
|
||||
sep = "\n"
|
||||
return sep + sep.join(
|
||||
hexdump(self, self.linesize))
|
||||
@@ -0,0 +1,74 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# py3compat.py
|
||||
#
|
||||
# Some Python2&3 compatibility code
|
||||
#-------------------------------------------------------------------------------
|
||||
import sys
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
try:
|
||||
from collections.abc import MutableMapping # python >= 3.3
|
||||
except ImportError:
|
||||
from collections import MutableMapping # python < 3.3
|
||||
|
||||
|
||||
if PY3:
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
|
||||
def bchr(i):
|
||||
""" When iterating over b'...' in Python 2 you get single b'_' chars
|
||||
and in Python 3 you get integers. Call bchr to always turn this
|
||||
to single b'_' chars.
|
||||
"""
|
||||
return bytes((i,))
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
|
||||
def int2byte(i):
|
||||
return bytes((i,))
|
||||
|
||||
def byte2int(b):
|
||||
return b
|
||||
|
||||
def str2bytes(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def str2unicode(s):
|
||||
return s
|
||||
|
||||
def bytes2str(b):
|
||||
return b.decode('latin-1')
|
||||
|
||||
def decodebytes(b, encoding):
|
||||
return bytes(b, encoding)
|
||||
|
||||
advance_iterator = next
|
||||
|
||||
else:
|
||||
import cStringIO
|
||||
StringIO = BytesIO = cStringIO.StringIO
|
||||
|
||||
int2byte = chr
|
||||
byte2int = ord
|
||||
bchr = lambda i: i
|
||||
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
|
||||
def str2bytes(s):
|
||||
return s
|
||||
|
||||
def str2unicode(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
|
||||
def bytes2str(b):
|
||||
return b
|
||||
|
||||
def decodebytes(b, encoding):
|
||||
return b.decode(encoding)
|
||||
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
634
.venv/lib/python3.11/site-packages/elftools/construct/macros.py
Normal file
634
.venv/lib/python3.11/site-packages/elftools/construct/macros.py
Normal file
@@ -0,0 +1,634 @@
|
||||
from .lib.py3compat import int2byte
|
||||
from .lib import (BitStreamReader, BitStreamWriter, 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)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# fields
|
||||
#===============================================================================
|
||||
def Field(name, length):
|
||||
"""
|
||||
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 callable(length):
|
||||
return MetaField(name, length)
|
||||
else:
|
||||
return StaticField(name, length)
|
||||
|
||||
def BitField(name, length, swapped = False, signed = False, bytesize = 8):
|
||||
"""
|
||||
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("\\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("\\xe1\\x1f")
|
||||
Container(a = 7, b = False, bar = Container(d = 15, e = 1), c = 8)
|
||||
"""
|
||||
|
||||
return BitIntegerAdapter(Field(name, length),
|
||||
length,
|
||||
swapped=swapped,
|
||||
signed=signed,
|
||||
bytesize=bytesize
|
||||
)
|
||||
|
||||
def Padding(length, pattern = b"\x00", strict = False):
|
||||
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, truth = 1, falsehood = 0, default = False):
|
||||
"""
|
||||
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 : int2byte(truth), False : int2byte(falsehood)},
|
||||
default = default,
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
# field shortcuts
|
||||
#===============================================================================
|
||||
def Bit(name):
|
||||
"""a 1-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 1)
|
||||
def Nibble(name):
|
||||
"""a 4-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 4)
|
||||
def Octet(name):
|
||||
"""an 8-bit BitField; must be enclosed in a BitStruct"""
|
||||
return BitField(name, 8)
|
||||
|
||||
def UBInt8(name):
|
||||
"""unsigned, big endian 8-bit integer"""
|
||||
return FormatField(name, ">", "B")
|
||||
def UBInt16(name):
|
||||
"""unsigned, big endian 16-bit integer"""
|
||||
return FormatField(name, ">", "H")
|
||||
def UBInt32(name):
|
||||
"""unsigned, big endian 32-bit integer"""
|
||||
return FormatField(name, ">", "L")
|
||||
def UBInt64(name):
|
||||
"""unsigned, big endian 64-bit integer"""
|
||||
return FormatField(name, ">", "Q")
|
||||
|
||||
def SBInt8(name):
|
||||
"""signed, big endian 8-bit integer"""
|
||||
return FormatField(name, ">", "b")
|
||||
def SBInt16(name):
|
||||
"""signed, big endian 16-bit integer"""
|
||||
return FormatField(name, ">", "h")
|
||||
def SBInt32(name):
|
||||
"""signed, big endian 32-bit integer"""
|
||||
return FormatField(name, ">", "l")
|
||||
def SBInt64(name):
|
||||
"""signed, big endian 64-bit integer"""
|
||||
return FormatField(name, ">", "q")
|
||||
|
||||
def ULInt8(name):
|
||||
"""unsigned, little endian 8-bit integer"""
|
||||
return FormatField(name, "<", "B")
|
||||
def ULInt16(name):
|
||||
"""unsigned, little endian 16-bit integer"""
|
||||
return FormatField(name, "<", "H")
|
||||
def ULInt32(name):
|
||||
"""unsigned, little endian 32-bit integer"""
|
||||
return FormatField(name, "<", "L")
|
||||
def ULInt64(name):
|
||||
"""unsigned, little endian 64-bit integer"""
|
||||
return FormatField(name, "<", "Q")
|
||||
|
||||
def SLInt8(name):
|
||||
"""signed, little endian 8-bit integer"""
|
||||
return FormatField(name, "<", "b")
|
||||
def SLInt16(name):
|
||||
"""signed, little endian 16-bit integer"""
|
||||
return FormatField(name, "<", "h")
|
||||
def SLInt32(name):
|
||||
"""signed, little endian 32-bit integer"""
|
||||
return FormatField(name, "<", "l")
|
||||
def SLInt64(name):
|
||||
"""signed, little endian 64-bit integer"""
|
||||
return FormatField(name, "<", "q")
|
||||
|
||||
def UNInt8(name):
|
||||
"""unsigned, native endianity 8-bit integer"""
|
||||
return FormatField(name, "=", "B")
|
||||
def UNInt16(name):
|
||||
"""unsigned, native endianity 16-bit integer"""
|
||||
return FormatField(name, "=", "H")
|
||||
def UNInt32(name):
|
||||
"""unsigned, native endianity 32-bit integer"""
|
||||
return FormatField(name, "=", "L")
|
||||
def UNInt64(name):
|
||||
"""unsigned, native endianity 64-bit integer"""
|
||||
return FormatField(name, "=", "Q")
|
||||
|
||||
def SNInt8(name):
|
||||
"""signed, native endianity 8-bit integer"""
|
||||
return FormatField(name, "=", "b")
|
||||
def SNInt16(name):
|
||||
"""signed, native endianity 16-bit integer"""
|
||||
return FormatField(name, "=", "h")
|
||||
def SNInt32(name):
|
||||
"""signed, native endianity 32-bit integer"""
|
||||
return FormatField(name, "=", "l")
|
||||
def SNInt64(name):
|
||||
"""signed, native endianity 64-bit integer"""
|
||||
return FormatField(name, "=", "q")
|
||||
|
||||
def BFloat32(name):
|
||||
"""big endian, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, ">", "f")
|
||||
def LFloat32(name):
|
||||
"""little endian, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, "<", "f")
|
||||
def NFloat32(name):
|
||||
"""native endianity, 32-bit IEEE floating point number"""
|
||||
return FormatField(name, "=", "f")
|
||||
|
||||
def BFloat64(name):
|
||||
"""big endian, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, ">", "d")
|
||||
def LFloat64(name):
|
||||
"""little endian, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, "<", "d")
|
||||
def NFloat64(name):
|
||||
"""native endianity, 64-bit IEEE floating point number"""
|
||||
return FormatField(name, "=", "d")
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# arrays
|
||||
#===============================================================================
|
||||
def Array(count, subcon):
|
||||
"""
|
||||
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("\\x01\\x02\\x03\\x04")
|
||||
[1, 2, 3, 4]
|
||||
>>> c.parse("\\x01\\x02\\x03\\x04\\x05\\x06")
|
||||
[1, 2, 3, 4]
|
||||
>>> c.build([5,6,7,8])
|
||||
'\\x05\\x06\\x07\\x08'
|
||||
>>> c.build([5,6,7,8,9])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.RangeError: expected 4..4, found 5
|
||||
"""
|
||||
|
||||
if callable(count):
|
||||
con = MetaArray(count, subcon)
|
||||
else:
|
||||
con = MetaArray(lambda ctx: count, subcon)
|
||||
con._clear_flag(con.FLAG_DYNAMIC)
|
||||
return con
|
||||
|
||||
def PrefixedArray(subcon, length_field = UBInt8("length")):
|
||||
"""an array prefixed by a length field.
|
||||
* subcon - the subcon to be repeated
|
||||
* length_field - a construct returning an integer
|
||||
"""
|
||||
return LengthValueAdapter(
|
||||
Sequence(subcon.name,
|
||||
length_field,
|
||||
Array(lambda ctx: ctx[length_field.name], subcon),
|
||||
nested = False
|
||||
)
|
||||
)
|
||||
|
||||
def OpenRange(mincount, subcon):
|
||||
from sys import maxsize
|
||||
return Range(mincount, maxsize, subcon)
|
||||
|
||||
def GreedyRange(subcon):
|
||||
"""
|
||||
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("\\x01")
|
||||
[1]
|
||||
>>> c.parse("\\x01\\x02\\x03")
|
||||
[1, 2, 3]
|
||||
>>> c.parse("\\x01\\x02\\x03\\x04\\x05\\x06")
|
||||
[1, 2, 3, 4, 5, 6]
|
||||
>>> c.parse("")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.RangeError: expected 1..2147483647, found 0
|
||||
>>> c.build([1,2])
|
||||
'\\x01\\x02'
|
||||
>>> c.build([])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
construct.core.RangeError: expected 1..2147483647, found 0
|
||||
"""
|
||||
|
||||
return OpenRange(1, subcon)
|
||||
|
||||
def OptionalGreedyRange(subcon):
|
||||
"""
|
||||
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("")
|
||||
[]
|
||||
>>> c.parse("\\x01\\x02")
|
||||
[1, 2]
|
||||
>>> c.build([])
|
||||
''
|
||||
>>> c.build([1,2])
|
||||
'\\x01\\x02'
|
||||
"""
|
||||
|
||||
return OpenRange(0, subcon)
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# subconstructs
|
||||
#===============================================================================
|
||||
def Optional(subcon):
|
||||
"""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):
|
||||
"""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):
|
||||
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 = 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, modulus = 4, pattern = b"\x00"):
|
||||
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):
|
||||
return (modulus - (subcon._sizeof(ctx) % modulus)) % modulus
|
||||
return SeqOfOne(subcon.name,
|
||||
subcon,
|
||||
# ??????
|
||||
# ??????
|
||||
# ??????
|
||||
# ??????
|
||||
Padding(padlength, pattern = pattern),
|
||||
nested = False,
|
||||
)
|
||||
|
||||
def SeqOfOne(name, *args, **kw):
|
||||
"""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):
|
||||
"""embeds a struct into the enclosing struct.
|
||||
* subcon - the struct to embed
|
||||
"""
|
||||
return Reconfig(subcon.name, subcon, subcon.FLAG_EMBED)
|
||||
|
||||
def Rename(newname, subcon):
|
||||
"""renames an existing construct
|
||||
* newname - the new name
|
||||
* subcon - the subcon to rename
|
||||
"""
|
||||
return Reconfig(newname, subcon)
|
||||
|
||||
def Alias(newname, oldname):
|
||||
"""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, mapping, default = NotImplemented):
|
||||
"""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, **kw):
|
||||
"""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, **kw):
|
||||
"""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
|
||||
#===============================================================================
|
||||
def AlignedStruct(name, *subcons, **kw):
|
||||
"""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, *subcons):
|
||||
"""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):
|
||||
"""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, length, encoding=None, padchar=None, paddir="right",
|
||||
trimdir="right"):
|
||||
"""
|
||||
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 str padchar: optional character to pad out strings
|
||||
:param str paddir: direction to pad out strings; one of "right", "left",
|
||||
or "both"
|
||||
:param str trim: direction to trim strings; one of "right", "left"
|
||||
|
||||
>>> from construct import String
|
||||
>>> String("foo", 5).parse("hello")
|
||||
'hello'
|
||||
>>>
|
||||
>>> String("foo", 12, encoding = "utf8").parse("hello joh\\xd4\\x83n")
|
||||
u'hello joh\\u0503n'
|
||||
>>>
|
||||
>>> foo = String("foo", 10, padchar = "X", paddir = "right")
|
||||
>>> foo.parse("helloXXXXX")
|
||||
'hello'
|
||||
>>> foo.build("hello")
|
||||
'helloXXXXX'
|
||||
"""
|
||||
|
||||
con = 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, length_field=UBInt8("length"), encoding=None):
|
||||
"""
|
||||
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("\\x05hello")
|
||||
'hello'
|
||||
>>> foo.build("hello world")
|
||||
'\\x0bhello world'
|
||||
>>>
|
||||
>>> foo = PascalString("foo", length_field = UBInt16("length"))
|
||||
>>> foo.parse("\\x00\\x05hello")
|
||||
'hello'
|
||||
>>> foo.build("hello")
|
||||
'\\x00\\x05hello'
|
||||
"""
|
||||
|
||||
return StringAdapter(
|
||||
LengthValueAdapter(
|
||||
Sequence(name,
|
||||
length_field,
|
||||
Field("data", lambda ctx: ctx[length_field.name]),
|
||||
)
|
||||
),
|
||||
encoding=encoding,
|
||||
)
|
||||
|
||||
def CString(name, terminators=b"\x00", encoding=None,
|
||||
char_field=Field(None, 1)):
|
||||
"""
|
||||
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, predicate, then_subcon, else_subcon):
|
||||
"""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, subcon, elsevalue = None):
|
||||
"""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, subcon, force_build = True):
|
||||
"""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):
|
||||
return ConstAdapter(Field(None, len(data)), data)
|
||||
@@ -0,0 +1,80 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/abbrevtable.py
|
||||
#
|
||||
# DWARF abbreviation table
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..common.utils import struct_parse, dwarf_assert
|
||||
|
||||
|
||||
class AbbrevTable(object):
|
||||
""" Represents a DWARF abbreviation table.
|
||||
"""
|
||||
__slots__ = ('structs', 'stream', 'offset', '_abbrev_map')
|
||||
def __init__(self, structs, stream, offset):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" Parse the abbrev table from the stream
|
||||
"""
|
||||
map = {}
|
||||
self.stream.seek(self.offset)
|
||||
while True:
|
||||
decl_code = 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(object):
|
||||
""" 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, decl):
|
||||
self.code = code
|
||||
self.decl = decl
|
||||
self._has_children = decl['children_flag'] == 'DW_CHILDREN_yes'
|
||||
|
||||
def has_children(self):
|
||||
return self._has_children
|
||||
|
||||
def iter_attr_specs(self):
|
||||
""" 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):
|
||||
return self.decl[entry]
|
||||
126
.venv/lib/python3.11/site-packages/elftools/dwarf/aranges.py
Normal file
126
.venv/lib/python3.11/site-packages/elftools/dwarf/aranges.py
Normal file
@@ -0,0 +1,126 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/aranges.py
|
||||
#
|
||||
# DWARF aranges section decoding (.debug_aranges)
|
||||
#
|
||||
# Dorothy Chen (dorothchen@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from ..common.utils import struct_parse
|
||||
from bisect import bisect_right
|
||||
import math
|
||||
|
||||
# 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
|
||||
ARangeEntry = namedtuple('ARangeEntry',
|
||||
'begin_addr length info_offset unit_length version address_size segment_size')
|
||||
|
||||
class ARanges(object):
|
||||
""" 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, size, structs):
|
||||
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):
|
||||
""" 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=False):
|
||||
""" 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 = []
|
||||
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 = 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 = struct_parse(addr_size('addr'), self.stream)
|
||||
length = 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):
|
||||
""" 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
|
||||
727
.venv/lib/python3.11/site-packages/elftools/dwarf/callframe.py
Normal file
727
.venv/lib/python3.11/site-packages/elftools/dwarf/callframe.py
Normal file
@@ -0,0 +1,727 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/callframe.py
|
||||
#
|
||||
# DWARF call frame information
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import copy, os
|
||||
from collections import namedtuple
|
||||
from ..common.utils import (
|
||||
struct_parse, dwarf_assert, preserve_stream_pos, iterbytes)
|
||||
from ..construct import Struct, Switch
|
||||
from .enums import DW_EH_encoding_flags
|
||||
from .structs import DWARFStructs
|
||||
from .constants import *
|
||||
|
||||
|
||||
class CallFrameInfo(object):
|
||||
""" 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, size, address, base_structs,
|
||||
for_eh_frame=False):
|
||||
self.stream = stream
|
||||
self.size = size
|
||||
self.address = address
|
||||
self.base_structs = base_structs
|
||||
self.entries = 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 = {}
|
||||
|
||||
# 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):
|
||||
""" 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):
|
||||
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):
|
||||
""" 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 = 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 = 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.
|
||||
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)
|
||||
aug_bytes = self._read_augmentation_data(entry_structs)
|
||||
lsda_encoding = 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)
|
||||
else:
|
||||
lsda_pointer = None
|
||||
|
||||
# For convenience, compute the end offset for this entry
|
||||
end_offset = (
|
||||
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)
|
||||
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, offset, end_offset):
|
||||
""" 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:
|
||||
opcode = struct_parse(structs.the_Dwarf_uint8, self.stream, offset)
|
||||
args = []
|
||||
|
||||
primary = opcode & _PRIMARY_MASK
|
||||
primary_arg = opcode & _PRIMARY_ARG_MASK
|
||||
if primary == DW_CFA_advance_loc:
|
||||
args = [primary_arg]
|
||||
elif primary == DW_CFA_offset:
|
||||
args = [
|
||||
primary_arg,
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
elif primary == DW_CFA_restore:
|
||||
args = [primary_arg]
|
||||
# primary == 0 and real opcode is extended
|
||||
elif opcode in (DW_CFA_nop, DW_CFA_remember_state,
|
||||
DW_CFA_restore_state, DW_CFA_AARCH64_negate_ra_state):
|
||||
args = []
|
||||
elif opcode == DW_CFA_set_loc:
|
||||
args = [
|
||||
struct_parse(structs.the_Dwarf_target_addr, self.stream)]
|
||||
elif opcode == DW_CFA_advance_loc1:
|
||||
args = [struct_parse(structs.the_Dwarf_uint8, self.stream)]
|
||||
elif opcode == DW_CFA_advance_loc2:
|
||||
args = [struct_parse(structs.the_Dwarf_uint16, self.stream)]
|
||||
elif opcode == DW_CFA_advance_loc4:
|
||||
args = [struct_parse(structs.the_Dwarf_uint32, self.stream)]
|
||||
elif opcode in (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)]
|
||||
elif opcode in (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)]
|
||||
elif opcode == DW_CFA_def_cfa_offset_sf:
|
||||
args = [struct_parse(structs.the_Dwarf_sleb128, self.stream)]
|
||||
elif opcode == DW_CFA_def_cfa_expression:
|
||||
args = [struct_parse(
|
||||
structs.Dwarf_dw_form['DW_FORM_block'], self.stream)]
|
||||
elif opcode in (DW_CFA_expression, DW_CFA_val_expression):
|
||||
args = [
|
||||
struct_parse(structs.the_Dwarf_uleb128, self.stream),
|
||||
struct_parse(
|
||||
structs.Dwarf_dw_form['DW_FORM_block'], self.stream)]
|
||||
elif opcode in (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)]
|
||||
elif opcode == DW_CFA_GNU_args_size:
|
||||
args = [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
|
||||
|
||||
else:
|
||||
dwarf_assert(False, 'Unknown CFI opcode: 0x%x' % opcode)
|
||||
|
||||
instructions.append(CallFrameInstruction(opcode=opcode, args=args))
|
||||
offset = self.stream.tell()
|
||||
return instructions
|
||||
|
||||
def _parse_cie_for_fde(self, fde_offset, fde_header, entry_structs):
|
||||
""" 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 = fde_header['CIE_pointer']
|
||||
cie_offset = (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, entry_structs):
|
||||
""" 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 = header.get('augmentation')
|
||||
if not augmentation:
|
||||
return ('', {})
|
||||
|
||||
# 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 = {
|
||||
b'z': entry_structs.Dwarf_uleb128('length'),
|
||||
b'L': entry_structs.Dwarf_uint8('LSDA_encoding'),
|
||||
b'R': entry_structs.Dwarf_uint8('FDE_encoding'),
|
||||
b'S': True,
|
||||
b'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 = []
|
||||
aug_dict = {}
|
||||
|
||||
for b in iterbytes(augmentation):
|
||||
try:
|
||||
fld = available_fields[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):
|
||||
""" 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 = 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, stream_offset, encoding):
|
||||
""" 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 = 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, offset):
|
||||
""" 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 = [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)
|
||||
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 = 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):
|
||||
"""
|
||||
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):
|
||||
""" Given an opcode, return the instruction name.
|
||||
"""
|
||||
primary = opcode & _PRIMARY_MASK
|
||||
if primary == 0:
|
||||
return _OPCODE_NAME_MAP[opcode]
|
||||
else:
|
||||
return _OPCODE_NAME_MAP[primary]
|
||||
|
||||
|
||||
class CallFrameInstruction(object):
|
||||
""" 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, args):
|
||||
self.opcode = opcode
|
||||
self.args = args
|
||||
|
||||
def __repr__(self):
|
||||
return '%s (0x%x): %s' % (
|
||||
instruction_name(self.opcode), self.opcode, self.args)
|
||||
|
||||
|
||||
class CFIEntry(object):
|
||||
""" 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, structs, instructions, offset,
|
||||
augmentation_dict=None, augmentation_bytes=b'', cie=None):
|
||||
self.header = header
|
||||
self.structs = structs
|
||||
self.instructions = instructions
|
||||
self.offset = offset
|
||||
self.cie = cie
|
||||
self._decoded_table = None
|
||||
self.augmentation_dict = augmentation_dict if augmentation_dict else {}
|
||||
self.augmentation_bytes = augmentation_bytes
|
||||
|
||||
def get_decoded(self):
|
||||
""" 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.
|
||||
"""
|
||||
if self._decoded_table is None:
|
||||
self._decoded_table = self._decode_CFI_table()
|
||||
return self._decoded_table
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _decode_CFI_table(self):
|
||||
""" Decode the instructions contained in the given CFI entry and return
|
||||
a DecodedCallFrameTable.
|
||||
"""
|
||||
if isinstance(self, CIE):
|
||||
# For a CIE, initialize cur_line to an "empty" line
|
||||
cie = self
|
||||
cur_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.
|
||||
cie = self.cie
|
||||
cie_decoded_table = cie.get_decoded()
|
||||
if len(cie_decoded_table.table) > 0:
|
||||
last_line_in_CIE = copy.copy(cie_decoded_table.table[-1])
|
||||
cur_line = copy.copy(last_line_in_CIE)
|
||||
else:
|
||||
cur_line = dict(cfa=CFARule(reg=None, offset=0))
|
||||
cur_line['pc'] = self['initial_location']
|
||||
reg_order = copy.copy(cie_decoded_table.reg_order)
|
||||
|
||||
table = []
|
||||
|
||||
# Keeps a stack for the use of DW_CFA_{remember|restore}_state
|
||||
# instructions.
|
||||
line_stack = []
|
||||
|
||||
def _add_to_order(regnum):
|
||||
# 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.
|
||||
|
||||
name = instruction_name(instr.opcode)
|
||||
|
||||
if name == 'DW_CFA_set_loc':
|
||||
table.append(copy.copy(cur_line))
|
||||
cur_line['pc'] = instr.args[0]
|
||||
elif name in ( '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']
|
||||
elif name == 'DW_CFA_def_cfa':
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=instr.args[1])
|
||||
elif name == 'DW_CFA_def_cfa_sf':
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=instr.args[1] * cie['code_alignment_factor'])
|
||||
elif name == 'DW_CFA_def_cfa_register':
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=instr.args[0],
|
||||
offset=cur_line['cfa'].offset)
|
||||
elif name == 'DW_CFA_def_cfa_offset':
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=cur_line['cfa'].reg,
|
||||
offset=instr.args[0])
|
||||
elif name == 'DW_CFA_def_cfa_offset_sf':
|
||||
cur_line['cfa'] = CFARule(
|
||||
reg=cur_line['cfa'].reg,
|
||||
offset=instr.args[0] * cie['data_alignment_factor'])
|
||||
elif name == 'DW_CFA_def_cfa_expression':
|
||||
cur_line['cfa'] = CFARule(expr=instr.args[0])
|
||||
elif name == 'DW_CFA_undefined':
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(RegisterRule.UNDEFINED)
|
||||
elif name == 'DW_CFA_same_value':
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(RegisterRule.SAME_VALUE)
|
||||
elif name in ( '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'])
|
||||
elif name in ('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'])
|
||||
elif name == 'DW_CFA_register':
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.REGISTER,
|
||||
instr.args[1])
|
||||
elif name == 'DW_CFA_expression':
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.EXPRESSION,
|
||||
instr.args[1])
|
||||
elif name == 'DW_CFA_val_expression':
|
||||
_add_to_order(instr.args[0])
|
||||
cur_line[instr.args[0]] = RegisterRule(
|
||||
RegisterRule.VAL_EXPRESSION,
|
||||
instr.args[1])
|
||||
elif name in ('DW_CFA_restore', 'DW_CFA_restore_extended'):
|
||||
_add_to_order(instr.args[0])
|
||||
dwarf_assert(
|
||||
isinstance(self, FDE),
|
||||
'%s instruction must be in a FDE' % name)
|
||||
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)
|
||||
elif name == 'DW_CFA_remember_state':
|
||||
line_stack.append(copy.deepcopy(cur_line))
|
||||
elif name == 'DW_CFA_restore_state':
|
||||
pc = cur_line['pc']
|
||||
cur_line = line_stack.pop()
|
||||
cur_line['pc'] = pc
|
||||
|
||||
# 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, structs, instructions, offset, augmentation_bytes=None, cie=None, lsda_pointer=None):
|
||||
super(FDE, self).__init__(header, structs, instructions, offset, augmentation_bytes=augmentation_bytes, cie=cie)
|
||||
self.lsda_pointer = lsda_pointer
|
||||
|
||||
|
||||
class ZERO(object):
|
||||
""" 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):
|
||||
self.offset = offset
|
||||
|
||||
|
||||
class RegisterRule(object):
|
||||
""" 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, arg=None):
|
||||
self.type = type
|
||||
self.arg = arg
|
||||
|
||||
def __repr__(self):
|
||||
return 'RegisterRule(%s, %s)' % (self.type, self.arg)
|
||||
|
||||
|
||||
class CFARule(object):
|
||||
""" 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=None, offset=None, expr=None):
|
||||
self.reg = reg
|
||||
self.offset = offset
|
||||
self.expr = expr
|
||||
|
||||
def __repr__(self):
|
||||
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.
|
||||
#
|
||||
DecodedCallFrameTable = namedtuple(
|
||||
'DecodedCallFrameTable', 'table reg_order')
|
||||
|
||||
|
||||
#---------------- PRIVATE ----------------#
|
||||
|
||||
_PRIMARY_MASK = 0b11000000
|
||||
_PRIMARY_ARG_MASK = 0b00111111
|
||||
|
||||
# This dictionary is filled by automatically scanning the constants module
|
||||
# for DW_CFA_* instructions, and mapping their values to names. Since all
|
||||
# names were imported from constants with `import *`, we look in globals()
|
||||
_OPCODE_NAME_MAP = {}
|
||||
for name in list(globals().keys()):
|
||||
if name.startswith('DW_CFA'):
|
||||
_OPCODE_NAME_MAP[globals()[name]] = name
|
||||
245
.venv/lib/python3.11/site-packages/elftools/dwarf/compileunit.py
Normal file
245
.venv/lib/python3.11/site-packages/elftools/dwarf/compileunit.py
Normal file
@@ -0,0 +1,245 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/compileunit.py
|
||||
#
|
||||
# DWARF compile unit
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from bisect import bisect_right
|
||||
from .die import DIE
|
||||
from ..common.utils import dwarf_assert
|
||||
|
||||
|
||||
class CompileUnit(object):
|
||||
""" 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, dwarfinfo, structs, cu_offset, cu_die_offset):
|
||||
""" 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
|
||||
|
||||
# The abbreviation table for this CU. Filled lazily when DIEs are
|
||||
# requested.
|
||||
self._abbrev_table = None
|
||||
|
||||
# A list of DIEs belonging to this CU.
|
||||
# This list is lazily constructed as DIEs are iterated over.
|
||||
self._dielist = []
|
||||
# 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 = []
|
||||
|
||||
def dwarf_format(self):
|
||||
""" Get the DWARF format (32 or 64) for this CU
|
||||
"""
|
||||
return self.structs.dwarf_format
|
||||
|
||||
def get_abbrev_table(self):
|
||||
""" Get the abbreviation table (AbbrevTable object) for this CU
|
||||
"""
|
||||
if self._abbrev_table is None:
|
||||
self._abbrev_table = self.dwarfinfo.get_abbrev_table(
|
||||
self['debug_abbrev_offset'])
|
||||
return self._abbrev_table
|
||||
|
||||
def get_top_DIE(self):
|
||||
""" 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 len(self._diemap) > 0:
|
||||
return self._dielist[0]
|
||||
|
||||
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):
|
||||
""" Returns whether the top DIE in this CU has already been parsed and cached.
|
||||
No parsing on demand!
|
||||
"""
|
||||
return len(self._diemap) > 0
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self['unit_length'] + self.structs.initial_length_field_size()
|
||||
|
||||
def get_DIE_from_refaddr(self, refaddr):
|
||||
""" 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):
|
||||
""" Iterate over all the DIEs in the CU, 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):
|
||||
""" 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
|
||||
|
||||
cur_offset = child._terminator.offset + child._terminator.size
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _iter_DIE_subtree(self, 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():
|
||||
for d in die.cu._iter_DIE_subtree(c):
|
||||
yield d
|
||||
yield die._terminator
|
||||
|
||||
def _get_cached_DIE(self, offset):
|
||||
""" 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
|
||||
230
.venv/lib/python3.11/site-packages/elftools/dwarf/constants.py
Normal file
230
.venv/lib/python3.11/site-packages/elftools/dwarf/constants.py
Normal file
@@ -0,0 +1,230 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/constants.py
|
||||
#
|
||||
# Constants and flags
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# Inline codes
|
||||
#
|
||||
DW_INL_not_inlined = 0
|
||||
DW_INL_inlined = 1
|
||||
DW_INL_declared_not_inlined = 2
|
||||
DW_INL_declared_inlined = 3
|
||||
|
||||
|
||||
# Source languages
|
||||
#
|
||||
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
|
||||
DW_LANG_Mips_Assembler = 0x8001
|
||||
DW_LANG_Upc = 0x8765
|
||||
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
|
||||
DW_LANG_GOOGLE_RenderScript = 0x8e57
|
||||
DW_LANG_BORLAND_Delphi = 0xb000
|
||||
|
||||
|
||||
# Encoding
|
||||
#
|
||||
DW_ATE_void = 0x0
|
||||
DW_ATE_address = 0x1
|
||||
DW_ATE_boolean = 0x2
|
||||
DW_ATE_complex_float = 0x3
|
||||
DW_ATE_float = 0x4
|
||||
DW_ATE_signed = 0x5
|
||||
DW_ATE_signed_char = 0x6
|
||||
DW_ATE_unsigned = 0x7
|
||||
DW_ATE_unsigned_char = 0x8
|
||||
DW_ATE_imaginary_float = 0x9
|
||||
DW_ATE_packed_decimal = 0xa
|
||||
DW_ATE_numeric_string = 0xb
|
||||
DW_ATE_edited = 0xc
|
||||
DW_ATE_signed_fixed = 0xd
|
||||
DW_ATE_unsigned_fixed = 0xe
|
||||
DW_ATE_decimal_float = 0xf
|
||||
DW_ATE_UTF = 0x10
|
||||
DW_ATE_UCS = 0x11
|
||||
DW_ATE_ASCII = 0x12
|
||||
DW_ATE_lo_user = 0x80
|
||||
DW_ATE_hi_user = 0xff
|
||||
DW_ATE_HP_float80 = 0x80
|
||||
DW_ATE_HP_complex_float80 = 0x81
|
||||
DW_ATE_HP_float128 = 0x82
|
||||
DW_ATE_HP_complex_float128 = 0x83
|
||||
DW_ATE_HP_floathpintel = 0x84
|
||||
DW_ATE_HP_imaginary_float80 = 0x85
|
||||
DW_ATE_HP_imaginary_float128 = 0x86
|
||||
|
||||
|
||||
# Access
|
||||
#
|
||||
DW_ACCESS_public = 1
|
||||
DW_ACCESS_protected = 2
|
||||
DW_ACCESS_private = 3
|
||||
|
||||
|
||||
# Visibility
|
||||
#
|
||||
DW_VIS_local = 1
|
||||
DW_VIS_exported = 2
|
||||
DW_VIS_qualified = 3
|
||||
|
||||
|
||||
# Virtuality
|
||||
#
|
||||
DW_VIRTUALITY_none = 0
|
||||
DW_VIRTUALITY_virtual = 1
|
||||
DW_VIRTUALITY_pure_virtual = 2
|
||||
|
||||
|
||||
# ID case
|
||||
#
|
||||
DW_ID_case_sensitive = 0
|
||||
DW_ID_up_case = 1
|
||||
DW_ID_down_case = 2
|
||||
DW_ID_case_insensitive = 3
|
||||
|
||||
|
||||
# Calling convention
|
||||
#
|
||||
DW_CC_normal = 0x1
|
||||
DW_CC_program = 0x2
|
||||
DW_CC_nocall = 0x3
|
||||
DW_CC_pass_by_reference = 0x4
|
||||
DW_CC_pass_by_valuee = 0x5
|
||||
|
||||
|
||||
# Ordering
|
||||
#
|
||||
DW_ORD_row_major = 0
|
||||
DW_ORD_col_major = 1
|
||||
|
||||
|
||||
# Line program opcodes
|
||||
#
|
||||
DW_LNS_copy = 0x01
|
||||
DW_LNS_advance_pc = 0x02
|
||||
DW_LNS_advance_line = 0x03
|
||||
DW_LNS_set_file = 0x04
|
||||
DW_LNS_set_column = 0x05
|
||||
DW_LNS_negate_stmt = 0x06
|
||||
DW_LNS_set_basic_block = 0x07
|
||||
DW_LNS_const_add_pc = 0x08
|
||||
DW_LNS_fixed_advance_pc = 0x09
|
||||
DW_LNS_set_prologue_end = 0x0a
|
||||
DW_LNS_set_epilogue_begin = 0x0b
|
||||
DW_LNS_set_isa = 0x0c
|
||||
DW_LNE_end_sequence = 0x01
|
||||
DW_LNE_set_address = 0x02
|
||||
DW_LNE_define_file = 0x03
|
||||
DW_LNE_set_discriminator = 0x04
|
||||
DW_LNE_lo_user = 0x80
|
||||
DW_LNE_hi_user = 0xff
|
||||
|
||||
# Line program header content types
|
||||
#
|
||||
DW_LNCT_path = 0x01
|
||||
DW_LNCT_directory_index = 0x02
|
||||
DW_LNCT_timestamp = 0x03
|
||||
DW_LNCT_size = 0x04
|
||||
DW_LNCT_MD5 = 0x05
|
||||
DW_LNCT_lo_user = 0x2000
|
||||
DW_LNCT_LLVM_source = 0x2001
|
||||
DW_LNCT_LLVM_is_MD5 = 0x2002
|
||||
DW_LNCT_hi_user = 0x3fff
|
||||
|
||||
# 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.
|
||||
#
|
||||
DW_CFA_advance_loc = 0b01000000
|
||||
DW_CFA_offset = 0b10000000
|
||||
DW_CFA_restore = 0b11000000
|
||||
DW_CFA_nop = 0x00
|
||||
DW_CFA_set_loc = 0x01
|
||||
DW_CFA_advance_loc1 = 0x02
|
||||
DW_CFA_advance_loc2 = 0x03
|
||||
DW_CFA_advance_loc4 = 0x04
|
||||
DW_CFA_offset_extended = 0x05
|
||||
DW_CFA_restore_extended = 0x06
|
||||
DW_CFA_undefined = 0x07
|
||||
DW_CFA_same_value = 0x08
|
||||
DW_CFA_register = 0x09
|
||||
DW_CFA_remember_state = 0x0a
|
||||
DW_CFA_restore_state = 0x0b
|
||||
DW_CFA_def_cfa = 0x0c
|
||||
DW_CFA_def_cfa_register = 0x0d
|
||||
DW_CFA_def_cfa_offset = 0x0e
|
||||
DW_CFA_def_cfa_expression = 0x0f
|
||||
DW_CFA_expression = 0x10
|
||||
DW_CFA_offset_extended_sf = 0x11
|
||||
DW_CFA_def_cfa_sf = 0x12
|
||||
DW_CFA_def_cfa_offset_sf = 0x13
|
||||
DW_CFA_val_offset = 0x14
|
||||
DW_CFA_val_offset_sf = 0x15
|
||||
DW_CFA_val_expression = 0x16
|
||||
DW_CFA_GNU_window_save = 0x2d # Used on SPARC, not in the corpus
|
||||
DW_CFA_AARCH64_negate_ra_state = 0x2d
|
||||
DW_CFA_GNU_args_size = 0x2e
|
||||
|
||||
|
||||
# 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").
|
||||
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
|
||||
@@ -0,0 +1,244 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# 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 ..common.utils import bytes2str
|
||||
|
||||
cpp_symbols = dict(
|
||||
pointer = "*",
|
||||
reference = "&",
|
||||
const = "const",
|
||||
volatile = "volatile")
|
||||
|
||||
def describe_cpp_datatype(var_die):
|
||||
return str(parse_cpp_datatype(var_die))
|
||||
|
||||
def parse_cpp_datatype(var_die):
|
||||
"""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 not 'DW_AT_type' in var_die.attributes:
|
||||
t.tag = ''
|
||||
return t
|
||||
|
||||
type_die = var_die.get_DIE_from_attribute('DW_AT_type')
|
||||
|
||||
mods = []
|
||||
# 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 not 'DW_AT_type' 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 = tuple(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)
|
||||
params = ", ".join(params)
|
||||
if 'DW_AT_type' in type_die.attributes:
|
||||
retval_type = parse_cpp_datatype(type_die)
|
||||
is_pointer = retval_type.modifiers and retval_type.modifiers[-1] == 'pointer'
|
||||
retval_type = str(retval_type)
|
||||
if not is_pointer:
|
||||
retval_type += " "
|
||||
else:
|
||||
retval_type = "void "
|
||||
|
||||
if len(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 = (_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()
|
||||
while 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(object):
|
||||
""" 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):
|
||||
self.name = None
|
||||
self.modifiers = () # Reads left to right
|
||||
self.scopes = () # Reads left to right
|
||||
self.tag = None
|
||||
self.dimensions = None
|
||||
|
||||
def __str__(self):
|
||||
# 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 len(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 len(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):
|
||||
return bytes2str(die.attributes['DW_AT_name'].value)
|
||||
|
||||
def safe_DIE_name(die, default = ''):
|
||||
return bytes2str(die.attributes['DW_AT_name'].value) if 'DW_AT_name' in die.attributes else default
|
||||
|
||||
def DIE_type(die):
|
||||
return die.get_DIE_from_attribute("DW_AT_type")
|
||||
|
||||
class ClassDesc(object):
|
||||
def __init__(self):
|
||||
self.scopes = ()
|
||||
self.const_member = False
|
||||
|
||||
def get_class_spec_if_member(func_spec, the_func):
|
||||
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 = []
|
||||
while 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, param):
|
||||
if param_spec.tag == 'DW_TAG_formal_parameter':
|
||||
if 'DW_AT_name' in param.attributes:
|
||||
name = DIE_name(param)
|
||||
elif 'DW_AT_name' in param_spec.attributes:
|
||||
name = DIE_name(param_spec)
|
||||
else:
|
||||
name = None
|
||||
type = parse_cpp_datatype(param_spec)
|
||||
return str(type)
|
||||
else: # unspecified_parameters AKA variadic
|
||||
return "..."
|
||||
|
||||
def DIE_is_ptr_to_member_struct(type_die):
|
||||
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):
|
||||
"""Given a DIE with DW_TAG_foo_type, returns foo"""
|
||||
if isinstance(die.tag, int): # User-defined tag
|
||||
return ""
|
||||
return die.tag[7:-5]
|
||||
|
||||
def _array_subtype_size(sub):
|
||||
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,682 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# 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 collections import defaultdict
|
||||
|
||||
from .constants import *
|
||||
from .dwarf_expr import DWARFExprParser
|
||||
from .die import DIE
|
||||
from ..common.utils import preserve_stream_pos, dwarf_assert, bytes2str
|
||||
from .callframe import instruction_name, CIE, FDE
|
||||
|
||||
|
||||
def set_global_machine_arch(machine_arch):
|
||||
global _MACHINE_ARCH
|
||||
_MACHINE_ARCH = machine_arch
|
||||
|
||||
|
||||
def describe_attr_value(attr, die, section_offset):
|
||||
""" 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):
|
||||
""" Given a CFI entry (CIE or FDE), return the textual description of its
|
||||
instructions.
|
||||
"""
|
||||
def _assert_FDE_instruction(instr):
|
||||
dwarf_assert(
|
||||
isinstance(entry, FDE),
|
||||
'Unexpected instruction "%s" for a CIE' % instr)
|
||||
|
||||
def _full_reg_name(regnum):
|
||||
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
|
||||
else: # FDE
|
||||
cie = entry.cie
|
||||
pc = entry['initial_location']
|
||||
|
||||
s = ''
|
||||
for instr in entry.instructions:
|
||||
name = instruction_name(instr.opcode)
|
||||
|
||||
if name in ('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'])
|
||||
elif name in ( '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]))
|
||||
elif name == 'DW_CFA_register':
|
||||
s += ' %s: %s in %s' % (
|
||||
name, _full_reg_name(instr.args[0]),
|
||||
_full_reg_name(instr.args[1]))
|
||||
elif name == 'DW_CFA_set_loc':
|
||||
pc = instr.args[0]
|
||||
s += ' %s: %08x\n' % (name, pc)
|
||||
elif name in ( 'DW_CFA_advance_loc1', 'DW_CFA_advance_loc2',
|
||||
'DW_CFA_advance_loc4', 'DW_CFA_advance_loc'):
|
||||
_assert_FDE_instruction(instr)
|
||||
factored_offset = instr.args[0] * cie['code_alignment_factor']
|
||||
s += ' %s: %s to %08x\n' % (
|
||||
name, factored_offset, factored_offset + pc)
|
||||
pc += factored_offset
|
||||
elif name in ( 'DW_CFA_remember_state', 'DW_CFA_restore_state',
|
||||
'DW_CFA_nop', 'DW_CFA_AARCH64_negate_ra_state'):
|
||||
s += ' %s\n' % name
|
||||
elif name == 'DW_CFA_def_cfa':
|
||||
s += ' %s: %s ofs %s\n' % (
|
||||
name, _full_reg_name(instr.args[0]), instr.args[1])
|
||||
elif name == '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'])
|
||||
elif name in ('DW_CFA_def_cfa_offset', 'DW_CFA_GNU_args_size'):
|
||||
s += ' %s: %s\n' % (name, instr.args[0])
|
||||
elif name == 'DW_CFA_def_cfa_offset_sf':
|
||||
s += ' %s: %s\n' % (name, instr.args[0]*entry.cie['data_alignment_factor'])
|
||||
elif name == '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]))
|
||||
elif name == '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]))
|
||||
else:
|
||||
s += ' %s: <??>\n' % name
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def describe_CFI_register_rule(rule):
|
||||
s = _DESCR_CFI_REGISTER_RULE_TYPE[rule.type]
|
||||
if rule.type in ('OFFSET', 'VAL_OFFSET'):
|
||||
s += '%+d' % rule.arg
|
||||
elif rule.type == 'REGISTER':
|
||||
s += describe_reg_name(rule.arg)
|
||||
return s
|
||||
|
||||
|
||||
def describe_CFI_CFA_rule(rule):
|
||||
if rule.expr:
|
||||
return 'exp'
|
||||
else:
|
||||
return '%s%+d' % (describe_reg_name(rule.reg), rule.offset)
|
||||
|
||||
|
||||
def describe_DWARF_expr(expr, structs, cu_offset=None):
|
||||
""" 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) + ')'
|
||||
|
||||
|
||||
def describe_reg_name(regnum, machine_arch=None, default=True):
|
||||
""" 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):
|
||||
"""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 = None
|
||||
|
||||
# Implements the alternative format of readelf: lowercase hex, prefixed with 0x unless 0
|
||||
def _format_hex(n):
|
||||
return '0x%x' % n if n != 0 else '0'
|
||||
|
||||
def _describe_attr_ref(attr, die, section_offset):
|
||||
return '<%s>' % _format_hex(attr.value + die.cu.cu_offset)
|
||||
|
||||
def _describe_attr_ref_sig8(attr, die, section_offset):
|
||||
return 'signature: %s' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_value_passthrough(attr, die, section_offset):
|
||||
return attr.value
|
||||
|
||||
def _describe_attr_hex(attr, die, section_offset):
|
||||
return '%s' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_hex_addr(attr, die, section_offset):
|
||||
return '<%s>' % _format_hex(attr.value)
|
||||
|
||||
def _describe_attr_split_64bit(attr, die, section_offset):
|
||||
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, die, section_offset):
|
||||
return '(indirect string, offset: %s): %s' % (
|
||||
_format_hex(attr.raw_value), bytes2str(attr.value))
|
||||
|
||||
def _describe_attr_line_strp(attr, die, section_offset):
|
||||
return '(indirect line string, offset: %s): %s' % (
|
||||
_format_hex(attr.raw_value), bytes2str(attr.value))
|
||||
|
||||
def _describe_attr_string(attr, die, section_offset):
|
||||
return bytes2str(attr.value)
|
||||
|
||||
def _describe_attr_debool(attr, die, section_offset):
|
||||
""" 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, die, section_offset):
|
||||
""" Some forms may simply mean that an attribute is present,
|
||||
without providing any value.
|
||||
"""
|
||||
return '1'
|
||||
|
||||
def _describe_attr_block(attr, die, section_offset):
|
||||
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, default, default_interpolate_value=False):
|
||||
""" Create a mapping function from attribute parameters to an extra
|
||||
value that should be displayed.
|
||||
"""
|
||||
def mapper(attr, die, section_offset):
|
||||
if default_interpolate_value:
|
||||
d = default % attr.value
|
||||
else:
|
||||
d = default
|
||||
return mapping.get(attr.value, d)
|
||||
return mapper
|
||||
|
||||
|
||||
def _make_extra_string(s=''):
|
||||
""" Create an extra function that just returns a constant string.
|
||||
"""
|
||||
def extra(attr, die, section_offset):
|
||||
return s
|
||||
return extra
|
||||
|
||||
|
||||
_DWARF_EXPR_DUMPER_CACHE = {}
|
||||
|
||||
def _location_list_extra(attr, die, section_offset):
|
||||
# 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, die, section_offset):
|
||||
# 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, die, section_offset):
|
||||
# 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)
|
||||
#print '&&& ref_die', ref_die
|
||||
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(object):
|
||||
""" 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):
|
||||
self.structs = structs
|
||||
self.expr_parser = DWARFExprParser(self.structs)
|
||||
self._init_lookups()
|
||||
|
||||
def dump_expr(self, expr, cu_offset=None):
|
||||
""" Parse and dump a DWARF expression. expr should be 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(expr)
|
||||
s = []
|
||||
for deo in parsed:
|
||||
s.append(self._dump_to_string(deo.op, deo.op_name, deo.args, cu_offset))
|
||||
return '; '.join(s)
|
||||
|
||||
def _init_lookups(self):
|
||||
self._ops_with_decimal_arg = set([
|
||||
'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 = set(['DW_OP_bregx'])
|
||||
|
||||
self._ops_with_hex_arg = set(
|
||||
['DW_OP_addr', 'DW_OP_call2', 'DW_OP_call4', 'DW_OP_call_ref'])
|
||||
|
||||
def _dump_to_string(self, opcode, opcode_name, args, cu_offset=None):
|
||||
# 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 len(args) == 0:
|
||||
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
|
||||
352
.venv/lib/python3.11/site-packages/elftools/dwarf/die.py
Normal file
352
.venv/lib/python3.11/site-packages/elftools/dwarf/die.py
Normal file
@@ -0,0 +1,352 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/die.py
|
||||
#
|
||||
# DWARF Debugging Information Entry
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from collections import namedtuple, OrderedDict
|
||||
import os
|
||||
|
||||
from ..common.exceptions import DWARFError, ELFParseError
|
||||
from ..common.utils import bytes2str, struct_parse, preserve_stream_pos
|
||||
from .enums import DW_FORM_raw2name
|
||||
from .dwarf_util import _resolve_via_offset_table, _get_base_offset
|
||||
from ..construct import ConstructError
|
||||
|
||||
|
||||
# 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.
|
||||
AttributeValue = namedtuple(
|
||||
'AttributeValue', 'name form value raw_value offset indirection_length')
|
||||
|
||||
|
||||
class DIE(object):
|
||||
""" 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, stream, offset):
|
||||
""" 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 = OrderedDict()
|
||||
self.tag = None
|
||||
self.has_children = None
|
||||
self.abbrev_code = 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 = None
|
||||
self._parent = None
|
||||
|
||||
self._parse_DIE()
|
||||
|
||||
def is_null(self):
|
||||
""" Is this a null entry?
|
||||
"""
|
||||
return self.tag is None
|
||||
|
||||
def get_DIE_from_attribute(self, name):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" Iterates all children of this DIE
|
||||
"""
|
||||
return self.cu.iter_DIE_children(self)
|
||||
|
||||
def iter_siblings(self):
|
||||
""" 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):
|
||||
self._parent = die
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def _search_ancestor_offspring(self):
|
||||
""" 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 = 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.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):
|
||||
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):
|
||||
return self.__repr__()
|
||||
|
||||
def _parse_DIE(self):
|
||||
""" 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)
|
||||
|
||||
# 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:
|
||||
raw_value = structs.Dwarf_dw_form[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):
|
||||
# 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 = 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 as err:
|
||||
raise DWARFError('Found DW_FORM_indirect with unknown real form 0x%x' % real_form_code)
|
||||
|
||||
raw_value = struct_parse(structs.Dwarf_dw_form[real_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, raw_value):
|
||||
""" 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:
|
||||
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:
|
||||
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:
|
||||
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):
|
||||
""" 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)
|
||||
284
.venv/lib/python3.11/site-packages/elftools/dwarf/dwarf_expr.py
Normal file
284
.venv/lib/python3.11/site-packages/elftools/dwarf/dwarf_expr.py
Normal file
@@ -0,0 +1,284 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarf_expr.py
|
||||
#
|
||||
# Decoding DWARF expressions
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from collections import namedtuple
|
||||
from io import BytesIO
|
||||
|
||||
from ..common.utils import struct_parse, bytelist2string, read_blob
|
||||
from ..common.exceptions import DWARFError
|
||||
|
||||
|
||||
# DWARF expression opcodes. name -> opcode mapping
|
||||
DW_OP_name2opcode = 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_hi_user=0xff,
|
||||
)
|
||||
|
||||
def _generate_dynamic_values(map, prefix, index_start, index_end, value_start):
|
||||
""" Generate values in a map (dict) dynamically. Each key starts with
|
||||
a (string) prefix, followed by an index in the inclusive range
|
||||
[index_start, index_end]. The values start at value_start.
|
||||
"""
|
||||
for index in range(index_start, index_end + 1):
|
||||
name = '%s%s' % (prefix, index)
|
||||
value = value_start + index - index_start
|
||||
map[name] = value
|
||||
|
||||
_generate_dynamic_values(DW_OP_name2opcode, 'DW_OP_lit', 0, 31, 0x30)
|
||||
_generate_dynamic_values(DW_OP_name2opcode, 'DW_OP_reg', 0, 31, 0x50)
|
||||
_generate_dynamic_values(DW_OP_name2opcode, 'DW_OP_breg', 0, 31, 0x70)
|
||||
|
||||
# opcode -> name mapping
|
||||
DW_OP_opcode2name = dict((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.
|
||||
DWARFExprOp = namedtuple('DWARFExprOp', 'op op_name args offset')
|
||||
|
||||
|
||||
class DWARFExprParser(object):
|
||||
"""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):
|
||||
self._dispatch_table = _init_dispatch_table(structs)
|
||||
|
||||
def parse_expr(self, expr):
|
||||
""" Parses expr (a list of integers) into a list of DWARFExprOp.
|
||||
|
||||
The list can potentially be nested.
|
||||
"""
|
||||
stream = BytesIO(bytelist2string(expr))
|
||||
parsed = []
|
||||
|
||||
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 len(byte) == 0:
|
||||
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):
|
||||
"""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 = {}
|
||||
def add(opcode_name, func):
|
||||
table[DW_OP_name2opcode[opcode_name]] = func
|
||||
|
||||
def parse_noargs():
|
||||
return lambda stream: []
|
||||
|
||||
def parse_op_addr():
|
||||
return lambda stream: [struct_parse(structs.the_Dwarf_target_addr,
|
||||
stream)]
|
||||
|
||||
def parse_arg_struct(arg_struct):
|
||||
return lambda stream: [struct_parse(arg_struct, stream)]
|
||||
|
||||
def parse_arg_struct2(arg1_struct, arg2_struct):
|
||||
return lambda stream: [struct_parse(arg1_struct, stream),
|
||||
struct_parse(arg2_struct, stream)]
|
||||
|
||||
# ULEB128, then an expression of that length
|
||||
def parse_nestedexpr():
|
||||
def parse(stream):
|
||||
size = struct_parse(structs.the_Dwarf_uleb128, stream)
|
||||
nested_expr_blob = read_blob(stream, size)
|
||||
return [DWARFExprParser(structs).parse_expr(nested_expr_blob)]
|
||||
return parse
|
||||
|
||||
# ULEB128, then a blob of that size
|
||||
def parse_blob():
|
||||
return lambda stream: [read_blob(stream, struct_parse(structs.the_Dwarf_uleb128, stream))]
|
||||
|
||||
# ULEB128 with datatype DIE offset, then byte, then a blob of that size
|
||||
def parse_typedblob():
|
||||
return lambda stream: [struct_parse(structs.the_Dwarf_uleb128, stream), read_blob(stream, 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():
|
||||
def parse(stream):
|
||||
op = 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))
|
||||
|
||||
add('DW_OP_fbreg', parse_arg_struct(structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_regx', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_bregx', parse_arg_struct2(structs.the_Dwarf_uleb128,
|
||||
structs.the_Dwarf_sleb128))
|
||||
add('DW_OP_piece', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
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_convert', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
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_GNU_convert', parse_arg_struct(structs.the_Dwarf_uleb128))
|
||||
add('DW_OP_WASM_location', parse_wasmloc())
|
||||
|
||||
return table
|
||||
@@ -0,0 +1,73 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarf_utils.py
|
||||
#
|
||||
# Minor, shared DWARF helpers
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
import os, binascii
|
||||
from ..construct.macros import UBInt32, UBInt64, ULInt32, ULInt64, Array
|
||||
from ..common.exceptions import DWARFError
|
||||
from ..common.utils import preserve_stream_pos, struct_parse
|
||||
|
||||
def _get_base_offset(cu, base_attribute_name):
|
||||
"""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 not base_attribute_name 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, cu, index, base_attribute_name):
|
||||
"""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, structs, parser):
|
||||
"""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):
|
||||
""" 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 len(d):
|
||||
checksum = binascii.crc32(d, checksum)
|
||||
d = file.read(4096)
|
||||
return checksum
|
||||
733
.venv/lib/python3.11/site-packages/elftools/dwarf/dwarfinfo.py
Normal file
733
.venv/lib/python3.11/site-packages/elftools/dwarf/dwarfinfo.py
Normal file
@@ -0,0 +1,733 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/dwarfinfo.py
|
||||
#
|
||||
# DWARFInfo - Main class for accessing DWARF debug information
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
from collections import namedtuple, OrderedDict
|
||||
from bisect import bisect_right
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 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.
|
||||
#
|
||||
DebugSectionDescriptor = namedtuple('DebugSectionDescriptor',
|
||||
'stream name global_offset size address')
|
||||
|
||||
|
||||
# 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)
|
||||
#
|
||||
DwarfConfig = namedtuple('DwarfConfig',
|
||||
'little_endian machine_arch default_address_size')
|
||||
|
||||
|
||||
class DWARFInfo(object):
|
||||
""" Acts also as a "context" to other major objects, bridging between
|
||||
various parts of the debug information.
|
||||
"""
|
||||
def __init__(self,
|
||||
config,
|
||||
debug_info_sec,
|
||||
debug_aranges_sec,
|
||||
debug_abbrev_sec,
|
||||
debug_frame_sec,
|
||||
eh_frame_sec,
|
||||
debug_str_sec,
|
||||
debug_loc_sec,
|
||||
debug_ranges_sec,
|
||||
debug_line_sec,
|
||||
debug_pubtypes_sec,
|
||||
debug_pubnames_sec,
|
||||
debug_addr_sec,
|
||||
debug_str_offsets_sec,
|
||||
debug_line_str_sec,
|
||||
debug_loclists_sec,
|
||||
debug_rnglists_sec,
|
||||
debug_sup_sec,
|
||||
gnu_debugaltlink_sec,
|
||||
debug_types_sec
|
||||
):
|
||||
""" 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 = 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 = {}
|
||||
# Cache for program lines tables: a dict keyed by offset
|
||||
self._linetable_cache = {}
|
||||
|
||||
# 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 = []
|
||||
self._cu_offsets_map = []
|
||||
|
||||
# DWARF v4 type units by sig8 - OrderedDict created when needed
|
||||
self._type_units_by_sig = None
|
||||
|
||||
@property
|
||||
def has_debug_info(self):
|
||||
""" 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):
|
||||
""" Return whether this contains debug types information.
|
||||
"""
|
||||
return bool(self.debug_types_sec)
|
||||
|
||||
def get_DIE_from_lut_entry(self, lut_entry):
|
||||
""" 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, cu=None):
|
||||
""" 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):
|
||||
""" 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.
|
||||
"""
|
||||
self._parse_debug_types()
|
||||
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):
|
||||
""" 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')
|
||||
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):
|
||||
""" 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')
|
||||
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):
|
||||
""" 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.
|
||||
|
||||
"""
|
||||
self._parse_debug_types()
|
||||
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):
|
||||
""" Yield all the compile units (CompileUnit objects) in the debug info
|
||||
"""
|
||||
return self._parse_CUs_iter()
|
||||
|
||||
def iter_TUs(self):
|
||||
"""Yield all the type units (TypeUnit objects) in the debug_types
|
||||
"""
|
||||
return self._parse_TUs_iter()
|
||||
|
||||
def get_abbrev_table(self, offset):
|
||||
""" 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).
|
||||
"""
|
||||
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):
|
||||
""" Obtain a string from the string table section, given an offset
|
||||
relative to the section.
|
||||
"""
|
||||
return parse_cstring_from_stream(self.debug_str_sec.stream, offset)
|
||||
|
||||
def get_string_from_linetable(self, offset):
|
||||
""" Obtain a string from the string table section, given an offset
|
||||
relative to the section.
|
||||
"""
|
||||
return parse_cstring_from_stream(self.debug_line_str_sec.stream, offset)
|
||||
|
||||
def line_program_for_CU(self, CU):
|
||||
""" 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):
|
||||
""" Does this dwarf info have a dwarf_frame CFI section?
|
||||
"""
|
||||
return self.debug_frame_sec is not None
|
||||
|
||||
def CFI_entries(self):
|
||||
""" Get a list of dwarf_frame CFI entries from the .debug_frame section.
|
||||
"""
|
||||
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):
|
||||
""" Does this dwarf info have a eh_frame CFI section?
|
||||
"""
|
||||
return self.eh_frame_sec is not None
|
||||
|
||||
def EH_CFI_entries(self):
|
||||
""" Get a list of eh_frame CFI entries from the .eh_frame section.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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, addr_index):
|
||||
"""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=0):
|
||||
""" 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=0):
|
||||
""" 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
|
||||
|
||||
def _parse_debug_types(self):
|
||||
""" Check if the .debug_types section is previously parsed. If not,
|
||||
parse all TUs and store them in an OrderedDict using their unique
|
||||
64-bit signature as the key.
|
||||
|
||||
See .get_TU_by_sig8().
|
||||
"""
|
||||
if self._type_units_by_sig is not None:
|
||||
return
|
||||
self._type_units_by_sig = OrderedDict()
|
||||
|
||||
if self.debug_types_sec is None:
|
||||
return
|
||||
|
||||
# Parse all the Type Units in the types section for access by sig8
|
||||
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 = (offset +
|
||||
tu['unit_length'] +
|
||||
tu.structs.initial_length_field_size())
|
||||
self._type_units_by_sig[tu['signature']] = tu
|
||||
|
||||
def _cached_CU_at_offset(self, offset):
|
||||
""" 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):
|
||||
""" 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.
|
||||
#
|
||||
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):
|
||||
""" 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.
|
||||
#
|
||||
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):
|
||||
""" DWARF version supported by this parser
|
||||
"""
|
||||
return 2 <= version <= 5
|
||||
|
||||
def _parse_line_program_at_offset(self, offset, structs):
|
||||
""" 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]
|
||||
|
||||
lineprog_header = struct_parse(
|
||||
structs.Dwarf_lineprog_header,
|
||||
self.debug_line_sec.stream,
|
||||
offset)
|
||||
|
||||
# DWARF5: resolve names
|
||||
def resolve_strings(self, lineprog_header, format_field, data_field):
|
||||
if lineprog_header.get(format_field, False):
|
||||
data = lineprog_header[data_field]
|
||||
for field in lineprog_header[format_field]:
|
||||
def replace_value(data, content_type, replacer):
|
||||
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_fromtable)
|
||||
else:
|
||||
replace_value(data, field.content_type, lambda x: str(x))
|
||||
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(self, lineprog_header, 'directory_entry_format', 'directories')
|
||||
resolve_strings(self, 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):
|
||||
"""
|
||||
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
|
||||
|
||||
547
.venv/lib/python3.11/site-packages/elftools/dwarf/enums.py
Normal file
547
.venv/lib/python3.11/site-packages/elftools/dwarf/enums.py
Normal file
@@ -0,0 +1,547 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# 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
|
||||
)
|
||||
|
||||
ENUM_DW_LANG = dict(
|
||||
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,
|
||||
DW_LANG_lo_user = 0x8000,
|
||||
DW_LANG_hi_user = 0xffff
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
262
.venv/lib/python3.11/site-packages/elftools/dwarf/lineprogram.py
Normal file
262
.venv/lib/python3.11/site-packages/elftools/dwarf/lineprogram.py
Normal file
@@ -0,0 +1,262 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/lineprogram.py
|
||||
#
|
||||
# DWARF line number program
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
import copy
|
||||
from collections import namedtuple
|
||||
|
||||
from ..common.utils import struct_parse, dwarf_assert
|
||||
from .constants import *
|
||||
|
||||
|
||||
# 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.
|
||||
#
|
||||
LineProgramEntry = namedtuple(
|
||||
'LineProgramEntry', 'command is_extended args state')
|
||||
|
||||
|
||||
class LineState(object):
|
||||
""" 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):
|
||||
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):
|
||||
a = ['<LineState %x:' % id(self)]
|
||||
a.append(' address = 0x%x' % self.address)
|
||||
for attr in ('file', 'line', 'column', 'is_stmt', 'basic_block',
|
||||
'end_sequence', 'prologue_end', 'epilogue_begin', 'isa',
|
||||
'discriminator'):
|
||||
a.append(' %s = %s' % (attr, getattr(self, attr)))
|
||||
return '\n'.join(a) + '>\n'
|
||||
|
||||
|
||||
class LineProgram(object):
|
||||
""" 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, stream, structs,
|
||||
program_start_offset, program_end_offset):
|
||||
"""
|
||||
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
|
||||
self._decoded_entries = None
|
||||
|
||||
def get_entries(self):
|
||||
""" 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.
|
||||
"""
|
||||
if self._decoded_entries is None:
|
||||
self._decoded_entries = self._decode_line_program()
|
||||
return self._decoded_entries
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _decode_line_program(self):
|
||||
entries = []
|
||||
state = LineState(self.header['default_is_stmt'])
|
||||
|
||||
def add_entry_new_state(cmd, args, is_extended=False):
|
||||
# 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, args, is_extended=False):
|
||||
# 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 = 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 = self['maximum_operations_per_instruction']
|
||||
adjusted_opcode = opcode - self['opcode_base']
|
||||
operation_advance = adjusted_opcode // self['line_range']
|
||||
address_addend = (
|
||||
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 = 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 = struct_parse(self.structs.the_Dwarf_uleb128,
|
||||
self.stream)
|
||||
ex_opcode = 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 = 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,361 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/locationlists.py
|
||||
#
|
||||
# DWARF location lists section decoding (.debug_loc)
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from ..common.exceptions import DWARFError
|
||||
from ..common.utils import struct_parse
|
||||
from .dwarf_util import _iter_CUs_in_section
|
||||
|
||||
LocationExpr = namedtuple('LocationExpr', 'loc_expr')
|
||||
LocationEntry = namedtuple('LocationEntry', 'entry_offset entry_length begin_offset end_offset loc_expr is_absolute')
|
||||
BaseAddressEntry = namedtuple('BaseAddressEntry', 'entry_offset entry_length base_address')
|
||||
LocationViewPair = namedtuple('LocationViewPair', 'entry_offset begin end')
|
||||
|
||||
def _translate_startx_length(e, cu):
|
||||
start_offset = 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 = {
|
||||
'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)),
|
||||
'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),
|
||||
'DW_LLE_startx_length' : _translate_startx_length
|
||||
}
|
||||
|
||||
class LocationListsPair(object):
|
||||
"""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, streamv5, structs, dwarfinfo=None):
|
||||
self._loc = LocationLists(streamv4, structs, 4, dwarfinfo)
|
||||
self._loclists = LocationLists(streamv5, structs, 5, dwarfinfo)
|
||||
|
||||
def get_location_list_at_offset(self, offset, die=None):
|
||||
"""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):
|
||||
"""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):
|
||||
"""See LocationLists.iter_CUs()
|
||||
|
||||
There are no CUs in DWARFv4 sections.
|
||||
"""
|
||||
raise DWARFError("Iterating through two sections is not supported")
|
||||
|
||||
class LocationLists(object):
|
||||
""" 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, structs, version=4, dwarfinfo=None):
|
||||
self.stream = stream
|
||||
self.structs = structs
|
||||
self.dwarfinfo = dwarfinfo
|
||||
self.version = version
|
||||
self._max_addr = 2 ** (self.structs.address_size * 8) - 1
|
||||
|
||||
def get_location_list_at_offset(self, offset, die=None):
|
||||
""" 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.
|
||||
"""
|
||||
if self.version >= 5 and die is None:
|
||||
raise DWARFError("For this binary, \"die\" needs to be provided")
|
||||
self.stream.seek(offset, os.SEEK_SET)
|
||||
return self._parse_location_list_from_stream_v5(die.cu) if self.version >= 5 else self._parse_location_list_from_stream()
|
||||
|
||||
def iter_location_lists(self):
|
||||
""" 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
|
||||
for cu in self.dwarfinfo.iter_CUs():
|
||||
cu_ver = 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 = die.attributes['DW_AT_GNU_locviews'].value
|
||||
list_offset = 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
|
||||
all_offsets = list(all_offsets)
|
||||
all_offsets.sort()
|
||||
|
||||
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 = 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 = all_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 all_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):
|
||||
"""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")
|
||||
|
||||
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):
|
||||
lst = []
|
||||
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
|
||||
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 = struct_parse(
|
||||
self.structs.the_Dwarf_uint16, self.stream)
|
||||
loc_expr = [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=None):
|
||||
""" 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)
|
||||
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, die):
|
||||
off = entry.entry_offset
|
||||
len = entry.entry_end_offset - off
|
||||
type = 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):
|
||||
stream = self.stream
|
||||
list_offset = locviews.get(stream.tell(), None)
|
||||
pairs = []
|
||||
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(object):
|
||||
""" 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):
|
||||
self.location_lists = location_lists
|
||||
|
||||
@staticmethod
|
||||
def attribute_has_location(attr, dwarf_version):
|
||||
""" 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, dwarf_version, die = None):
|
||||
""" 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):
|
||||
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.
|
||||
else:
|
||||
raise ValueError("Attribute does not have location information")
|
||||
|
||||
#------ PRIVATE ------#
|
||||
|
||||
@staticmethod
|
||||
def _attribute_has_loc_expr(attr, dwarf_version):
|
||||
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, dwarf_version):
|
||||
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, dwarf_version):
|
||||
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):
|
||||
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'))
|
||||
198
.venv/lib/python3.11/site-packages/elftools/dwarf/namelut.py
Normal file
198
.venv/lib/python3.11/site-packages/elftools/dwarf/namelut.py
Normal file
@@ -0,0 +1,198 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# 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
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
import collections
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
from ..common.utils import struct_parse
|
||||
from bisect import bisect_right
|
||||
import math
|
||||
from ..construct import CString, Struct, If
|
||||
|
||||
NameLUTEntry = collections.namedtuple('NameLUTEntry', 'cu_ofs die_ofs')
|
||||
|
||||
class NameLUT(Mapping):
|
||||
"""
|
||||
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, size, structs):
|
||||
|
||||
self._stream = stream
|
||||
self._size = size
|
||||
self._structs = structs
|
||||
# entries are lazily loaded on demand.
|
||||
self._entries = None
|
||||
# CU headers (for readelf).
|
||||
self._cu_headers = None
|
||||
|
||||
def get_entries(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return self._entries
|
||||
|
||||
def set_entries(self, entries, cu_headers):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns the number of entries in the NameLUT.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return len(self._entries)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""
|
||||
Returns a namedtuple - NameLUTEntry(cu_ofs, die_ofs) - that corresponds
|
||||
to the given symbol name.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return self._entries.get(name)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Returns an iterator to the NameLUT dictionary.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return iter(self._entries)
|
||||
|
||||
def items(self):
|
||||
"""
|
||||
Returns the NameLUT dictionary items.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return self._entries.items()
|
||||
|
||||
def get(self, name, default=None):
|
||||
"""
|
||||
Returns NameLUTEntry(cu_ofs, die_ofs) for the provided symbol name or
|
||||
None if the symbol does not exist in the corresponding section.
|
||||
"""
|
||||
if self._entries is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
return self._entries.get(name, default)
|
||||
|
||||
def get_cu_headers(self):
|
||||
"""
|
||||
Returns all CU headers. Mainly required for readelf.
|
||||
"""
|
||||
if self._cu_headers is None:
|
||||
self._entries, self._cu_headers = self._get_entries()
|
||||
|
||||
return self._cu_headers
|
||||
|
||||
def _get_entries(self):
|
||||
"""
|
||||
Parse the (name, cu_ofs, die_ofs) information from this section and
|
||||
store as a dictionary.
|
||||
"""
|
||||
|
||||
self._stream.seek(0)
|
||||
entries = OrderedDict()
|
||||
cu_headers = []
|
||||
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)
|
||||
199
.venv/lib/python3.11/site-packages/elftools/dwarf/ranges.py
Normal file
199
.venv/lib/python3.11/site-packages/elftools/dwarf/ranges.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/ranges.py
|
||||
#
|
||||
# DWARF ranges section decoding (.debug_ranges)
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import os
|
||||
from collections import namedtuple
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..common.exceptions import DWARFError
|
||||
from .dwarf_util import _iter_CUs_in_section
|
||||
|
||||
|
||||
RangeEntry = namedtuple('RangeEntry', 'entry_offset entry_length begin_offset end_offset is_absolute')
|
||||
BaseAddressEntry = namedtuple('BaseAddressEntry', 'entry_offset base_address')
|
||||
# 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, cu):
|
||||
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 = {
|
||||
'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)),
|
||||
'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),
|
||||
'DW_RLE_startx_length': _translate_startx_length
|
||||
}
|
||||
|
||||
class RangeListsPair(object):
|
||||
"""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, streamv5, structs, dwarfinfo=None):
|
||||
self._ranges = RangeLists(streamv4, structs, 4, dwarfinfo)
|
||||
self._rnglists = RangeLists(streamv5, structs, 5, dwarfinfo)
|
||||
|
||||
def get_range_list_at_offset(self, offset, cu=None):
|
||||
"""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):
|
||||
"""Gets an untranslated v5 rangelist from the v5 section.
|
||||
"""
|
||||
return self._rnglists.get_range_list_at_offset_ex(offset)
|
||||
|
||||
def iter_range_lists(self):
|
||||
"""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):
|
||||
"""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):
|
||||
"""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, cu):
|
||||
"""Forwards a V5 entry translation request to the V5 section
|
||||
"""
|
||||
return self._rnglists.translate_v5_entry(entry, cu)
|
||||
|
||||
class RangeLists(object):
|
||||
""" 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, structs, version, dwarfinfo):
|
||||
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, cu=None):
|
||||
""" 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):
|
||||
"""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):
|
||||
""" 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()
|
||||
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):
|
||||
"""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")
|
||||
|
||||
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):
|
||||
"""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, cu):
|
||||
"""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):
|
||||
if self.version >= 5:
|
||||
return list(entry_translate[entry.entry_type](entry, cu)
|
||||
for entry
|
||||
in struct_parse(self.structs.Dwarf_rnglists_entries, self.stream))
|
||||
else:
|
||||
lst = []
|
||||
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
|
||||
577
.venv/lib/python3.11/site-packages/elftools/dwarf/structs.py
Normal file
577
.venv/lib/python3.11/site-packages/elftools/dwarf/structs.py
Normal file
@@ -0,0 +1,577 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# 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 logging.config import valid_ident
|
||||
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, Sequence,
|
||||
String, Switch, Value
|
||||
)
|
||||
from ..common.construct_utils import (RepeatUntilExcluding, ULEB128, SLEB128,
|
||||
StreamOffset, ULInt24, UBInt24)
|
||||
from .enums import *
|
||||
|
||||
|
||||
class DWARFStructs(object):
|
||||
""" 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.
|
||||
"""
|
||||
|
||||
# Cache for structs instances based on creation parameters. Structs
|
||||
# initialization is expensive and we don't won't to repeat it
|
||||
# unnecessarily.
|
||||
_structs_cache = {}
|
||||
|
||||
def __new__(cls, little_endian, dwarf_format, address_size, dwarf_version=2):
|
||||
""" 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):
|
||||
""" Size of an initial length field.
|
||||
"""
|
||||
return 4 if self.dwarf_format == 32 else 12
|
||||
|
||||
def _create_structs(self):
|
||||
if self.little_endian:
|
||||
self.Dwarf_uint8 = ULInt8
|
||||
self.Dwarf_uint16 = ULInt16
|
||||
self.Dwarf_uint24 = 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):
|
||||
def _InitialLength(name):
|
||||
# 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):
|
||||
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):
|
||||
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'), **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):
|
||||
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):
|
||||
self.Dwarf_abbrev_declaration = Struct('Dwarf_abbrev_entry',
|
||||
Enum(self.Dwarf_uleb128('tag'), **ENUM_DW_TAG),
|
||||
Enum(self.Dwarf_uint8('children_flag'), **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'), **ENUM_DW_AT),
|
||||
Enum(self.Dwarf_uleb128('form'), **ENUM_DW_FORM),
|
||||
If(lambda ctx: ctx['form'] == 'DW_FORM_implicit_const',
|
||||
self.Dwarf_sleb128('value')))))
|
||||
|
||||
def _create_debugsup(self):
|
||||
# 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):
|
||||
self.Dwarf_debugaltlink = Struct('Elf_debugaltlink',
|
||||
CString("sup_filename"),
|
||||
String("sup_checksum", length=20))
|
||||
|
||||
def _create_dw_form(self):
|
||||
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_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 resovle
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
# 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: len(ctx.name) != 0,
|
||||
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, structs, format_field):
|
||||
Construct.__init__(self, name)
|
||||
self.structs = structs
|
||||
self.format_field = format_field
|
||||
|
||||
def _parse(self, stream, context):
|
||||
# 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])
|
||||
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'), **ENUM_DW_LNCT),
|
||||
Enum(self.Dwarf_uleb128('form'), **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'), **ENUM_DW_LNCT),
|
||||
Enum(self.Dwarf_uleb128('form'), **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: len(obj.name) == 0,
|
||||
self.Dwarf_lineprog_file_entry)) # array name is file_entry
|
||||
)
|
||||
|
||||
def _create_callframe_entry_headers(self):
|
||||
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):
|
||||
""" Create a struct for DW_FORM_block<size>
|
||||
"""
|
||||
return PrefixedArray(
|
||||
subcon=self.Dwarf_uint8('elem'),
|
||||
length_field=length_field(''))
|
||||
|
||||
def _create_loclists_parsers(self):
|
||||
""" 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'), **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):
|
||||
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'), **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, context):
|
||||
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))
|
||||
259
.venv/lib/python3.11/site-packages/elftools/dwarf/typeunit.py
Normal file
259
.venv/lib/python3.11/site-packages/elftools/dwarf/typeunit.py
Normal file
@@ -0,0 +1,259 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: dwarf/typeunit.py
|
||||
#
|
||||
# DWARF type unit
|
||||
#
|
||||
# Dinkar Khandalekar (contact@dinkar.dev)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from bisect import bisect_right
|
||||
from .die import DIE
|
||||
from ..common.utils import dwarf_assert
|
||||
|
||||
|
||||
class TypeUnit(object):
|
||||
""" 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, dwarfinfo, structs, tu_offset, tu_die_offset):
|
||||
""" 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
|
||||
|
||||
# The abbreviation table for this TU. Filled lazily when DIEs are
|
||||
# requested.
|
||||
self._abbrev_table = None
|
||||
|
||||
# A list of DIEs belonging to this TU.
|
||||
# This list is lazily constructed as DIEs are iterated over.
|
||||
self._dielist = []
|
||||
# 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 = []
|
||||
|
||||
@property
|
||||
def cu_offset(self):
|
||||
"""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):
|
||||
"""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):
|
||||
""" Get the DWARF format (32 or 64) for this TU
|
||||
"""
|
||||
return self.structs.dwarf_format
|
||||
|
||||
def get_abbrev_table(self):
|
||||
""" Get the abbreviation table (AbbrevTable object) for this TU
|
||||
"""
|
||||
if self._abbrev_table is None:
|
||||
self._abbrev_table = self.dwarfinfo.get_abbrev_table(
|
||||
self['debug_abbrev_offset'])
|
||||
return self._abbrev_table
|
||||
|
||||
def get_top_DIE(self):
|
||||
""" 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 len(self._diemap) > 0:
|
||||
return self._dielist[0]
|
||||
|
||||
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):
|
||||
""" Returns whether the top DIE in this TU has already been parsed and cached.
|
||||
No parsing on demand!
|
||||
"""
|
||||
return len(self._diemap) > 0
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self['unit_length'] + self.structs.initial_length_field_size()
|
||||
|
||||
def iter_DIEs(self):
|
||||
""" 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):
|
||||
""" 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
|
||||
|
||||
cur_offset = child._terminator.offset + child._terminator.size
|
||||
|
||||
def get_DIE_from_refaddr(self, refaddr):
|
||||
""" 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):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _iter_DIE_subtree(self, 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():
|
||||
for d in die.cu._iter_DIE_subtree(c):
|
||||
yield d
|
||||
yield die._terminator
|
||||
|
||||
def _get_cached_DIE(self, offset):
|
||||
""" 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
|
||||
@@ -0,0 +1 @@
|
||||
EHABI_INDEX_ENTRY_SIZE = 8
|
||||
284
.venv/lib/python3.11/site-packages/elftools/ehabi/decoder.py
Normal file
284
.venv/lib/python3.11/site-packages/elftools/ehabi/decoder.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# -------------------------------------------------------------------------------
|
||||
# elftools: ehabi/decoder.py
|
||||
#
|
||||
# Decode ARM exception handler bytecode.
|
||||
#
|
||||
# LeadroyaL (leadroyal@qq.com)
|
||||
# This code is in the public domain
|
||||
# -------------------------------------------------------------------------------
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class EHABIBytecodeDecoder(object):
|
||||
""" 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):
|
||||
self._bytecode_array = bytecode_array
|
||||
self._index = None
|
||||
self.mnemonic_array = None
|
||||
self._decode()
|
||||
|
||||
def _decode(self):
|
||||
""" 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):
|
||||
# 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):
|
||||
# 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, count):
|
||||
return ((1 << (count + 1)) - 1) << start
|
||||
|
||||
def _printGPR(self, gpr_mask):
|
||||
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, prefix):
|
||||
hits = [prefix + str(i) for i in range(32) if vfp_mask & (1 << i) != 0]
|
||||
return '{%s}' % ', '.join(hits)
|
||||
|
||||
def _decode_1000iiii_iiiiiiii(self):
|
||||
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):
|
||||
self._index += 1
|
||||
return 'reserved (ARM MOVrr)'
|
||||
|
||||
def _decode_10011111(self):
|
||||
self._index += 1
|
||||
return 'reserved (WiMMX MOVrr)'
|
||||
|
||||
def _decode_1001nnnn(self):
|
||||
# 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):
|
||||
# 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):
|
||||
# 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):
|
||||
# SW.startLine() << format("0x%02X ; finish\n", Opcode);
|
||||
self._index += 1
|
||||
return 'finish'
|
||||
|
||||
def _decode_10110001_0000iiii(self):
|
||||
# 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):
|
||||
# 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):
|
||||
# these two decoders are equal
|
||||
return self._decode_11001001_sssscccc()
|
||||
|
||||
def _decode_101101nn(self):
|
||||
return self._spare()
|
||||
|
||||
def _decode_10111nnn(self):
|
||||
# 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):
|
||||
# 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):
|
||||
# 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):
|
||||
# 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):
|
||||
# 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):
|
||||
return self._spare()
|
||||
|
||||
def _decode_11000nnn(self):
|
||||
# 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):
|
||||
# these two decoders are equal
|
||||
return self._decode_10111nnn()
|
||||
|
||||
def _decode_11xxxyyy(self):
|
||||
return self._spare()
|
||||
|
||||
def _spare(self):
|
||||
self._index += 1
|
||||
return 'spare'
|
||||
|
||||
_DECODE_RECIPE_TYPE = namedtuple('_DECODE_RECIPE_TYPE', 'mask value handler')
|
||||
|
||||
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(object):
|
||||
""" Single mnemonic item.
|
||||
"""
|
||||
|
||||
def __init__(self, bytecode, mnemonic):
|
||||
self.bytecode = bytecode
|
||||
self.mnemonic = mnemonic
|
||||
|
||||
def __repr__(self):
|
||||
return '%s ; %s' % (' '.join(['0x%02x' % x for x in self.bytecode]), self.mnemonic)
|
||||
209
.venv/lib/python3.11/site-packages/elftools/ehabi/ehabiinfo.py
Normal file
209
.venv/lib/python3.11/site-packages/elftools/ehabi/ehabiinfo.py
Normal file
@@ -0,0 +1,209 @@
|
||||
# -------------------------------------------------------------------------------
|
||||
# elftools: ehabi/ehabiinfo.py
|
||||
#
|
||||
# Decoder for ARM exception handler bytecode.
|
||||
#
|
||||
# LeadroyaL (leadroyal@qq.com)
|
||||
# This code is in the public domain
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
|
||||
from .decoder import EHABIBytecodeDecoder
|
||||
from .constants import EHABI_INDEX_ENTRY_SIZE
|
||||
from .structs import EHABIStructs
|
||||
|
||||
|
||||
class EHABIInfo(object):
|
||||
""" 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, little_endian):
|
||||
self._arm_idx_section = arm_idx_section
|
||||
self._struct = EHABIStructs(little_endian)
|
||||
self._num_entry = None
|
||||
|
||||
def section_name(self):
|
||||
return self._arm_idx_section.name
|
||||
|
||||
def section_offset(self):
|
||||
return self._arm_idx_section['sh_offset']
|
||||
|
||||
def num_entry(self):
|
||||
""" Number of exception handler entry in the section.
|
||||
"""
|
||||
if self._num_entry is None:
|
||||
self._num_entry = self._arm_idx_section['sh_size'] // EHABI_INDEX_ENTRY_SIZE
|
||||
return self._num_entry
|
||||
|
||||
def get_entry(self, n):
|
||||
""" 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(object):
|
||||
""" 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,
|
||||
personality,
|
||||
bytecode_array,
|
||||
eh_table_offset=None,
|
||||
unwindable=True,
|
||||
corrupt=False):
|
||||
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):
|
||||
if self.bytecode_array:
|
||||
return EHABIBytecodeDecoder(self.bytecode_array).mnemonic_array
|
||||
else:
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return "<EHABIEntry function_offset=0x%x, personality=%d, %sbytecode=%s>" % (
|
||||
self.function_offset,
|
||||
self.personality,
|
||||
"eh_table_offset=0x%x, " % self.eh_table_offset if self.eh_table_offset else "",
|
||||
self.bytecode_array)
|
||||
|
||||
|
||||
class CorruptEHABIEntry(EHABIEntry):
|
||||
""" This entry is corrupt. Attribute #corrupt will be True.
|
||||
"""
|
||||
|
||||
def __init__(self, reason):
|
||||
super(CorruptEHABIEntry, self).__init__(function_offset=None, personality=None, bytecode_array=None,
|
||||
corrupt=True)
|
||||
self.reason = reason
|
||||
|
||||
def __repr__(self):
|
||||
return "<CorruptEHABIEntry reason=%s>" % self.reason
|
||||
|
||||
|
||||
class CannotUnwindEHABIEntry(EHABIEntry):
|
||||
""" This function cannot be unwind. Attribute #unwindable will be False.
|
||||
"""
|
||||
|
||||
def __init__(self, function_offset):
|
||||
super(CannotUnwindEHABIEntry, self).__init__(function_offset, personality=None, bytecode_array=None,
|
||||
unwindable=False)
|
||||
|
||||
def __repr__(self):
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, function_offset, personality):
|
||||
super(GenericEHABIEntry, self).__init__(function_offset, personality, bytecode_array=None)
|
||||
|
||||
def __repr__(self):
|
||||
return "<GenericEHABIEntry function_offset=0x%x, personality=0x%x>" % (self.function_offset, self.personality)
|
||||
|
||||
|
||||
def arm_expand_prel31(address, place):
|
||||
"""
|
||||
address: uint32
|
||||
place: uint32
|
||||
return: uint64
|
||||
"""
|
||||
location = address & 0x7fffffff
|
||||
if location & 0x04000000:
|
||||
location |= 0xffffffff80000000
|
||||
return location + place & 0xffffffffffffffff
|
||||
47
.venv/lib/python3.11/site-packages/elftools/ehabi/structs.py
Normal file
47
.venv/lib/python3.11/site-packages/elftools/ehabi/structs.py
Normal file
@@ -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(object):
|
||||
""" 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):
|
||||
self._little_endian = little_endian
|
||||
self._create_structs()
|
||||
|
||||
def _create_structs(self):
|
||||
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):
|
||||
self.EH_index_struct = Struct(
|
||||
'EH_index',
|
||||
self.EHABI_uint32('word0'),
|
||||
self.EHABI_uint32('word1')
|
||||
)
|
||||
|
||||
def _create_exception_handler_table(self):
|
||||
self.EH_table_struct = Struct(
|
||||
'EH_table',
|
||||
self.EHABI_uint32('word0'),
|
||||
)
|
||||
176
.venv/lib/python3.11/site-packages/elftools/elf/constants.py
Normal file
176
.venv/lib/python3.11/site-packages/elftools/elf/constants.py
Normal file
@@ -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(object):
|
||||
""" 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(object):
|
||||
"""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(object):
|
||||
""" 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(object):
|
||||
""" 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(object):
|
||||
""" 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(object):
|
||||
""" 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(object):
|
||||
""" 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(object):
|
||||
VER_FLG_BASE=0x1
|
||||
VER_FLG_WEAK=0x2
|
||||
VER_FLG_INFO=0x4
|
||||
1068
.venv/lib/python3.11/site-packages/elftools/elf/descriptions.py
Normal file
1068
.venv/lib/python3.11/site-packages/elftools/elf/descriptions.py
Normal file
File diff suppressed because it is too large
Load Diff
358
.venv/lib/python3.11/site-packages/elftools/elf/dynamic.py
Normal file
358
.venv/lib/python3.11/site-packages/elftools/elf/dynamic.py
Normal file
@@ -0,0 +1,358 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/dynamic.py
|
||||
#
|
||||
# ELF Dynamic Tags
|
||||
#
|
||||
# Mike Frysinger (vapier@gentoo.org)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import itertools
|
||||
|
||||
from collections import defaultdict
|
||||
from .hash import ELFHashTable, GNUHashTable
|
||||
from .sections import Section, Symbol
|
||||
from .enums import ENUM_D_TAG
|
||||
from .segments import Segment
|
||||
from .relocation import RelocationTable, RelrRelocationTable
|
||||
from ..common.exceptions import ELFError
|
||||
from ..common.utils import elf_assert, struct_parse, parse_cstring_from_stream
|
||||
|
||||
|
||||
class _DynamicStringTable(object):
|
||||
""" 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, table_offset):
|
||||
self._stream = stream
|
||||
self._table_offset = table_offset
|
||||
|
||||
def get_string(self, offset):
|
||||
""" 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(object):
|
||||
""" 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, stringtable):
|
||||
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):
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
def __repr__(self):
|
||||
return '<DynamicTag (%s): %r>' % (self.entry.d_tag, self.entry)
|
||||
|
||||
def __str__(self):
|
||||
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(object):
|
||||
""" Shared functionality between dynamic sections and segments.
|
||||
"""
|
||||
def __init__(self, stream, elffile, stringtable, position, empty):
|
||||
"""
|
||||
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
|
||||
|
||||
def get_table_offset(self, tag_name):
|
||||
""" Return the virtual address and file offset of a dynamic table.
|
||||
"""
|
||||
ptr = None
|
||||
for tag in self._iter_tags(type=tag_name):
|
||||
ptr = tag['d_ptr']
|
||||
break
|
||||
|
||||
# If we found a virtual address, locate the offset in the file
|
||||
# by using the program headers.
|
||||
offset = None
|
||||
if ptr:
|
||||
offset = next(self.elffile.address_offsets(ptr), None)
|
||||
|
||||
return ptr, offset
|
||||
|
||||
def _get_stringtable(self):
|
||||
""" 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:
|
||||
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)
|
||||
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')
|
||||
return self._stringtable
|
||||
|
||||
def _iter_tags(self, type=None):
|
||||
""" 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=None):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" Get the tag at index #n from the file (DynamicTag object)
|
||||
"""
|
||||
return DynamicTag(self._get_tag(n), self._get_stringtable())
|
||||
|
||||
def num_tags(self):
|
||||
""" 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
|
||||
|
||||
def get_relocation_tables(self):
|
||||
""" Load all available relocation tables from DYNAMIC tags.
|
||||
|
||||
Returns a dictionary mapping found table types (REL, RELA,
|
||||
RELR, JMPREL) to RelocationTable objects.
|
||||
"""
|
||||
|
||||
result = {}
|
||||
|
||||
if list(self.iter_tags('DT_REL')):
|
||||
result['REL'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_REL')[1],
|
||||
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],
|
||||
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],
|
||||
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],
|
||||
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, name, elffile):
|
||||
Section.__init__(self, header, name, elffile)
|
||||
stringtable = elffile.get_section(header['sh_link'], ('SHT_STRTAB', 'SHT_NOBITS'))
|
||||
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, stream, elffile):
|
||||
# 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 = None
|
||||
for section in elffile.iter_sections():
|
||||
if (isinstance(section, DynamicSection) and
|
||||
section['sh_offset'] == header['p_offset']):
|
||||
stringtable = elffile.get_section(section['sh_link'])
|
||||
break
|
||||
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()
|
||||
self._num_symbols = None
|
||||
self._symbol_name_map = None
|
||||
|
||||
def num_symbols(self):
|
||||
""" Number of symbols in the table recovered from DT_SYMTAB
|
||||
"""
|
||||
if self._num_symbols is not None:
|
||||
return self._num_symbols
|
||||
|
||||
# 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:
|
||||
hash_section = GNUHashTable(self.elffile, gnu_hash_offset, self)
|
||||
self._num_symbols = hash_section.get_number_of_symbols()
|
||||
|
||||
# If DT_GNU_HASH did not exist, maybe we can use DT_HASH
|
||||
if self._num_symbols is None:
|
||||
_, 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, self)
|
||||
self._num_symbols = hash_section.get_number_of_symbols()
|
||||
|
||||
if self._num_symbols is None:
|
||||
# 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 = 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 None:
|
||||
# Use the end of segment that contains DT_SYMTAB.
|
||||
for segment in self.elffile.iter_segments():
|
||||
if (segment['p_vaddr'] <= tab_ptr and
|
||||
tab_ptr <= (segment['p_vaddr'] + segment['p_filesz'])):
|
||||
nearest_ptr = segment['p_vaddr'] + segment['p_filesz']
|
||||
|
||||
end_ptr = nearest_ptr
|
||||
self._num_symbols = (end_ptr - tab_ptr) // self._symbol_size
|
||||
|
||||
if self._num_symbols is None:
|
||||
raise ELFError('Cannot determine the end of DT_SYMTAB.')
|
||||
|
||||
return self._num_symbols
|
||||
|
||||
def get_symbol(self, index):
|
||||
""" 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):
|
||||
""" Get a symbol(s) by name. Return None if no symbol by the given name
|
||||
exists.
|
||||
"""
|
||||
# The first time this method is called, construct a name to number
|
||||
# mapping
|
||||
#
|
||||
if self._symbol_name_map is None:
|
||||
self._symbol_name_map = defaultdict(list)
|
||||
for i, sym in enumerate(self.iter_symbols()):
|
||||
self._symbol_name_map[sym.name].append(i)
|
||||
symnums = self._symbol_name_map.get(name)
|
||||
return [self.get_symbol(i) for i in symnums] if symnums else None
|
||||
|
||||
def iter_symbols(self):
|
||||
""" 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))
|
||||
931
.venv/lib/python3.11/site-packages/elftools/elf/elffile.py
Normal file
931
.venv/lib/python3.11/site-packages/elftools/elf/elffile.py
Normal file
@@ -0,0 +1,931 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/elffile.py
|
||||
#
|
||||
# ELFFile - main class for accessing ELF files
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
import io
|
||||
from io import BytesIO
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
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
|
||||
|
||||
class ELFFile(object):
|
||||
""" 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 file 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, stream_loader=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._section_header_stringtable = \
|
||||
self._get_section_header_stringtable()
|
||||
self._section_name_map = None
|
||||
self.stream_loader = stream_loader
|
||||
|
||||
@classmethod
|
||||
def load_from_path(cls, path):
|
||||
"""Takes a path to a file on the local filesystem, and returns an
|
||||
ELFFile from it, setting up a correct stream_loader relative to the
|
||||
original file.
|
||||
"""
|
||||
stream = open(path, 'rb')
|
||||
return ELFFile(stream, ELFFile.make_relative_loader(path))
|
||||
|
||||
@staticmethod
|
||||
def make_relative_loader(base_path):
|
||||
""" Return a function that takes a potentially relative path,
|
||||
resolves it against base_path (bytes or str), and opens a file at that.
|
||||
|
||||
ELFFile uses functions like that for resolving DWARF links.
|
||||
"""
|
||||
if isinstance(base_path, str):
|
||||
base_path = base_path.encode('UTF-8') # resolver takes a bytes path
|
||||
base_directory = os.path.dirname(base_path)
|
||||
def loader(rel_path):
|
||||
if not os.path.isabs(rel_path):
|
||||
rel_path = os.path.join(base_directory, rel_path)
|
||||
return open(rel_path, 'rb')
|
||||
return loader
|
||||
|
||||
def num_sections(self):
|
||||
""" 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:
|
||||
return self._get_section_header(0)['sh_size']
|
||||
return self['e_shnum']
|
||||
|
||||
def get_section(self, n, type=None):
|
||||
""" 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):
|
||||
""" 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']))
|
||||
return self._make_section(section_header)
|
||||
|
||||
def _get_linked_strtab_section(self, n):
|
||||
""" 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']))
|
||||
return self._make_section(section_header)
|
||||
|
||||
def get_section_by_name(self, name):
|
||||
""" Get a section from the file, by name. Return None if no such
|
||||
section exists.
|
||||
"""
|
||||
# The first time this method is called, construct a name to number
|
||||
# mapping
|
||||
#
|
||||
if self._section_name_map is None:
|
||||
self._make_section_name_map()
|
||||
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):
|
||||
""" Gets the index of the section by name. Return None if no such
|
||||
section name exists.
|
||||
"""
|
||||
# The first time this method is called, construct a name to number
|
||||
# mapping
|
||||
#
|
||||
if self._section_name_map is None:
|
||||
self._make_section_name_map()
|
||||
return self._section_name_map.get(section_name, None)
|
||||
|
||||
def has_section(self, section_name):
|
||||
""" Section existence check by name, without the overhead of parsing if found.
|
||||
"""
|
||||
if self._section_name_map is None:
|
||||
self._make_section_name_map()
|
||||
return section_name in self._section_name_map
|
||||
|
||||
def iter_sections(self, type=None):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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=None):
|
||||
""" 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, size=1):
|
||||
""" 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=False):
|
||||
""" 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=True, follow_links=True):
|
||||
""" 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(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 = tuple(map(lambda x: '.z' + x[1:], section_names))
|
||||
|
||||
# As it is loaded in the process image, .eh_frame cannot be compressed
|
||||
section_names += ('.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 = {}
|
||||
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):
|
||||
""" 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):
|
||||
""" 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):
|
||||
"""
|
||||
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(supfilepath)
|
||||
supelffile = ELFFile(stream)
|
||||
dwarf_info = supelffile.get_dwarf_info()
|
||||
stream.close()
|
||||
return dwarf_info
|
||||
return None
|
||||
|
||||
|
||||
def has_ehabi_info(self):
|
||||
""" 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):
|
||||
""" 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.
|
||||
"""
|
||||
_ret = []
|
||||
if self['e_type'] == 'ET_REL':
|
||||
# TODO: support relocatable file
|
||||
assert False, "Current version of pyelftools doesn't support relocatable file."
|
||||
for section in self.iter_sections(type='SHT_ARM_EXIDX'):
|
||||
_ret.append(EHABIInfo(section, self.little_endian))
|
||||
return _ret if len(_ret) > 0 else None
|
||||
|
||||
def get_machine_arch(self):
|
||||
""" 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):
|
||||
""" 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:
|
||||
return self._get_section_header(0)['sh_link']
|
||||
|
||||
#-------------------------------- PRIVATE --------------------------------#
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _identify_file(self):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" Find the header of section #n, parse it and return the struct
|
||||
"""
|
||||
|
||||
stream_pos = self._section_offset(n)
|
||||
if stream_pos > self.stream_len:
|
||||
return None
|
||||
|
||||
return struct_parse(
|
||||
self.structs.Elf_Shdr,
|
||||
self.stream,
|
||||
stream_pos=stream_pos)
|
||||
|
||||
def _get_section_name(self, section_header):
|
||||
""" Given a section header, find this section's name in the file's
|
||||
string table
|
||||
"""
|
||||
if self._section_header_stringtable is None:
|
||||
raise ELFParseError("String Table not found")
|
||||
|
||||
name_offset = section_header['sh_name']
|
||||
return self._section_header_stringtable.get_string(name_offset)
|
||||
|
||||
def _make_section(self, section_header):
|
||||
""" 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)
|
||||
|
||||
def _make_section_name_map(self):
|
||||
self._section_name_map = {}
|
||||
for i, sec in enumerate(self.iter_sections()):
|
||||
self._section_name_map[sec.name] = i
|
||||
|
||||
def _make_symbol_table_section(self, section_header, name):
|
||||
""" 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, name):
|
||||
""" 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, name):
|
||||
""" 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, name):
|
||||
""" 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, name):
|
||||
""" 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, name):
|
||||
""" Create a GNUVerSymSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self.get_section(linked_strtab_index)
|
||||
return GNUVerSymSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
symboltable=strtab_section)
|
||||
|
||||
def _make_elf_hash_section(self, section_header, name):
|
||||
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, name):
|
||||
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):
|
||||
""" 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))
|
||||
|
||||
def _get_section_header_stringtable(self):
|
||||
""" Get the string table section corresponding to the section header
|
||||
table.
|
||||
"""
|
||||
stringtable_section_num = self.get_shstrndx()
|
||||
|
||||
stringtable_section_header = self._get_section_header(stringtable_section_num)
|
||||
if stringtable_section_header is None:
|
||||
return None
|
||||
|
||||
return StringTableSection(
|
||||
header=stringtable_section_header,
|
||||
name='',
|
||||
elffile=self)
|
||||
|
||||
def _parse_elf_header(self):
|
||||
""" 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, relocate_dwarf_sections):
|
||||
""" 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):
|
||||
""" 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):
|
||||
self.stream.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.close()
|
||||
|
||||
def has_phantom_bytes(self):
|
||||
"""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
|
||||
1680
.venv/lib/python3.11/site-packages/elftools/elf/enums.py
Normal file
1680
.venv/lib/python3.11/site-packages/elftools/elf/enums.py
Normal file
File diff suppressed because it is too large
Load Diff
225
.venv/lib/python3.11/site-packages/elftools/elf/gnuversions.py
Normal file
225
.venv/lib/python3.11/site-packages/elftools/elf/gnuversions.py
Normal file
@@ -0,0 +1,225 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# elftools: elf/gnuversions.py
|
||||
#
|
||||
# ELF sections
|
||||
#
|
||||
# Yann Rouillard (yann@pleiades.fr.eu.org)
|
||||
# This code is in the public domain
|
||||
#------------------------------------------------------------------------------
|
||||
from ..construct import CString
|
||||
from ..common.utils import struct_parse, elf_assert
|
||||
from .sections import Section, Symbol
|
||||
|
||||
|
||||
class Version(object):
|
||||
""" 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, name=None):
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to entry
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class VersionAuxiliary(object):
|
||||
""" 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, name):
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" 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, name, elffile, stringtable,
|
||||
field_prefix, version_struct, version_auxiliaries_struct):
|
||||
super(GNUVersionSection, self).__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):
|
||||
""" Number of version entries in the section
|
||||
"""
|
||||
return self['sh_info']
|
||||
|
||||
def _field_name(self, name, auxiliary=False):
|
||||
""" 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, count):
|
||||
""" 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):
|
||||
""" 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, name, elffile, stringtable):
|
||||
super(GNUVerNeedSection, self).__init__(
|
||||
header, name, elffile, stringtable, 'vn',
|
||||
elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux)
|
||||
self._has_indexes = None
|
||||
|
||||
def has_indexes(self):
|
||||
""" 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
|
||||
"""
|
||||
if self._has_indexes is None:
|
||||
self._has_indexes = False
|
||||
for _, vernaux_iter in self.iter_versions():
|
||||
for vernaux in vernaux_iter:
|
||||
if vernaux['vna_other']:
|
||||
self._has_indexes = True
|
||||
break
|
||||
|
||||
return self._has_indexes
|
||||
|
||||
def iter_versions(self):
|
||||
for verneed, vernaux in super(GNUVerNeedSection, self).iter_versions():
|
||||
verneed.name = self.stringtable.get_string(verneed['vn_file'])
|
||||
yield verneed, vernaux
|
||||
|
||||
def get_version(self, index):
|
||||
""" 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, name, elffile, stringtable):
|
||||
super(GNUVerDefSection, self).__init__(
|
||||
header, name, elffile, stringtable, 'vd',
|
||||
elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux)
|
||||
|
||||
def get_version(self, index):
|
||||
""" 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, name, elffile, symboltable):
|
||||
super(GNUVerSymSection, self).__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def num_symbols(self):
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize']
|
||||
|
||||
def get_symbol(self, n):
|
||||
""" 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):
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
186
.venv/lib/python3.11/site-packages/elftools/elf/hash.py
Normal file
186
.venv/lib/python3.11/site-packages/elftools/elf/hash.py
Normal file
@@ -0,0 +1,186 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/hash.py
|
||||
#
|
||||
# ELF hash table sections
|
||||
#
|
||||
# Andreas Ziegler (andreas.ziegler@fau.de)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
import struct
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from .sections import Section
|
||||
|
||||
|
||||
class ELFHashTable(object):
|
||||
""" 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, start_offset, symboltable):
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
self.params = struct_parse(self.elffile.structs.Elf_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
|
||||
def get_number_of_symbols(self):
|
||||
""" Get the number of symbols from the hash table parameters.
|
||||
"""
|
||||
return self.params['nchains']
|
||||
|
||||
def get_symbol(self, name):
|
||||
""" 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.name == name:
|
||||
return sym
|
||||
symndx = self.params['chains'][symndx]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def elf_hash(name):
|
||||
""" 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, name, elffile, symboltable):
|
||||
Section.__init__(self, header, name, elffile)
|
||||
ELFHashTable.__init__(self, elffile, self['sh_offset'], symboltable)
|
||||
|
||||
|
||||
class GNUHashTable(object):
|
||||
""" 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, start_offset, symboltable):
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
self.params = struct_parse(self.elffile.structs.Gnu_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
# Element sizes in the hash table
|
||||
self._wordsize = self.elffile.structs.Elf_word('').sizeof()
|
||||
self._xwordsize = self.elffile.structs.Elf_xword('').sizeof()
|
||||
self._chain_pos = start_offset + 4 * self._wordsize + \
|
||||
self.params['bloom_size'] * self._xwordsize + \
|
||||
self.params['nbuckets'] * self._wordsize
|
||||
|
||||
def get_number_of_symbols(self):
|
||||
""" 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):
|
||||
""" 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):
|
||||
""" 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 name == symbol.name:
|
||||
return symbol
|
||||
|
||||
if cur_hash & 1:
|
||||
break
|
||||
symidx += 1
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def gnu_hash(key):
|
||||
""" 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, name, elffile, symboltable):
|
||||
Section.__init__(self, header, name, elffile)
|
||||
GNUHashTable.__init__(self, elffile, self['sh_offset'], symboltable)
|
||||
70
.venv/lib/python3.11/site-packages/elftools/elf/notes.py
Normal file
70
.venv/lib/python3.11/site-packages/elftools/elf/notes.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/notes.py
|
||||
#
|
||||
# ELF notes
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..common.utils import struct_parse, bytes2hex, roundup, bytes2str
|
||||
from ..construct import CString
|
||||
|
||||
|
||||
def iter_notes(elffile, offset, size):
|
||||
""" 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 = 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 = 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 = 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'] = bytes2hex(desc_data)
|
||||
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 = []
|
||||
# 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 = offset + note['n_descsz']
|
||||
while off < current_note_end:
|
||||
p = 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
|
||||
506
.venv/lib/python3.11/site-packages/elftools/elf/relocation.py
Normal file
506
.venv/lib/python3.11/site-packages/elftools/elf/relocation.py
Normal file
@@ -0,0 +1,506 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/relocation.py
|
||||
#
|
||||
# ELF relocations
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from collections import namedtuple
|
||||
|
||||
from ..common.exceptions import ELFRelocationError
|
||||
from ..common.utils import elf_assert, struct_parse
|
||||
from .sections import Section
|
||||
from .enums import (
|
||||
ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS,
|
||||
ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64, ENUM_RELOC_TYPE_PPC64,
|
||||
ENUM_RELOC_TYPE_S390X, ENUM_RELOC_TYPE_BPF, ENUM_RELOC_TYPE_LOONGARCH,
|
||||
ENUM_D_TAG)
|
||||
from ..construct import Container
|
||||
|
||||
|
||||
class Relocation(object):
|
||||
""" Relocation object - representing a single relocation entry. Allows
|
||||
dictionary-like access to the entry's fields.
|
||||
|
||||
Can be either a REL or RELA relocation.
|
||||
"""
|
||||
def __init__(self, entry, elffile):
|
||||
self.entry = entry
|
||||
self.elffile = elffile
|
||||
|
||||
def is_RELA(self):
|
||||
""" Is this a RELA relocation? If not, it's REL.
|
||||
"""
|
||||
return 'r_addend' in self.entry
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
def __repr__(self):
|
||||
return '<Relocation (%s): %s>' % (
|
||||
'RELA' if self.is_RELA() else 'REL',
|
||||
self.entry)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class RelocationTable(object):
|
||||
""" Shared functionality between relocation sections and relocation tables
|
||||
"""
|
||||
|
||||
def __init__(self, elffile, offset, size, is_rela):
|
||||
self._stream = elffile.stream
|
||||
self._elffile = elffile
|
||||
self._elfstructs = elffile.structs
|
||||
self._size = size
|
||||
self._offset = offset
|
||||
self._is_rela = is_rela
|
||||
|
||||
if is_rela:
|
||||
self.entry_struct = self._elfstructs.Elf_Rela
|
||||
else:
|
||||
self.entry_struct = self._elfstructs.Elf_Rel
|
||||
|
||||
self.entry_size = self.entry_struct.sizeof()
|
||||
|
||||
def is_RELA(self):
|
||||
""" Is this a RELA relocation section? If not, it's REL.
|
||||
"""
|
||||
return self._is_rela
|
||||
|
||||
def num_relocations(self):
|
||||
""" Number of relocations in the section
|
||||
"""
|
||||
return self._size // self.entry_size
|
||||
|
||||
def get_relocation(self, n):
|
||||
""" Get the relocation at index #n from the section (Relocation object)
|
||||
"""
|
||||
entry_offset = self._offset + n * self.entry_size
|
||||
entry = struct_parse(
|
||||
self.entry_struct,
|
||||
self._stream,
|
||||
stream_pos=entry_offset)
|
||||
return Relocation(entry, self._elffile)
|
||||
|
||||
def iter_relocations(self):
|
||||
""" Yield all the relocations in the section
|
||||
"""
|
||||
for i in range(self.num_relocations()):
|
||||
yield self.get_relocation(i)
|
||||
|
||||
|
||||
class RelocationSection(Section, RelocationTable):
|
||||
""" ELF relocation section. Serves as a collection of Relocation entries.
|
||||
"""
|
||||
def __init__(self, header, name, elffile):
|
||||
Section.__init__(self, header, name, elffile)
|
||||
RelocationTable.__init__(self, self.elffile,
|
||||
self['sh_offset'], self['sh_size'], header['sh_type'] == 'SHT_RELA')
|
||||
|
||||
elf_assert(header['sh_type'] in ('SHT_REL', 'SHT_RELA'),
|
||||
'Unknown relocation type section')
|
||||
elf_assert(header['sh_entsize'] == self.entry_size,
|
||||
'Expected sh_entsize of %s section to be %s' % (
|
||||
header['sh_type'], self.entry_size))
|
||||
|
||||
|
||||
class RelrRelocationTable(object):
|
||||
""" RELR compressed relocation table. This stores relative relocations
|
||||
in a compressed format. An entry with an even value serves as an
|
||||
'anchor' that defines a base address. Following this entry are one or
|
||||
more bitmaps for consecutive addresses after the anchor which determine
|
||||
if the corresponding relocation exists (if the bit is 1) or if it is
|
||||
skipped. Addends are stored at the respective addresses (as in REL
|
||||
relocations).
|
||||
"""
|
||||
|
||||
def __init__(self, elffile, offset, size, entrysize):
|
||||
self._elffile = elffile
|
||||
self._offset = offset
|
||||
self._size = size
|
||||
self._relr_struct = self._elffile.structs.Elf_Relr
|
||||
self._entrysize = self._relr_struct.sizeof()
|
||||
self._cached_relocations = None
|
||||
|
||||
elf_assert(self._entrysize == entrysize,
|
||||
'Expected RELR entry size to be %s, got %s' % (
|
||||
self._entrysize, entrysize))
|
||||
|
||||
def iter_relocations(self):
|
||||
""" Yield all the relocations in the section
|
||||
"""
|
||||
|
||||
# If DT_RELRSZ is zero, offset is meaningless and could be None.
|
||||
if self._size == 0:
|
||||
return []
|
||||
|
||||
limit = self._offset + self._size
|
||||
relr = self._offset
|
||||
# The addresses of relocations in a bitmap are calculated from a base
|
||||
# value provided in an initial 'anchor' relocation.
|
||||
base = None
|
||||
while relr < limit:
|
||||
entry = struct_parse(self._relr_struct,
|
||||
self._elffile.stream,
|
||||
stream_pos=relr)
|
||||
entry_offset = entry['r_offset']
|
||||
if (entry_offset & 1) == 0:
|
||||
# We found an anchor, take the current value as the base address
|
||||
# for the following bitmaps and move the 'where' pointer to the
|
||||
# beginning of the first bitmap.
|
||||
base = entry_offset
|
||||
base += self._entrysize
|
||||
yield Relocation(entry, self._elffile)
|
||||
else:
|
||||
# We're processing a bitmap.
|
||||
elf_assert(base is not None, 'RELR bitmap without base address')
|
||||
i = 0
|
||||
while True:
|
||||
# Iterate over all bits except the least significant one.
|
||||
entry_offset = (entry_offset >> 1)
|
||||
if entry_offset == 0:
|
||||
break
|
||||
# if the current LSB is set, we have a relocation at the
|
||||
# corresponding address so generate a Relocation with the
|
||||
# matching offset
|
||||
if (entry_offset & 1) != 0:
|
||||
calc_offset = base + i * self._entrysize
|
||||
yield Relocation(Container(r_offset = calc_offset),
|
||||
self._elffile)
|
||||
i += 1
|
||||
# Advance 'base' past the current bitmap (8 == CHAR_BIT). There
|
||||
# are 63 (or 31 for 32-bit ELFs) entries in each bitmap, and
|
||||
# every bit corresponds to an ELF_addr-sized relocation.
|
||||
base += (8 * self._entrysize - 1) * self._elffile.structs.Elf_addr('').sizeof()
|
||||
# Advance to the next entry
|
||||
relr += self._entrysize
|
||||
|
||||
def num_relocations(self):
|
||||
""" Number of relocations in the section
|
||||
"""
|
||||
if self._cached_relocations is None:
|
||||
self._cached_relocations = list(self.iter_relocations())
|
||||
return len(self._cached_relocations)
|
||||
|
||||
def get_relocation(self, n):
|
||||
""" Get the relocation at index #n from the section (Relocation object)
|
||||
"""
|
||||
if self._cached_relocations is None:
|
||||
self._cached_relocations = list(self.iter_relocations())
|
||||
return self._cached_relocations[n]
|
||||
|
||||
|
||||
class RelrRelocationSection(Section, RelrRelocationTable):
|
||||
""" ELF RELR relocation section. Serves as a collection of RELR relocation entries.
|
||||
"""
|
||||
def __init__(self, header, name, elffile):
|
||||
Section.__init__(self, header, name, elffile)
|
||||
RelrRelocationTable.__init__(self, self.elffile,
|
||||
self['sh_offset'], self['sh_size'], self['sh_entsize'])
|
||||
|
||||
|
||||
class RelocationHandler(object):
|
||||
""" Handles the logic of relocations in ELF files.
|
||||
"""
|
||||
def __init__(self, elffile):
|
||||
self.elffile = elffile
|
||||
|
||||
def find_relocations_for_section(self, section):
|
||||
""" Given a section, find the relocation section for it in the ELF
|
||||
file. Return a RelocationSection object, or None if none was
|
||||
found.
|
||||
"""
|
||||
reloc_section_names = (
|
||||
'.rel' + section.name,
|
||||
'.rela' + section.name)
|
||||
# Find the relocation section aimed at this one. Currently assume
|
||||
# that either .rel or .rela section exists for this section, but
|
||||
# not both.
|
||||
for relsection in self.elffile.iter_sections():
|
||||
if ( isinstance(relsection, RelocationSection) and
|
||||
relsection.name in reloc_section_names):
|
||||
return relsection
|
||||
return None
|
||||
|
||||
def apply_section_relocations(self, stream, reloc_section):
|
||||
""" Apply all relocations in reloc_section (a RelocationSection object)
|
||||
to the given stream, that contains the data of the section that is
|
||||
being relocated. The stream is modified as a result.
|
||||
"""
|
||||
# The symbol table associated with this relocation section
|
||||
symtab = self.elffile.get_section(reloc_section['sh_link'])
|
||||
for reloc in reloc_section.iter_relocations():
|
||||
self._do_apply_relocation(stream, reloc, symtab)
|
||||
|
||||
def _do_apply_relocation(self, stream, reloc, symtab):
|
||||
# Preparations for performing the relocation: obtain the value of
|
||||
# the symbol mentioned in the relocation, as well as the relocation
|
||||
# recipe which tells us how to actually perform it.
|
||||
# All peppered with some sanity checking.
|
||||
if reloc['r_info_sym'] >= symtab.num_symbols():
|
||||
raise ELFRelocationError(
|
||||
'Invalid symbol reference in relocation: index %s' % (
|
||||
reloc['r_info_sym']))
|
||||
sym_value = symtab.get_symbol(reloc['r_info_sym'])['st_value']
|
||||
|
||||
reloc_type = reloc['r_info_type']
|
||||
recipe = None
|
||||
|
||||
if self.elffile.get_machine_arch() == 'x86':
|
||||
if reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected RELA relocation for x86: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_X86.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'x64':
|
||||
if not reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected REL relocation for x64: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_X64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'MIPS':
|
||||
if reloc.is_RELA():
|
||||
if reloc_type == ENUM_RELOC_TYPE_MIPS['R_MIPS_64']:
|
||||
if reloc['r_type2'] != 0 or reloc['r_type3'] != 0 or reloc['r_ssym'] != 0:
|
||||
raise ELFRelocationError(
|
||||
'Multiple relocations in R_MIPS_64 are not implemented: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_MIPS_RELA.get(reloc_type, None)
|
||||
else:
|
||||
recipe = self._RELOCATION_RECIPES_MIPS_REL.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'ARM':
|
||||
if reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected RELA relocation for ARM: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_ARM.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'AArch64':
|
||||
recipe = self._RELOCATION_RECIPES_AARCH64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == '64-bit PowerPC':
|
||||
recipe = self._RELOCATION_RECIPES_PPC64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'IBM S/390':
|
||||
recipe = self._RELOCATION_RECIPES_S390X.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'Linux BPF - in-kernel virtual machine':
|
||||
recipe = self._RELOCATION_RECIPES_EBPF.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'LoongArch':
|
||||
if not reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected REL relocation for LoongArch: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_LOONGARCH.get(reloc_type, None)
|
||||
|
||||
if recipe is None:
|
||||
raise ELFRelocationError(
|
||||
'Unsupported relocation type: %s' % reloc_type)
|
||||
|
||||
# So now we have everything we need to actually perform the relocation.
|
||||
# Let's get to it:
|
||||
|
||||
# 0. Find out which struct we're going to be using to read this value
|
||||
# from the stream and write it back.
|
||||
if recipe.bytesize == 4:
|
||||
value_struct = self.elffile.structs.Elf_word('')
|
||||
elif recipe.bytesize == 8:
|
||||
value_struct = self.elffile.structs.Elf_word64('')
|
||||
elif recipe.bytesize == 1:
|
||||
value_struct = self.elffile.structs.Elf_byte('')
|
||||
elif recipe.bytesize == 2:
|
||||
value_struct = self.elffile.structs.Elf_half('')
|
||||
else:
|
||||
raise ELFRelocationError('Invalid bytesize %s for relocation' %
|
||||
recipe.bytesize)
|
||||
|
||||
# 1. Read the value from the stream (with correct size and endianness)
|
||||
original_value = struct_parse(
|
||||
value_struct,
|
||||
stream,
|
||||
stream_pos=reloc['r_offset'])
|
||||
# 2. Apply the relocation to the value, acting according to the recipe
|
||||
relocated_value = recipe.calc_func(
|
||||
value=original_value,
|
||||
sym_value=sym_value,
|
||||
offset=reloc['r_offset'],
|
||||
addend=reloc['r_addend'] if recipe.has_addend else 0)
|
||||
# 3. Write the relocated value back into the stream
|
||||
stream.seek(reloc['r_offset'])
|
||||
|
||||
# Make sure the relocated value fits back by wrapping it around. This
|
||||
# looks like a problem, but it seems to be the way this is done in
|
||||
# binutils too.
|
||||
relocated_value = relocated_value % (2 ** (recipe.bytesize * 8))
|
||||
value_struct.build_stream(relocated_value, stream)
|
||||
|
||||
# Relocations are represented by "recipes". Each recipe specifies:
|
||||
# bytesize: The number of bytes to read (and write back) to the section.
|
||||
# This is the unit of data on which relocation is performed.
|
||||
# has_addend: Does this relocation have an extra addend?
|
||||
# calc_func: A function that performs the relocation on an extracted
|
||||
# value, and returns the updated value.
|
||||
#
|
||||
_RELOCATION_RECIPE_TYPE = namedtuple('_RELOCATION_RECIPE_TYPE',
|
||||
'bytesize has_addend calc_func')
|
||||
|
||||
def _reloc_calc_identity(value, sym_value, offset, addend=0):
|
||||
return value
|
||||
|
||||
def _reloc_calc_sym_plus_value(value, sym_value, offset, addend=0):
|
||||
return sym_value + value + addend
|
||||
|
||||
def _reloc_calc_sym_plus_value_pcrel(value, sym_value, offset, addend=0):
|
||||
return sym_value + value - offset
|
||||
|
||||
def _reloc_calc_sym_plus_addend(value, sym_value, offset, addend=0):
|
||||
return sym_value + addend
|
||||
|
||||
def _reloc_calc_sym_plus_addend_pcrel(value, sym_value, offset, addend=0):
|
||||
return sym_value + addend - offset
|
||||
|
||||
def _reloc_calc_value_minus_sym_addend(value, sym_value, offset, addend=0):
|
||||
return value - sym_value - addend
|
||||
|
||||
def _arm_reloc_calc_sym_plus_value_pcrel(value, sym_value, offset, addend=0):
|
||||
return sym_value // 4 + value - offset // 4
|
||||
|
||||
def _bpf_64_32_reloc_calc_sym_plus_addend(value, sym_value, offset, addend=0):
|
||||
return (sym_value + addend) // 8 - 1
|
||||
|
||||
_RELOCATION_RECIPES_ARM = {
|
||||
ENUM_RELOC_TYPE_ARM['R_ARM_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_ARM['R_ARM_CALL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_arm_reloc_calc_sym_plus_value_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_AARCH64 = {
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_ABS64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_PREL32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
}
|
||||
|
||||
# https://dmz-portal.mips.com/wiki/MIPS_relocation_types
|
||||
_RELOCATION_RECIPES_MIPS_REL = {
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
}
|
||||
_RELOCATION_RECIPES_MIPS_RELA = {
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_PPC64 = {
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_ADDR32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_REL32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_ADDR64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_X86 = {
|
||||
ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_i386['R_386_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_i386['R_386_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_X64 = {
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_32S']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
# https://www.kernel.org/doc/html/latest/bpf/llvm_reloc.html#different-relocation-types
|
||||
_RELOCATION_RECIPES_EBPF = {
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_bpf_64_32_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_NODYLD32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_ABS64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
}
|
||||
|
||||
# https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc
|
||||
_RELOCATION_RECIPES_LOONGARCH = {
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD8']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=1, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB8']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=1, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD16']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=2, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB16']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=2, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_32_PCREL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_64_PCREL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_S390X = {
|
||||
ENUM_RELOC_TYPE_S390X['R_390_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_S390X['R_390_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_S390X['R_390_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
|
||||
594
.venv/lib/python3.11/site-packages/elftools/elf/sections.py
Normal file
594
.venv/lib/python3.11/site-packages/elftools/elf/sections.py
Normal file
@@ -0,0 +1,594 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/sections.py
|
||||
#
|
||||
# ELF sections
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..common.exceptions import ELFCompressionError
|
||||
from ..common.utils import struct_parse, elf_assert, parse_cstring_from_stream
|
||||
from collections import defaultdict
|
||||
from .constants import SH_FLAGS
|
||||
from .notes import iter_notes
|
||||
|
||||
import zlib
|
||||
|
||||
|
||||
class Section(object):
|
||||
""" Base class for ELF sections. Also used for all sections types that have
|
||||
no special functionality.
|
||||
|
||||
Allows dictionary-like access to the section header. For example:
|
||||
> sec = Section(...)
|
||||
> sec['sh_type'] # section type
|
||||
"""
|
||||
def __init__(self, header, name, elffile):
|
||||
self.header = header
|
||||
self.name = name
|
||||
self.elffile = elffile
|
||||
self.stream = self.elffile.stream
|
||||
self.structs = self.elffile.structs
|
||||
self._compressed = header['sh_flags'] & SH_FLAGS.SHF_COMPRESSED
|
||||
|
||||
if self.compressed:
|
||||
# Read the compression header now to know about the size/alignment
|
||||
# of the decompressed data.
|
||||
header = struct_parse(self.structs.Elf_Chdr,
|
||||
self.stream,
|
||||
stream_pos=self['sh_offset'])
|
||||
self._compression_type = header['ch_type']
|
||||
self._decompressed_size = header['ch_size']
|
||||
self._decompressed_align = header['ch_addralign']
|
||||
else:
|
||||
self._decompressed_size = header['sh_size']
|
||||
self._decompressed_align = header['sh_addralign']
|
||||
|
||||
@property
|
||||
def compressed(self):
|
||||
""" Is this section compressed?
|
||||
"""
|
||||
return self._compressed
|
||||
|
||||
@property
|
||||
def data_size(self):
|
||||
""" Return the logical size for this section's data.
|
||||
|
||||
This can be different from the .sh_size header field when the section
|
||||
is compressed.
|
||||
"""
|
||||
return self._decompressed_size
|
||||
|
||||
@property
|
||||
def data_alignment(self):
|
||||
""" Return the logical alignment for this section's data.
|
||||
|
||||
This can be different from the .sh_addralign header field when the
|
||||
section is compressed.
|
||||
"""
|
||||
return self._decompressed_align
|
||||
|
||||
def data(self):
|
||||
""" The section data from the file.
|
||||
|
||||
Note that data is decompressed if the stored section data is
|
||||
compressed.
|
||||
"""
|
||||
# If this section is NOBITS, there is no data. provide a dummy answer
|
||||
if self.header['sh_type'] == 'SHT_NOBITS':
|
||||
return b'\0'*self.data_size
|
||||
|
||||
# If this section is compressed, deflate it
|
||||
if self.compressed:
|
||||
c_type = self._compression_type
|
||||
if c_type == 'ELFCOMPRESS_ZLIB':
|
||||
# Read the data to decompress starting right after the
|
||||
# compression header until the end of the section.
|
||||
hdr_size = self.structs.Elf_Chdr.sizeof()
|
||||
self.stream.seek(self['sh_offset'] + hdr_size)
|
||||
compressed = self.stream.read(self['sh_size'] - hdr_size)
|
||||
|
||||
decomp = zlib.decompressobj()
|
||||
result = decomp.decompress(compressed, self.data_size)
|
||||
else:
|
||||
raise ELFCompressionError(
|
||||
'Unknown compression type: {:#0x}'.format(c_type)
|
||||
)
|
||||
|
||||
if len(result) != self._decompressed_size:
|
||||
raise ELFCompressionError(
|
||||
'Decompressed data is {} bytes long, should be {} bytes'
|
||||
' long'.format(len(result), self._decompressed_size)
|
||||
)
|
||||
else:
|
||||
self.stream.seek(self['sh_offset'])
|
||||
result = self.stream.read(self._decompressed_size)
|
||||
|
||||
return result
|
||||
|
||||
def is_null(self):
|
||||
""" Is this a null section?
|
||||
"""
|
||||
return False
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
return self.header == other.header
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.header)
|
||||
|
||||
|
||||
class NullSection(Section):
|
||||
""" ELF NULL section
|
||||
"""
|
||||
def is_null(self):
|
||||
return True
|
||||
|
||||
|
||||
class StringTableSection(Section):
|
||||
""" ELF string table section.
|
||||
"""
|
||||
def get_string(self, offset):
|
||||
""" Get the string stored at the given offset in this string table.
|
||||
"""
|
||||
table_offset = self['sh_offset']
|
||||
s = parse_cstring_from_stream(self.stream, table_offset + offset)
|
||||
return s.decode('utf-8', errors='replace') if s else ''
|
||||
|
||||
|
||||
class SymbolTableIndexSection(Section):
|
||||
""" A section containing the section header table indices corresponding
|
||||
to symbols in the linked symbol table. This section has to exist if the
|
||||
symbol table contains an entry with a section header index set to
|
||||
SHN_XINDEX (0xffff). The format of the section is described at
|
||||
https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html
|
||||
"""
|
||||
def __init__(self, header, name, elffile, symboltable):
|
||||
super(SymbolTableIndexSection, self).__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def get_section_index(self, n):
|
||||
""" Get the section header table index for the symbol with index #n.
|
||||
The section contains an array of Elf32_word values with one entry
|
||||
for every symbol in the associated symbol table.
|
||||
"""
|
||||
return struct_parse(self.elffile.structs.Elf_word(''), self.stream,
|
||||
self['sh_offset'] + n * self['sh_entsize'])
|
||||
|
||||
|
||||
class SymbolTableSection(Section):
|
||||
""" ELF symbol table section. Has an associated StringTableSection that's
|
||||
passed in the constructor.
|
||||
"""
|
||||
def __init__(self, header, name, elffile, stringtable):
|
||||
super(SymbolTableSection, self).__init__(header, name, elffile)
|
||||
self.stringtable = stringtable
|
||||
elf_assert(self['sh_entsize'] > 0,
|
||||
'Expected entry size of section %r to be > 0' % name)
|
||||
elf_assert(self['sh_size'] % self['sh_entsize'] == 0,
|
||||
'Expected section size to be a multiple of entry size in section %r' % name)
|
||||
self._symbol_name_map = None
|
||||
|
||||
def num_symbols(self):
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize']
|
||||
|
||||
def get_symbol(self, n):
|
||||
""" Get the symbol at index #n from the table (Symbol object)
|
||||
"""
|
||||
# Grab the symbol's entry from the stream
|
||||
entry_offset = self['sh_offset'] + n * self['sh_entsize']
|
||||
entry = struct_parse(
|
||||
self.structs.Elf_Sym,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
# Find the symbol name in the associated string table
|
||||
name = self.stringtable.get_string(entry['st_name'])
|
||||
return Symbol(entry, name)
|
||||
|
||||
def get_symbol_by_name(self, name):
|
||||
""" Get a symbol(s) by name. Return None if no symbol by the given name
|
||||
exists.
|
||||
"""
|
||||
# The first time this method is called, construct a name to number
|
||||
# mapping
|
||||
#
|
||||
if self._symbol_name_map is None:
|
||||
self._symbol_name_map = defaultdict(list)
|
||||
for i, sym in enumerate(self.iter_symbols()):
|
||||
self._symbol_name_map[sym.name].append(i)
|
||||
symnums = self._symbol_name_map.get(name)
|
||||
return [self.get_symbol(i) for i in symnums] if symnums else None
|
||||
|
||||
def iter_symbols(self):
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
|
||||
|
||||
class Symbol(object):
|
||||
""" Symbol object - representing a single symbol entry from a symbol table
|
||||
section.
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to the
|
||||
symbol entry.
|
||||
"""
|
||||
def __init__(self, entry, name):
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class SUNWSyminfoTableSection(Section):
|
||||
""" ELF .SUNW Syminfo table section.
|
||||
Has an associated SymbolTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(self, header, name, elffile, symboltable):
|
||||
super(SUNWSyminfoTableSection, self).__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def num_symbols(self):
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize'] - 1
|
||||
|
||||
def get_symbol(self, n):
|
||||
""" 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_Sunw_Syminfo,
|
||||
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):
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(1, self.num_symbols() + 1):
|
||||
yield self.get_symbol(i)
|
||||
|
||||
|
||||
class NoteSection(Section):
|
||||
""" ELF NOTE section. Knows how to parse notes.
|
||||
"""
|
||||
def iter_notes(self):
|
||||
""" Yield all the notes in the section. Each result is a dictionary-
|
||||
like object with "n_name", "n_type", and "n_desc" fields, amongst
|
||||
others.
|
||||
"""
|
||||
return iter_notes(self.elffile, self['sh_offset'], self['sh_size'])
|
||||
|
||||
|
||||
class StabSection(Section):
|
||||
""" ELF stab section.
|
||||
"""
|
||||
def iter_stabs(self):
|
||||
""" Yield all stab entries. Result type is ELFStructs.Elf_Stabs.
|
||||
"""
|
||||
offset = self['sh_offset']
|
||||
size = self['sh_size']
|
||||
end = offset + size
|
||||
while offset < end:
|
||||
stabs = struct_parse(
|
||||
self.structs.Elf_Stabs,
|
||||
self.stream,
|
||||
stream_pos=offset)
|
||||
stabs['n_offset'] = offset
|
||||
offset += self.structs.Elf_Stabs.sizeof()
|
||||
self.stream.seek(offset)
|
||||
yield stabs
|
||||
|
||||
class Attribute(object):
|
||||
""" Attribute object - representing a build attribute of ELF files.
|
||||
"""
|
||||
def __init__(self, tag):
|
||||
self._tag = tag
|
||||
self.extra = None
|
||||
|
||||
@property
|
||||
def tag(self):
|
||||
return self._tag['tag']
|
||||
|
||||
def __repr__(self):
|
||||
s = '<%s (%s): %r>' % \
|
||||
(self.__class__.__name__, self.tag, self.value)
|
||||
s += ' %s' % self.extra if self.extra is not None else ''
|
||||
return s
|
||||
|
||||
|
||||
class AttributesSubsubsection(Section):
|
||||
""" Subsubsection of an ELF attribute section's subsection.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset, attribute):
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
self.structs = structs
|
||||
self.attribute = attribute
|
||||
|
||||
self.header = self.attribute(self.structs, self.stream)
|
||||
|
||||
self.attr_start = self.stream.tell()
|
||||
|
||||
def iter_attributes(self, tag=None):
|
||||
""" Yield all attributes (limit to |tag| if specified).
|
||||
"""
|
||||
for attribute in self._make_attributes():
|
||||
if tag is None or attribute.tag == tag:
|
||||
yield attribute
|
||||
|
||||
@property
|
||||
def num_attributes(self):
|
||||
""" Number of attributes in the subsubsection.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_attributes()) + 1
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
""" List of all attributes in the subsubsection.
|
||||
"""
|
||||
return [self.header] + list(self.iter_attributes())
|
||||
|
||||
def _make_attributes(self):
|
||||
""" Create all attributes for this subsubsection except the first one
|
||||
which is the header.
|
||||
"""
|
||||
end = self.offset + self.header.value
|
||||
|
||||
self.stream.seek(self.attr_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
yield self.attribute(self.structs, self.stream)
|
||||
|
||||
def __repr__(self):
|
||||
s = "<%s (%s): %d bytes>"
|
||||
return s % (self.__class__.__name__,
|
||||
self.header.tag[4:], self.header.value)
|
||||
|
||||
|
||||
class AttributesSubsection(Section):
|
||||
""" Subsection of an ELF attributes section.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset, header, subsubsection):
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
self.structs = structs
|
||||
self.subsubsection = subsubsection
|
||||
|
||||
self.header = struct_parse(header, self.stream, self.offset)
|
||||
|
||||
self.subsubsec_start = self.stream.tell()
|
||||
|
||||
def iter_subsubsections(self, scope=None):
|
||||
""" Yield all subsubsections (limit to |scope| if specified).
|
||||
"""
|
||||
for subsubsec in self._make_subsubsections():
|
||||
if scope is None or subsubsec.header.tag == scope:
|
||||
yield subsubsec
|
||||
|
||||
@property
|
||||
def num_subsubsections(self):
|
||||
""" Number of subsubsections in the subsection.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_subsubsections())
|
||||
|
||||
@property
|
||||
def subsubsections(self):
|
||||
""" List of all subsubsections in the subsection.
|
||||
"""
|
||||
return list(self.iter_subsubsections())
|
||||
|
||||
def _make_subsubsections(self):
|
||||
""" Create all subsubsections for this subsection.
|
||||
"""
|
||||
end = self.offset + self['length']
|
||||
|
||||
self.stream.seek(self.subsubsec_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
subsubsec = self.subsubsection(self.stream,
|
||||
self.structs,
|
||||
self.stream.tell())
|
||||
self.stream.seek(self.subsubsec_start + subsubsec.header.value)
|
||||
yield subsubsec
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries.
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def __repr__(self):
|
||||
s = "<%s (%s): %d bytes>"
|
||||
return s % (self.__class__.__name__,
|
||||
self.header['vendor_name'], self.header['length'])
|
||||
|
||||
|
||||
class AttributesSection(Section):
|
||||
""" ELF attributes section.
|
||||
"""
|
||||
def __init__(self, header, name, elffile, subsection):
|
||||
super(AttributesSection, self).__init__(header, name, elffile)
|
||||
self.subsection = subsection
|
||||
|
||||
fv = struct_parse(self.structs.Elf_byte('format_version'),
|
||||
self.stream,
|
||||
self['sh_offset'])
|
||||
|
||||
elf_assert(chr(fv) == 'A',
|
||||
"Unknown attributes version %s, expecting 'A'." % chr(fv))
|
||||
|
||||
self.subsec_start = self.stream.tell()
|
||||
|
||||
def iter_subsections(self, vendor_name=None):
|
||||
""" Yield all subsections (limit to |vendor_name| if specified).
|
||||
"""
|
||||
for subsec in self._make_subsections():
|
||||
if vendor_name is None or subsec['vendor_name'] == vendor_name:
|
||||
yield subsec
|
||||
|
||||
@property
|
||||
def num_subsections(self):
|
||||
""" Number of subsections in the section.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_subsections())
|
||||
|
||||
@property
|
||||
def subsections(self):
|
||||
""" List of all subsections in the section.
|
||||
"""
|
||||
return list(self.iter_subsections())
|
||||
|
||||
def _make_subsections(self):
|
||||
""" Create all subsections for this section.
|
||||
"""
|
||||
end = self['sh_offset'] + self.data_size
|
||||
|
||||
self.stream.seek(self.subsec_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
subsec = self.subsection(self.stream,
|
||||
self.structs,
|
||||
self.stream.tell())
|
||||
self.stream.seek(self.subsec_start + subsec['length'])
|
||||
yield subsec
|
||||
|
||||
|
||||
class ARMAttribute(Attribute):
|
||||
""" ARM attribute object - representing a build attribute of ARM ELF files.
|
||||
"""
|
||||
def __init__(self, structs, stream):
|
||||
super(ARMAttribute, self).__init__(
|
||||
struct_parse(structs.Elf_Arm_Attribute_Tag, stream))
|
||||
|
||||
if self.tag in ('TAG_FILE', 'TAG_SECTION', 'TAG_SYMBOL'):
|
||||
self.value = struct_parse(structs.Elf_word('value'), stream)
|
||||
|
||||
if self.tag != 'TAG_FILE':
|
||||
self.extra = []
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'), stream)
|
||||
|
||||
while s_number != 0:
|
||||
self.extra.append(s_number)
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'),
|
||||
stream)
|
||||
|
||||
elif self.tag in ('TAG_CPU_RAW_NAME', 'TAG_CPU_NAME', 'TAG_CONFORMANCE'):
|
||||
self.value = struct_parse(structs.Elf_ntbs('value',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_COMPATIBILITY':
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
self.extra = struct_parse(structs.Elf_ntbs('vendor_name',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_ALSO_COMPATIBLE_WITH':
|
||||
self.value = ARMAttribute(structs, stream)
|
||||
|
||||
if type(self.value.value) is not str:
|
||||
nul = struct_parse(structs.Elf_byte('nul'), stream)
|
||||
elf_assert(nul == 0,
|
||||
"Invalid terminating byte %r, expecting NUL." % nul)
|
||||
|
||||
else:
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
|
||||
|
||||
class ARMAttributesSubsubsection(AttributesSubsubsection):
|
||||
""" Subsubsection of an ELF .ARM.attributes section's subsection.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset):
|
||||
super(ARMAttributesSubsubsection, self).__init__(
|
||||
stream, structs, offset, ARMAttribute)
|
||||
|
||||
|
||||
class ARMAttributesSubsection(AttributesSubsection):
|
||||
""" Subsection of an ELF .ARM.attributes section.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset):
|
||||
super(ARMAttributesSubsection, self).__init__(
|
||||
stream, structs, offset,
|
||||
structs.Elf_Attr_Subsection_Header,
|
||||
ARMAttributesSubsubsection)
|
||||
|
||||
|
||||
class ARMAttributesSection(AttributesSection):
|
||||
""" ELF .ARM.attributes section.
|
||||
"""
|
||||
def __init__(self, header, name, elffile):
|
||||
super(ARMAttributesSection, self).__init__(
|
||||
header, name, elffile, ARMAttributesSubsection)
|
||||
|
||||
|
||||
class RISCVAttribute(Attribute):
|
||||
""" Attribute of an ELF .riscv.attributes section.
|
||||
"""
|
||||
def __init__(self, structs, stream):
|
||||
super(RISCVAttribute, self).__init__(
|
||||
struct_parse(structs.Elf_RiscV_Attribute_Tag, stream))
|
||||
|
||||
if self.tag in ('TAG_FILE', 'TAG_SECTION', 'TAG_SYMBOL'):
|
||||
self.value = struct_parse(structs.Elf_word('value'), stream)
|
||||
|
||||
if self.tag != 'TAG_FILE':
|
||||
self.extra = []
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'), stream)
|
||||
|
||||
while s_number != 0:
|
||||
self.extra.append(s_number)
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_ARCH':
|
||||
self.value = struct_parse(structs.Elf_ntbs('value',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
else:
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
|
||||
|
||||
class RISCVAttributesSubsubsection(AttributesSubsubsection):
|
||||
""" Subsubsection of an ELF .riscv.attributes subsection.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset):
|
||||
super(RISCVAttributesSubsubsection, self).__init__(
|
||||
stream, structs, offset, RISCVAttribute)
|
||||
|
||||
|
||||
class RISCVAttributesSubsection(AttributesSubsection):
|
||||
""" Subsection of an ELF .riscv.attributes section.
|
||||
"""
|
||||
def __init__(self, stream, structs, offset):
|
||||
super(RISCVAttributesSubsection, self).__init__(
|
||||
stream, structs, offset,
|
||||
structs.Elf_Attr_Subsection_Header,
|
||||
RISCVAttributesSubsubsection)
|
||||
|
||||
|
||||
class RISCVAttributesSection(AttributesSection):
|
||||
""" ELF .riscv.attributes section.
|
||||
"""
|
||||
def __init__(self, header, name, elffile):
|
||||
super(RISCVAttributesSection, self).__init__(
|
||||
header, name, elffile, RISCVAttributesSubsection)
|
||||
127
.venv/lib/python3.11/site-packages/elftools/elf/segments.py
Normal file
127
.venv/lib/python3.11/site-packages/elftools/elf/segments.py
Normal file
@@ -0,0 +1,127 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/segments.py
|
||||
#
|
||||
# ELF segments
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..construct import CString
|
||||
from ..common.utils import struct_parse
|
||||
from .constants import SH_FLAGS
|
||||
from .notes import iter_notes
|
||||
|
||||
|
||||
class Segment(object):
|
||||
def __init__(self, header, stream):
|
||||
self.header = header
|
||||
self.stream = stream
|
||||
|
||||
def data(self):
|
||||
""" The segment data from the file.
|
||||
"""
|
||||
self.stream.seek(self['p_offset'])
|
||||
return self.stream.read(self['p_filesz'])
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def section_in_segment(self, section):
|
||||
""" Is the given section contained in this segment?
|
||||
|
||||
Note: this tries to reproduce the intricate rules of the
|
||||
ELF_SECTION_IN_SEGMENT_STRICT macro of the header
|
||||
elf/include/internal.h in the source of binutils.
|
||||
"""
|
||||
# Only the 'strict' checks from ELF_SECTION_IN_SEGMENT_1 are included
|
||||
segtype = self['p_type']
|
||||
sectype = section['sh_type']
|
||||
secflags = section['sh_flags']
|
||||
|
||||
# Only PT_LOAD, PT_GNU_RELRO and PT_TLS segments can contain SHF_TLS
|
||||
# sections
|
||||
if ( secflags & SH_FLAGS.SHF_TLS and
|
||||
segtype in ('PT_TLS', 'PT_GNU_RELRO', 'PT_LOAD')):
|
||||
pass
|
||||
# PT_TLS segment contains only SHF_TLS sections, PT_PHDR no sections
|
||||
# at all
|
||||
elif ( (secflags & SH_FLAGS.SHF_TLS) == 0 and
|
||||
segtype not in ('PT_TLS', 'PT_PHDR')):
|
||||
pass
|
||||
else:
|
||||
return False
|
||||
|
||||
# PT_LOAD and similar segments only have SHF_ALLOC sections.
|
||||
if ( (secflags & SH_FLAGS.SHF_ALLOC) == 0 and
|
||||
segtype in ('PT_LOAD', 'PT_DYNAMIC', 'PT_GNU_EH_FRAME',
|
||||
'PT_GNU_RELRO', 'PT_GNU_STACK')):
|
||||
return False
|
||||
|
||||
# In ELF_SECTION_IN_SEGMENT_STRICT the flag check_vma is on, so if
|
||||
# this is an alloc section, check whether its VMA is in bounds.
|
||||
if secflags & SH_FLAGS.SHF_ALLOC:
|
||||
secaddr = section['sh_addr']
|
||||
vaddr = self['p_vaddr']
|
||||
|
||||
# This checks that the section is wholly contained in the segment.
|
||||
# The third condition is the 'strict' one - an empty section will
|
||||
# not match at the very end of the segment (unless the segment is
|
||||
# also zero size, which is handled by the second condition).
|
||||
|
||||
# Seva 2024-07-12: a zero length section at a zero offset
|
||||
# in a zero length segment should match - in GNU readelf, p_memsz
|
||||
# is unsigned, on a zero length segment p_memsz-1 wraps around
|
||||
# and the third condition matches.
|
||||
if not (secaddr >= vaddr and
|
||||
secaddr - vaddr + section['sh_size'] <= self['p_memsz'] and
|
||||
(self['p_memsz'] == 0 or secaddr - vaddr <= self['p_memsz'] - 1)):
|
||||
return False
|
||||
|
||||
# If we've come this far and it's a NOBITS section, it's in the segment
|
||||
if sectype == 'SHT_NOBITS':
|
||||
return True
|
||||
|
||||
secoffset = section['sh_offset']
|
||||
poffset = self['p_offset']
|
||||
|
||||
# Same logic as with secaddr vs. vaddr checks above, just on offsets in
|
||||
# the file
|
||||
# Seva 2024-07-12: similar discrepancy with readelf from unsignedness of p_filesz
|
||||
return (secoffset >= poffset and
|
||||
secoffset - poffset + section['sh_size'] <= self['p_filesz'] and
|
||||
(self['p_filesz'] == 0 or secoffset - poffset <= self['p_filesz'] - 1))
|
||||
|
||||
|
||||
class InterpSegment(Segment):
|
||||
""" INTERP segment. Knows how to obtain the path to the interpreter used
|
||||
for this ELF file.
|
||||
"""
|
||||
def __init__(self, header, stream):
|
||||
super(InterpSegment, self).__init__(header, stream)
|
||||
|
||||
def get_interp_name(self):
|
||||
""" Obtain the interpreter path used for this ELF file.
|
||||
"""
|
||||
path_offset = self['p_offset']
|
||||
return struct_parse(
|
||||
CString('', encoding='utf-8'),
|
||||
self.stream,
|
||||
stream_pos=path_offset)
|
||||
|
||||
|
||||
class NoteSegment(Segment):
|
||||
""" NOTE segment. Knows how to parse notes.
|
||||
"""
|
||||
def __init__(self, header, stream, elffile):
|
||||
super(NoteSegment, self).__init__(header, stream)
|
||||
self.elffile = elffile
|
||||
|
||||
def iter_notes(self):
|
||||
|
||||
""" Yield all the notes in the segment. Each result is a dictionary-
|
||||
like object with "n_name", "n_type", and "n_desc" fields, amongst
|
||||
others.
|
||||
"""
|
||||
return iter_notes(self.elffile, self['p_offset'], self['p_filesz'])
|
||||
571
.venv/lib/python3.11/site-packages/elftools/elf/structs.py
Normal file
571
.venv/lib/python3.11/site-packages/elftools/elf/structs.py
Normal file
@@ -0,0 +1,571 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/structs.py
|
||||
#
|
||||
# Encapsulation of Construct structs for parsing an ELF file, adjusted for
|
||||
# correct endianness and word-size.
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from ..construct import (
|
||||
UBInt8, UBInt16, UBInt32, UBInt64,
|
||||
ULInt8, ULInt16, ULInt32, ULInt64,
|
||||
SBInt32, SLInt32, SBInt64, SLInt64,
|
||||
Struct, Array, Enum, Padding, BitStruct, BitField, Value, String, CString,
|
||||
Switch, Field
|
||||
)
|
||||
from ..common.construct_utils import ULEB128
|
||||
from ..common.utils import roundup
|
||||
from .enums import *
|
||||
|
||||
|
||||
class ELFStructs(object):
|
||||
""" Accessible attributes:
|
||||
|
||||
Elf_{byte|half|word|word64|addr|offset|sword|xword|xsword}:
|
||||
Data chunks, as specified by the ELF standard, adjusted for
|
||||
correct endianness and word-size.
|
||||
|
||||
Elf_Ehdr:
|
||||
ELF file header
|
||||
|
||||
Elf_Phdr:
|
||||
Program header
|
||||
|
||||
Elf_Shdr:
|
||||
Section header
|
||||
|
||||
Elf_Sym:
|
||||
Symbol table entry
|
||||
|
||||
Elf_Rel, Elf_Rela:
|
||||
Entries in relocation sections
|
||||
"""
|
||||
def __init__(self, little_endian=True, elfclass=32):
|
||||
assert elfclass == 32 or elfclass == 64
|
||||
self.little_endian = little_endian
|
||||
self.elfclass = elfclass
|
||||
self.e_type = None
|
||||
self.e_machine = None
|
||||
self.e_ident_osabi = None
|
||||
|
||||
def __getstate__(self):
|
||||
return self.little_endian, self.elfclass, self.e_type, self.e_machine, self.e_ident_osabi
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.little_endian, self.elfclass, e_type, e_machine, e_osabi = state
|
||||
self.create_basic_structs()
|
||||
self.create_advanced_structs(e_type, e_machine, e_osabi)
|
||||
|
||||
def create_basic_structs(self):
|
||||
""" Create word-size related structs and ehdr struct needed for
|
||||
initial determining of ELF type.
|
||||
"""
|
||||
if self.little_endian:
|
||||
self.Elf_byte = ULInt8
|
||||
self.Elf_half = ULInt16
|
||||
self.Elf_word = ULInt32
|
||||
self.Elf_word64 = ULInt64
|
||||
self.Elf_addr = ULInt32 if self.elfclass == 32 else ULInt64
|
||||
self.Elf_offset = self.Elf_addr
|
||||
self.Elf_sword = SLInt32
|
||||
self.Elf_xword = ULInt32 if self.elfclass == 32 else ULInt64
|
||||
self.Elf_sxword = SLInt32 if self.elfclass == 32 else SLInt64
|
||||
else:
|
||||
self.Elf_byte = UBInt8
|
||||
self.Elf_half = UBInt16
|
||||
self.Elf_word = UBInt32
|
||||
self.Elf_word64 = UBInt64
|
||||
self.Elf_addr = UBInt32 if self.elfclass == 32 else UBInt64
|
||||
self.Elf_offset = self.Elf_addr
|
||||
self.Elf_sword = SBInt32
|
||||
self.Elf_xword = UBInt32 if self.elfclass == 32 else UBInt64
|
||||
self.Elf_sxword = SBInt32 if self.elfclass == 32 else SBInt64
|
||||
self._create_ehdr()
|
||||
self._create_leb128()
|
||||
self._create_ntbs()
|
||||
|
||||
def create_advanced_structs(self, e_type=None, e_machine=None, e_ident_osabi=None):
|
||||
""" Create all ELF structs except the ehdr. They may possibly depend
|
||||
on provided e_type and/or e_machine parsed from ehdr.
|
||||
"""
|
||||
self.e_type = e_type
|
||||
self.e_machine = e_machine
|
||||
self.e_ident_osabi = e_ident_osabi
|
||||
|
||||
self._create_phdr()
|
||||
self._create_shdr()
|
||||
self._create_chdr()
|
||||
self._create_sym()
|
||||
self._create_rel()
|
||||
self._create_dyn()
|
||||
self._create_sunw_syminfo()
|
||||
self._create_gnu_verneed()
|
||||
self._create_gnu_verdef()
|
||||
self._create_gnu_versym()
|
||||
self._create_gnu_abi()
|
||||
self._create_gnu_property()
|
||||
self._create_note(e_type)
|
||||
self._create_stabs()
|
||||
self._create_attributes_subsection()
|
||||
self._create_arm_attributes()
|
||||
self._create_riscv_attributes()
|
||||
self._create_elf_hash()
|
||||
self._create_gnu_hash()
|
||||
self._create_gnu_debuglink()
|
||||
|
||||
#-------------------------------- PRIVATE --------------------------------#
|
||||
|
||||
def _create_ehdr(self):
|
||||
self.Elf_Ehdr = Struct('Elf_Ehdr',
|
||||
Struct('e_ident',
|
||||
Array(4, self.Elf_byte('EI_MAG')),
|
||||
Enum(self.Elf_byte('EI_CLASS'), **ENUM_EI_CLASS),
|
||||
Enum(self.Elf_byte('EI_DATA'), **ENUM_EI_DATA),
|
||||
Enum(self.Elf_byte('EI_VERSION'), **ENUM_E_VERSION),
|
||||
Enum(self.Elf_byte('EI_OSABI'), **ENUM_EI_OSABI),
|
||||
self.Elf_byte('EI_ABIVERSION'),
|
||||
Padding(7)
|
||||
),
|
||||
Enum(self.Elf_half('e_type'), **ENUM_E_TYPE),
|
||||
Enum(self.Elf_half('e_machine'), **ENUM_E_MACHINE),
|
||||
Enum(self.Elf_word('e_version'), **ENUM_E_VERSION),
|
||||
self.Elf_addr('e_entry'),
|
||||
self.Elf_offset('e_phoff'),
|
||||
self.Elf_offset('e_shoff'),
|
||||
self.Elf_word('e_flags'),
|
||||
self.Elf_half('e_ehsize'),
|
||||
self.Elf_half('e_phentsize'),
|
||||
self.Elf_half('e_phnum'),
|
||||
self.Elf_half('e_shentsize'),
|
||||
self.Elf_half('e_shnum'),
|
||||
self.Elf_half('e_shstrndx'),
|
||||
)
|
||||
|
||||
def _create_leb128(self):
|
||||
self.Elf_uleb128 = ULEB128
|
||||
|
||||
def _create_ntbs(self):
|
||||
self.Elf_ntbs = CString
|
||||
|
||||
def _create_phdr(self):
|
||||
p_type_dict = ENUM_P_TYPE_BASE
|
||||
if self.e_machine == 'EM_ARM':
|
||||
p_type_dict = ENUM_P_TYPE_ARM
|
||||
elif self.e_machine == 'EM_AARCH64':
|
||||
p_type_dict = ENUM_P_TYPE_AARCH64
|
||||
elif self.e_machine == 'EM_MIPS':
|
||||
p_type_dict = ENUM_P_TYPE_MIPS
|
||||
elif self.e_machine == 'EM_RISCV':
|
||||
p_type_dict = ENUM_P_TYPE_RISCV
|
||||
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Phdr = Struct('Elf_Phdr',
|
||||
Enum(self.Elf_word('p_type'), **p_type_dict),
|
||||
self.Elf_offset('p_offset'),
|
||||
self.Elf_addr('p_vaddr'),
|
||||
self.Elf_addr('p_paddr'),
|
||||
self.Elf_word('p_filesz'),
|
||||
self.Elf_word('p_memsz'),
|
||||
self.Elf_word('p_flags'),
|
||||
self.Elf_word('p_align'),
|
||||
)
|
||||
else: # 64
|
||||
self.Elf_Phdr = Struct('Elf_Phdr',
|
||||
Enum(self.Elf_word('p_type'), **p_type_dict),
|
||||
self.Elf_word('p_flags'),
|
||||
self.Elf_offset('p_offset'),
|
||||
self.Elf_addr('p_vaddr'),
|
||||
self.Elf_addr('p_paddr'),
|
||||
self.Elf_xword('p_filesz'),
|
||||
self.Elf_xword('p_memsz'),
|
||||
self.Elf_xword('p_align'),
|
||||
)
|
||||
|
||||
def _create_shdr(self):
|
||||
"""Section header parsing.
|
||||
|
||||
Depends on e_machine because of machine-specific values in sh_type.
|
||||
"""
|
||||
sh_type_dict = ENUM_SH_TYPE_BASE
|
||||
if self.e_machine == 'EM_ARM':
|
||||
sh_type_dict = ENUM_SH_TYPE_ARM
|
||||
elif self.e_machine == 'EM_AARCH64':
|
||||
sh_type_dict = ENUM_SH_TYPE_AARCH64
|
||||
elif self.e_machine == 'EM_X86_64':
|
||||
sh_type_dict = ENUM_SH_TYPE_AMD64
|
||||
elif self.e_machine == 'EM_MIPS':
|
||||
sh_type_dict = ENUM_SH_TYPE_MIPS
|
||||
if self.e_machine == 'EM_RISCV':
|
||||
sh_type_dict = ENUM_SH_TYPE_RISCV
|
||||
|
||||
self.Elf_Shdr = Struct('Elf_Shdr',
|
||||
self.Elf_word('sh_name'),
|
||||
Enum(self.Elf_word('sh_type'), **sh_type_dict),
|
||||
self.Elf_xword('sh_flags'),
|
||||
self.Elf_addr('sh_addr'),
|
||||
self.Elf_offset('sh_offset'),
|
||||
self.Elf_xword('sh_size'),
|
||||
self.Elf_word('sh_link'),
|
||||
self.Elf_word('sh_info'),
|
||||
self.Elf_xword('sh_addralign'),
|
||||
self.Elf_xword('sh_entsize'),
|
||||
)
|
||||
|
||||
def _create_chdr(self):
|
||||
# Structure of compressed sections header. It is documented in Oracle
|
||||
# "Linker and Libraries Guide", Part IV ELF Application Binary
|
||||
# Interface, Chapter 13 Object File Format, Section Compression:
|
||||
# https://docs.oracle.com/cd/E53394_01/html/E54813/section_compression.html
|
||||
fields = [
|
||||
Enum(self.Elf_word('ch_type'), **ENUM_ELFCOMPRESS_TYPE),
|
||||
self.Elf_xword('ch_size'),
|
||||
self.Elf_xword('ch_addralign'),
|
||||
]
|
||||
if self.elfclass == 64:
|
||||
fields.insert(1, self.Elf_word('ch_reserved'))
|
||||
self.Elf_Chdr = Struct('Elf_Chdr', *fields)
|
||||
|
||||
def _create_rel(self):
|
||||
# r_info is also taken apart into r_info_sym and r_info_type. This is
|
||||
# done in Value to avoid endianity issues while parsing.
|
||||
if self.elfclass == 32:
|
||||
fields = [self.Elf_xword('r_info'),
|
||||
Value('r_info_sym',
|
||||
lambda ctx: (ctx['r_info'] >> 8) & 0xFFFFFF),
|
||||
Value('r_info_type',
|
||||
lambda ctx: ctx['r_info'] & 0xFF)]
|
||||
elif self.e_machine == 'EM_MIPS': # ELF64 MIPS
|
||||
fields = [
|
||||
# The MIPS ELF64 specification
|
||||
# (https://www.linux-mips.org/pub/linux/mips/doc/ABI/elf64-2.4.pdf)
|
||||
# provides a non-standard relocation structure definition.
|
||||
self.Elf_word('r_sym'),
|
||||
self.Elf_byte('r_ssym'),
|
||||
self.Elf_byte('r_type3'),
|
||||
self.Elf_byte('r_type2'),
|
||||
self.Elf_byte('r_type'),
|
||||
|
||||
# Synthetize usual fields for compatibility with other
|
||||
# architectures. This allows relocation consumers (including
|
||||
# our readelf tests) to work without worrying about MIPS64
|
||||
# oddities.
|
||||
Value('r_info_sym', lambda ctx: ctx['r_sym']),
|
||||
Value('r_info_ssym', lambda ctx: ctx['r_ssym']),
|
||||
Value('r_info_type', lambda ctx: ctx['r_type']),
|
||||
Value('r_info_type2', lambda ctx: ctx['r_type2']),
|
||||
Value('r_info_type3', lambda ctx: ctx['r_type3']),
|
||||
Value('r_info',
|
||||
lambda ctx: (ctx['r_sym'] << 32)
|
||||
| (ctx['r_ssym'] << 24)
|
||||
| (ctx['r_type3'] << 16)
|
||||
| (ctx['r_type2'] << 8)
|
||||
| ctx['r_type']),
|
||||
]
|
||||
else: # Other 64 ELFs
|
||||
fields = [self.Elf_xword('r_info'),
|
||||
Value('r_info_sym',
|
||||
lambda ctx: (ctx['r_info'] >> 32) & 0xFFFFFFFF),
|
||||
Value('r_info_type',
|
||||
lambda ctx: ctx['r_info'] & 0xFFFFFFFF)]
|
||||
|
||||
self.Elf_Rel = Struct('Elf_Rel',
|
||||
self.Elf_addr('r_offset'),
|
||||
*fields)
|
||||
|
||||
fields_and_addend = fields + [self.Elf_sxword('r_addend')]
|
||||
self.Elf_Rela = Struct('Elf_Rela',
|
||||
self.Elf_addr('r_offset'),
|
||||
*fields_and_addend
|
||||
)
|
||||
|
||||
# Elf32_Relr is typedef'd as Elf32_Word, Elf64_Relr as Elf64_Xword
|
||||
# (see the glibc patch, for example:
|
||||
# https://sourceware.org/pipermail/libc-alpha/2021-October/132029.html)
|
||||
# For us, this is the same as self.Elf_addr (or self.Elf_xword).
|
||||
self.Elf_Relr = Struct('Elf_Relr', self.Elf_addr('r_offset'))
|
||||
|
||||
def _create_dyn(self):
|
||||
d_tag_dict = dict(ENUM_D_TAG_COMMON)
|
||||
if self.e_machine in ENUMMAP_EXTRA_D_TAG_MACHINE:
|
||||
d_tag_dict.update(ENUMMAP_EXTRA_D_TAG_MACHINE[self.e_machine])
|
||||
elif self.e_ident_osabi == 'ELFOSABI_SOLARIS':
|
||||
d_tag_dict.update(ENUM_D_TAG_SOLARIS)
|
||||
|
||||
self.Elf_Dyn = Struct('Elf_Dyn',
|
||||
Enum(self.Elf_sxword('d_tag'), **d_tag_dict),
|
||||
self.Elf_xword('d_val'),
|
||||
Value('d_ptr', lambda ctx: ctx['d_val']),
|
||||
)
|
||||
|
||||
def _create_sym(self):
|
||||
# st_info is hierarchical. To access the type, use
|
||||
# container['st_info']['type']
|
||||
st_info_struct = BitStruct('st_info',
|
||||
Enum(BitField('bind', 4), **ENUM_ST_INFO_BIND),
|
||||
Enum(BitField('type', 4), **ENUM_ST_INFO_TYPE))
|
||||
# st_other is hierarchical. To access the visibility,
|
||||
# use container['st_other']['visibility']
|
||||
st_other_struct = BitStruct('st_other',
|
||||
# https://openpowerfoundation.org/wp-content/uploads/2016/03/ABI64BitOpenPOWERv1.1_16July2015_pub4.pdf
|
||||
# See 3.4.1 Symbol Values.
|
||||
Enum(BitField('local', 3), **ENUM_ST_LOCAL),
|
||||
Padding(2),
|
||||
Enum(BitField('visibility', 3), **ENUM_ST_VISIBILITY))
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Sym = Struct('Elf_Sym',
|
||||
self.Elf_word('st_name'),
|
||||
self.Elf_addr('st_value'),
|
||||
self.Elf_word('st_size'),
|
||||
st_info_struct,
|
||||
st_other_struct,
|
||||
Enum(self.Elf_half('st_shndx'), **ENUM_ST_SHNDX),
|
||||
)
|
||||
else:
|
||||
self.Elf_Sym = Struct('Elf_Sym',
|
||||
self.Elf_word('st_name'),
|
||||
st_info_struct,
|
||||
st_other_struct,
|
||||
Enum(self.Elf_half('st_shndx'), **ENUM_ST_SHNDX),
|
||||
self.Elf_addr('st_value'),
|
||||
self.Elf_xword('st_size'),
|
||||
)
|
||||
|
||||
def _create_sunw_syminfo(self):
|
||||
self.Elf_Sunw_Syminfo = Struct('Elf_Sunw_Syminfo',
|
||||
Enum(self.Elf_half('si_boundto'), **ENUM_SUNW_SYMINFO_BOUNDTO),
|
||||
self.Elf_half('si_flags'),
|
||||
)
|
||||
|
||||
def _create_gnu_verneed(self):
|
||||
# Structure of "version needed" entries is documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Verneed = Struct('Elf_Verneed',
|
||||
self.Elf_half('vn_version'),
|
||||
self.Elf_half('vn_cnt'),
|
||||
self.Elf_word('vn_file'),
|
||||
self.Elf_word('vn_aux'),
|
||||
self.Elf_word('vn_next'),
|
||||
)
|
||||
self.Elf_Vernaux = Struct('Elf_Vernaux',
|
||||
self.Elf_word('vna_hash'),
|
||||
self.Elf_half('vna_flags'),
|
||||
self.Elf_half('vna_other'),
|
||||
self.Elf_word('vna_name'),
|
||||
self.Elf_word('vna_next'),
|
||||
)
|
||||
|
||||
def _create_gnu_verdef(self):
|
||||
# Structure of "version definition" entries are documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Verdef = Struct('Elf_Verdef',
|
||||
self.Elf_half('vd_version'),
|
||||
self.Elf_half('vd_flags'),
|
||||
self.Elf_half('vd_ndx'),
|
||||
self.Elf_half('vd_cnt'),
|
||||
self.Elf_word('vd_hash'),
|
||||
self.Elf_word('vd_aux'),
|
||||
self.Elf_word('vd_next'),
|
||||
)
|
||||
self.Elf_Verdaux = Struct('Elf_Verdaux',
|
||||
self.Elf_word('vda_name'),
|
||||
self.Elf_word('vda_next'),
|
||||
)
|
||||
|
||||
def _create_gnu_versym(self):
|
||||
# Structure of "version symbol" entries are documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Versym = Struct('Elf_Versym',
|
||||
Enum(self.Elf_half('ndx'), **ENUM_VERSYM),
|
||||
)
|
||||
|
||||
def _create_gnu_abi(self):
|
||||
# Structure of GNU ABI notes is documented in
|
||||
# https://code.woboq.org/userspace/glibc/csu/abi-note.S.html
|
||||
self.Elf_abi = Struct('Elf_abi',
|
||||
Enum(self.Elf_word('abi_os'), **ENUM_NOTE_ABI_TAG_OS),
|
||||
self.Elf_word('abi_major'),
|
||||
self.Elf_word('abi_minor'),
|
||||
self.Elf_word('abi_tiny'),
|
||||
)
|
||||
|
||||
def _create_gnu_debugaltlink(self):
|
||||
self.Elf_debugaltlink = Struct('Elf_debugaltlink',
|
||||
CString("sup_filename"),
|
||||
String("sup_checksum", length=20))
|
||||
|
||||
def _create_gnu_property(self):
|
||||
# Structure of GNU property notes is documented in
|
||||
# https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
|
||||
def roundup_padding(ctx):
|
||||
if self.elfclass == 32:
|
||||
return roundup(ctx.pr_datasz, 2) - ctx.pr_datasz
|
||||
return roundup(ctx.pr_datasz, 3) - ctx.pr_datasz
|
||||
|
||||
def classify_pr_data(ctx):
|
||||
if type(ctx.pr_type) is not str:
|
||||
return None
|
||||
if ctx.pr_type.startswith('GNU_PROPERTY_X86_'):
|
||||
return ('GNU_PROPERTY_X86_*', 4, 0)
|
||||
elif ctx.pr_type.startswith('GNU_PROPERTY_AARCH64_'):
|
||||
return ('GNU_PROPERTY_AARCH64_*', 4, 0)
|
||||
elif ctx.pr_type.startswith('GNU_PROPERTY_RISCV_'):
|
||||
return ('GNU_PROPERTY_RISCV_*', 4, 0)
|
||||
return (ctx.pr_type, ctx.pr_datasz, self.elfclass)
|
||||
|
||||
self.Elf_Prop = Struct('Elf_Prop',
|
||||
Enum(self.Elf_word('pr_type'), **ENUM_NOTE_GNU_PROPERTY_TYPE),
|
||||
self.Elf_word('pr_datasz'),
|
||||
Switch('pr_data', classify_pr_data, {
|
||||
('GNU_PROPERTY_STACK_SIZE', 4, 32): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_STACK_SIZE', 8, 64): self.Elf_word64('pr_data'),
|
||||
('GNU_PROPERTY_X86_*', 4, 0): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_AARCH64_*', 4, 0): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_RISCV_*', 4, 0): self.Elf_word('pr_data'),
|
||||
},
|
||||
default=Field('pr_data', lambda ctx: ctx.pr_datasz)
|
||||
),
|
||||
Padding(roundup_padding)
|
||||
)
|
||||
|
||||
def _create_note(self, e_type=None):
|
||||
# Structure of "PT_NOTE" section
|
||||
|
||||
self.Elf_ugid = self.Elf_half if self.elfclass == 32 and self.e_machine in {
|
||||
'EM_MN10300',
|
||||
'EM_ARM',
|
||||
'EM_CRIS',
|
||||
'EM_CYGNUS_FRV',
|
||||
'EM_386',
|
||||
'EM_M32R',
|
||||
'EM_68K',
|
||||
'EM_S390',
|
||||
'EM_SH',
|
||||
'EM_SPARC',
|
||||
} else self.Elf_word
|
||||
|
||||
self.Elf_Nhdr = Struct('Elf_Nhdr',
|
||||
self.Elf_word('n_namesz'),
|
||||
self.Elf_word('n_descsz'),
|
||||
Enum(self.Elf_word('n_type'),
|
||||
**(ENUM_NOTE_N_TYPE if e_type != "ET_CORE"
|
||||
else ENUM_CORE_NOTE_N_TYPE)),
|
||||
)
|
||||
|
||||
# A process psinfo structure according to
|
||||
# http://elixir.free-electrons.com/linux/v2.6.35/source/include/linux/elfcore.h#L84
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Prpsinfo = Struct('Elf_Prpsinfo',
|
||||
self.Elf_byte('pr_state'),
|
||||
String('pr_sname', 1),
|
||||
self.Elf_byte('pr_zomb'),
|
||||
self.Elf_byte('pr_nice'),
|
||||
self.Elf_xword('pr_flag'),
|
||||
self.Elf_ugid('pr_uid'),
|
||||
self.Elf_ugid('pr_gid'),
|
||||
self.Elf_word('pr_pid'),
|
||||
self.Elf_word('pr_ppid'),
|
||||
self.Elf_word('pr_pgrp'),
|
||||
self.Elf_word('pr_sid'),
|
||||
String('pr_fname', 16),
|
||||
String('pr_psargs', 80),
|
||||
)
|
||||
else: # 64
|
||||
self.Elf_Prpsinfo = Struct('Elf_Prpsinfo',
|
||||
self.Elf_byte('pr_state'),
|
||||
String('pr_sname', 1),
|
||||
self.Elf_byte('pr_zomb'),
|
||||
self.Elf_byte('pr_nice'),
|
||||
Padding(4),
|
||||
self.Elf_xword('pr_flag'),
|
||||
self.Elf_ugid('pr_uid'),
|
||||
self.Elf_ugid('pr_gid'),
|
||||
self.Elf_word('pr_pid'),
|
||||
self.Elf_word('pr_ppid'),
|
||||
self.Elf_word('pr_pgrp'),
|
||||
self.Elf_word('pr_sid'),
|
||||
String('pr_fname', 16),
|
||||
String('pr_psargs', 80),
|
||||
)
|
||||
|
||||
# A PT_NOTE of type NT_FILE matching the definition in
|
||||
# https://chromium.googlesource.com/
|
||||
# native_client/nacl-binutils/+/upstream/master/binutils/readelf.c
|
||||
# Line 15121
|
||||
self.Elf_Nt_File = Struct('Elf_Nt_File',
|
||||
self.Elf_xword("num_map_entries"),
|
||||
self.Elf_xword("page_size"),
|
||||
Array(lambda ctx: ctx.num_map_entries,
|
||||
Struct('Elf_Nt_File_Entry',
|
||||
self.Elf_addr('vm_start'),
|
||||
self.Elf_addr('vm_end'),
|
||||
self.Elf_offset('page_offset'))),
|
||||
Array(lambda ctx: ctx.num_map_entries,
|
||||
CString('filename')))
|
||||
|
||||
def _create_stabs(self):
|
||||
# Structure of one stabs entry, see binutils/bfd/stabs.c
|
||||
# Names taken from https://sourceware.org/gdb/current/onlinedocs/stabs.html#Overview
|
||||
self.Elf_Stabs = Struct('Elf_Stabs',
|
||||
self.Elf_word('n_strx'),
|
||||
self.Elf_byte('n_type'),
|
||||
self.Elf_byte('n_other'),
|
||||
self.Elf_half('n_desc'),
|
||||
self.Elf_word('n_value'),
|
||||
)
|
||||
|
||||
def _create_attributes_subsection(self):
|
||||
# Structure of a build attributes subsection header. A subsection is
|
||||
# either public to all tools that process the ELF file or private to
|
||||
# the vendor's tools.
|
||||
self.Elf_Attr_Subsection_Header = Struct('Elf_Attr_Subsection',
|
||||
self.Elf_word('length'),
|
||||
self.Elf_ntbs('vendor_name',
|
||||
encoding='utf-8')
|
||||
)
|
||||
|
||||
def _create_arm_attributes(self):
|
||||
# Structure of an ARM build attribute tag.
|
||||
self.Elf_Arm_Attribute_Tag = Struct('Elf_Arm_Attribute_Tag',
|
||||
Enum(self.Elf_uleb128('tag'),
|
||||
**ENUM_ATTR_TAG_ARM)
|
||||
)
|
||||
|
||||
def _create_riscv_attributes(self):
|
||||
# Structure of a RISC-V build attribute tag.
|
||||
self.Elf_RiscV_Attribute_Tag = Struct('Elf_RiscV_Attribute_Tag',
|
||||
Enum(self.Elf_uleb128('tag'),
|
||||
**ENUM_ATTR_TAG_RISCV)
|
||||
)
|
||||
|
||||
def _create_elf_hash(self):
|
||||
# Structure of the old SYSV-style hash table header. It is documented
|
||||
# in the Oracle "Linker and Libraries Guide", Part IV ELF Application
|
||||
# Binary Interface, Chapter 14 Object File Format, Section Hash Table
|
||||
# Section:
|
||||
# https://docs.oracle.com/cd/E53394_01/html/E54813/chapter6-48031.html
|
||||
|
||||
self.Elf_Hash = Struct('Elf_Hash',
|
||||
self.Elf_word('nbuckets'),
|
||||
self.Elf_word('nchains'),
|
||||
Array(lambda ctx: ctx['nbuckets'], self.Elf_word('buckets')),
|
||||
Array(lambda ctx: ctx['nchains'], self.Elf_word('chains')))
|
||||
|
||||
def _create_gnu_hash(self):
|
||||
# Structure of the GNU-style hash table header. Documentation for this
|
||||
# table is mostly in the GLIBC source code, a good explanation of the
|
||||
# format can be found in this blog post:
|
||||
# https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/
|
||||
self.Gnu_Hash = Struct('Gnu_Hash',
|
||||
self.Elf_word('nbuckets'),
|
||||
self.Elf_word('symoffset'),
|
||||
self.Elf_word('bloom_size'),
|
||||
self.Elf_word('bloom_shift'),
|
||||
Array(lambda ctx: ctx['bloom_size'], self.Elf_xword('bloom')),
|
||||
Array(lambda ctx: ctx['nbuckets'], self.Elf_word('buckets')))
|
||||
|
||||
def _create_gnu_debuglink(self):
|
||||
self.Gnu_debuglink = Struct('Gnu_debuglink',
|
||||
CString("filename"),
|
||||
Padding(lambda ctx: 3 - len(ctx.filename) % 4, strict=True),
|
||||
self.Elf_word("checksum"))
|
||||
Reference in New Issue
Block a user