建立 MAG160C 逆向工程交接仓库

This commit is contained in:
ZXCLI
2026-08-11 19:08:44 +08:00
commit 8409b27ba3
3135 changed files with 534408 additions and 0 deletions
@@ -0,0 +1,90 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/abbrevtable.py
#
# DWARF abbreviation table
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from typing import IO, TYPE_CHECKING, Any
from ..common.utils import struct_parse
if TYPE_CHECKING:
from collections.abc import Iterator
from ..construct.lib.container import Container
from .structs import DWARFStructs
class AbbrevTable:
""" Represents a DWARF abbreviation table.
"""
__slots__ = ('structs', 'stream', 'offset', '_abbrev_map')
def __init__(self, structs: DWARFStructs, stream: IO[bytes], offset: int) -> None:
""" Create new abbreviation table. Parses the actual table from the
stream and stores it internally.
structs:
A DWARFStructs instance for parsing the data
stream, offset:
The stream and offset into the stream where this abbreviation
table lives.
"""
self.structs = structs
self.stream = stream
self.offset = offset
self._abbrev_map = self._parse_abbrev_table()
def get_abbrev(self, code: int) -> AbbrevDecl:
""" Get the AbbrevDecl for a given code. Raise KeyError if no
declaration for this code exists.
"""
return self._abbrev_map[code]
def _parse_abbrev_table(self) -> dict[int, AbbrevDecl]:
""" Parse the abbrev table from the stream
"""
map: dict[int, AbbrevDecl] = {}
self.stream.seek(self.offset)
while True:
decl_code: int = struct_parse(
struct=self.structs.the_Dwarf_uleb128,
stream=self.stream)
if decl_code == 0:
break
declaration = struct_parse(
struct=self.structs.Dwarf_abbrev_declaration,
stream=self.stream)
map[decl_code] = AbbrevDecl(decl_code, declaration)
return map
class AbbrevDecl:
""" Wraps a parsed abbreviation declaration, exposing its fields with
dict-like access, and adding some convenience methods.
The abbreviation declaration represents an "entry" that points to it.
"""
__slots__ = ('code', 'decl', '_has_children')
def __init__(self, code: int, decl: Container) -> None:
self.code = code
self.decl = decl
self._has_children = decl['children_flag'] == 'DW_CHILDREN_yes'
def has_children(self) -> bool:
return self._has_children
def iter_attr_specs(self) -> Iterator[tuple[str, str]]:
""" Iterate over the attribute specifications for the entry. Yield
(name, form) pairs.
"""
for attr_spec in self['attr_spec']:
yield attr_spec.name, attr_spec.form
def __getitem__(self, entry: str) -> Any:
return self.decl[entry]
@@ -0,0 +1,142 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/aranges.py
#
# DWARF aranges section decoding (.debug_aranges)
#
# Dorothy Chen (dorothchen@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from typing import IO, TYPE_CHECKING, NamedTuple
from ..common.utils import struct_parse
from bisect import bisect_right
import math
if TYPE_CHECKING:
from collections.abc import Callable
from ..construct.core import Construct
from .structs import DWARFStructs
# An entry in the aranges table;
# begin_addr: The beginning address in the CU
# length: The length of the address range in this entry
# info_offset: The CU's offset into .debug_info
# see 6.1.2 in DWARF4 docs for explanation of the remaining fields
class ARangeEntry(NamedTuple):
begin_addr: int
length: int
info_offset: int
unit_length: int
version: int
address_size: int
segment_size: int
class ARanges:
""" ARanges table in DWARF
stream, size:
A stream holding the .debug_aranges section, and its size
structs:
A DWARFStructs instance for parsing the data
"""
def __init__(self, stream: IO[bytes], size: int, structs: DWARFStructs) -> None:
self.stream = stream
self.size = size
self.structs = structs
# Get entries of aranges table in the form of ARangeEntry tuples
self.entries = self._get_entries()
# Sort entries by the beginning address
self.entries.sort(key=lambda entry: entry.begin_addr)
# Create list of keys (first addresses) for better searching
self.keys = [entry.begin_addr for entry in self.entries]
def cu_offset_at_addr(self, addr: int) -> int | None:
""" Given an address, get the offset of the CU it belongs to, where
'offset' refers to the offset in the .debug_info section.
"""
tup = self.entries[bisect_right(self.keys, addr) - 1]
if tup.begin_addr <= addr < tup.begin_addr + tup.length:
return tup.info_offset
else:
return None
#------ PRIVATE ------#
def _get_entries(self, need_empty: bool = False) -> list[ARangeEntry]:
""" Populate self.entries with ARangeEntry tuples for each range of addresses
Terminating null entries of CU blocks are not returned, unless
need_empty is set to True and the CU block contains nothing but
a null entry. The null entry will have both address and length
set to 0.
"""
self.stream.seek(0)
entries: list[ARangeEntry] = []
offset = 0
# one loop == one "set" == one CU
while offset < self.size :
aranges_header = struct_parse(self.structs.Dwarf_aranges_header,
self.stream, offset)
addr_size = self._get_addr_size_struct(aranges_header["address_size"])
# No segmentation
if aranges_header["segment_size"] == 0:
# pad to nearest multiple of tuple size
tuple_size: int = aranges_header["address_size"] * 2
fp = self.stream.tell()
seek_to = int(math.ceil(fp/float(tuple_size)) * tuple_size)
self.stream.seek(seek_to)
# We now have a binary with empty arange sections - nothing but a NULL entry.
# To keep compatibility with readelf, we need to return those.
# A two level list would be a prettier solution, but this will be compatible.
got_entries = False
# entries in this set/CU
addr: int = struct_parse(addr_size('addr'), self.stream)
length: int = struct_parse(addr_size('length'), self.stream)
while addr != 0 or length != 0 or (not got_entries and need_empty):
# 'begin_addr length info_offset version address_size segment_size'
entries.append(
ARangeEntry(begin_addr=addr,
length=length,
info_offset=aranges_header["debug_info_offset"],
unit_length=aranges_header["unit_length"],
version=aranges_header["version"],
address_size=aranges_header["address_size"],
segment_size=aranges_header["segment_size"]))
got_entries = True
if addr != 0 or length != 0:
addr = struct_parse(addr_size('addr'), self.stream)
length = struct_parse(addr_size('length'), self.stream)
# Segmentation exists in executable
elif aranges_header["segment_size"] != 0:
raise NotImplementedError("Segmentation not implemented")
offset = (offset
+ aranges_header.unit_length
+ self.structs.initial_length_field_size())
return entries
def _get_addr_size_struct(self, addr_header_value: int) -> Callable[[str], Construct]:
""" Given this set's header value (int) for the address size,
get the Construct representation of that size
"""
if addr_header_value == 4:
return self.structs.Dwarf_uint32
else:
assert addr_header_value == 8
return self.structs.Dwarf_uint64
@@ -0,0 +1,780 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/callframe.py
#
# DWARF call frame information
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import copy
import os
from functools import cached_property
from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, cast
from warnings import warn
from ..common.utils import (
struct_parse, dwarf_assert, preserve_stream_pos)
from ..construct import Struct, Switch
from ..construct.lib.container import Container
from .enums import DW_EH_encoding_flags
from .structs import DWARFStructs
from .constants import DW_CFA
if TYPE_CHECKING:
from collections.abc import Callable
from ..construct.core import Construct
from ..construct.lib.container import ListContainer
Line = dict[Any, Any]
# TypedDict only supprts `str` as key, but "Line" mixes str|int.
# class Line(TypedDict, total=False):
# pc: int
# cfa: CFARule
# "int": RegisterRule
Augmentation = dict[str | bool, int | Container | Literal[True]]
# TypedDict only supprts `str` as key, but "Stack Frame" is signaled as `True: True`.
# class Augmentation(TypedDict, total=False):
# length: int
# LSDA_encoding: int
# FDE_encoding: int
# personality: Container
# "True": Literal[True]
class CallFrameInfo:
""" DWARF CFI (Call Frame Info)
Note that this also supports unwinding information as found in .eh_frame
sections: its format differs slightly from the one in .debug_frame. See
<http://www.airs.com/blog/archives/460>.
stream, size:
A stream holding the .debug_frame section, and the size of the
section in it.
address:
Virtual address for this section. This is used to decode relative
addresses.
base_structs:
The structs to be used as the base for parsing this section.
Eventually, each entry gets its own structs based on the initial
length field it starts with. The address_size, however, is taken
from base_structs. This appears to be a limitation of the DWARFv3
standard, fixed in v4.
A discussion I had on dwarf-discuss confirms this.
So for DWARFv4 we'll take the address size from the CIE header,
but for earlier versions will use the elfclass of the containing
file; more sophisticated methods are used by libdwarf and others,
such as guessing which CU contains which FDEs (based on their
address ranges) and taking the address_size from those CUs.
"""
def __init__(
self,
stream: IO[bytes],
size: int,
address: int,
base_structs: DWARFStructs,
for_eh_frame: bool = False,
) -> None:
self.stream = stream
self.size = size
self.address = address
self.base_structs = base_structs
self.entries: list[CFIEntry | ZERO] | None = None
# Map between an offset in the stream and the entry object found at this
# offset. Useful for assigning CIE to FDEs according to the CIE_pointer
# header field which contains a stream offset.
self._entry_cache: dict[int, CFIEntry] = {}
# The .eh_frame and .debug_frame section use almost the same CFI
# encoding, but there are tiny variations we need to handle during
# parsing.
self.for_eh_frame = for_eh_frame
def get_entries(self) -> list[CFIEntry | ZERO]:
""" Get a list of entries that constitute this CFI. The list consists
of CIE or FDE objects, in the order of their appearance in the
section.
"""
if self.entries is None:
self.entries = self._parse_entries()
return self.entries
#-------------------------
def _parse_entries(self) -> list[CFIEntry | ZERO]:
entries = []
offset = 0
while offset < self.size:
entries.append(self._parse_entry_at(offset))
offset = self.stream.tell()
return entries
def _parse_entry_at(self, offset: int) -> CFIEntry | ZERO:
""" Parse an entry from self.stream starting with the given offset.
Return the entry object. self.stream will point right after the
entry (even if pulled from the cache).
"""
if offset in self._entry_cache:
entry = self._entry_cache[offset]
self.stream.seek(entry.header.length +
entry.structs.initial_length_field_size(), os.SEEK_CUR)
return entry
entry_length: int = struct_parse(
self.base_structs.the_Dwarf_uint32, self.stream, offset)
if self.for_eh_frame and entry_length == 0:
return ZERO(offset)
dwarf_format = 64 if entry_length == 0xFFFFFFFF else 32
# Theoretically possible to have a DWARF bitness transition here.
# DWARF version doesn't matter (CIEs are versioned separately), endianness can't change.
# The structs are cached though, so no extraneous creation.
entry_structs = DWARFStructs(
little_endian=self.base_structs.little_endian,
dwarf_format=dwarf_format,
address_size=self.base_structs.address_size)
# Read the next field to see whether this is a CIE or FDE
CIE_id: int = struct_parse(
entry_structs.the_Dwarf_offset, self.stream)
if self.for_eh_frame:
is_CIE = CIE_id == 0
else:
is_CIE = (
(dwarf_format == 32 and CIE_id == 0xFFFFFFFF) or
CIE_id == 0xFFFFFFFFFFFFFFFF)
# Parse the header, which goes up to and excluding the sequence of
# instructions.
if is_CIE:
header_struct = (entry_structs.EH_CIE_header
if self.for_eh_frame else
entry_structs.Dwarf_CIE_header)
header = struct_parse(
header_struct, self.stream, offset)
else:
header = self._parse_fde_header(entry_structs, offset)
# If the augmentation string is not empty, hope to find a length field
# in order to skip the data specified augmentation.
lsda_pointer: int | None = None
aug_dict: Augmentation | None = None
if is_CIE:
aug_bytes, aug_dict = self._parse_cie_augmentation(
header, entry_structs)
else:
cie = self._parse_cie_for_fde(offset, header, entry_structs)
assert isinstance(cie, CFIEntry)
aug_bytes = self._read_augmentation_data(entry_structs)
lsda_encoding = cast(int, cie.augmentation_dict.get('LSDA_encoding', DW_EH_encoding_flags['DW_EH_PE_omit']))
if lsda_encoding != DW_EH_encoding_flags['DW_EH_PE_omit']:
# parse LSDA pointer
lsda_pointer = self._parse_lsda_pointer(entry_structs,
self.stream.tell() - len(aug_bytes),
lsda_encoding)
# For convenience, compute the end offset for this entry
end_offset: int = (
offset + header.length +
entry_structs.initial_length_field_size())
# At this point self.stream is at the start of the instruction list
# for this entry
instructions = self._parse_instructions(
entry_structs, self.stream.tell(), end_offset)
if is_CIE:
entry = CIE(
header=header, instructions=instructions, offset=offset,
augmentation_dict=aug_dict,
augmentation_bytes=aug_bytes,
structs=entry_structs)
else: # FDE
cie = self._parse_cie_for_fde(offset, header, entry_structs)
assert isinstance(cie, CIE)
entry = FDE(
header=header, instructions=instructions, offset=offset,
structs=entry_structs, cie=cie,
augmentation_bytes=aug_bytes,
lsda_pointer=lsda_pointer,
)
self._entry_cache[offset] = entry
return entry
def _parse_instructions(
self,
structs: DWARFStructs,
offset: int,
end_offset: int,
) -> list[CallFrameInstruction]:
""" Parse a list of CFI instructions from self.stream, starting with
the offset and until (not including) end_offset.
Return a list of CallFrameInstruction objects.
"""
instructions = []
while offset < end_offset:
raw_opcode: int = struct_parse(structs.the_Dwarf_uint8, self.stream, offset)
opcode, *args = DW_CFA.parse_raw_opcode(raw_opcode)
match opcode:
case DW_CFA.advance_loc | DW_CFA.restore | DW_CFA.nop | DW_CFA.remember_state | DW_CFA.restore_state | DW_CFA.AARCH64_negate_ra_state:
pass
case DW_CFA.offset:
args += [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
case DW_CFA.set_loc:
args = [struct_parse(structs.the_Dwarf_target_addr, self.stream)]
case DW_CFA.advance_loc1:
args = [struct_parse(structs.the_Dwarf_uint8, self.stream)]
case DW_CFA.advance_loc2:
args = [struct_parse(structs.the_Dwarf_uint16, self.stream)]
case DW_CFA.advance_loc4:
args = [struct_parse(structs.the_Dwarf_uint32, self.stream)]
case DW_CFA.offset_extended | DW_CFA.register | DW_CFA.def_cfa | DW_CFA.val_offset:
args = [
struct_parse(structs.the_Dwarf_uleb128, self.stream),
struct_parse(structs.the_Dwarf_uleb128, self.stream)]
case DW_CFA.restore_extended | DW_CFA.undefined | DW_CFA.same_value | DW_CFA.def_cfa_register | DW_CFA.def_cfa_offset:
args = [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
case DW_CFA.def_cfa_offset_sf:
args = [struct_parse(structs.the_Dwarf_sleb128, self.stream)]
case DW_CFA.def_cfa_expression:
struct = structs.Dwarf_dw_form['DW_FORM_block']
assert struct is not None
args = [struct_parse(struct, self.stream)]
case DW_CFA.expression | DW_CFA.val_expression:
struct = structs.Dwarf_dw_form['DW_FORM_block']
assert struct is not None
args = [
struct_parse(structs.the_Dwarf_uleb128, self.stream),
struct_parse(struct, self.stream)]
case DW_CFA.offset_extended_sf | DW_CFA.def_cfa_sf | DW_CFA.val_offset_sf:
args = [
struct_parse(structs.the_Dwarf_uleb128, self.stream),
struct_parse(structs.the_Dwarf_sleb128, self.stream)]
case DW_CFA.GNU_args_size:
args = [struct_parse(structs.the_Dwarf_uleb128, self.stream)]
case _:
dwarf_assert(False, f'Unknown CFI opcode: {raw_opcode:#04x}')
instructions.append(CallFrameInstruction(opcode=opcode, args=args))
offset = self.stream.tell()
return instructions
def _parse_cie_for_fde(
self,
fde_offset: int,
fde_header: Container,
entry_structs: DWARFStructs,
) -> CFIEntry | ZERO:
""" Parse the CIE that corresponds to an FDE.
"""
# Determine the offset of the CIE that corresponds to this FDE
if self.for_eh_frame:
# CIE_pointer contains the offset for a reverse displacement from
# the section offset of the CIE_pointer field itself (not from the
# FDE header offset).
cie_displacement: int = fde_header['CIE_pointer']
cie_offset: int = (fde_offset + entry_structs.dwarf_format // 8
- cie_displacement)
else:
cie_offset = fde_header['CIE_pointer']
# Then read it
with preserve_stream_pos(self.stream):
return self._parse_entry_at(cie_offset)
def _parse_cie_augmentation(
self,
header: Container,
entry_structs: DWARFStructs,
) -> tuple[bytes, Augmentation]:
""" Parse CIE augmentation data from the annotation string in `header`.
Return a tuple that contains 1) the augmentation data as a string
(without the length field) and 2) the augmentation data as a dict.
"""
augmentation: bytes | None = header.get('augmentation')
if not augmentation:
return (b'', {})
# Ignore armcc augmentations.
if augmentation.startswith(b'armcc'):
return (b'', {})
# Augmentation parsing works in minimal mode here: we need the length
# field to be able to skip unhandled augmentation fields.
assert augmentation.startswith(b'z'), (
'Unhandled augmentation string: {}'.format(repr(augmentation)))
available_fields: dict[str, Construct | Literal[True]] = {
'z': entry_structs.Dwarf_uleb128('length'),
'L': entry_structs.Dwarf_uint8('LSDA_encoding'),
'R': entry_structs.Dwarf_uint8('FDE_encoding'),
'S': True,
'P': Struct(
'personality',
entry_structs.Dwarf_uint8('encoding'),
Switch('function', lambda ctx: ctx.encoding & 0x0f, {
enc: fld_cons('function')
for enc, fld_cons
in self._eh_encoding_to_field(entry_structs).items()})),
}
# Build the Struct we will be using to parse the augmentation data.
# Stop as soon as we are not able to match the augmentation string.
fields: list[Construct] = []
aug_dict: Augmentation = {}
for b in augmentation:
try:
fld = available_fields[chr(b)]
except KeyError:
break
if fld is True:
aug_dict[fld] = True
else:
fields.append(fld)
# Read the augmentation twice: once with the Struct, once for the raw
# bytes. Read the raw bytes last so we are sure we leave the stream
# pointing right after the augmentation: the Struct may be incomplete
# (missing trailing fields) due to an unknown char: see the KeyError
# above.
offset = self.stream.tell()
struct = Struct('Augmentation_Data', *fields)
aug_dict.update(struct_parse(struct, self.stream, offset))
self.stream.seek(offset)
aug_bytes = self._read_augmentation_data(entry_structs)
return (aug_bytes, aug_dict)
def _read_augmentation_data(self, entry_structs: DWARFStructs) -> bytes:
""" Read augmentation data.
This assumes that the augmentation string starts with 'z', i.e. that
augmentation data is prefixed by a length field, which is not returned.
"""
if not self.for_eh_frame:
return b''
augmentation_data_length: int = struct_parse(
Struct('Dummy_Augmentation_Data',
entry_structs.Dwarf_uleb128('length')),
self.stream)['length']
return self.stream.read(augmentation_data_length)
def _parse_lsda_pointer(self, structs: DWARFStructs, stream_offset: int, encoding: int) -> int:
""" Parse bytes to get an LSDA pointer.
The basic encoding (lower four bits of the encoding) describes how the values are encoded in a CIE or an FDE.
The modifier (upper four bits of the encoding) describes how the raw values, after decoded using a basic
encoding, should be modified before using.
Ref: https://www.airs.com/blog/archives/460
"""
assert encoding != DW_EH_encoding_flags['DW_EH_PE_omit']
basic_encoding = encoding & 0x0f
modifier = encoding & 0xf0
formats = self._eh_encoding_to_field(structs)
ptr: int = struct_parse(
Struct('Augmentation_Data',
formats[basic_encoding]('LSDA_pointer')),
self.stream, stream_pos=stream_offset)['LSDA_pointer']
if modifier == DW_EH_encoding_flags['DW_EH_PE_absptr']:
pass
elif modifier == DW_EH_encoding_flags['DW_EH_PE_pcrel']:
ptr += self.address + stream_offset
else:
assert False, 'Unsupported encoding modifier for LSDA pointer: {:#x}'.format(modifier)
return ptr
def _parse_fde_header(self, entry_structs: DWARFStructs, offset: int) -> Container:
""" Compute a struct to parse the header of the current FDE.
"""
if not self.for_eh_frame:
return struct_parse(entry_structs.Dwarf_FDE_header, self.stream,
offset)
fields: list[Construct] = [entry_structs.Dwarf_initial_length('length'),
entry_structs.Dwarf_offset('CIE_pointer')]
# Parse the couple of header fields that are always here so we can
# fetch the corresponding CIE.
minimal_header = struct_parse(Struct('eh_frame_minimal_header',
*fields), self.stream, offset)
cie = self._parse_cie_for_fde(offset, minimal_header, entry_structs)
assert isinstance(cie, CFIEntry)
initial_location_offset = self.stream.tell()
# Try to parse the initial location. We need the initial location in
# order to create a meaningful FDE, so assume it's there. Omission does
# not seem to happen in practice.
encoding = cast(int, cie.augmentation_dict['FDE_encoding'])
assert encoding != DW_EH_encoding_flags['DW_EH_PE_omit']
basic_encoding = encoding & 0x0f
encoding_modifier = encoding & 0xf0
# Depending on the specified encoding, complete the header Struct
formats = self._eh_encoding_to_field(entry_structs)
fields.append(formats[basic_encoding]('initial_location'))
fields.append(formats[basic_encoding]('address_range'))
result = struct_parse(Struct('Dwarf_FDE_header', *fields),
self.stream, offset)
if encoding_modifier == 0:
pass
elif encoding_modifier == DW_EH_encoding_flags['DW_EH_PE_pcrel']:
# Start address is relative to the address of the
# "initial_location" field.
result['initial_location'] += (
self.address + initial_location_offset)
else:
assert False, 'Unsupported encoding: {:#x}'.format(encoding)
return result
@staticmethod
def _eh_encoding_to_field(
entry_structs: DWARFStructs,
) -> dict[int, Callable[[str], Construct]]:
"""
Return a mapping from basic encodings (DW_EH_encoding_flags) the
corresponding field constructors (for instance
entry_structs.Dwarf_uint32).
"""
return {
DW_EH_encoding_flags['DW_EH_PE_absptr']:
entry_structs.Dwarf_target_addr,
DW_EH_encoding_flags['DW_EH_PE_uleb128']:
entry_structs.Dwarf_uleb128,
DW_EH_encoding_flags['DW_EH_PE_udata2']:
entry_structs.Dwarf_uint16,
DW_EH_encoding_flags['DW_EH_PE_udata4']:
entry_structs.Dwarf_uint32,
DW_EH_encoding_flags['DW_EH_PE_udata8']:
entry_structs.Dwarf_uint64,
DW_EH_encoding_flags['DW_EH_PE_sleb128']:
entry_structs.Dwarf_sleb128,
DW_EH_encoding_flags['DW_EH_PE_sdata2']:
entry_structs.Dwarf_int16,
DW_EH_encoding_flags['DW_EH_PE_sdata4']:
entry_structs.Dwarf_int32,
DW_EH_encoding_flags['DW_EH_PE_sdata8']:
entry_structs.Dwarf_int64,
}
def instruction_name(opcode: DW_CFA) -> str:
""" Given an opcode, return the instruction name.
"""
warn("Switch to DW_CFA.FQN", DeprecationWarning, stacklevel=2)
return opcode.FQN
class CallFrameInstruction:
""" An instruction in the CFI section. opcode is the instruction
opcode, numeric - as it appears in the section. args is a list of
arguments (including arguments embedded in the low bits of some
instructions, when applicable), decoded from the stream.
"""
def __init__(self, opcode: DW_CFA, args: list[Any]) -> None:
self.opcode = opcode
self.args = args
def __repr__(self) -> str:
return f"{self.opcode.FQN} ({self.opcode.value:#02x}): {self.args}"
class CFIEntry:
""" A common base class for CFI entries.
Contains a header and a list of instructions (CallFrameInstruction).
offset: the offset of this entry from the beginning of the section
cie: for FDEs, a CIE pointer is required
augmentation_dict: Augmentation data as a parsed struct (dict): see
CallFrameInfo._parse_cie_augmentation and
http://www.airs.com/blog/archives/460.
augmentation_bytes: Augmentation data as a chain of bytes: see
CallFrameInfo._parse_cie_augmentation and
http://www.airs.com/blog/archives/460.
"""
def __init__(
self,
header: Container,
structs: DWARFStructs,
instructions: list[CallFrameInstruction],
offset: int,
augmentation_dict: Augmentation | None = None,
augmentation_bytes: bytes | None = b'',
cie: CIE | None = None,
) -> None:
self.header = header
self.structs = structs
self.instructions = instructions
self.offset = offset
self.cie = cie
self.augmentation_dict = augmentation_dict or {}
self.augmentation_bytes = augmentation_bytes
def get_decoded(self) -> DecodedCallFrameTable:
""" Decode the CFI contained in this entry and return a
DecodedCallFrameTable object representing it. See the documentation
of that class to understand how to interpret the decoded table.
"""
return self._decode_CFI_table
def __getitem__(self, name: str) -> Any:
""" Implement dict-like access to header entries
"""
return self.header[name]
@cached_property
def _decode_CFI_table(self) -> DecodedCallFrameTable:
""" Decode the instructions contained in the given CFI entry and return
a DecodedCallFrameTable.
"""
last_line_in_CIE: Line | None = None
if isinstance(self, CIE):
# For a CIE, initialize cur_line to an "empty" line
cie = self
cur_line: Line = dict(pc=0, cfa=CFARule(reg=None, offset=0))
reg_order = []
else: # FDE
# For a FDE, we need to decode the attached CIE first, because its
# decoded table is needed. Its "initial instructions" describe a
# line that serves as the base (first) line in the FDE's table.
assert self.cie is not None
cie = self.cie
cie_decoded_table = cie.get_decoded()
pc = self['initial_location']
if cie_decoded_table.table:
last_line_in_CIE = copy.copy(cie_decoded_table.table[-1])
cur_line = dict(last_line_in_CIE, pc=pc)
else:
cur_line = dict(cfa=CFARule(reg=None, offset=0), pc=pc)
reg_order = copy.copy(cie_decoded_table.reg_order)
table: list[Line] = []
# Keeps a stack for the use of DW_CFA.{remember|restore}_state
# instructions.
line_stack: list[Line] = []
def _add_to_order(regnum: int) -> None:
# DW_CFA.restore and others remove registers from cur_line,
# but they stay in reg_order. Avoid duplicates.
if regnum not in reg_order:
reg_order.append(regnum)
for instr in self.instructions:
# Throughout this loop, cur_line is the current line. Some
# instructions add it to the table, but most instructions just
# update it without adding it to the table.
match instr.opcode:
case DW_CFA.set_loc:
table.append(copy.copy(cur_line))
cur_line['pc'] = instr.args[0]
case DW_CFA.advance_loc1 | DW_CFA.advance_loc2 | DW_CFA.advance_loc4 | DW_CFA.advance_loc:
table.append(copy.copy(cur_line))
cur_line['pc'] += instr.args[0] * cie['code_alignment_factor']
case DW_CFA.def_cfa:
cur_line['cfa'] = CFARule(
reg=instr.args[0],
offset=instr.args[1])
case DW_CFA.def_cfa_sf:
cur_line['cfa'] = CFARule(
reg=instr.args[0],
offset=instr.args[1] * cie['code_alignment_factor'])
case DW_CFA.def_cfa_register:
cur_line['cfa'] = CFARule(
reg=instr.args[0],
offset=cur_line['cfa'].offset)
case DW_CFA.def_cfa_offset:
cur_line['cfa'] = CFARule(
reg=cur_line['cfa'].reg,
offset=instr.args[0])
case DW_CFA.def_cfa_offset_sf:
cur_line['cfa'] = CFARule(
reg=cur_line['cfa'].reg,
offset=instr.args[0] * cie['data_alignment_factor'])
case DW_CFA.def_cfa_expression:
cur_line['cfa'] = CFARule(expr=instr.args[0])
case DW_CFA.undefined:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(RegisterRule.UNDEFINED)
case DW_CFA.same_value:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(RegisterRule.SAME_VALUE)
case DW_CFA.offset | DW_CFA.offset_extended | DW_CFA.offset_extended_sf:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(
RegisterRule.OFFSET,
instr.args[1] * cie['data_alignment_factor'])
case DW_CFA.val_offset | DW_CFA.val_offset_sf:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(
RegisterRule.VAL_OFFSET,
instr.args[1] * cie['data_alignment_factor'])
case DW_CFA.register:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(
RegisterRule.REGISTER,
instr.args[1])
case DW_CFA.expression:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(
RegisterRule.EXPRESSION,
instr.args[1])
case DW_CFA.val_expression:
_add_to_order(instr.args[0])
cur_line[instr.args[0]] = RegisterRule(
RegisterRule.VAL_EXPRESSION,
instr.args[1])
case DW_CFA.restore | DW_CFA.restore_extended as cfa:
_add_to_order(instr.args[0])
dwarf_assert(
isinstance(self, FDE),
f'{cfa.FQN} instruction must be in a FDE')
assert last_line_in_CIE is not None
if instr.args[0] in last_line_in_CIE:
cur_line[instr.args[0]] = last_line_in_CIE[instr.args[0]]
else:
cur_line.pop(instr.args[0], None)
case DW_CFA.remember_state:
line_stack.append(copy.deepcopy(cur_line))
case DW_CFA.restore_state:
pc = cur_line['pc']
cur_line = line_stack.pop()
cur_line['pc'] = pc
case DW_CFA.nop | DW_CFA.AARCH64_negate_ra_state:
pass
case _:
dwarf_assert(False, f"Unknown CFI opcode: {instr.opcode:#02x}")
# The current line is appended to the table after all instructions
# have ended, if there were instructions.
if cur_line['cfa'].reg is not None or len(cur_line) > 2:
table.append(cur_line)
return DecodedCallFrameTable(table=table, reg_order=reg_order)
# A CIE and FDE have exactly the same functionality, except that a FDE has
# a pointer to its CIE. The functionality was wholly encapsulated in CFIEntry,
# so the CIE and FDE classes exists separately for identification (instead
# of having an explicit "entry_type" field in CFIEntry).
#
class CIE(CFIEntry):
pass
class FDE(CFIEntry):
def __init__(
self,
header: Container,
structs: DWARFStructs,
instructions: list[CallFrameInstruction],
offset: int,
augmentation_bytes: bytes | None = None,
cie: CIE | None = None,
lsda_pointer: int | None = None,
) -> None:
super().__init__(header, structs, instructions, offset, augmentation_bytes=augmentation_bytes, cie=cie)
self.lsda_pointer = lsda_pointer
class ZERO:
""" End marker for the sequence of CIE/FDE.
This is specific to `.eh_frame` sections: this kind of entry does not exist
in pure DWARF. `readelf` displays these as "ZERO terminator", hence the
class name.
"""
def __init__(self, offset: int) -> None:
self.offset = offset
class RegisterRule:
""" Register rules are used to find registers in call frames. Each rule
consists of a type (enumeration following DWARFv3 section 6.4.1)
and an optional argument to augment the type.
"""
UNDEFINED = 'UNDEFINED'
SAME_VALUE = 'SAME_VALUE'
OFFSET = 'OFFSET'
VAL_OFFSET = 'VAL_OFFSET'
REGISTER = 'REGISTER'
EXPRESSION = 'EXPRESSION'
VAL_EXPRESSION = 'VAL_EXPRESSION'
ARCHITECTURAL = 'ARCHITECTURAL'
def __init__(self, type: str, arg: int | ListContainer | None = None) -> None:
self.type = type
self.arg = arg
def __repr__(self) -> str:
return 'RegisterRule(%s, %s)' % (self.type, self.arg)
class CFARule:
""" A CFA rule is used to compute the CFA for each location. It either
consists of a register+offset, or a DWARF expression.
"""
def __init__(
self,
reg: int | None = None,
offset: int | None = None,
expr: ListContainer | None = None,
) -> None:
self.reg = reg
self.offset = offset
self.expr = expr
def __repr__(self) -> str:
return 'CFARule(reg=%s, offset=%s, expr=%s)' % (
self.reg, self.offset, self.expr)
# Represents the decoded CFI for an entry, which is just a large table,
# according to DWARFv3 section 6.4.1
#
# DecodedCallFrameTable is a simple named tuple to group together the table
# and the register appearance order.
#
# table:
#
# A list of dicts that represent "lines" in the decoded table. Each line has
# some special dict entries: 'pc' for the location/program counter (LOC),
# and 'cfa' for the CFARule to locate the CFA on that line.
# The other entries are keyed by register numbers with RegisterRule values,
# and describe the rules for these registers.
#
# reg_order:
#
# A list of register numbers that are described in the table by the order of
# their appearance.
#
class DecodedCallFrameTable(NamedTuple):
table: list[Line]
reg_order: list[int]
@@ -0,0 +1,300 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/compileunit.py
#
# DWARF compile unit
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from bisect import bisect_right
from functools import cached_property
from typing import TYPE_CHECKING, Any
from .die import DIE
from ..common.utils import dwarf_assert
if TYPE_CHECKING:
from collections.abc import Iterator
from ..construct.lib.container import Container
from .abbrevtable import AbbrevTable
from .dwarfinfo import DWARFInfo
from .structs import DWARFStructs
class CompileUnit:
""" A DWARF compilation unit (CU).
A normal compilation unit typically represents the text and data
contributed to an executable by a single relocatable object file.
It may be derived from several source files,
including pre-processed "include files"
Serves as a container and context to DIEs that describe objects and code
belonging to a compilation unit.
CU header entries can be accessed as dict keys from this object, i.e.
cu = CompileUnit(...)
cu['version'] # version field of the CU header
To get the top-level DIE describing the compilation unit, call the
get_top_DIE method.
"""
def __init__(
self,
header: Container,
dwarfinfo: DWARFInfo,
structs: DWARFStructs,
cu_offset: int,
cu_die_offset: int,
) -> None:
""" header:
CU header for this compile unit
dwarfinfo:
The DWARFInfo context object which created this one
structs:
A DWARFStructs instance suitable for this compile unit
cu_offset:
Offset in the stream to the beginning of this CU (its header)
cu_die_offset:
Offset in the stream of the top DIE of this CU
"""
self.dwarfinfo = dwarfinfo
self.header = header
self.structs = structs
self.cu_offset = cu_offset
self.cu_die_offset = cu_die_offset
# A list of DIEs belonging to this CU.
# This list is lazily constructed as DIEs are iterated over.
self._dielist: list[DIE] = []
# A list of file offsets, corresponding (by index) to the DIEs
# in `self._dielist`. This list exists separately from
# `self._dielist` to make it binary searchable, enabling the
# DIE population strategy used in `iter_DIE_children`.
# Like `self._dielist`, this list is lazily constructed
# as DIEs are iterated over.
self._diemap: list[int] = []
def dwarf_format(self) -> int:
""" Get the DWARF format (32 or 64) for this CU
"""
return self.structs.dwarf_format
def get_abbrev_table(self) -> AbbrevTable:
""" Get the abbreviation table (AbbrevTable object) for this CU
"""
return self._abbrev_table
@cached_property
def _abbrev_table(self) -> AbbrevTable:
return self.dwarfinfo.get_abbrev_table(self['debug_abbrev_offset'])
def get_top_DIE(self) -> DIE:
""" Get the top DIE (which is either a DW_TAG_compile_unit or
DW_TAG_partial_unit) of this CU
"""
# Note that a top DIE always has minimal offset and is therefore
# at the beginning of our lists, so no bisect is required.
if self._diemap:
return self._dielist[0]
assert self.dwarfinfo.debug_info_sec is not None
top = DIE(
cu=self,
stream=self.dwarfinfo.debug_info_sec.stream,
offset=self.cu_die_offset)
self._dielist.insert(0, top)
self._diemap.insert(0, self.cu_die_offset)
top._translate_indirect_attributes() # Can't translate indirect attributes until the top DIE has been parsed to the end
return top
def has_top_DIE(self) -> bool:
""" Returns whether the top DIE in this CU has already been parsed and cached.
No parsing on demand!
"""
return bool(self._diemap)
@property
def size(self) -> int:
return self['unit_length'] + self.structs.initial_length_field_size()
def get_DIE_from_refaddr(self, refaddr: int) -> DIE:
""" Obtain a DIE contained in this CU from a reference.
refaddr:
The offset into the .debug_info section, which must be
contained in this CU or a DWARFError will be raised.
When using a reference class attribute with a form that is
relative to the compile unit, add unit add the compile unit's
.cu_addr before calling this function.
"""
# All DIEs are after the cu header and within the unit
dwarf_assert(
self.cu_die_offset <= refaddr < self.cu_offset + self.size,
'refaddr %s not in DIE range of CU %s' % (refaddr, self.cu_offset))
return self._get_cached_DIE(refaddr)
def iter_DIEs(self) -> Iterator[DIE]:
""" Iterate over all the DIEs in the CU, in order of their appearance.
Note that null DIEs will also be returned.
"""
assert self.dwarfinfo.debug_info_sec is not None
stm = self.dwarfinfo.debug_info_sec.stream
pos = self.cu_die_offset
end_pos = self.cu_offset + self.size
die = self.get_top_DIE()
yield die
pos += die.size
parent: DIE | None = die
i = 1
while pos < end_pos:
if i < len(self._diemap) and self._diemap[i] == pos: # DIE already cached
die = self._dielist[i]
else:
die = DIE(self, stm, pos)
self._dielist.insert(i, die)
self._diemap.insert(i, pos)
i += 1
die._parent = parent
if die.tag is None and parent is not None:
parent._terminator = die
parent = parent._parent
if die.has_children:
parent = die
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
# Falls back to subtree traversal in the supplemental DWARF. Any way to streamline that too?
supp_die = die.get_DIE_from_attribute('DW_AT_import')
yield from supp_die.cu._iter_DIE_subtree(supp_die)
else:
yield die
pos += die.size
def iter_DIE_children(self, die: DIE) -> Iterator[DIE]:
""" Given a DIE, yields either its children, without null DIE list
terminator, or nothing, if that DIE has no children.
The null DIE terminator is saved in that DIE when iteration ended.
"""
if not die.has_children:
return
# `cur_offset` tracks the stream offset of the next DIE to yield
# as we iterate over our children,
cur_offset = die.offset + die.size
while True:
child = self._get_cached_DIE(cur_offset)
child.set_parent(die)
if child.is_null():
die._terminator = child
return
yield child
if not child.has_children:
cur_offset += child.size
elif "DW_AT_sibling" in child.attributes:
sibling = child.attributes["DW_AT_sibling"]
if sibling.form in ('DW_FORM_ref1', 'DW_FORM_ref2',
'DW_FORM_ref4', 'DW_FORM_ref8',
'DW_FORM_ref', 'DW_FORM_ref_udata'):
cur_offset = sibling.value + self.cu_offset
elif sibling.form == 'DW_FORM_ref_addr':
cur_offset = sibling.value
else:
raise NotImplementedError('sibling in form %s' % sibling.form)
else:
# If no DW_AT_sibling attribute is provided by the producer
# then the whole child subtree must be parsed to find its next
# sibling. There is one zero byte representing null DIE
# terminating children list. It is used to locate child subtree
# bounds.
# If children are not parsed yet, this instruction will manage
# to recursive call of this function which will result in
# setting of `_terminator` attribute of the `child`.
if child._terminator is None:
for _ in self.iter_DIE_children(child):
pass
assert child._terminator is not None
cur_offset = child._terminator.offset + child._terminator.size
#------ PRIVATE ------#
def __getitem__(self, name: str) -> Any:
""" Implement dict-like access to header entries
"""
return self.header[name]
def _iter_DIE_subtree(self, die: DIE) -> Iterator[DIE]:
""" Given a DIE, this yields it with its subtree including null DIEs
(child list terminators).
"""
# If the die is an imported unit, replace it with what it refers to if
# we can
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
die = die.get_DIE_from_attribute('DW_AT_import')
yield die
if die.has_children:
for c in die.iter_children():
yield from die.cu._iter_DIE_subtree(c)
assert die._terminator is not None
yield die._terminator
def _get_cached_DIE(self, offset: int) -> DIE:
""" Given a DIE offset, look it up in the cache. If not present,
parse the DIE and insert it into the cache.
offset:
The offset of the DIE in the debug_info section to retrieve.
The stream reference is copied from the top DIE. The top die will
also be parsed and cached if needed.
See also get_DIE_from_refaddr(self, refaddr).
"""
# The top die must be in the cache if any DIE is in the cache.
# The stream is the same for all DIEs in this CU, so populate
# the top DIE and obtain a reference to its stream.
top_die_stream = self.get_top_DIE().stream
# `offset` is the offset in the stream of the DIE we want to return.
# The map is maintined as a parallel array to the list. We call
# bisect each time to ensure new DIEs are inserted in the correct
# order within both `self._dielist` and `self._diemap`.
i = bisect_right(self._diemap, offset)
# Note that `self._diemap` cannot be empty because a the top DIE
# was inserted by the call to .get_top_DIE(). Also it has the minimal
# offset, so the bisect_right insert point will always be at least 1.
if offset == self._diemap[i - 1]:
die = self._dielist[i - 1]
else:
die = DIE(cu=self, stream=top_die_stream, offset=offset)
self._dielist.insert(i, die)
self._diemap.insert(i, offset)
return die
@@ -0,0 +1,277 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/constants.py
#
# Constants and flags
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import Self # 3.11+
class _IntEnum(int, Enum): # Py3.11: enum.ReprEnum
def __repr__(self) -> str:
return int.__str__(self.value)
@property # Py3.11+: enum.property
def FQN(self) -> str:
return f"{self.__class__.__name__}_{self.name}"
class DW_INL(_IntEnum):
"""Inline codes."""
not_inlined = 0
inlined = 1
declared_not_inlined = 2
declared_inlined = 3
class DW_LANG(_IntEnum):
"""Source languages."""
C89 = 0x0001
C = 0x0002
Ada83 = 0x0003
C_plus_plus = 0x0004
Cobol74 = 0x0005
Cobol85 = 0x0006
Fortran77 = 0x0007
Fortran90 = 0x0008
Pascal83 = 0x0009
Modula2 = 0x000a
Java = 0x000b
C99 = 0x000c
Ada95 = 0x000d
Fortran95 = 0x000e
PLI = 0x000f
ObjC = 0x0010
ObjC_plus_plus = 0x0011
UPC = 0x0012
D = 0x0013
Python = 0x0014
OpenCL = 0x0015
Go = 0x0016
Modula3 = 0x0017
Haskell = 0x0018
C_plus_plus_03 = 0x0019
C_plus_plus_11 = 0x001a
OCaml = 0x001b
Rust = 0x001c
C11 = 0x001d
Swift = 0x001e
Julia = 0x001f
Dylan = 0x0020
C_plus_plus_14 = 0x0021
Fortran03 = 0x0022
Fortran08 = 0x0023
RenderScript = 0x0024
BLISS = 0x0025
Mips_Assembler = 0x8001
Upc = 0x8765
HP_Bliss = 0x8003
HP_Basic91 = 0x8004
HP_Pascal91 = 0x8005
HP_IMacro = 0x8006
HP_Assembler = 0x8007
GOOGLE_RenderScript = 0x8e57
BORLAND_Delphi = 0xb000
class DW_ATE(_IntEnum):
"""Encodings."""
void = 0x0
address = 0x1
boolean = 0x2
complex_float = 0x3
float = 0x4
signed = 0x5
signed_char = 0x6
unsigned = 0x7
unsigned_char = 0x8
imaginary_float = 0x9
packed_decimal = 0xa
numeric_string = 0xb
edited = 0xc
signed_fixed = 0xd
unsigned_fixed = 0xe
decimal_float = 0xf
UTF = 0x10
UCS = 0x11
ASCII = 0x12
lo_user = 0x80
hi_user = 0xff
HP_float80 = 0x80
HP_complex_float80 = 0x81
HP_float128 = 0x82
HP_complex_float128 = 0x83
HP_floathpintel = 0x84
HP_imaginary_float80 = 0x85
HP_imaginary_float128 = 0x86
class DW_ACCESS(_IntEnum):
"""Access."""
public = 1
protected = 2
private = 3
class DW_VIS(_IntEnum):
"""Visibility."""
local = 1
exported = 2
qualified = 3
class DW_VIRTUALITY(_IntEnum):
"""Virtuality."""
none = 0
virtual = 1
pure_virtual = 2
class DW_ID(_IntEnum):
"""ID cases."""
case_sensitive = 0
up_case = 1
down_case = 2
case_insensitive = 3
class DW_CC(_IntEnum):
"""Calling conventions."""
normal = 0x1
program = 0x2
nocall = 0x3
pass_by_reference = 0x4
pass_by_valuee = 0x5
class DW_ORD(_IntEnum):
"""Orderings."""
row_major = 0
col_major = 1
class DW_LNS(_IntEnum):
"""Line program opcodes."""
copy = 0x01
advance_pc = 0x02
advance_line = 0x03
set_file = 0x04
set_column = 0x05
negate_stmt = 0x06
set_basic_block = 0x07
const_add_pc = 0x08
fixed_advance_pc = 0x09
set_prologue_end = 0x0a
set_epilogue_begin = 0x0b
set_isa = 0x0c
class DW_LNE(_IntEnum):
"""Line program extended opcodes."""
end_sequence = 0x01
set_address = 0x02
define_file = 0x03
set_discriminator = 0x04
lo_user = 0x80
hi_user = 0xff
class DW_LNCT(_IntEnum):
"""Line program header content types."""
path = 0x01
directory_index = 0x02
timestamp = 0x03
size = 0x04
MD5 = 0x05
lo_user = 0x2000
LLVM_source = 0x2001
LLVM_is_MD5 = 0x2002
hi_user = 0x3fff
class DW_CFA(_IntEnum):
"""
Call frame instructions.
Note that the first 3 instructions have the so-called "primary opcode"
(as described in DWARFv3 7.23), so only their highest 2 bits take part
in the opcode decoding. They are kept as constants with the low bits masked
out, and the callframe module knows how to handle this.
The other instructions use an "extended opcode" encoded just in the low 6
bits, with the high 2 bits, so these constants are exactly as they would
appear in an actual file.
"""
advance_loc = 0b01000000
offset = 0b10000000
restore = 0b11000000
nop = 0x00
set_loc = 0x01
advance_loc1 = 0x02
advance_loc2 = 0x03
advance_loc4 = 0x04
offset_extended = 0x05
restore_extended = 0x06
undefined = 0x07
same_value = 0x08
register = 0x09
remember_state = 0x0a
restore_state = 0x0b
def_cfa = 0x0c
def_cfa_register = 0x0d
def_cfa_offset = 0x0e
def_cfa_expression = 0x0f
expression = 0x10
offset_extended_sf = 0x11
def_cfa_sf = 0x12
def_cfa_offset_sf = 0x13
val_offset = 0x14
val_offset_sf = 0x15
val_expression = 0x16
AARCH64_negate_ra_state = 0x2d
GNU_window_save = 0x2d # Used on SPARC, not in the corpus
GNU_args_size = 0x2e
@classmethod
def parse_raw_opcode(cls, /, opcode: int, *, __MASK: int = 0b11_00_0000) -> tuple[Self, int] | tuple[Self]:
"""Extract primary or extended opcode from raw byte."""
if primary := opcode & __MASK:
return (cls(primary), opcode & ~__MASK)
return (cls(opcode),)
class DW_UT(_IntEnum):
"""
Compilation unit types.
DWARFv5 introduces the "unit_type" field to each CU header, allowing
individual CUs to indicate whether they're complete, partial, and so forth.
See DWARFv5 3.1 ("Unit Entries") and 7.5.1 ("Unit Headers").
"""
compile = 0x01
type = 0x02
partial = 0x03
skeleton = 0x04
split_compile = 0x05
split_type = 0x06
lo_user = 0x80
hi_user = 0xff
# Add back legacy names `DW_UT_type = DW_UT.type` for `from .constants import *`.
# These are invisible to typing as the members are added dynamically by code!
# Use __members__ to also add aliases like DW_CFA.{AARCH64_negate_ra_state,GNU_window_save}.
globals().update({
f"{enum_name}_{member_name}": member.value
for enum_name, enum in globals().items()
if enum_name.startswith("DW_") and issubclass(enum, _IntEnum)
for member_name, member in enum.__members__.items()
})
@@ -0,0 +1,249 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/datatype_cpp.py
#
# First draft at restoring the source level name a C/C++ datatype
# from DWARF data. Aiming at compatibility with llvm-dwarfdump v15.
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from typing import TYPE_CHECKING
from ..common.utils import bytes2str
if TYPE_CHECKING:
from .die import DIE
cpp_symbols = dict(
pointer = "*",
reference = "&",
const = "const",
volatile = "volatile")
def describe_cpp_datatype(var_die: DIE) -> str:
return str(parse_cpp_datatype(var_die))
def parse_cpp_datatype(var_die: DIE) -> TypeDesc:
"""Given a DIE that describes a variable, a parameter, or a member
with DW_AT_type in it, tries to return the C++ datatype as a string
Returns a TypeDesc.
Does not follow typedefs, doesn't resolve array element types
or struct members. Not good for a debugger.
"""
t = TypeDesc()
if 'DW_AT_type' not in var_die.attributes:
t.tag = ''
return t
type_die = var_die.get_DIE_from_attribute('DW_AT_type')
mods: list[str] = []
# Unlike readelf, dwarfdump doesn't chase typedefs
while type_die.tag in ('DW_TAG_const_type', 'DW_TAG_volatile_type', 'DW_TAG_pointer_type', 'DW_TAG_reference_type'):
modifier = _strip_type_tag(type_die) # const/volatile/reference/pointer
mods.insert(0, modifier)
if 'DW_AT_type' not in type_die.attributes: # void* is encoded as a pointer to nothing
t.name = t.tag = "void"
t.modifiers = tuple(mods)
return t
type_die = type_die.get_DIE_from_attribute('DW_AT_type')
# From this point on, type_die doesn't change
t.tag = _strip_type_tag(type_die)
t.modifiers = tuple(mods)
if t.tag in ('ptr_to_member', 'subroutine'):
if t.tag == 'ptr_to_member':
ptr_prefix = DIE_name(type_die.get_DIE_from_attribute('DW_AT_containing_type')) + "::"
type_die = type_die.get_DIE_from_attribute('DW_AT_type')
elif "DW_AT_object_pointer" in type_die.attributes: # Older compiler... Subroutine, but with an object pointer
ptr_prefix = DIE_name(DIE_type(DIE_type(type_die.get_DIE_from_attribute('DW_AT_object_pointer')))) + "::"
else: # Not a pointer to member
ptr_prefix = ''
if t.tag == 'subroutine':
params = ", ".join(
format_function_param(p, p)
for p in type_die.iter_children()
if p.tag in ("DW_TAG_formal_parameter", "DW_TAG_unspecified_parameters") and 'DW_AT_artificial' not in p.attributes
)
if 'DW_AT_type' in type_die.attributes:
datatype = parse_cpp_datatype(type_die)
is_pointer = datatype.modifiers and datatype.modifiers[-1] == 'pointer'
retval_type = str(datatype)
if not is_pointer:
retval_type += " "
else:
retval_type = "void "
if mods and mods[-1] == 'pointer':
mods.pop()
t.modifiers = tuple(mods)
t.name = "%s(%s*)(%s)" % (retval_type, ptr_prefix, params)
else:
t.name = "%s(%s)" % (retval_type, params)
return t
elif DIE_is_ptr_to_member_struct(type_die):
dt = parse_cpp_datatype(next(type_die.iter_children())) # The first element is pfn, a function pointer with a this
dt.modifiers = tuple(dt.modifiers[:-1]) # Pop the extra pointer
dt.tag = "ptr_to_member_type" # Not a function pointer per se
return dt
elif t.tag == 'array':
t.dimensions = tuple(_array_subtype_size(sub)
for sub
in type_die.iter_children()
if sub.tag == 'DW_TAG_subrange_type')
t.name = describe_cpp_datatype(type_die)
return t
# Now the nonfunction types
# Blank name is sometimes legal (unnamed unions, etc)
t.name = safe_DIE_name(type_die, t.tag + " ")
# Check the nesting - important for parameters
parent = type_die.get_parent()
scopes: list[str] = []
while parent and parent.tag in ('DW_TAG_class_type', 'DW_TAG_structure_type', 'DW_TAG_union_type', 'DW_TAG_namespace'):
scopes.insert(0, safe_DIE_name(parent, _strip_type_tag(parent) + " "))
# If unnamed scope, fall back to scope type - like "structure "
parent = parent.get_parent()
t.scopes = tuple(scopes)
return t
#--------------------------------------------------
class TypeDesc:
""" Encapsulates a description of a datatype, as parsed from DWARF DIEs.
Not enough to display the variable in the debugger, but enough
to produce a type description string similar to those of llvm-dwarfdump.
name - name for primitive datatypes, element name for arrays, the
whole name for functions and function pouinters
modifiers - a collection of "const"/"pointer"/"reference", from the
chain of DIEs preceeding the real type DIE
scopes - a collection of struct/class/namespace names, parents of the
real type DIE
tag - the tag of the real type DIE, stripped of initial DW_TAG_ and
final _type
dimensions - the collection of array dimensions, if the type is an
array. -1 means an array of unknown dimension.
"""
def __init__(self) -> None:
self.name: str
self.modifiers: tuple[str, ...] = () # Reads left to right
self.scopes: tuple[str, ...] = () # Reads left to right
self.tag: str | None = None
self.dimensions: tuple[int, ...] | None = None
def __str__(self) -> str:
# Some reference points from dwarfdump:
# const->pointer->const->char = const char *const
# const->reference->const->int = const const int &
# const->reference->int = const int &
name = str(self.name)
mods = self.modifiers
parts = []
# Initial const/volatile applies to the var ifself, other consts apply to the pointee
if mods and mods[0] in ('const', 'volatile'):
parts.append(mods[0])
mods = mods[1:]
# ref->const in the end, const goes in front
if mods[-2:] == ("reference", "const"):
parts.append("const")
mods = mods[0:-1]
if self.scopes:
name = '::'.join(self.scopes)+'::' + name
parts.append(name)
if mods:
parts.append("".join(cpp_symbols[mod] for mod in mods))
if self.dimensions:
dims = "".join('[%s]' % (str(dim) if dim > 0 else '',)
for dim in self.dimensions)
else:
dims = ''
return " ".join(parts)+dims
def DIE_name(die: DIE) -> str:
return bytes2str(die.attributes['DW_AT_name'].value)
def safe_DIE_name(die: DIE, default: str = '') -> str:
return bytes2str(die.attributes['DW_AT_name'].value) if 'DW_AT_name' in die.attributes else default
def DIE_type(die: DIE) -> DIE:
return die.get_DIE_from_attribute("DW_AT_type")
class ClassDesc:
def __init__(self) -> None:
self.scopes: tuple[str, ...] = ()
self.const_member: bool = False
def get_class_spec_if_member(func_spec: DIE, the_func: DIE) -> ClassDesc | None:
if 'DW_AT_object_pointer' in the_func.attributes:
this_param = the_func.get_DIE_from_attribute('DW_AT_object_pointer')
this_type = parse_cpp_datatype(this_param)
class_spec = ClassDesc()
class_spec.scopes = (*this_type.scopes, this_type.name)
class_spec.const_member = any(("const", "pointer") == this_type.modifiers[i:i+2]
for i in range(len(this_type.modifiers))) # const -> pointer -> const for this arg of const
return class_spec
# Check the parent element chain - could be a class
parent = func_spec.get_parent()
scopes: list[str] = []
while parent and parent.tag in ("DW_TAG_class_type", "DW_TAG_structure_type", "DW_TAG_namespace"):
scopes.insert(0, DIE_name(parent))
parent = parent.get_parent()
if scopes:
cs = ClassDesc()
cs.scopes = tuple(scopes)
return cs
return None
def format_function_param(param_spec: DIE, param: DIE) -> str:
if param_spec.tag == 'DW_TAG_formal_parameter':
type = parse_cpp_datatype(param_spec)
return str(type)
else: # unspecified_parameters AKA variadic
return "..."
def DIE_is_ptr_to_member_struct(type_die: DIE) -> bool:
if type_die.tag == 'DW_TAG_structure_type':
members = tuple(die for die in type_die.iter_children() if die.tag == "DW_TAG_member")
return len(members) == 2 and safe_DIE_name(members[0]) == "__pfn" and safe_DIE_name(members[1]) == "__delta"
return False
def _strip_type_tag(die: DIE) -> str:
"""Given a DIE with DW_TAG_foo_type, returns foo"""
if not isinstance(die.tag, str): # User-defined tag
return ""
return die.tag[7:-5]
def _array_subtype_size(sub: DIE) -> int:
if 'DW_AT_upper_bound' in sub.attributes:
return sub.attributes['DW_AT_upper_bound'].value + 1
if 'DW_AT_count' in sub.attributes:
return sub.attributes['DW_AT_count'].value
else:
return -1
@@ -0,0 +1,721 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/descriptions.py
#
# Textual descriptions of the various values and enums of DWARF
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from collections import defaultdict
from typing import TYPE_CHECKING, Any, Literal, overload
from .constants import (
DW_ACCESS, DW_ATE, DW_CC, DW_CFA, DW_ID, DW_INL, DW_LANG, DW_ORD, DW_VIRTUALITY, DW_VIS,
)
from .dwarf_expr import DWARFExprParser
from .die import DIE
from ..common.utils import preserve_stream_pos, dwarf_assert, bytes2str
from .callframe import CIE, FDE
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Mapping
from typing import TypeVar
from .callframe import CallFrameInstruction, CFARule, CFIEntry, RegisterRule
from .die import AttributeValue
from .structs import DWARFStructs
_INT = TypeVar("_INT", bound=int)
def set_global_machine_arch(machine_arch: str) -> None:
global _MACHINE_ARCH
_MACHINE_ARCH = machine_arch
def describe_attr_value(attr: AttributeValue, die: DIE, section_offset: int) -> str:
""" Given an attribute attr, return the textual representation of its
value, suitable for tools like readelf.
To cover all cases, this function needs some extra arguments:
die: the DIE this attribute was extracted from
section_offset: offset in the stream of the section the DIE belongs to
"""
descr_func = _ATTR_DESCRIPTION_MAP[attr.form]
val_description = descr_func(attr, die, section_offset)
# For some attributes we can display further information
extra_info_func = _EXTRA_INFO_DESCRIPTION_MAP[attr.name]
extra_info = extra_info_func(attr, die, section_offset)
return str(val_description) + '\t' + extra_info
def describe_CFI_instructions(entry: CFIEntry) -> str:
""" Given a CFI entry (CIE or FDE), return the textual description of its
instructions.
"""
def _assert_FDE_instruction(instr: CallFrameInstruction) -> None:
dwarf_assert(
isinstance(entry, FDE),
'Unexpected instruction "%s" for a CIE' % instr)
def _full_reg_name(regnum: int) -> str:
regname = describe_reg_name(regnum, _MACHINE_ARCH, False)
if regname:
return 'r%s (%s)' % (regnum, regname)
else:
return 'r%s' % regnum
if isinstance(entry, CIE):
cie = entry
pc: int | None = None
else: # FDE
assert entry.cie is not None
cie = entry.cie
pc = entry['initial_location']
s = ''
for instr in entry.instructions:
name = instr.opcode.FQN
match instr.opcode:
case DW_CFA.offset | DW_CFA.offset_extended | DW_CFA.offset_extended_sf | DW_CFA.val_offset | DW_CFA.val_offset_sf:
s += ' %s: %s at cfa%+d\n' % (
name, _full_reg_name(instr.args[0]),
instr.args[1] * cie['data_alignment_factor'])
case DW_CFA.restore | DW_CFA.restore_extended | DW_CFA.undefined | DW_CFA.same_value | DW_CFA.def_cfa_register:
s += ' %s: %s\n' % (name, _full_reg_name(instr.args[0]))
case DW_CFA.register:
s += ' %s: %s in %s' % (
name, _full_reg_name(instr.args[0]),
_full_reg_name(instr.args[1]))
case DW_CFA.set_loc:
pc = instr.args[0]
assert pc is not None
s += ' %s: %08x\n' % (name, pc)
case DW_CFA.advance_loc1 | DW_CFA.advance_loc2 | DW_CFA.advance_loc4 | DW_CFA.advance_loc:
_assert_FDE_instruction(instr)
assert pc is not None
factored_offset: int = instr.args[0] * cie['code_alignment_factor']
s += ' %s: %s to %08x\n' % (
name, factored_offset, factored_offset + pc)
pc += factored_offset
case DW_CFA.remember_state | DW_CFA.restore_state | DW_CFA.nop | DW_CFA.AARCH64_negate_ra_state:
s += ' %s\n' % name
case DW_CFA.def_cfa:
s += ' %s: %s ofs %s\n' % (
name, _full_reg_name(instr.args[0]), instr.args[1])
case DW_CFA.def_cfa_sf:
s += ' %s: %s ofs %s\n' % (
name, _full_reg_name(instr.args[0]),
instr.args[1] * cie['data_alignment_factor'])
case DW_CFA.def_cfa_offset | DW_CFA.GNU_args_size:
s += ' %s: %s\n' % (name, instr.args[0])
case DW_CFA.def_cfa_offset_sf:
assert entry.cie is not None
s += ' %s: %s\n' % (name, instr.args[0]*entry.cie['data_alignment_factor'])
case DW_CFA.def_cfa_expression:
expr_dumper = ExprDumper(entry.structs)
# readelf output is missing a colon for DW_CFA.def_cfa_expression
s += ' %s (%s)\n' % (name, expr_dumper.dump_expr(instr.args[0]))
case DW_CFA.expression:
expr_dumper = ExprDumper(entry.structs)
s += ' %s: %s (%s)\n' % (
name, _full_reg_name(instr.args[0]),
expr_dumper.dump_expr(instr.args[1]))
case _:
s += ' %s: <??>\n' % name
return s
def describe_CFI_register_rule(rule: RegisterRule) -> str:
s = _DESCR_CFI_REGISTER_RULE_TYPE[rule.type]
if rule.type in ('OFFSET', 'VAL_OFFSET'):
assert isinstance(rule.arg, int)
s += '%+d' % rule.arg
elif rule.type == 'REGISTER':
assert isinstance(rule.arg, int)
reg = describe_reg_name(rule.arg)
s += reg
return s
def describe_CFI_CFA_rule(rule: CFARule) -> str:
if rule.expr:
return 'exp'
else:
assert isinstance(rule.reg, int)
assert isinstance(rule.offset, int)
return '%s%+d' % (describe_reg_name(rule.reg), rule.offset)
def describe_DWARF_expr(expr: Any, structs: DWARFStructs, cu_offset: int | None = None) -> str:
""" Textual description of a DWARF expression encoded in 'expr'.
structs should come from the entity encompassing the expression - it's
needed to be able to parse it correctly.
"""
# Since this function can be called a lot, initializing a fresh new
# ExprDumper per call is expensive. So a rudimentary caching scheme is in
# place to create only one such dumper per instance of structs.
cache_key = id(structs)
if cache_key not in _DWARF_EXPR_DUMPER_CACHE:
_DWARF_EXPR_DUMPER_CACHE[cache_key] = \
ExprDumper(structs)
dwarf_expr_dumper = _DWARF_EXPR_DUMPER_CACHE[cache_key]
return '(' + dwarf_expr_dumper.dump_expr(expr, cu_offset) + ')'
@overload
def describe_reg_name(regnum: int, machine_arch: str | None, default: Literal[False]) -> str | None: ...
@overload
def describe_reg_name(regnum: int, machine_arch: str | None = ..., default: Literal[True] = ...) -> str: ...
def describe_reg_name(regnum: int, machine_arch: str | None = None, default: bool = True) -> str | None:
""" Provide a textual description for a register name, given its serial
number. The number is expected to be valid.
"""
if machine_arch is None:
machine_arch = _MACHINE_ARCH
if machine_arch == 'x86':
return _REG_NAMES_x86[regnum]
elif machine_arch == 'x64':
return _REG_NAMES_x64[regnum]
elif machine_arch == 'AArch64':
return _REG_NAMES_AArch64[regnum]
elif default:
return 'r%s' % regnum
else:
return None
def describe_form_class(form: str) -> str | None:
"""For a given form name, determine its value class.
For example, given 'DW_FORM.data1' returns 'constant'.
For some forms, like DW_FORM.indirect and DW_FORM.sec_offset, the class is
not hard-coded and extra information is required. For these, None is
returned.
"""
return _FORM_CLASS[form]
#-------------------------------------------------------------------------------
# The machine architecture. Set globally via set_global_machine_arch
#
_MACHINE_ARCH: str | None = None
# Implements the alternative format of readelf: lowercase hex, prefixed with 0x unless 0
def _format_hex(n: int) -> str:
return '0x%x' % n if n != 0 else '0'
def _describe_attr_ref(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return '<%s>' % _format_hex(attr.value + die.cu.cu_offset)
def _describe_attr_ref_sig8(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return 'signature: %s' % _format_hex(attr.value)
def _describe_attr_value_passthrough(
attr: AttributeValue,
die: DIE,
section_offset: int,
) -> str | int:
return attr.value
def _describe_attr_hex(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return '%s' % _format_hex(attr.value)
def _describe_attr_hex_addr(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return '<%s>' % _format_hex(attr.value)
def _describe_attr_split_64bit(attr: AttributeValue, die: DIE, section_offset: int) -> str:
low_word = attr.value & 0xFFFFFFFF
high_word = (attr.value >> 32) & 0xFFFFFFFF
return '%s %s' % (_format_hex(low_word), _format_hex(high_word))
def _describe_attr_strp(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return '(indirect string, offset: %s): %s' % (
_format_hex(attr.raw_value), bytes2str(attr.value))
def _describe_attr_line_strp(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return '(indirect line string, offset: %s): %s' % (
_format_hex(attr.raw_value), bytes2str(attr.value))
def _describe_attr_string(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return bytes2str(attr.value)
def _describe_attr_debool(attr: AttributeValue, die: DIE, section_offset: int) -> str:
""" To be consistent with readelf, generate 1 for True flags, 0 for False
flags.
"""
return '1' if attr.value else '0'
def _describe_attr_present(attr: AttributeValue, die: DIE, section_offset: int) -> str:
""" Some forms may simply mean that an attribute is present,
without providing any value.
"""
return '1'
def _describe_attr_block(attr: AttributeValue, die: DIE, section_offset: int) -> str:
s = '%s byte block: ' % len(attr.value)
s += ' '.join('%x' % item for item in attr.value) + ' '
return s
_ATTR_DESCRIPTION_MAP = defaultdict(
lambda: _describe_attr_value_passthrough, # default_factory
DW_FORM_ref1=_describe_attr_ref,
DW_FORM_ref2=_describe_attr_ref,
DW_FORM_ref4=_describe_attr_ref,
DW_FORM_ref8=_describe_attr_split_64bit,
DW_FORM_ref_udata=_describe_attr_ref,
DW_FORM_ref_addr=_describe_attr_hex_addr,
DW_FORM_data4=_describe_attr_hex,
DW_FORM_data8=_describe_attr_hex,
DW_FORM_addr=_describe_attr_hex,
DW_FORM_sec_offset=_describe_attr_hex,
DW_FORM_flag=_describe_attr_debool,
DW_FORM_data1=_describe_attr_value_passthrough,
DW_FORM_data2=_describe_attr_value_passthrough,
DW_FORM_sdata=_describe_attr_value_passthrough,
DW_FORM_udata=_describe_attr_value_passthrough,
DW_FORM_string=_describe_attr_string,
DW_FORM_strp=_describe_attr_strp,
DW_FORM_line_strp=_describe_attr_line_strp,
DW_FORM_block1=_describe_attr_block,
DW_FORM_block2=_describe_attr_block,
DW_FORM_block4=_describe_attr_block,
DW_FORM_block=_describe_attr_block,
DW_FORM_flag_present=_describe_attr_present,
DW_FORM_exprloc=_describe_attr_block,
DW_FORM_ref_sig8=_describe_attr_ref_sig8,
)
_FORM_CLASS = dict(
DW_FORM_addr='address',
DW_FORM_block2='block',
DW_FORM_block4='block',
DW_FORM_data2='constant',
DW_FORM_data4='constant',
DW_FORM_data8='constant',
DW_FORM_string='string',
DW_FORM_block='block',
DW_FORM_block1='block',
DW_FORM_data1='constant',
DW_FORM_flag='flag',
DW_FORM_sdata='constant',
DW_FORM_strp='string',
DW_FORM_udata='constant',
DW_FORM_ref_addr='reference',
DW_FORM_ref1='reference',
DW_FORM_ref2='reference',
DW_FORM_ref4='reference',
DW_FORM_ref8='reference',
DW_FORM_ref_udata='reference',
DW_FORM_indirect=None,
DW_FORM_sec_offset=None,
DW_FORM_exprloc='exprloc',
DW_FORM_flag_present='flag',
DW_FORM_ref_sig8='reference',
)
_DESCR_DW_INL = {
DW_INL.not_inlined: '(not inlined)',
DW_INL.inlined: '(inlined)',
DW_INL.declared_not_inlined: '(declared as inline but ignored)',
DW_INL.declared_inlined: '(declared as inline and inlined)',
}
_DESCR_DW_LANG = {
DW_LANG.C89: '(ANSI C)',
DW_LANG.C: '(non-ANSI C)',
DW_LANG.Ada83: '(Ada)',
DW_LANG.C_plus_plus: '(C++)',
DW_LANG.Cobol74: '(Cobol 74)',
DW_LANG.Cobol85: '(Cobol 85)',
DW_LANG.Fortran77: '(FORTRAN 77)',
DW_LANG.Fortran90: '(Fortran 90)',
DW_LANG.Pascal83: '(ANSI Pascal)',
DW_LANG.Modula2: '(Modula 2)',
DW_LANG.Java: '(Java)',
DW_LANG.C99: '(ANSI C99)',
DW_LANG.Ada95: '(ADA 95)',
DW_LANG.Fortran95: '(Fortran 95)',
DW_LANG.PLI: '(PLI)',
DW_LANG.ObjC: '(Objective C)',
DW_LANG.ObjC_plus_plus: '(Objective C++)',
DW_LANG.UPC: '(Unified Parallel C)',
DW_LANG.D: '(D)',
DW_LANG.Python: '(Python)',
DW_LANG.OpenCL: '(OpenCL)',
DW_LANG.Go: '(Go)',
DW_LANG.Modula3: '(Modula 3)',
DW_LANG.Haskell: '(Haskell)',
DW_LANG.C_plus_plus_03: '(C++03)',
DW_LANG.C_plus_plus_11: '(C++11)',
DW_LANG.OCaml: '(OCaml)',
DW_LANG.Rust: '(Rust)',
DW_LANG.C11: '(C11)',
DW_LANG.Swift: '(Swift)',
DW_LANG.Julia: '(Julia)',
DW_LANG.Dylan: '(Dylan)',
DW_LANG.C_plus_plus_14: '(C++14)',
DW_LANG.Fortran03: '(Fortran 03)',
DW_LANG.Fortran08: '(Fortran 08)',
DW_LANG.RenderScript: '(RenderScript)',
DW_LANG.BLISS: '(Bliss)', # Not in binutils
DW_LANG.Mips_Assembler: '(MIPS assembler)',
DW_LANG.HP_Bliss: '(HP Bliss)',
DW_LANG.HP_Basic91: '(HP Basic 91)',
DW_LANG.HP_Pascal91: '(HP Pascal 91)',
DW_LANG.HP_IMacro: '(HP IMacro)',
DW_LANG.HP_Assembler: '(HP assembler)'
}
_DESCR_DW_ATE = {
DW_ATE.void: '(void)',
DW_ATE.address: '(machine address)',
DW_ATE.boolean: '(boolean)',
DW_ATE.complex_float: '(complex float)',
DW_ATE.float: '(float)',
DW_ATE.signed: '(signed)',
DW_ATE.signed_char: '(signed char)',
DW_ATE.unsigned: '(unsigned)',
DW_ATE.unsigned_char: '(unsigned char)',
DW_ATE.imaginary_float: '(imaginary float)',
DW_ATE.decimal_float: '(decimal float)',
DW_ATE.packed_decimal: '(packed_decimal)',
DW_ATE.numeric_string: '(numeric_string)',
DW_ATE.edited: '(edited)',
DW_ATE.signed_fixed: '(signed_fixed)',
DW_ATE.unsigned_fixed: '(unsigned_fixed)',
DW_ATE.UTF: '(unicode string)',
DW_ATE.HP_float80: '(HP_float80)',
DW_ATE.HP_complex_float80: '(HP_complex_float80)',
DW_ATE.HP_float128: '(HP_float128)',
DW_ATE.HP_complex_float128: '(HP_complex_float128)',
DW_ATE.HP_floathpintel: '(HP_floathpintel)',
DW_ATE.HP_imaginary_float80: '(HP_imaginary_float80)',
DW_ATE.HP_imaginary_float128: '(HP_imaginary_float128)',
}
_DESCR_DW_ACCESS = {
DW_ACCESS.public: '(public)',
DW_ACCESS.protected: '(protected)',
DW_ACCESS.private: '(private)',
}
_DESCR_DW_VIS = {
DW_VIS.local: '(local)',
DW_VIS.exported: '(exported)',
DW_VIS.qualified: '(qualified)',
}
_DESCR_DW_VIRTUALITY = {
DW_VIRTUALITY.none: '(none)',
DW_VIRTUALITY.virtual: '(virtual)',
DW_VIRTUALITY.pure_virtual: '(pure virtual)',
}
_DESCR_DW_ID_CASE = {
DW_ID.case_sensitive: '(case_sensitive)',
DW_ID.up_case: '(up_case)',
DW_ID.down_case: '(down_case)',
DW_ID.case_insensitive: '(case_insensitive)',
}
_DESCR_DW_CC = {
DW_CC.normal: '(normal)',
DW_CC.program: '(program)',
DW_CC.nocall: '(nocall)',
DW_CC.pass_by_reference: '(pass by ref)',
DW_CC.pass_by_valuee: '(pass by value)',
}
_DESCR_DW_ORD = {
DW_ORD.row_major: '(row major)',
DW_ORD.col_major: '(column major)',
}
_DESCR_CFI_REGISTER_RULE_TYPE = dict(
UNDEFINED='u',
SAME_VALUE='s',
OFFSET='c',
VAL_OFFSET='v',
REGISTER='',
EXPRESSION='exp',
VAL_EXPRESSION='vexp',
ARCHITECTURAL='a',
)
def _make_extra_mapper(
mapping: Mapping[_INT, str],
default: str,
default_interpolate_value: bool = False,
) -> Callable[[AttributeValue, DIE, int], str]:
""" Create a mapping function from attribute parameters to an extra
value that should be displayed.
"""
def mapper(attr: AttributeValue, die: DIE, section_offset: int) -> str:
if default_interpolate_value:
d = default % attr.value
else:
d = default
return mapping.get(attr.value, d)
return mapper
def _make_extra_string(s: str = '') -> Callable[[AttributeValue, DIE, int], str]:
""" Create an extra function that just returns a constant string.
"""
def extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
return s
return extra
_DWARF_EXPR_DUMPER_CACHE: dict[int, ExprDumper] = {}
def _location_list_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
# According to section 2.6 of the DWARF spec v3, class loclistptr means
# a location list, and class block means a location expression.
# DW_FORM.sec_offset is new in DWARFv4 as a section offset.
if attr.form in ('DW_FORM_data4', 'DW_FORM_data8', 'DW_FORM_sec_offset'):
return '(location list)'
else:
return describe_DWARF_expr(attr.value, die.cu.structs, die.cu.cu_offset)
def _data_member_location_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
# According to section 5.5.6 of the DWARF spec v4, a data member location
# can be an integer offset, or a location description.
#
if attr.form in ('DW_FORM_data1', 'DW_FORM_data2',
'DW_FORM_data4', 'DW_FORM_data8',
'DW_FORM_sdata', 'DW_FORM_implicit_const'):
return '' # No extra description needed
else:
return describe_DWARF_expr(attr.value, die.cu.structs, die.cu.cu_offset)
def _import_extra(attr: AttributeValue, die: DIE, section_offset: int) -> str:
# For DW_AT_import the value points to a DIE (that can be either in the
# current DIE's CU or in another CU, depending on the FORM). The extra
# information for it is the abbreviation number in this DIE and its tag.
if attr.form == 'DW_FORM_ref_addr':
# Absolute offset value
ref_die_offset = section_offset + attr.value
else:
# Relative offset to the current DIE's CU
ref_die_offset = attr.value + die.cu.cu_offset
# Now find the CU this DIE belongs to (since we have to find its abbrev
# table). This is done by linearly scanning through all CUs, looking for
# one spanning an address space containing the referred DIE's offset.
for cu in die.dwarfinfo.iter_CUs():
if cu['unit_length'] + cu.cu_offset > ref_die_offset >= cu.cu_offset:
# Once we have the CU, we can actually parse this DIE from the
# stream.
with preserve_stream_pos(die.stream):
ref_die = DIE(cu, die.stream, ref_die_offset)
return '[Abbrev Number: %s (%s)]' % (
ref_die.abbrev_code, ref_die.tag)
return '[unknown]'
_EXTRA_INFO_DESCRIPTION_MAP = defaultdict(
lambda: _make_extra_string(''), # default_factory
DW_AT_inline=_make_extra_mapper(
_DESCR_DW_INL, '(Unknown inline attribute value: %x)',
default_interpolate_value=True),
DW_AT_language=_make_extra_mapper(
_DESCR_DW_LANG, '(Unknown: %x)', default_interpolate_value=True),
DW_AT_encoding=_make_extra_mapper(_DESCR_DW_ATE, '(unknown type)'),
DW_AT_accessibility=_make_extra_mapper(
_DESCR_DW_ACCESS, '(unknown accessibility)'),
DW_AT_visibility=_make_extra_mapper(
_DESCR_DW_VIS, '(unknown visibility)'),
DW_AT_virtuality=_make_extra_mapper(
_DESCR_DW_VIRTUALITY, '(unknown virtuality)'),
DW_AT_identifier_case=_make_extra_mapper(
_DESCR_DW_ID_CASE, '(unknown case)'),
DW_AT_calling_convention=_make_extra_mapper(
_DESCR_DW_CC, '(unknown convention)'),
DW_AT_ordering=_make_extra_mapper(
_DESCR_DW_ORD, '(undefined)'),
DW_AT_frame_base=_location_list_extra,
DW_AT_location=_location_list_extra,
DW_AT_string_length=_location_list_extra,
DW_AT_return_addr=_location_list_extra,
DW_AT_data_member_location=_data_member_location_extra,
DW_AT_vtable_elem_location=_location_list_extra,
DW_AT_segment=_location_list_extra,
DW_AT_static_link=_location_list_extra,
DW_AT_use_location=_location_list_extra,
DW_AT_allocated=_location_list_extra,
DW_AT_associated=_location_list_extra,
DW_AT_data_location=_location_list_extra,
DW_AT_stride=_location_list_extra,
DW_AT_call_value=_location_list_extra,
DW_AT_import=_import_extra,
DW_AT_GNU_call_site_value=_location_list_extra,
DW_AT_GNU_call_site_data_value=_location_list_extra,
DW_AT_GNU_call_site_target=_location_list_extra,
DW_AT_GNU_call_site_target_clobbered=_location_list_extra,
)
# 8 in a line, for easier counting
_REG_NAMES_x86 = [
'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi',
'eip', 'eflags', '<none>', 'st0', 'st1', 'st2', 'st3', 'st4',
'st5', 'st6', 'st7', '<none>', '<none>', 'xmm0', 'xmm1', 'xmm2',
'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7', 'mm0', 'mm1', 'mm2',
'mm3', 'mm4', 'mm5', 'mm6', 'mm7', 'fcw', 'fsw', 'mxcsr',
'es', 'cs', 'ss', 'ds', 'fs', 'gs', '<none>', '<none>', 'tr', 'ldtr'
]
_REG_NAMES_x64 = [
'rax', 'rdx', 'rcx', 'rbx', 'rsi', 'rdi', 'rbp', 'rsp',
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15',
'rip', 'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6',
'xmm7', 'xmm8', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14',
'xmm15', 'st0', 'st1', 'st2', 'st3', 'st4', 'st5', 'st6',
'st7', 'mm0', 'mm1', 'mm2', 'mm3', 'mm4', 'mm5', 'mm6',
'mm7', 'rflags', 'es', 'cs', 'ss', 'ds', 'fs', 'gs',
'<none>', '<none>', 'fs.base', 'gs.base', '<none>', '<none>', 'tr', 'ldtr',
'mxcsr', 'fcw', 'fsw'
]
# https://developer.arm.com/documentation/ihi0057/e/?lang=en#dwarf-register-names
_REG_NAMES_AArch64 = [
'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7',
'x8', 'x9', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15',
'x16', 'x17', 'x18', 'x19', 'x20', 'x21', 'x22', 'x23',
'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30', 'sp',
'<none>', 'ELR_mode', 'RA_SIGN_STATE', '<none>', '<none>', '<none>', '<none>', '<none>',
'<none>', '<none>', '<none>', '<none>', '<none>', '<none>', 'VG', 'FFR',
'p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7',
'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15',
'v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7',
'v8', 'v9', 'v10', 'v11', 'v12', 'v13', 'v14', 'v15',
'v16', 'v17', 'v18', 'v19', 'v20', 'v21', 'v22', 'v23',
'v24', 'v25', 'v26', 'v27', 'v28', 'v29', 'v30', 'v31',
'z0', 'z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7',
'z8', 'z9', 'z10', 'z11', 'z12', 'z13', 'z14', 'z15',
'z16', 'z17', 'z18', 'z19', 'z20', 'z21', 'z22', 'z23',
'z24', 'z25', 'z26', 'z27', 'z28', 'z29', 'z30', 'z31'
]
class ExprDumper:
""" A dumper for DWARF expressions that dumps a textual
representation of the complete expression.
Usage: after creation, call dump_expr repeatedly - it's stateless.
"""
def __init__(self, structs: DWARFStructs) -> None:
self.structs = structs
self.expr_parser = DWARFExprParser(self.structs)
self._init_lookups()
def dump_expr(self, expr: bytes | Iterable[int], cu_offset: int | None = None) -> str:
""" Parse and dump a DWARF expression.
expr should be bytes or a list of (integer) byte values.
cu_offset is the cu_offset
value from the CU object where the expression resides.
Only affects a handful of GNU opcodes, if None is provided,
that's not a crash condition, only the expression dump will
not be consistent of that of readelf.
Returns a string representing the expression.
"""
parsed = self.expr_parser.parse_expr(bytes(expr))
return '; '.join(
self._dump_to_string(deo.op, deo.op_name, deo.args, cu_offset)
for deo in parsed
)
def _init_lookups(self) -> None:
self._ops_with_decimal_arg = {
'DW_OP_const1u', 'DW_OP_const1s', 'DW_OP_const2u', 'DW_OP_const2s',
'DW_OP_const4u', 'DW_OP_const4s', 'DW_OP_const8u', 'DW_OP_const8s',
'DW_OP_constu', 'DW_OP_consts', 'DW_OP_pick', 'DW_OP_plus_uconst',
'DW_OP_bra', 'DW_OP_skip', 'DW_OP_fbreg', 'DW_OP_piece',
'DW_OP_deref_size', 'DW_OP_xderef_size', 'DW_OP_regx'}
for n in range(0, 32):
self._ops_with_decimal_arg.add('DW_OP_breg%s' % n)
self._ops_with_two_decimal_args = {'DW_OP_bregx'}
self._ops_with_hex_arg = {
'DW_OP_addr', 'DW_OP_call2', 'DW_OP_call4', 'DW_OP_call_ref'}
def _dump_to_string(
self,
opcode: int,
opcode_name: str,
args: list[Any],
cu_offset: int | None = None,
) -> str:
# Some GNU ops contain an offset from the current CU as an argument,
# but readelf emits those ops with offset from the info section
# so we need the base offset of the parent CU.
# If omitted, arguments on some GNU opcodes will be off.
if cu_offset is None:
cu_offset = 0
if not args:
if opcode_name.startswith('DW_OP_reg'):
regnum = int(opcode_name[9:])
return '%s (%s)' % (
opcode_name,
describe_reg_name(regnum, _MACHINE_ARCH))
else:
return opcode_name
elif opcode_name in self._ops_with_decimal_arg:
if opcode_name.startswith('DW_OP_breg'):
regnum = int(opcode_name[10:])
return '%s (%s): %s' % (
opcode_name,
describe_reg_name(regnum, _MACHINE_ARCH),
args[0])
elif opcode_name.endswith('regx'):
# applies to both regx and bregx
return '%s: %s (%s)' % (
opcode_name,
args[0],
describe_reg_name(args[0], _MACHINE_ARCH))
else:
return '%s: %s' % (opcode_name, args[0])
elif opcode_name in self._ops_with_hex_arg:
return '%s: %x' % (opcode_name, args[0])
elif opcode_name in self._ops_with_two_decimal_args:
return '%s: %s %s' % (opcode_name, args[0], args[1])
elif opcode_name in ('DW_OP_GNU_entry_value', 'DW_OP_entry_value'):
return '%s: (%s)' % (opcode_name, ','.join([self._dump_to_string(deo.op, deo.op_name, deo.args, cu_offset) for deo in args[0]]))
elif opcode_name == 'DW_OP_implicit_value':
return "%s %s byte block: %s" % (opcode_name, len(args[0]), ''.join(["%x " % b for b in args[0]]))
elif opcode_name == 'DW_OP_GNU_parameter_ref':
return "%s: <0x%x>" % (opcode_name, args[0] + cu_offset)
elif opcode_name in ('DW_OP_GNU_implicit_pointer', 'DW_OP_implicit_pointer'):
return "%s: <0x%x> %d" % (opcode_name, args[0], args[1])
elif opcode_name in ('DW_OP_GNU_convert', 'DW_OP_convert'):
return "%s <0x%x>" % (opcode_name, args[0] + cu_offset)
elif opcode_name in ('DW_OP_GNU_deref_type', 'DW_OP_deref_type'):
return "%s: %d <0x%x>" % (opcode_name, args[0], args[1] + cu_offset)
elif opcode_name in ('DW_OP_GNU_const_type', 'DW_OP_const_type'):
return "%s: <0x%x> %d byte block: %s " % (opcode_name, args[0] + cu_offset, len(args[1]), ' '.join("%x" % b for b in args[1]))
elif opcode_name in ('DW_OP_GNU_regval_type', 'DW_OP_regval_type'):
return "%s: %d (%s) <0x%x>" % (opcode_name, args[0], describe_reg_name(args[0], _MACHINE_ARCH), args[1] + cu_offset)
elif opcode_name == 'DW_OP_bit_piece':
return '%s: size: %s offset: %s' % (opcode_name, args[0], args[1])
else:
return '<unknown %s>' % opcode_name
+373
View File
@@ -0,0 +1,373 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/die.py
#
# DWARF Debugging Information Entry
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import os
from typing import IO, TYPE_CHECKING, Any, NamedTuple
from ..common.exceptions import DWARFError, ELFParseError
from ..common.utils import bytes2str, struct_parse
from .enums import DW_FORM_raw2name
from .dwarf_util import _resolve_via_offset_table, _get_base_offset
from ..construct import ConstructError
if TYPE_CHECKING:
from collections.abc import Iterator
from .compileunit import CompileUnit
from .typeunit import TypeUnit
# AttributeValue - describes an attribute value in the DIE:
#
# name:
# The name (DW_AT_*) of this attribute
#
# form:
# The DW_FORM_* name of this attribute
#
# value:
# The value parsed from the section and translated accordingly to the form
# (e.g. for a DW_FORM_strp it's the actual string taken from the string table)
#
# raw_value:
# Raw value as parsed from the section - used for debugging and presentation
# (e.g. for a DW_FORM_strp it's the raw string offset into the table)
#
# offset:
# Offset of this attribute's value in the stream (absolute offset, relative
# the beginning of the whole stream)
#
# indirection_length:
# If the form of the attribute is DW_FORM_indirect, the form will contain
# the resolved form, and this will contain the length of the indirection chain.
# 0 means no indirection.
class AttributeValue(NamedTuple):
name: str
form: str
value: Any
raw_value: int
offset: int
indirection_length: int
class DIE:
""" A DWARF debugging information entry. On creation, parses itself from
the stream. Each DIE is held by a CU.
Accessible attributes:
tag:
The DIE tag
size:
The size this DIE occupies in the section
offset:
The offset of this DIE in the stream
attributes:
An ordered dictionary mapping attribute names to values. It's
ordered to preserve the order of attributes in the section
has_children:
Specifies whether this DIE has children
abbrev_code:
The abbreviation code pointing to an abbreviation entry (note
that this is for informational purposes only - this object
interacts with its abbreviation table transparently).
See also the public methods.
"""
def __init__(self, cu: CompileUnit | TypeUnit, stream: IO[bytes], offset: int) -> None:
""" cu:
CompileUnit object this DIE belongs to. Used to obtain context
information (structs, abbrev table, etc.)
stream, offset:
The stream and offset into it where this DIE's data is located
"""
self.cu = cu
self.dwarfinfo = self.cu.dwarfinfo # get DWARFInfo context
self.stream = stream
self.offset = offset
self.attributes: dict[str, Any] = {}
self.tag: str | int | None = None
self.has_children: bool | None = None
self.abbrev_code: int | None = None
self.size = 0
# Null DIE terminator. It can be used to obtain offset range occupied
# by this DIE including its whole subtree.
self._terminator: DIE | None = None
self._parent: DIE | None = None
self._parse_DIE()
def is_null(self) -> bool:
""" Is this a null entry?
"""
return self.tag is None
def get_DIE_from_attribute(self, name: str) -> DIE:
""" Return the DIE referenced by the named attribute of this DIE.
The attribute must be in the reference attribute class.
name:
The name of the attribute in the reference class.
"""
attr = self.attributes[name]
if attr.form in ('DW_FORM_ref1', 'DW_FORM_ref2', 'DW_FORM_ref4',
'DW_FORM_ref8', 'DW_FORM_ref', 'DW_FORM_ref_udata'):
refaddr = self.cu.cu_offset + attr.raw_value
return self.cu.get_DIE_from_refaddr(refaddr)
elif attr.form in ('DW_FORM_ref_addr'):
return self.cu.dwarfinfo.get_DIE_from_refaddr(attr.raw_value)
elif attr.form in ('DW_FORM_ref_sig8'):
return self.cu.dwarfinfo.get_DIE_by_sig8(attr.raw_value)
elif attr.form in ('DW_FORM_ref_sup4', 'DW_FORM_ref_sup8', 'DW_FORM_GNU_ref_alt'):
if self.dwarfinfo.supplementary_dwarfinfo:
return self.dwarfinfo.supplementary_dwarfinfo.get_DIE_from_refaddr(attr.raw_value)
# FIXME: how to distinguish supplementary files from dwo ?
raise NotImplementedError('%s to dwo' % attr.form)
else:
raise DWARFError('%s is not a reference class form attribute' % attr)
def get_parent(self) -> DIE | None:
""" Return the parent DIE of this DIE, or None if the DIE has no
parent (i.e. is a top-level DIE).
"""
if self._parent is None:
self._search_ancestor_offspring()
return self._parent
def get_full_path(self) -> str:
""" Return the full path filename for the DIE.
The filename is the join of 'DW_AT_comp_dir' and 'DW_AT_name',
either of which may be missing in practice. Note that its value is
usually a string taken from the .debug_string section and the
returned value will be a string.
"""
comp_dir_attr = self.attributes.get('DW_AT_comp_dir', None)
comp_dir = bytes2str(comp_dir_attr.value) if comp_dir_attr else ''
fname_attr = self.attributes.get('DW_AT_name', None)
fname = bytes2str(fname_attr.value) if fname_attr else ''
return os.path.join(comp_dir, fname)
def iter_children(self) -> Iterator[DIE]:
""" Iterates all children of this DIE
"""
return self.cu.iter_DIE_children(self)
def iter_siblings(self) -> Iterator[DIE]:
""" Yield all siblings of this DIE
"""
parent = self.get_parent()
if parent:
for sibling in parent.iter_children():
if sibling is not self:
yield sibling
else:
raise StopIteration()
# The following methods are used while creating the DIE and should not be
# interesting to consumers
#
def set_parent(self, die: DIE) -> None:
self._parent = die
#------ PRIVATE ------#
def _search_ancestor_offspring(self) -> None:
""" Search our ancestors identifying their offspring to find our parent.
DIEs are stored as a flattened tree. The top DIE is the ancestor
of all DIEs in the unit. Each parent is guaranteed to be at
an offset less than their children. In each generation of children
the sibling with the closest offset not greater than our offset is
our ancestor.
"""
# This code is called when get_parent notices that the _parent has
# not been identified. To avoid execution for each sibling record all
# the children of any parent iterated. Assuming get_parent will also be
# called for siblings, it is more efficient if siblings references are
# provided and no worse than a single walk if they are missing, while
# stopping iteration early could result in O(n^2) walks.
search: DIE = self.cu.get_top_DIE()
while search.offset < self.offset:
prev = search
for child in search.iter_children():
child.set_parent(search)
if child.offset <= self.offset:
prev = child
# We also need to check the offset of the terminator DIE
if search.has_children and search._terminator and search._terminator.offset <= self.offset:
prev = search._terminator
# If we didn't find a closer parent, give up, don't loop.
# Either we mis-parsed an ancestor or someone created a DIE
# by an offset that was not actually the start of a DIE.
if prev is search:
raise ValueError("offset %s not in CU %s DIE tree" %
(self.offset, self.cu.cu_offset))
search = prev
def __repr__(self) -> str:
s = 'DIE %s, size=%s, has_children=%s\n' % (
self.tag, self.size, self.has_children)
for attrname, attrval in self.attributes.items():
s += ' |%-18s: %s\n' % (attrname, attrval)
return s
def __str__(self) -> str:
return self.__repr__()
def _parse_DIE(self) -> None:
""" Parses the DIE info from the section, based on the abbreviation
table of the CU
"""
try:
structs = self.cu.structs
stream = self.stream
# A DIE begins with the abbreviation code. Read it and use it to
# obtain the abbrev declaration for this DIE.
# Note: here and elsewhere, preserve_stream_pos is used on operations
# that manipulate the stream by reading data from it.
stream.seek(self.offset)
self.abbrev_code = structs.the_Dwarf_uleb128.parse_stream(stream)
assert self.abbrev_code is not None
# This may be a null entry
if self.abbrev_code == 0:
self.size = stream.tell() - self.offset
return
abbrev_decl = self.cu.get_abbrev_table().get_abbrev(self.abbrev_code)
self.tag = abbrev_decl['tag']
self.has_children = abbrev_decl.has_children()
# Guided by the attributes listed in the abbreviation declaration, parse
# values from the stream.
for spec in abbrev_decl['attr_spec']:
form = spec.form
name = spec.name
attr_offset = stream.tell()
indirection_length = 0
# Special case here: the attribute value is stored in the attribute
# definition in the abbreviation spec, not in the DIE itself.
if form == 'DW_FORM_implicit_const':
value = spec.value
raw_value = value
# Another special case: the attribute value is a form code followed by the real value in that form
elif form == 'DW_FORM_indirect':
(form, raw_value, indirection_length) = self._resolve_indirect()
value = self._translate_attr_value(form, raw_value)
else:
dw_form = structs.Dwarf_dw_form[form]
assert dw_form is not None
raw_value = dw_form.parse_stream(stream)
value = self._translate_attr_value(form, raw_value)
self.attributes[name] = AttributeValue(
name=name,
form=form,
value=value,
raw_value=raw_value,
offset=attr_offset,
indirection_length = indirection_length)
self.size = stream.tell() - self.offset
except ConstructError as e:
raise ELFParseError(str(e))
def _resolve_indirect(self) -> tuple[str, int, int]:
# Supports arbitrary indirection nesting (the standard doesn't prohibit that).
# Expects the stream to be at the real form.
# Returns (form, raw_value, length).
structs = self.cu.structs
length = 1
real_form_code: int = struct_parse(structs.the_Dwarf_uleb128, self.stream) # Numeric form code
while True:
try:
real_form = DW_FORM_raw2name[real_form_code] # Form name or exception if bogus code
except KeyError:
raise DWARFError('Found DW_FORM_indirect with unknown real form 0x%x' % real_form_code)
dw_form = structs.Dwarf_dw_form[real_form]
assert dw_form is not None
raw_value: int = struct_parse(dw_form, self.stream)
if real_form != 'DW_FORM_indirect': # Happy path: one level of indirection
return (real_form, raw_value, length)
else: # Indirection cascade
length += 1
real_form_code = raw_value
# And continue parsing
# No explicit infinite loop guard because the stream will end eventually
def _translate_attr_value(self, form: str, raw_value: Any) -> Any:
""" Translate a raw attr value according to the form
"""
# Indirect forms can only be parsed if the top DIE of this CU has already been parsed
# and listed in the CU, since the top DIE would have to contain the DW_AT_xxx_base attributes.
# This breaks if there is an indirect encoding in the top DIE itself before the
# corresponding _base, and it was seen in the wild.
# There is a hook in get_top_DIE() to resolve those lazily.
translate_indirect = self.cu.has_top_DIE() or self.offset != self.cu.cu_die_offset
if form == 'DW_FORM_strp':
return self.dwarfinfo.get_string_from_table(raw_value)
elif form == 'DW_FORM_line_strp':
return self.dwarfinfo.get_string_from_linetable(raw_value)
elif form in ('DW_FORM_GNU_strp_alt', 'DW_FORM_strp_sup') and self.dwarfinfo.supplementary_dwarfinfo:
return self.dwarfinfo.supplementary_dwarfinfo.get_string_from_table(raw_value)
elif form == 'DW_FORM_flag':
return not raw_value == 0
elif form == 'DW_FORM_flag_present':
return True
elif form in ('DW_FORM_addrx', 'DW_FORM_addrx1', 'DW_FORM_addrx2', 'DW_FORM_addrx3', 'DW_FORM_addrx4') and translate_indirect:
return self.cu.dwarfinfo.get_addr(self.cu, raw_value)
elif form in ('DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4') and translate_indirect:
assert self.dwarfinfo.debug_str_offsets_sec is not None
stream = self.dwarfinfo.debug_str_offsets_sec.stream
base_offset = _get_base_offset(self.cu, 'DW_AT_str_offsets_base')
offset_size = 4 if self.cu.structs.dwarf_format == 32 else 8
str_offset = struct_parse(self.cu.structs.the_Dwarf_offset, stream, base_offset + raw_value*offset_size)
return self.dwarfinfo.get_string_from_table(str_offset)
elif form == 'DW_FORM_loclistx' and translate_indirect:
assert self.dwarfinfo.debug_loclists_sec is not None
return _resolve_via_offset_table(self.dwarfinfo.debug_loclists_sec.stream, self.cu, raw_value, 'DW_AT_loclists_base')
elif form == 'DW_FORM_rnglistx' and translate_indirect:
assert self.dwarfinfo.debug_rnglists_sec is not None
return _resolve_via_offset_table(self.dwarfinfo.debug_rnglists_sec.stream, self.cu, raw_value, 'DW_AT_rnglists_base')
return raw_value
def _translate_indirect_attributes(self) -> None:
""" This is a hook to translate the DW_FORM_...x values in the top DIE
once the top DIE is parsed to the end. They can't be translated
while the top DIE is being parsed, because they implicitly make a
reference to the DW_AT_xxx_base attribute in the same DIE that may
not have been parsed yet.
"""
for key, attr in self.attributes.items():
if attr.form in ('DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4',
'DW_FORM_addrx', 'DW_FORM_addrx1', 'DW_FORM_addrx2', 'DW_FORM_addrx3', 'DW_FORM_addrx4',
'DW_FORM_loclistx', 'DW_FORM_rnglistx'):
# Can't change value in place, got to replace the whole attribute record
self.attributes[key] = AttributeValue(
name=attr.name,
form=attr.form,
value=self._translate_attr_value(attr.form, attr.raw_value),
raw_value=attr.raw_value,
offset=attr.offset,
indirection_length=attr.indirection_length)
@@ -0,0 +1,293 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/dwarf_expr.py
#
# Decoding DWARF expressions
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from io import BytesIO
from typing import IO, TYPE_CHECKING, Any, NamedTuple
from ..common.utils import struct_parse
from ..common.exceptions import DWARFError
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Mapping
from ..construct.core import Construct
from .structs import DWARFStructs
# DWARF expression opcodes. name -> opcode mapping
DW_OP_name2opcode: Mapping[str, int] = dict(
DW_OP_addr=0x03,
DW_OP_deref=0x06,
DW_OP_const1u=0x08,
DW_OP_const1s=0x09,
DW_OP_const2u=0x0a,
DW_OP_const2s=0x0b,
DW_OP_const4u=0x0c,
DW_OP_const4s=0x0d,
DW_OP_const8u=0x0e,
DW_OP_const8s=0x0f,
DW_OP_constu=0x10,
DW_OP_consts=0x11,
DW_OP_dup=0x12,
DW_OP_drop=0x13,
DW_OP_over=0x14,
DW_OP_pick=0x15,
DW_OP_swap=0x16,
DW_OP_rot=0x17,
DW_OP_xderef=0x18,
DW_OP_abs=0x19,
DW_OP_and=0x1a,
DW_OP_div=0x1b,
DW_OP_minus=0x1c,
DW_OP_mod=0x1d,
DW_OP_mul=0x1e,
DW_OP_neg=0x1f,
DW_OP_not=0x20,
DW_OP_or=0x21,
DW_OP_plus=0x22,
DW_OP_plus_uconst=0x23,
DW_OP_shl=0x24,
DW_OP_shr=0x25,
DW_OP_shra=0x26,
DW_OP_xor=0x27,
DW_OP_bra=0x28,
DW_OP_eq=0x29,
DW_OP_ge=0x2a,
DW_OP_gt=0x2b,
DW_OP_le=0x2c,
DW_OP_lt=0x2d,
DW_OP_ne=0x2e,
DW_OP_skip=0x2f,
DW_OP_regx=0x90,
DW_OP_fbreg=0x91,
DW_OP_bregx=0x92,
DW_OP_piece=0x93,
DW_OP_deref_size=0x94,
DW_OP_xderef_size=0x95,
DW_OP_nop=0x96,
DW_OP_push_object_address=0x97,
DW_OP_call2=0x98,
DW_OP_call4=0x99,
DW_OP_call_ref=0x9a,
DW_OP_form_tls_address=0x9b,
DW_OP_call_frame_cfa=0x9c,
DW_OP_bit_piece=0x9d,
DW_OP_implicit_value=0x9e,
DW_OP_stack_value=0x9f,
DW_OP_implicit_pointer=0xa0,
DW_OP_addrx=0xa1,
DW_OP_constx=0xa2,
DW_OP_entry_value=0xa3,
DW_OP_const_type=0xa4,
DW_OP_regval_type=0xa5,
DW_OP_deref_type=0xa6,
DW_OP_xderef_type=0xa7,
DW_OP_convert=0xa8,
DW_OP_reinterpret=0xa9,
DW_OP_lo_user=0xe0,
DW_OP_GNU_push_tls_address=0xe0,
DW_OP_WASM_location=0xed,
DW_OP_GNU_uninit=0xf0,
DW_OP_GNU_implicit_pointer=0xf2,
DW_OP_GNU_entry_value=0xf3,
DW_OP_GNU_const_type=0xf4,
DW_OP_GNU_regval_type=0xf5,
DW_OP_GNU_deref_type=0xf6,
DW_OP_GNU_convert=0xf7,
DW_OP_GNU_parameter_ref=0xfa,
DW_OP_GNU_addr_index=0xfb,
DW_OP_GNU_const_index=0xfc,
DW_OP_GNU_variable_value=0xfd,
DW_OP_hi_user=0xff,
**{f"DW_OP_lit{val}": 0x30 + val for val in range(0, 32)},
**{f"DW_OP_reg{val}": 0x50 + val for val in range(0, 32)},
**{f"DW_OP_breg{val}": 0x70 + val for val in range(0, 32)},
)
# opcode -> name mapping
DW_OP_opcode2name: Mapping[int, str] = {v: k for k, v in DW_OP_name2opcode.items()}
# Each parsed DWARF expression is returned as this type with its numeric opcode,
# op name (as a string) and a list of arguments.
class DWARFExprOp(NamedTuple):
op: int
op_name: str
args: list[Any]
offset: int
class DWARFExprParser:
"""DWARF expression parser.
When initialized, requires structs to cache a dispatch table. After that,
parse_expr can be called repeatedly - it's stateless.
"""
def __init__(self, structs: DWARFStructs) -> None:
self._dispatch_table = _init_dispatch_table(structs)
def parse_expr(self, expr: bytes | Iterable[int]) -> list[DWARFExprOp]:
""" Parses expr (bytes or a list of integers) into a list of DWARFExprOp.
The list can potentially be nested.
"""
stream = BytesIO(bytes(expr))
parsed: list[DWARFExprOp] = []
while True:
# Get the next opcode from the stream. If nothing is left in the
# stream, we're done.
offset = stream.tell()
byte = stream.read(1)
if not byte:
break
# Decode the opcode and its name.
op = ord(byte)
op_name = DW_OP_opcode2name.get(op, 'OP:0x%x' % op)
# Use dispatch table to parse args.
arg_parser = self._dispatch_table[op]
args = arg_parser(stream)
parsed.append(DWARFExprOp(op=op, op_name=op_name, args=args, offset=offset))
return parsed
def _init_dispatch_table(structs: DWARFStructs) -> dict[int, Callable[[IO[bytes]], list[Any]]]:
"""Creates a dispatch table for parsing args of an op.
Returns a dict mapping opcode to a function. The function accepts a stream
and return a list of parsed arguments for the opcode from the stream;
the stream is advanced by the function as needed.
"""
table: dict[int, Callable[[IO[bytes]], list[Any]]] = {}
def add(opcode_name: str, func: Callable[[IO[bytes]], list[Any]]) -> None:
table[DW_OP_name2opcode[opcode_name]] = func
def parse_noargs() -> Callable[[IO[bytes]], list[None]]:
return lambda stream: []
def parse_op_addr() -> Callable[[IO[bytes]], list[int]]:
return lambda stream: [struct_parse(structs.the_Dwarf_target_addr,
stream)]
def parse_arg_struct(arg_struct: Construct) -> Callable[[IO[bytes]], list[Any]]:
return lambda stream: [struct_parse(arg_struct, stream)]
def parse_arg_struct2(
arg1_struct: Construct,
arg2_struct: Construct,
) -> Callable[[IO[bytes]], list[Any]]:
return lambda stream: [struct_parse(arg1_struct, stream),
struct_parse(arg2_struct, stream)]
# ULEB128, then an expression of that length
def parse_nestedexpr() -> Callable[[IO[bytes]], list[list[DWARFExprOp]]]:
def parse(stream: IO[bytes]) -> list[list[DWARFExprOp]]:
size: int = struct_parse(structs.the_Dwarf_uleb128, stream)
nested_expr_blob = stream.read(size)
return [DWARFExprParser(structs).parse_expr(nested_expr_blob)]
return parse
# ULEB128, then a blob of that size
def parse_blob() -> Callable[[IO[bytes]], list[list[int]]]:
return lambda stream: [list(stream.read(struct_parse(structs.the_Dwarf_uleb128, stream)))]
# ULEB128 with datatype DIE offset, then byte, then a blob of that size
def parse_typedblob() -> Callable[[IO[bytes]], list[int | list[int]]]:
return lambda stream: [struct_parse(structs.the_Dwarf_uleb128, stream), list(stream.read(struct_parse(structs.the_Dwarf_uint8, stream)))]
# https://yurydelendik.github.io/webassembly-dwarf/
# Byte, then variant: 0, 1, 2 => uleb128, 3 => uint32
def parse_wasmloc() -> Callable[[IO[bytes]], list[int]]:
def parse(stream: IO[bytes]) -> list[int]:
op: int = struct_parse(structs.the_Dwarf_uint8, stream)
if 0 <= op <= 2:
return [op, struct_parse(structs.the_Dwarf_uleb128, stream)]
elif op == 3:
return [op, struct_parse(structs.the_Dwarf_uint32, stream)]
else:
raise DWARFError("Unknown operation code in DW_OP_WASM_location: %d" % (op,))
return parse
add('DW_OP_addr', parse_op_addr())
add('DW_OP_addrx', parse_arg_struct(structs.the_Dwarf_uleb128))
add('DW_OP_const1u', parse_arg_struct(structs.the_Dwarf_uint8))
add('DW_OP_const1s', parse_arg_struct(structs.Dwarf_int8('')))
add('DW_OP_const2u', parse_arg_struct(structs.the_Dwarf_uint16))
add('DW_OP_const2s', parse_arg_struct(structs.Dwarf_int16('')))
add('DW_OP_const4u', parse_arg_struct(structs.the_Dwarf_uint32))
add('DW_OP_const4s', parse_arg_struct(structs.Dwarf_int32('')))
add('DW_OP_const8u', parse_arg_struct(structs.Dwarf_uint64('')))
add('DW_OP_const8s', parse_arg_struct(structs.Dwarf_int64('')))
add('DW_OP_constu', parse_arg_struct(structs.the_Dwarf_uleb128))
add('DW_OP_consts', parse_arg_struct(structs.the_Dwarf_sleb128))
add('DW_OP_pick', parse_arg_struct(structs.the_Dwarf_uint8))
add('DW_OP_plus_uconst', parse_arg_struct(structs.the_Dwarf_uleb128))
add('DW_OP_bra', parse_arg_struct(structs.Dwarf_int16('')))
add('DW_OP_skip', parse_arg_struct(structs.Dwarf_int16('')))
for opname in [ 'DW_OP_deref', 'DW_OP_dup', 'DW_OP_drop', 'DW_OP_over',
'DW_OP_swap', 'DW_OP_swap', 'DW_OP_rot', 'DW_OP_xderef',
'DW_OP_abs', 'DW_OP_and', 'DW_OP_div', 'DW_OP_minus',
'DW_OP_mod', 'DW_OP_mul', 'DW_OP_neg', 'DW_OP_not',
'DW_OP_or', 'DW_OP_plus', 'DW_OP_shl', 'DW_OP_shr',
'DW_OP_shra', 'DW_OP_xor', 'DW_OP_eq', 'DW_OP_ge',
'DW_OP_gt', 'DW_OP_le', 'DW_OP_lt', 'DW_OP_ne', 'DW_OP_nop',
'DW_OP_push_object_address', 'DW_OP_form_tls_address',
'DW_OP_call_frame_cfa', 'DW_OP_stack_value',
'DW_OP_GNU_push_tls_address', 'DW_OP_GNU_uninit']:
add(opname, parse_noargs())
for n in range(0, 32):
add('DW_OP_lit%s' % n, parse_noargs())
add('DW_OP_reg%s' % n, parse_noargs())
add('DW_OP_breg%s' % n, parse_arg_struct(structs.the_Dwarf_sleb128))
for opname in [ 'DW_OP_regx', 'DW_OP_piece', 'DW_OP_convert', 'DW_OP_GNU_convert',
'DW_OP_GNU_addr_index', 'DW_OP_GNU_const_index', 'DW_OP_GNU_variable_value']:
add(opname, parse_arg_struct(structs.the_Dwarf_uleb128))
add('DW_OP_fbreg', parse_arg_struct(structs.the_Dwarf_sleb128))
add('DW_OP_bregx', parse_arg_struct2(structs.the_Dwarf_uleb128,
structs.the_Dwarf_sleb128))
add('DW_OP_bit_piece', parse_arg_struct2(structs.the_Dwarf_uleb128,
structs.the_Dwarf_uleb128))
add('DW_OP_deref_size', parse_arg_struct(structs.Dwarf_int8('')))
add('DW_OP_xderef_size', parse_arg_struct(structs.Dwarf_int8('')))
add('DW_OP_call2', parse_arg_struct(structs.the_Dwarf_uint16))
add('DW_OP_call4', parse_arg_struct(structs.the_Dwarf_uint32))
add('DW_OP_call_ref', parse_arg_struct(structs.the_Dwarf_offset))
add('DW_OP_implicit_value', parse_blob())
add('DW_OP_entry_value', parse_nestedexpr())
add('DW_OP_const_type', parse_typedblob())
add('DW_OP_regval_type', parse_arg_struct2(structs.the_Dwarf_uleb128,
structs.the_Dwarf_uleb128))
add('DW_OP_deref_type', parse_arg_struct2(structs.the_Dwarf_uint8,
structs.the_Dwarf_uleb128))
add('DW_OP_implicit_pointer', parse_arg_struct2(structs.the_Dwarf_offset,
structs.the_Dwarf_sleb128))
add('DW_OP_GNU_entry_value', parse_nestedexpr())
add('DW_OP_GNU_const_type', parse_typedblob())
add('DW_OP_GNU_regval_type', parse_arg_struct2(structs.the_Dwarf_uleb128,
structs.the_Dwarf_uleb128))
add('DW_OP_GNU_deref_type', parse_arg_struct2(structs.the_Dwarf_uint8,
structs.the_Dwarf_uleb128))
add('DW_OP_GNU_implicit_pointer', parse_arg_struct2(structs.the_Dwarf_offset,
structs.the_Dwarf_sleb128))
add('DW_OP_GNU_parameter_ref', parse_arg_struct(structs.the_Dwarf_offset))
add('DW_OP_WASM_location', parse_wasmloc())
return table
@@ -0,0 +1,95 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/dwarf_utils.py
#
# Minor, shared DWARF helpers
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import os
import binascii
from typing import IO, TYPE_CHECKING, Any
from ..construct.macros import Array
from ..common.exceptions import DWARFError
from ..common.utils import preserve_stream_pos, struct_parse
if TYPE_CHECKING:
from collections.abc import Iterator
from ..construct import Struct
from .compileunit import CompileUnit
from .structs import DWARFStructs
from .typeunit import TypeUnit
def _get_base_offset(cu: CompileUnit | TypeUnit, base_attribute_name: str) -> int:
"""Retrieves a required, base offset-type atribute
from the top DIE in the CU. Applies to several indirectly
encoded objects - range lists, location lists, strings, addresses.
"""
cu_top_die = cu.get_top_DIE()
if base_attribute_name not in cu_top_die.attributes:
raise DWARFError("The CU at offset 0x%x needs %s" % (cu.cu_offset, base_attribute_name))
return cu_top_die.attributes[base_attribute_name].value
def _resolve_via_offset_table(
stream: IO[bytes],
cu: CompileUnit | TypeUnit,
index: int,
base_attribute_name: str,
) -> int:
"""Given an index in the offset table and directions where to find it,
retrieves an offset. Works for loclists, rnglists.
The DWARF offset bitness of the CU block in the section matches that
of the CU record in dwarf_info. See DWARFv5 standard, section 7.4.
This is used for translating DW_FORM_loclistx, DW_FORM_rnglistx
via the offset table in the respective section.
"""
base_offset = _get_base_offset(cu, base_attribute_name)
# That's offset (within the rnglists/loclists/str_offsets section) of
# the offset table for this CU's block in that section, which in turn is indexed by the index.
offset_size = 4 if cu.structs.dwarf_format == 32 else 8
with preserve_stream_pos(stream):
return base_offset + struct_parse(cu.structs.the_Dwarf_offset, stream, base_offset + index*offset_size)
def _iter_CUs_in_section(
stream: IO[bytes],
structs: DWARFStructs,
parser: Struct,
) -> Iterator[Any]:
"""Iterates through the list of CU sections in loclists or rangelists. Almost identical structures there.
get_parser is a lambda that takes structs, returns the parser
"""
stream.seek(0, os.SEEK_END)
endpos = stream.tell()
stream.seek(0, os.SEEK_SET)
offset = 0
while offset < endpos:
header = struct_parse(parser, stream, offset)
if header.offset_count > 0:
offset_parser = structs.Dwarf_uint64 if header.is64 else structs.Dwarf_uint32
header['offsets'] = struct_parse(Array(header.offset_count, offset_parser('')), stream)
else:
header['offsets'] = False
yield header
offset = header.offset_after_length + header.unit_length
def _file_crc32(file: IO[bytes]) -> int:
""" Provided a readable binary stream, reads the stream to the end
and computes the CRC32 checksum of its contents,
with the initial value of 0.
"""
d = file.read(4096)
checksum = 0
while d:
checksum = binascii.crc32(d, checksum)
d = file.read(4096)
return checksum
@@ -0,0 +1,762 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/dwarfinfo.py
#
# DWARFInfo - Main class for accessing DWARF debug information
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from bisect import bisect_right
from functools import cached_property
from typing import IO, TYPE_CHECKING, NamedTuple
from ..construct.lib.container import Container
from ..common.exceptions import DWARFError
from ..common.utils import (struct_parse, dwarf_assert,
parse_cstring_from_stream)
from .structs import DWARFStructs
from .compileunit import CompileUnit
from .typeunit import TypeUnit
from .abbrevtable import AbbrevTable
from .lineprogram import LineProgram
from .callframe import CallFrameInfo
from .locationlists import LocationLists, LocationListsPair
from .ranges import RangeLists, RangeListsPair
from .aranges import ARanges
from .namelut import NameLUT
from .dwarf_util import _get_base_offset
if TYPE_CHECKING:
from collections.abc import Callable, Iterator
from ..construct.lib.container import ListContainer
from .callframe import ZERO, CFIEntry
from .die import DIE
from .namelut import NameLUTEntry
# Describes a debug section
#
# stream: a stream object containing the data of this section
# name: section name in the container file
# global_offset: the global offset of the section in its container file
# size: the size of the section's data, in bytes
# address: the virtual address for the section's data
#
# 'name' and 'global_offset' are for descriptional purposes only and
# aren't strictly required for the DWARF parsing to work. 'address' is required
# to properly decode the special '.eh_frame' format.
#
class DebugSectionDescriptor(NamedTuple):
stream: IO[bytes]
name: str
global_offset: int | None
size: int
address: int
# Some configuration parameters for the DWARF reader. This exists to allow
# DWARFInfo to be independent from any specific file format/container.
#
# little_endian:
# boolean flag specifying whether the data in the file is little endian
#
# machine_arch:
# Machine architecture as a string. For example 'x86' or 'x64'
#
# default_address_size:
# The default address size for the container file (sizeof pointer, in bytes)
#
class DwarfConfig(NamedTuple):
little_endian: bool
machine_arch: str
default_address_size: int
class DWARFInfo:
""" Acts also as a "context" to other major objects, bridging between
various parts of the debug information.
"""
def __init__(
self,
config: DwarfConfig,
debug_info_sec: DebugSectionDescriptor | None,
debug_aranges_sec: DebugSectionDescriptor | None,
debug_abbrev_sec: DebugSectionDescriptor | None,
debug_frame_sec: DebugSectionDescriptor | None,
eh_frame_sec: DebugSectionDescriptor | None,
debug_str_sec: DebugSectionDescriptor | None,
debug_loc_sec: DebugSectionDescriptor | None,
debug_ranges_sec: DebugSectionDescriptor | None,
debug_line_sec: DebugSectionDescriptor | None,
debug_pubtypes_sec: DebugSectionDescriptor | None,
debug_pubnames_sec: DebugSectionDescriptor | None,
debug_addr_sec: DebugSectionDescriptor | None,
debug_str_offsets_sec: DebugSectionDescriptor | None,
debug_line_str_sec: DebugSectionDescriptor | None,
debug_loclists_sec: DebugSectionDescriptor | None,
debug_rnglists_sec: DebugSectionDescriptor | None,
debug_sup_sec: DebugSectionDescriptor | None,
gnu_debugaltlink_sec: DebugSectionDescriptor | None,
debug_types_sec: DebugSectionDescriptor | None,
) -> None:
""" config:
A DwarfConfig object
debug_*_sec:
DebugSectionDescriptor for a section. Pass None for sections
that don't exist. These arguments are best given with
keyword syntax.
"""
self.config = config
self.debug_info_sec = debug_info_sec
self.debug_aranges_sec = debug_aranges_sec
self.debug_abbrev_sec = debug_abbrev_sec
self.debug_frame_sec = debug_frame_sec
self.eh_frame_sec = eh_frame_sec
self.debug_str_sec = debug_str_sec
self.debug_loc_sec = debug_loc_sec
self.debug_ranges_sec = debug_ranges_sec
self.debug_line_sec = debug_line_sec
self.debug_addr_sec = debug_addr_sec
self.debug_str_offsets_sec = debug_str_offsets_sec
self.debug_line_str_sec = debug_line_str_sec
self.debug_pubtypes_sec = debug_pubtypes_sec
self.debug_pubnames_sec = debug_pubnames_sec
self.debug_loclists_sec = debug_loclists_sec
self.debug_rnglists_sec = debug_rnglists_sec
self.debug_sup_sec = debug_sup_sec
self.gnu_debugaltlink_sec = gnu_debugaltlink_sec
self.debug_types_sec = debug_types_sec
# Sets the supplementary_dwarfinfo to None. Client code can set this
# to something else, typically a DWARFInfo file read from an ELFFile
# which path is stored in the debug_sup_sec or gnu_debugaltlink_sec.
self.supplementary_dwarfinfo: DWARFInfo | None = None
# This is the DWARFStructs the context uses, so it doesn't depend on
# DWARF format and address_size (these are determined per CU) - set them
# to default values.
self.structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=32,
address_size=self.config.default_address_size)
# Cache for abbrev tables: a dict keyed by offset
self._abbrevtable_cache: dict[int, AbbrevTable] = {}
# Cache for program lines tables: a dict keyed by offset
self._linetable_cache: dict[int, LineProgram] = {}
# Cache of compile units and map of their offsets for bisect lookup.
# Access with .iter_CUs(), .get_CU_containing(), and/or .get_CU_at().
self._cu_cache: list[CompileUnit] = []
self._cu_offsets_map: list[int] = []
@property
def has_debug_info(self) -> bool:
""" Return whether this contains debug information.
It can be not the case when the ELF only contains .eh_frame, which is
encoded DWARF but not actually for debugging.
"""
return bool(self.debug_info_sec)
def has_debug_types(self) -> bool:
""" Return whether this contains debug types information.
"""
return bool(self.debug_types_sec)
def get_DIE_from_lut_entry(self, lut_entry: NameLUTEntry) -> DIE:
""" Get the DIE from the pubnames or putbtypes lookup table entry.
lut_entry:
A NameLUTEntry object from a NameLUT instance (see
.get_pubmames and .get_pubtypes methods).
"""
cu = self.get_CU_at(lut_entry.cu_ofs)
return self.get_DIE_from_refaddr(lut_entry.die_ofs, cu)
def get_DIE_from_refaddr(self, refaddr: int, cu: CompileUnit | None = None) -> DIE:
""" Given a .debug_info section offset of a DIE, return the DIE.
refaddr:
The refaddr may come from a DW_FORM_ref_addr attribute.
cu:
The compile unit object, if known. If None a search
from the closest offset less than refaddr will be performed.
"""
if cu is None:
cu = self.get_CU_containing(refaddr)
return cu.get_DIE_from_refaddr(refaddr)
def get_DIE_by_sig8(self, sig8: int) -> DIE:
""" Find and return a DIE referenced by its type signature.
sig8:
The 8 byte signature (as a 64-bit unsigned integer)
Returns the DIE with the given type signature by searching
for the Type Unit with the matching signature then finding
the DIE at the offset given by the type_die field in the
Type Unit header.
Signatures are an 64-bit unsigned integers computed by the
DWARF producer as specified in the DWARF standard. Each
Type Unit contains one signature and the offset to the
corresponding DW_AT_type DIE in its unit header.
Describing a type can generate several DIEs. By moving
a DIE and its related DIEs to a Type Unit and generating
a hash of the DIEs and attributes in a flattened form
multiple Compile Units in a linked object can reference
the same DIE in the overall DWARF structure.
In DWARF v4 type units are identified by their appearance in the
.debug_types section.
"""
tu = self._type_units_by_sig.get(sig8)
if tu is None:
raise KeyError("Signature %016x not found in .debug_types" % sig8)
return tu._get_cached_DIE(tu.tu_offset + tu['type_offset'])
def get_CU_containing(self, refaddr: int) -> CompileUnit:
""" Find the CU that includes the given reference address in the
.debug_info section.
refaddr:
Either a refaddr of a DIE (possibly from a DW_FORM_ref_addr
attribute) or the section offset of a CU (possibly from an
aranges table).
This function will parse and cache CUs until the search criteria
is met, starting from the closest known offset lessthan or equal
to the given address.
"""
dwarf_assert(
self.has_debug_info,
'CU lookup but no debug info section')
assert self.debug_info_sec is not None
dwarf_assert(
0 <= refaddr < self.debug_info_sec.size,
"refaddr %s beyond .debug_info size" % refaddr)
# The CU containing the DIE we desire will be to the right of the
# DIE insert point. If we have a CU address, then it will be a
# match but the right insert minus one will still be the item.
# The first CU starts at offset 0, so start there if cache is empty.
i = bisect_right(self._cu_offsets_map, refaddr)
start = self._cu_offsets_map[i - 1] if i > 0 else 0
# parse CUs until we find one containing the desired address
for cu in self._parse_CUs_iter(start):
if cu.cu_offset <= refaddr < cu.cu_offset + cu.size:
return cu
raise ValueError("CU for reference address %s not found" % refaddr)
def get_CU_at(self, offset: int) -> CompileUnit:
""" Given a CU header offset, return the parsed CU.
offset:
The offset may be from an accelerated access table such as
the public names, public types, address range table, or
prior use.
This function will directly parse the CU doing no validation of
the offset beyond checking the size of the .debug_info section.
"""
dwarf_assert(
self.has_debug_info,
'CU lookup but no debug info section')
assert self.debug_info_sec is not None
dwarf_assert(
0 <= offset < self.debug_info_sec.size,
"offset %s beyond .debug_info size" % offset)
return self._cached_CU_at_offset(offset)
def get_TU_by_sig8(self, sig8: int) -> TypeUnit:
""" Find and return a Type Unit referenced by its signature
sig8:
The 8 byte unique signature (as a 64-bit unsigned integer)
Returns the TU with the given type signature by parsing the
.debug_types section.
"""
tu = self._type_units_by_sig.get(sig8)
if tu is None:
raise KeyError("Signature %016x not found in .debug_types" % sig8)
return tu
def iter_CUs(self) -> Iterator[CompileUnit]:
""" Yield all the compile units (CompileUnit objects) in the debug info
"""
return self._parse_CUs_iter()
def iter_TUs(self) -> Iterator[TypeUnit]:
"""Yield all the type units (TypeUnit objects) in the debug_types
"""
return self._parse_TUs_iter()
def get_abbrev_table(self, offset: int) -> AbbrevTable:
""" Get an AbbrevTable from the given offset in the debug_abbrev
section.
The only verification done on the offset is that it's within the
bounds of the section (if not, an exception is raised).
It is the caller's responsibility to make sure the offset actually
points to a valid abbreviation table.
AbbrevTable objects are cached internally (two calls for the same
offset will return the same object).
"""
assert self.debug_abbrev_sec is not None
dwarf_assert(
offset < self.debug_abbrev_sec.size,
"Offset '0x%x' to abbrev table out of section bounds" % offset)
if offset not in self._abbrevtable_cache:
self._abbrevtable_cache[offset] = AbbrevTable(
structs=self.structs,
stream=self.debug_abbrev_sec.stream,
offset=offset)
return self._abbrevtable_cache[offset]
def get_string_from_table(self, offset: int) -> bytes | None:
""" Obtain a string from the string table section, given an offset
relative to the section.
"""
assert self.debug_str_sec is not None
return parse_cstring_from_stream(self.debug_str_sec.stream, offset)
def get_string_from_linetable(self, offset: int) -> bytes | None:
""" Obtain a string from the string table section, given an offset
relative to the section.
"""
assert self.debug_line_str_sec is not None
return parse_cstring_from_stream(self.debug_line_str_sec.stream, offset)
def line_program_for_CU(self, CU: CompileUnit) -> LineProgram | None:
""" Given a CU object, fetch the line program it points to from the
.debug_line section.
If the CU doesn't point to a line program, return None.
Note about directory and file names. They are returned as two collections
in the lineprogram object's header - include_directory and file_entry.
In DWARFv5, they have introduced a different, extensible format for those
collections. So in a lineprogram v5+, there are two more collections in
the header - directories and file_names. Those might contain extra DWARFv5
information that is not exposed in include_directory and file_entry.
"""
# The line program is pointed to by the DW_AT_stmt_list attribute of
# the top DIE of a CU.
top_DIE = CU.get_top_DIE()
if 'DW_AT_stmt_list' in top_DIE.attributes:
return self._parse_line_program_at_offset(
top_DIE.attributes['DW_AT_stmt_list'].value, CU.structs)
else:
return None
def has_CFI(self) -> bool:
""" Does this dwarf info have a dwarf_frame CFI section?
"""
return self.debug_frame_sec is not None
def CFI_entries(self) -> list[CFIEntry | ZERO]:
""" Get a list of dwarf_frame CFI entries from the .debug_frame section.
"""
assert self.debug_frame_sec is not None
cfi = CallFrameInfo(
stream=self.debug_frame_sec.stream,
size=self.debug_frame_sec.size,
address=self.debug_frame_sec.address,
base_structs=self.structs)
return cfi.get_entries()
def has_EH_CFI(self) -> bool:
""" Does this dwarf info have a eh_frame CFI section?
"""
return self.eh_frame_sec is not None
def EH_CFI_entries(self) -> list[CFIEntry | ZERO]:
""" Get a list of eh_frame CFI entries from the .eh_frame section.
"""
assert self.eh_frame_sec is not None
cfi = CallFrameInfo(
stream=self.eh_frame_sec.stream,
size=self.eh_frame_sec.size,
address=self.eh_frame_sec.address,
base_structs=self.structs,
for_eh_frame=True)
return cfi.get_entries()
def get_pubtypes(self) -> NameLUT | None:
"""
Returns a NameLUT object that contains information read from the
.debug_pubtypes section in the ELF file.
NameLUT is essentially a dictionary containing the CU/DIE offsets of
each symbol. See the NameLUT doc string for more details.
"""
if self.debug_pubtypes_sec:
return NameLUT(self.debug_pubtypes_sec.stream,
self.debug_pubtypes_sec.size,
self.structs)
else:
return None
def get_pubnames(self) -> NameLUT | None:
"""
Returns a NameLUT object that contains information read from the
.debug_pubnames section in the ELF file.
NameLUT is essentially a dictionary containing the CU/DIE offsets of
each symbol. See the NameLUT doc string for more details.
"""
if self.debug_pubnames_sec:
return NameLUT(self.debug_pubnames_sec.stream,
self.debug_pubnames_sec.size,
self.structs)
else:
return None
def get_aranges(self) -> ARanges | None:
""" Get an ARanges object representing the .debug_aranges section of
the DWARF data, or None if the section doesn't exist
"""
if self.debug_aranges_sec:
return ARanges(self.debug_aranges_sec.stream,
self.debug_aranges_sec.size,
self.structs)
else:
return None
def location_lists(self) -> LocationLists | LocationListsPair | None:
""" Get a LocationLists object representing the .debug_loc/debug_loclists section of
the DWARF data, or None if this section doesn't exist.
If both sections exist, it returns a LocationListsPair.
"""
if self.debug_loclists_sec and self.debug_loc_sec is None:
return LocationLists(self.debug_loclists_sec.stream, self.structs, 5, self)
elif self.debug_loc_sec and self.debug_loclists_sec is None:
return LocationLists(self.debug_loc_sec.stream, self.structs, 4, self)
elif self.debug_loc_sec and self.debug_loclists_sec:
return LocationListsPair(self.debug_loc_sec.stream, self.debug_loclists_sec.stream, self.structs, self)
else:
return None
def range_lists(self) -> RangeLists | RangeListsPair | None:
""" Get a RangeLists object representing the .debug_ranges/.debug_rnglists section of
the DWARF data, or None if this section doesn't exist.
If both sections exist, it returns a RangeListsPair.
"""
if self.debug_rnglists_sec and self.debug_ranges_sec is None:
return RangeLists(self.debug_rnglists_sec.stream, self.structs, 5, self)
elif self.debug_ranges_sec and self.debug_rnglists_sec is None:
return RangeLists(self.debug_ranges_sec.stream, self.structs, 4, self)
elif self.debug_ranges_sec and self.debug_rnglists_sec:
return RangeListsPair(self.debug_ranges_sec.stream, self.debug_rnglists_sec.stream, self.structs, self)
else:
return None
def get_addr(self, cu: CompileUnit | TypeUnit, addr_index: int) -> int:
"""Provided a CU and an index, retrieves an address from the debug_addr section
"""
if not self.debug_addr_sec:
raise DWARFError('The file does not contain a debug_addr section for indirect address access')
# Selectors are not supported, but no assert on that. TODO?
cu_addr_base = _get_base_offset(cu, 'DW_AT_addr_base')
return struct_parse(cu.structs.the_Dwarf_target_addr, self.debug_addr_sec.stream, cu_addr_base + addr_index*cu.header.address_size)
#------ PRIVATE ------#
def _parse_CUs_iter(self, offset: int = 0) -> Iterator[CompileUnit]:
""" Iterate CU objects in order of appearance in the debug_info section.
offset:
The offset of the first CU to yield. Additional iterations
will return the sequential unit objects.
See .iter_CUs(), .get_CU_containing(), and .get_CU_at().
"""
if self.debug_info_sec is None:
return
while offset < self.debug_info_sec.size:
cu = self._cached_CU_at_offset(offset)
# Compute the offset of the next CU in the section. The unit_length
# field of the CU header contains its size not including the length
# field itself.
offset = (offset +
cu['unit_length'] +
cu.structs.initial_length_field_size())
yield cu
def _parse_TUs_iter(self, offset: int = 0) -> Iterator[TypeUnit]:
""" Iterate Type Unit objects in order of appearance in the debug_types section.
offset:
The offset of the first TU to yield. Additional iterations
will return the sequential unit objects.
See .iter_TUs().
"""
if self.debug_types_sec is None:
return
while offset < self.debug_types_sec.size:
tu = self._parse_TU_at_offset(offset)
# Compute the offset of the next TU in the section. The unit_length
# field of the TU header contains its size not including the length
# field itself.
offset = (offset +
tu['unit_length'] +
tu.structs.initial_length_field_size())
yield tu
@cached_property
def _type_units_by_sig(self) -> dict[int, TypeUnit]:
""" Check if the .debug_types section is previously parsed. If not,
parse all TUs and store them in an ordered dict using their unique
64-bit signature as the key.
See .get_TU_by_sig8().
"""
if self.debug_types_sec is None:
return {}
# Parse all the Type Units in the types section for access by sig8
units = {}
offset = 0
while offset < self.debug_types_sec.size:
tu = self._parse_TU_at_offset(offset)
# Compute the offset of the next TU in the section. The unit_length
# field of the TU header contains its size not including the length
# field itself.
offset += tu['unit_length'] + tu.structs.initial_length_field_size()
units[tu['signature']] = tu
return units
def _cached_CU_at_offset(self, offset: int) -> CompileUnit:
""" Return the CU with unit header at the given offset into the
debug_info section from the cache. If not present, the unit is
header is parsed and the object is installed in the cache.
offset:
The offset of the unit header in the .debug_info section
to of the unit to fetch from the cache.
See get_CU_at().
"""
# Find the insert point for the requested offset. With bisect_right,
# if this entry is present in the cache it will be the prior entry.
i = bisect_right(self._cu_offsets_map, offset)
if i >= 1 and offset == self._cu_offsets_map[i - 1]:
return self._cu_cache[i - 1]
# Parse the CU and insert the offset and object into the cache.
# The ._cu_offsets_map[] contains just the numeric offsets for the
# bisect_right search while the parallel indexed ._cu_cache[] holds
# the object references.
cu = self._parse_CU_at_offset(offset)
self._cu_offsets_map.insert(i, offset)
self._cu_cache.insert(i, cu)
return cu
def _parse_CU_at_offset(self, offset: int) -> CompileUnit:
""" Parse and return a CU at the given offset in the debug_info stream.
"""
# Section 7.4 (32-bit and 64-bit DWARF Formats) of the DWARF spec v3
# states that the first 32-bit word of the CU header determines
# whether the CU is represented with 32-bit or 64-bit DWARF format.
#
# So we peek at the first word in the CU header to determine its
# dwarf format. Based on it, we then create a new DWARFStructs
# instance suitable for this CU and use it to parse the rest.
#
assert self.debug_info_sec is not None
initial_length = struct_parse(
self.structs.the_Dwarf_uint32, self.debug_info_sec.stream, offset)
dwarf_format = 64 if initial_length == 0xFFFFFFFF else 32
# Temporary structs for parsing the header
# The structs for the rest of the CU depend on the header data.
#
cu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=4,
dwarf_version=2)
cu_header = struct_parse(
cu_structs.Dwarf_CU_header, self.debug_info_sec.stream, offset)
# structs for the rest of the CU, taking into account bitness and DWARF version
cu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=cu_header['address_size'],
dwarf_version=cu_header['version'])
cu_die_offset = self.debug_info_sec.stream.tell()
dwarf_assert(
self._is_supported_version(cu_header['version']),
"Expected supported DWARF version. Got '%s'" % cu_header['version'])
return CompileUnit(
header=cu_header,
dwarfinfo=self,
structs=cu_structs,
cu_offset=offset,
cu_die_offset=cu_die_offset)
def _parse_TU_at_offset(self, offset: int) -> TypeUnit:
""" Parse and return a Type Unit (TU) at the given offset in the debug_types stream.
"""
# Section 7.4 (32-bit and 64-bit DWARF Formats) of the DWARF spec v4
# states that the first 32-bit word of the TU header determines
# whether the TU is represented with 32-bit or 64-bit DWARF format.
#
# So we peek at the first word in the TU header to determine its
# dwarf format. Based on it, we then create a new DWARFStructs
# instance suitable for this TU and use it to parse the rest.
#
assert self.debug_types_sec is not None
initial_length = struct_parse(
self.structs.the_Dwarf_uint32, self.debug_types_sec.stream, offset)
dwarf_format = 64 if initial_length == 0xFFFFFFFF else 32
# Temporary structs for parsing the header
# The structs for the rest of the TU depend on the header data.
#
tu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=4,
dwarf_version=2)
tu_header = struct_parse(
tu_structs.Dwarf_TU_header, self.debug_types_sec.stream, offset)
# structs for the rest of the TU, taking into account bit-width and DWARF version
tu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=tu_header['address_size'],
dwarf_version=tu_header['version'])
tu_die_offset = self.debug_types_sec.stream.tell()
dwarf_assert(
self._is_supported_version(tu_header['version']),
"Expected supported DWARF version. Got '%s'" % tu_header['version'])
return TypeUnit(
header=tu_header,
dwarfinfo=self,
structs=tu_structs,
tu_offset=offset,
tu_die_offset=tu_die_offset)
def _is_supported_version(self, version: int) -> bool:
""" DWARF version supported by this parser
"""
return 2 <= version <= 5
def _parse_line_program_at_offset(self, offset: int, structs: DWARFStructs) -> LineProgram:
""" Given an offset to the .debug_line section, parse the line program
starting at this offset in the section and return it.
structs is the DWARFStructs object used to do this parsing.
"""
if offset in self._linetable_cache:
return self._linetable_cache[offset]
assert self.debug_line_sec is not None
lineprog_header = struct_parse(
structs.Dwarf_lineprog_header,
self.debug_line_sec.stream,
offset)
# DWARF5: resolve names
def resolve_strings(
lineprog_header: Container,
format_field: str,
data_field: str,
) -> None:
if lineprog_header.get(format_field, False):
data = lineprog_header[data_field]
for field in lineprog_header[format_field]:
def replace_value(
data: ListContainer,
content_type: str,
replacer: Callable[[int], bytes | None],
) -> None:
for entry in data:
entry[content_type] = replacer(entry[content_type])
if field.form == 'DW_FORM_line_strp':
replace_value(data, field.content_type, self.get_string_from_linetable)
elif field.form == 'DW_FORM_strp':
replace_value(data, field.content_type, self.get_string_from_table)
elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_GNU_strp_alt'):
if self.supplementary_dwarfinfo:
replace_value(data, field.content_type, self.supplementary_dwarfinfo.get_string_from_table)
else:
replace_value(data, field.content_type, lambda x: str(x).encode())
elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4'):
raise NotImplementedError()
resolve_strings(lineprog_header, 'directory_entry_format', 'directories')
resolve_strings(lineprog_header, 'file_name_entry_format', 'file_names')
# DWARF5: provide compatible file/directory name arrays for legacy lineprogram consumers
if lineprog_header.get('directories', False):
lineprog_header.include_directory = tuple(d.DW_LNCT_path for d in lineprog_header.directories)
if lineprog_header.get('file_names', False):
lineprog_header.file_entry = tuple(
Container(**{
'name':e.get('DW_LNCT_path'),
'dir_index': e.get('DW_LNCT_directory_index'),
'mtime': e.get('DW_LNCT_timestamp'),
'length': e.get('DW_LNCT_size')})
for e in lineprog_header.file_names)
# Calculate the offset to the next line program (see DWARF 6.2.4)
end_offset = ( offset + lineprog_header['unit_length'] +
structs.initial_length_field_size())
lineprogram = LineProgram(
header=lineprog_header,
stream=self.debug_line_sec.stream,
structs=structs,
program_start_offset=self.debug_line_sec.stream.tell(),
program_end_offset=end_offset)
self._linetable_cache[offset] = lineprogram
return lineprogram
def parse_debugsupinfo(self) -> bytes | None:
"""
Extract a filename from .debug_sup, .gnu_debualtlink sections.
"""
if self.debug_sup_sec is not None:
self.debug_sup_sec.stream.seek(0)
suplink = self.structs.Dwarf_debugsup.parse_stream(self.debug_sup_sec.stream)
if suplink.is_supplementary == 0:
return suplink.sup_filename
if self.gnu_debugaltlink_sec is not None:
self.gnu_debugaltlink_sec.stream.seek(0)
suplink = self.structs.Dwarf_debugaltlink.parse_stream(self.gnu_debugaltlink_sec.stream)
return suplink.sup_filename
# The section .gnu_debuglink with similarly looking contents
# has a different meaning - it doesn't point at supplementary DWARF,
# which is meant to be referenced from primary DWARF,
# it points at DWARF proper.
return None
@@ -0,0 +1,600 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/enums.py
#
# Mappings of enum names to values
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from ..construct import Pass
ENUM_DW_TAG = dict(
DW_TAG_null = 0x00,
DW_TAG_array_type = 0x01,
DW_TAG_class_type = 0x02,
DW_TAG_entry_point = 0x03,
DW_TAG_enumeration_type = 0x04,
DW_TAG_formal_parameter = 0x05,
DW_TAG_global_subroutine = 0x06,
DW_TAG_global_variable = 0x07,
DW_TAG_imported_declaration = 0x08,
DW_TAG_label = 0x0a,
DW_TAG_lexical_block = 0x0b,
DW_TAG_local_variable = 0x0c,
DW_TAG_member = 0x0d,
DW_TAG_pointer_type = 0x0f,
DW_TAG_reference_type = 0x10,
DW_TAG_compile_unit = 0x11,
DW_TAG_string_type = 0x12,
DW_TAG_structure_type = 0x13,
DW_TAG_subroutine = 0x14,
DW_TAG_subroutine_type = 0x15,
DW_TAG_typedef = 0x16,
DW_TAG_union_type = 0x17,
DW_TAG_unspecified_parameters = 0x18,
DW_TAG_variant = 0x19,
DW_TAG_common_block = 0x1a,
DW_TAG_common_inclusion = 0x1b,
DW_TAG_inheritance = 0x1c,
DW_TAG_inlined_subroutine = 0x1d,
DW_TAG_module = 0x1e,
DW_TAG_ptr_to_member_type = 0x1f,
DW_TAG_set_type = 0x20,
DW_TAG_subrange_type = 0x21,
DW_TAG_with_stmt = 0x22,
DW_TAG_access_declaration = 0x23,
DW_TAG_base_type = 0x24,
DW_TAG_catch_block = 0x25,
DW_TAG_const_type = 0x26,
DW_TAG_constant = 0x27,
DW_TAG_enumerator = 0x28,
DW_TAG_file_type = 0x29,
DW_TAG_friend = 0x2a,
DW_TAG_namelist = 0x2b,
DW_TAG_namelist_item = 0x2c,
DW_TAG_namelist_items = 0x2c,
DW_TAG_packed_type = 0x2d,
DW_TAG_subprogram = 0x2e,
# The DWARF standard defines these as _parameter, not _param, but we
# maintain compatibility with readelf.
DW_TAG_template_type_param = 0x2f,
DW_TAG_template_value_param = 0x30,
DW_TAG_thrown_type = 0x31,
DW_TAG_try_block = 0x32,
DW_TAG_variant_part = 0x33,
DW_TAG_variable = 0x34,
DW_TAG_volatile_type = 0x35,
DW_TAG_dwarf_procedure = 0x36,
DW_TAG_restrict_type = 0x37,
DW_TAG_interface_type = 0x38,
DW_TAG_namespace = 0x39,
DW_TAG_imported_module = 0x3a,
DW_TAG_unspecified_type = 0x3b,
DW_TAG_partial_unit = 0x3c,
DW_TAG_imported_unit = 0x3d,
DW_TAG_mutable_type = 0x3e,
DW_TAG_condition = 0x3f,
DW_TAG_shared_type = 0x40,
DW_TAG_type_unit = 0x41,
DW_TAG_rvalue_reference_type = 0x42,
DW_TAG_template_alias = 0x43,
DW_TAG_coarray_type = 0x44,
DW_TAG_generic_subrange = 0x45,
DW_TAG_dynamic_type = 0x46,
DW_TAG_atomic_type = 0x47,
DW_TAG_call_site = 0x48,
DW_TAG_call_site_parameter = 0x49,
DW_TAG_skeleton_unit = 0x4a,
DW_TAG_immutable_type = 0x4b,
# Tags between 0x4080 and 0xffff are user-defined.
# different implementations may overlap?
DW_TAG_lo_user = 0x4080,
DW_TAG_GNU_template_template_param = 0x4106,
DW_TAG_GNU_template_parameter_pack = 0x4107,
DW_TAG_GNU_formal_parameter_pack = 0x4108,
DW_TAG_GNU_call_site = 0x4109,
DW_TAG_GNU_call_site_parameter = 0x410a,
DW_TAG_APPLE_property = 0x4200,
DW_TAG_hi_user = 0xffff,
_default_ = Pass,
)
ENUM_DW_CHILDREN = dict(
DW_CHILDREN_no = 0x00,
DW_CHILDREN_yes = 0x01,
)
ENUM_DW_AT = dict(
DW_AT_null = 0x00,
DW_AT_sibling = 0x01,
DW_AT_location = 0x02,
DW_AT_name = 0x03,
DW_AT_fund_type = 0x05,
DW_AT_mod_fund_type = 0x06,
DW_AT_user_def_type = 0x07,
DW_AT_mod_u_d_type = 0x08,
DW_AT_ordering = 0x09,
DW_AT_subscr_data = 0x0a,
DW_AT_byte_size = 0x0b,
DW_AT_bit_offset = 0x0c,
DW_AT_bit_size = 0x0d,
DW_AT_element_list = 0x0f,
DW_AT_stmt_list = 0x10,
DW_AT_low_pc = 0x11,
DW_AT_high_pc = 0x12,
DW_AT_language = 0x13,
DW_AT_member = 0x14,
DW_AT_discr = 0x15,
DW_AT_discr_value = 0x16,
DW_AT_visibility = 0x17,
DW_AT_import = 0x18,
DW_AT_string_length = 0x19,
DW_AT_common_reference = 0x1a,
DW_AT_comp_dir = 0x1b,
DW_AT_const_value = 0x1c,
DW_AT_containing_type = 0x1d,
DW_AT_default_value = 0x1e,
DW_AT_friends = 0x1f,
DW_AT_inline = 0x20,
DW_AT_is_optional = 0x21,
DW_AT_lower_bound = 0x22,
DW_AT_program = 0x23,
DW_AT_private = 0x24,
DW_AT_producer = 0x25,
DW_AT_protected = 0x26,
DW_AT_prototyped = 0x27,
DW_AT_public = 0x28,
DW_AT_pure_virtual = 0x29,
DW_AT_return_addr = 0x2a,
# In DWARFv1, DW_AT_specification was at 0x2b, moved to 0x47 in v2
DW_AT_start_scope = 0x2c,
DW_AT_bit_stride = 0x2e,
DW_AT_stride_size = 0x2e,
DW_AT_upper_bound = 0x2f,
DW_AT_virtual = 0x30,
DW_AT_abstract_origin = 0x31,
DW_AT_accessibility = 0x32,
DW_AT_address_class = 0x33,
DW_AT_artificial = 0x34,
DW_AT_base_types = 0x35,
DW_AT_calling_convention = 0x36,
DW_AT_count = 0x37,
DW_AT_data_member_location = 0x38,
DW_AT_decl_column = 0x39,
DW_AT_decl_file = 0x3a,
DW_AT_decl_line = 0x3b,
DW_AT_declaration = 0x3c,
DW_AT_discr_list = 0x3d,
DW_AT_encoding = 0x3e,
DW_AT_external = 0x3f,
DW_AT_frame_base = 0x40,
DW_AT_friend = 0x41,
DW_AT_identifier_case = 0x42,
DW_AT_macro_info = 0x43,
DW_AT_namelist_item = 0x44,
DW_AT_priority = 0x45,
DW_AT_segment = 0x46,
DW_AT_specification = 0x47,
DW_AT_static_link = 0x48,
DW_AT_type = 0x49,
DW_AT_use_location = 0x4a,
DW_AT_variable_parameter = 0x4b,
DW_AT_virtuality = 0x4c,
DW_AT_vtable_elem_location = 0x4d,
DW_AT_allocated = 0x4e,
DW_AT_associated = 0x4f,
DW_AT_data_location = 0x50,
DW_AT_byte_stride = 0x51,
DW_AT_stride = 0x51,
DW_AT_entry_pc = 0x52,
DW_AT_use_UTF8 = 0x53,
DW_AT_extension = 0x54,
DW_AT_ranges = 0x55,
DW_AT_trampoline = 0x56,
DW_AT_call_column = 0x57,
DW_AT_call_file = 0x58,
DW_AT_call_line = 0x59,
DW_AT_description = 0x5a,
DW_AT_binary_scale = 0x5b,
DW_AT_decimal_scale = 0x5c,
DW_AT_small = 0x5d,
DW_AT_decimal_sign = 0x5e,
DW_AT_digit_count = 0x5f,
DW_AT_picture_string = 0x60,
DW_AT_mutable = 0x61,
DW_AT_threads_scaled = 0x62,
DW_AT_explicit = 0x63,
DW_AT_object_pointer = 0x64,
DW_AT_endianity = 0x65,
DW_AT_elemental = 0x66,
DW_AT_pure = 0x67,
DW_AT_recursive = 0x68,
DW_AT_signature = 0x69,
DW_AT_main_subprogram = 0x6a,
DW_AT_data_bit_offset = 0x6b,
DW_AT_const_expr = 0x6c,
DW_AT_enum_class = 0x6d,
DW_AT_linkage_name = 0x6e,
DW_AT_string_length_bit_size = 0x6f,
DW_AT_string_length_byte_size = 0x70,
DW_AT_rank = 0x71,
DW_AT_str_offsets_base = 0x72,
DW_AT_addr_base = 0x73,
DW_AT_rnglists_base = 0x74,
DW_AT_dwo_name = 0x76,
DW_AT_reference = 0x77,
DW_AT_rvalue_reference = 0x78,
DW_AT_macros = 0x79,
DW_AT_call_all_calls = 0x7a,
DW_AT_call_all_source_calls = 0x7b,
DW_AT_call_all_tail_calls = 0x7c,
DW_AT_call_return_pc = 0x7d,
DW_AT_call_value = 0x7e,
DW_AT_call_origin = 0x7f,
DW_AT_call_parameter = 0x80,
DW_AT_call_pc = 0x81,
DW_AT_call_tail_call = 0x82,
DW_AT_call_target = 0x83,
DW_AT_call_target_clobbered = 0x84,
DW_AT_call_data_location = 0x85,
DW_AT_call_data_value = 0x86,
DW_AT_noreturn = 0x87,
DW_AT_alignment = 0x88,
DW_AT_export_symbols = 0x89,
DW_AT_deleted = 0x8a,
DW_AT_defaulted = 0x8b,
DW_AT_loclists_base = 0x8c,
DW_AT_MIPS_fde = 0x2001,
DW_AT_MIPS_loop_begin = 0x2002,
DW_AT_MIPS_tail_loop_begin = 0x2003,
DW_AT_MIPS_epilog_begin = 0x2004,
DW_AT_MIPS_loop_unroll_factor = 0x2005,
DW_AT_MIPS_software_pipeline_depth = 0x2006,
DW_AT_MIPS_linkage_name = 0x2007,
DW_AT_MIPS_stride = 0x2008,
DW_AT_MIPS_abstract_name = 0x2009,
DW_AT_MIPS_clone_origin = 0x200a,
DW_AT_MIPS_has_inlines = 0x200b,
DW_AT_MIPS_stride_byte = 0x200c,
DW_AT_MIPS_stride_elem = 0x200d,
DW_AT_MIPS_ptr_dopetype = 0x200e,
DW_AT_MIPS_allocatable_dopetype = 0x200f,
DW_AT_MIPS_assumed_shape_dopetype = 0x2010,
DW_AT_MIPS_assumed_size = 0x2011,
DW_AT_HP_opt_level = 0x2014,
DW_AT_sf_names = 0x2101,
DW_AT_src_info = 0x2102,
DW_AT_mac_info = 0x2103,
DW_AT_src_coords = 0x2104,
DW_AT_body_begin = 0x2105,
DW_AT_body_end = 0x2106,
DW_AT_GNU_vector = 0x2107,
DW_AT_GNU_template_name = 0x2110,
DW_AT_GNU_odr_signature = 0x210f,
DW_AT_GNU_call_site_value = 0x2111,
DW_AT_GNU_call_site_data_value = 0x2112,
DW_AT_GNU_call_site_target = 0x2113,
DW_AT_GNU_call_site_target_clobbered = 0x2114,
DW_AT_GNU_tail_call = 0x2115,
DW_AT_GNU_all_tail_call_sites = 0x2116,
DW_AT_GNU_all_call_sites = 0x2117,
DW_AT_GNU_all_source_call_sites = 0x2118,
DW_AT_GNU_macros = 0x2119,
DW_AT_GNU_deleted = 0x211a,
DW_AT_GNU_dwo_name = 0x2130,
DW_AT_GNU_dwo_id = 0x2131,
DW_AT_GNU_ranges_base = 0x2132,
DW_AT_GNU_addr_base = 0x2133,
DW_AT_GNU_pubnames = 0x2134,
DW_AT_GNU_pubtypes = 0x2135,
DW_AT_GNU_discriminator = 0x2136,
DW_AT_GNU_locviews = 0x2137,
DW_AT_GNU_entry_view = 0x2138,
DW_AT_LLVM_include_path = 0x3e00,
DW_AT_LLVM_config_macros = 0x3e01,
DW_AT_LLVM_isysroot = 0x3e02, # sysroot elsewhere
DW_AT_LLVM_tag_offset = 0x3e03,
DW_AT_LLVM_apinotes = 0x3e07,
DW_AT_APPLE_optimized = 0x3fe1,
DW_AT_APPLE_flags = 0x3fe2,
DW_AT_APPLE_isa = 0x3fe3,
DW_AT_APPLE_block = 0x3fe4,
DW_AT_APPLE_major_runtime_vers = 0x3fe5,
DW_AT_APPLE_runtime_class = 0x3fe6,
DW_AT_APPLE_omit_frame_ptr = 0x3fe7,
DW_AT_APPLE_property_name = 0x3fe8,
DW_AT_APPLE_property_getter = 0x3fe9,
DW_AT_APPLE_property_setter = 0x3fea,
DW_AT_APPLE_property_attribute = 0x3feb,
DW_AT_APPLE_objc_complete_type = 0x3fec,
DW_AT_APPLE_property = 0x3fed,
DW_AT_APPLE_objc_direct = 0x3fee,
DW_AT_APPLE_sdk = 0x3fef,
_default_ = Pass,
)
ENUM_DW_FORM = dict(
DW_FORM_null = 0x00,
DW_FORM_addr = 0x01,
DW_FORM_ref = 0x02,
DW_FORM_block2 = 0x03,
DW_FORM_block4 = 0x04,
DW_FORM_data2 = 0x05,
DW_FORM_data4 = 0x06,
DW_FORM_data8 = 0x07,
DW_FORM_string = 0x08,
DW_FORM_block = 0x09,
DW_FORM_block1 = 0x0a,
DW_FORM_data1 = 0x0b,
DW_FORM_flag = 0x0c,
DW_FORM_sdata = 0x0d,
DW_FORM_strp = 0x0e,
DW_FORM_udata = 0x0f,
DW_FORM_ref_addr = 0x10,
DW_FORM_ref1 = 0x11,
DW_FORM_ref2 = 0x12,
DW_FORM_ref4 = 0x13,
DW_FORM_ref8 = 0x14,
DW_FORM_ref_udata = 0x15,
DW_FORM_indirect = 0x16,
DW_FORM_sec_offset = 0x17,
DW_FORM_exprloc = 0x18,
DW_FORM_flag_present = 0x19,
DW_FORM_strx = 0x1a,
DW_FORM_addrx = 0x1b,
DW_FORM_ref_sup4 = 0x1c,
DW_FORM_strp_sup = 0x1d,
DW_FORM_data16 = 0x1e,
DW_FORM_line_strp = 0x1f,
DW_FORM_ref_sig8 = 0x20,
DW_FORM_implicit_const = 0x21,
DW_FORM_loclistx = 0x22,
DW_FORM_rnglistx = 0x23,
DW_FORM_ref_sup8 = 0x24,
DW_FORM_strx1 = 0x25,
DW_FORM_strx2 = 0x26,
DW_FORM_strx3 = 0x27,
DW_FORM_strx4 = 0x28,
DW_FORM_addrx1 = 0x29,
DW_FORM_addrx2 = 0x2a,
DW_FORM_addrx3 = 0x2b,
DW_FORM_addrx4 = 0x2c,
DW_FORM_GNU_addr_index = 0x1f01,
DW_FORM_GNU_str_index = 0x1f02,
DW_FORM_GNU_ref_alt = 0x1f20,
DW_FORM_GNU_strp_alt = 0x1f21,
_default_ = Pass,
)
# Inverse mapping for ENUM_DW_FORM
DW_FORM_raw2name = dict((v, k) for k, v in ENUM_DW_FORM.items())
# See http://www.airs.com/blog/archives/460
DW_EH_encoding_flags = dict(
DW_EH_PE_absptr = 0x00,
DW_EH_PE_uleb128 = 0x01,
DW_EH_PE_udata2 = 0x02,
DW_EH_PE_udata4 = 0x03,
DW_EH_PE_udata8 = 0x04,
DW_EH_PE_signed = 0x08,
DW_EH_PE_sleb128 = 0x09,
DW_EH_PE_sdata2 = 0x0a,
DW_EH_PE_sdata4 = 0x0b,
DW_EH_PE_sdata8 = 0x0c,
DW_EH_PE_pcrel = 0x10,
DW_EH_PE_textrel = 0x20,
DW_EH_PE_datarel = 0x30,
DW_EH_PE_funcrel = 0x40,
DW_EH_PE_aligned = 0x50,
DW_EH_PE_indirect = 0x80,
DW_EH_PE_omit = 0xff,
)
ENUM_DW_LNCT = dict(
DW_LNCT_path = 0x1,
DW_LNCT_directory_index = 0x2,
DW_LNCT_timestamp = 0x3,
DW_LNCT_size = 0x4,
DW_LNCT_MD5 = 0x5,
DW_LNCT_lo_user = 0x2000,
DW_LNCT_LLVM_source = 0x2001,
DW_LNCT_LLVM_is_MD5 = 0x2002,
DW_LNCT_hi_user = 0x3fff
)
ENUM_DW_UT = dict(
DW_UT_compile = 0x01,
DW_UT_type = 0x02,
DW_UT_partial = 0x03,
DW_UT_skeleton = 0x04,
DW_UT_split_compile = 0x05,
DW_UT_split_type = 0x06,
DW_UT_lo_user = 0x80,
DW_UT_hi_user = 0xff
)
ENUM_DW_LLE = dict(
DW_LLE_end_of_list = 0x00,
DW_LLE_base_addressx = 0x01,
DW_LLE_startx_endx = 0x02,
DW_LLE_startx_length = 0x03,
DW_LLE_offset_pair = 0x04,
DW_LLE_default_location = 0x05,
DW_LLE_base_address = 0x06,
DW_LLE_start_end = 0x07,
DW_LLE_start_length = 0x08
)
ENUM_DW_RLE = dict(
DW_RLE_end_of_list = 0x00,
DW_RLE_base_addressx = 0x01,
DW_RLE_startx_endx = 0x02,
DW_RLE_startx_length = 0x03,
DW_RLE_offset_pair = 0x04,
DW_RLE_base_address = 0x05,
DW_RLE_start_end = 0x06,
DW_RLE_start_length = 0x07
)
# See https://dwarfstd.org/languages.html
ENUM_DW_LANG = dict(
# DWARF v5 and earlier
DW_LANG_C89 = 0x0001,
DW_LANG_C = 0x0002,
DW_LANG_Ada83 = 0x0003,
DW_LANG_C_plus_plus = 0x0004,
DW_LANG_Cobol74 = 0x0005,
DW_LANG_Cobol85 = 0x0006,
DW_LANG_Fortran77 = 0x0007,
DW_LANG_Fortran90 = 0x0008,
DW_LANG_Pascal83 = 0x0009,
DW_LANG_Modula2 = 0x000a,
DW_LANG_Java = 0x000b,
DW_LANG_C99 = 0x000c,
DW_LANG_Ada95 = 0x000d,
DW_LANG_Fortran95 = 0x000e,
DW_LANG_PLI = 0x000f,
DW_LANG_ObjC = 0x0010,
DW_LANG_ObjC_plus_plus = 0x0011,
DW_LANG_UPC = 0x0012,
DW_LANG_D = 0x0013,
DW_LANG_Python = 0x0014,
DW_LANG_OpenCL = 0x0015,
DW_LANG_Go = 0x0016,
DW_LANG_Modula3 = 0x0017,
DW_LANG_Haskell = 0x0018,
DW_LANG_C_plus_plus_03 = 0x0019,
DW_LANG_C_plus_plus_11 = 0x001a,
DW_LANG_OCaml = 0x001b,
DW_LANG_Rust = 0x001c,
DW_LANG_C11 = 0x001d,
DW_LANG_Swift = 0x001e,
DW_LANG_Julia = 0x001f,
DW_LANG_Dylan = 0x0020,
DW_LANG_C_plus_plus_14 = 0x0021,
DW_LANG_Fortran03 = 0x0022,
DW_LANG_Fortran08 = 0x0023,
DW_LANG_RenderScript = 0x0024,
DW_LANG_BLISS = 0x0025,
# After DWARF v5
DW_LANG_Kotlin = 0x0026,
DW_LANG_Zig = 0x0027,
DW_LANG_Crystal = 0x0028,
DW_LANG_C_plus_plus_17 = 0x002a,
DW_LANG_C_plus_plus_20 = 0x002b,
DW_LANG_C17 = 0x002c,
DW_LANG_Fortran18 = 0x002d,
DW_LANG_Ada2005 = 0x002e,
DW_LANG_Ada2012 = 0x002f,
DW_LANG_HIP = 0x0030,
DW_LANG_Assembly = 0x0031,
DW_LANG_C_sharp = 0x0032,
DW_LANG_Mojo = 0x0033,
DW_LANG_GLSL = 0x0034,
DW_LANG_GLSL_ES = 0x0035,
DW_LANG_HLSL = 0x0036,
DW_LANG_OpenCL_CPP = 0x0037,
DW_LANG_CPP_for_OpenCL = 0x0038,
DW_LANG_SYCL = 0x0039,
DW_LANG_C_plus_plus_23 = 0x003a,
DW_LANG_Odin = 0x003b,
DW_LANG_P4 = 0x003c,
DW_LANG_Metal = 0x003d,
DW_LANG_C23 = 0x003e,
DW_LANG_Fortran23 = 0x003f,
DW_LANG_Ruby = 0x0040,
DW_LANG_Move = 0x0041,
DW_LANG_Hylo = 0x0042,
DW_LANG_V = 0x0043,
DW_LANG_Algol68 = 0x0044,
DW_LANG_Nim = 0x0045,
DW_LANG_Erlang = 0x0046,
DW_LANG_Elixir = 0x0047,
DW_LANG_Gleam = 0x0048,
# Vendor extensions range
DW_LANG_lo_user = 0x8000,
DW_LANG_hi_user = 0xffff,
# Note: in absense of official names, the naming of the following
# extensions follows what's done in the include/dwarf2.h file in the GCC
# repo.
# Mips extensions
DW_LANG_Mips_Assembler = 0x8001,
# UPC (Unified Parallel C) extensions
DW_LANG_Upc = 0x8765, # Replaced by DW_LANG_UPC
# HP extensions
DW_LANG_HP_Bliss = 0x8003,
DW_LANG_HP_Basic91 = 0x8004,
DW_LANG_HP_Pascal91 = 0x8005,
DW_LANG_HP_IMacro = 0x8006,
DW_LANG_HP_Assembler = 0x8007,
# Rust extensions
DW_LANG_Rust_old = 0x9000 # Replaced by DW_LANG_Rust
)
ENUM_DW_ATE = dict(
DW_ATE_address = 0x01,
DW_ATE_boolean = 0x02,
DW_ATE_complex_float = 0x03,
DW_ATE_float = 0x04,
DW_ATE_signed = 0x05,
DW_ATE_signed_char = 0x06,
DW_ATE_unsigned = 0x07,
DW_ATE_unsigned_char = 0x08,
DW_ATE_imaginary_float = 0x09,
DW_ATE_packed_decimal = 0x0a,
DW_ATE_numeric_string = 0x0b,
DW_ATE_edited = 0x0c,
DW_ATE_signed_fixed = 0x0d,
DW_ATE_unsigned_fixed = 0x0e,
DW_ATE_decimal_float = 0x0f,
DW_ATE_UTF = 0x10,
DW_ATE_UCS = 0x11,
DW_ATE_ASCII = 0x12,
DW_ATE_lo_user = 0x80,
DW_ATE_hi_user = 0xff
)
ENUM_DW_ACCESS = dict(
DW_ACCESS_public = 0x01,
DW_ACCESS_protected = 0x02,
DW_ACCESS_private = 0x03
)
ENUM_DW_INL = dict(
DW_INL_not_inlined = 0x00,
DW_INL_inlined = 0x01,
DW_INL_declared_not_inlined = 0x02,
DW_INL_declared_inlined = 0x03
)
ENUM_DW_CC = dict(
DW_CC_normal = 0x01,
DW_CC_program = 0x02,
DW_CC_nocall = 0x03,
DW_CC_pass_by_reference = 0x04,
DW_CC_pass_by_value = 0x05,
DW_CC_lo_user = 0x40,
DW_CC_hi_user = 0xff
)
@@ -0,0 +1,280 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/lineprogram.py
#
# DWARF line number program
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import os
import copy
from functools import cached_property
from typing import IO, TYPE_CHECKING, Any, NamedTuple
from ..common.utils import struct_parse, dwarf_assert
from .constants import DW_LNE, DW_LNS
if TYPE_CHECKING:
from ..construct.lib.container import Container
from .structs import DWARFStructs
# LineProgramEntry - an entry in the line program.
# A line program is a sequence of encoded entries. Some of these entries add a
# new LineState (mapping between line and address), and some don't.
#
# command:
# The command/opcode - always numeric. For standard commands - it's the opcode
# that can be matched with one of the DW_LNS constants. For extended commands
# it's the extended opcode that can be matched with one of the DW_LNE
# constants. For special commands, it's the opcode itself.
#
# args:
# A list of decoded arguments of the command.
#
# is_extended:
# Since extended commands are encoded by a zero followed by an extended
# opcode, and these extended opcodes overlap with other opcodes, this
# flag is needed to mark that the command has an extended opcode.
#
# state:
# For commands that add a new state, it's the relevant LineState object.
# For commands that don't add a new state, it's None.
#
class LineProgramEntry(NamedTuple):
command: int
is_extended: bool
args: list[int]
state: LineState | None
class LineState:
""" Represents a line program state (or a "row" in the matrix
describing debug location information for addresses).
The instance variables of this class are the "state machine registers"
described in section 6.2.2 of DWARFv3
"""
def __init__(self, default_is_stmt: int) -> None:
self.address = 0
self.file = 1
self.line = 1
self.column = 0
self.op_index = 0
self.is_stmt = default_is_stmt
self.basic_block = False
self.end_sequence = False
self.prologue_end = False
self.epilogue_begin = False
self.isa = 0
self.discriminator = 0
def __repr__(self) -> str:
return '\n'.join((
'<LineState %x:' % id(self),
' address = 0x%x' % self.address,
*(
' %s = %s' % (attr, getattr(self, attr))
for attr in ('file', 'line', 'column', 'is_stmt', 'basic_block',
'end_sequence', 'prologue_end', 'epilogue_begin', 'isa',
'discriminator')
),
'>',
))
class LineProgram:
""" Builds a "line table", which is essentially the matrix described
in section 6.2 of DWARFv3. It's a list of LineState objects,
sorted by increasing address, so it can be used to obtain the
state information for each address.
"""
def __init__(
self,
header: Container,
stream: IO[bytes],
structs: DWARFStructs,
program_start_offset: int,
program_end_offset: int,
) -> None:
"""
header:
The header of this line program. Note: LineProgram may modify
its header by appending file entries if DW_LNE.define_file
instructions are encountered.
stream:
The stream this program can be read from.
structs:
A DWARFStructs instance suitable for this line program
program_{start|end}_offset:
Offset in the debug_line section stream where this program
starts (the actual program, after the header), and where it
ends.
The actual range includes start but not end: [start, end - 1]
"""
self.stream = stream
self.header = header
self.structs = structs
self.program_start_offset = program_start_offset
self.program_end_offset = program_end_offset
def get_entries(self) -> list[LineProgramEntry]:
""" Get the decoded entries for this line program. Return a list of
LineProgramEntry objects.
Note that this contains more information than absolutely required
for the line table. The line table can be easily extracted from
the list of entries by looking only at entries with non-None
state. The extra information is mainly for the purposes of display
with readelf and debugging.
"""
return self._decode_line_program
#------ PRIVATE ------#
def __getitem__(self, name: str) -> Any:
""" Implement dict-like access to header entries
"""
return self.header[name]
@cached_property
def _decode_line_program(self) -> list[LineProgramEntry]:
entries = []
state = LineState(self.header['default_is_stmt'])
def add_entry_new_state(cmd: int, args: list[int], is_extended: bool = False) -> None:
# Add an entry that sets a new state.
# After adding, clear some state registers.
entries.append(LineProgramEntry(
cmd, is_extended, args, copy.copy(state)))
state.discriminator = 0
state.basic_block = False
state.prologue_end = False
state.epilogue_begin = False
def add_entry_old_state(cmd: int, args: list[int], is_extended: bool = False) -> None:
# Add an entry that doesn't visibly set a new state
entries.append(LineProgramEntry(cmd, is_extended, args, None))
offset = self.program_start_offset
while offset < self.program_end_offset:
opcode: int = struct_parse(
self.structs.the_Dwarf_uint8,
self.stream,
offset)
# As an exercise in avoiding premature optimization, if...elif
# chains are used here for standard and extended opcodes instead
# of dispatch tables. This keeps the code much cleaner. Besides,
# the majority of instructions in a typical program are special
# opcodes anyway.
if opcode >= self.header['opcode_base']:
# Special opcode (follow the recipe in 6.2.5.1)
maximum_operations_per_instruction: int = self['maximum_operations_per_instruction']
adjusted_opcode: int = opcode - self['opcode_base']
operation_advance: int = adjusted_opcode // self['line_range']
address_addend: int = (
self['minimum_instruction_length'] *
((state.op_index + operation_advance) //
maximum_operations_per_instruction))
state.address += address_addend
state.op_index = (state.op_index + operation_advance) % maximum_operations_per_instruction
line_addend: int = self['line_base'] + (adjusted_opcode % self['line_range'])
state.line += line_addend
add_entry_new_state(
opcode, [line_addend, address_addend, state.op_index])
elif opcode == 0:
# Extended opcode: start with a zero byte, followed by
# instruction size and the instruction itself.
inst_len: int = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
ex_opcode: int = struct_parse(self.structs.the_Dwarf_uint8,
self.stream)
if ex_opcode == DW_LNE.end_sequence:
state.end_sequence = True
state.is_stmt = 0
add_entry_new_state(ex_opcode, [], is_extended=True)
# reset state
state = LineState(self.header['default_is_stmt'])
elif ex_opcode == DW_LNE.set_address:
operand: int = struct_parse(self.structs.the_Dwarf_target_addr,
self.stream)
state.address = operand
add_entry_old_state(ex_opcode, [operand], is_extended=True)
elif ex_opcode == DW_LNE.define_file:
operand = struct_parse(
self.structs.Dwarf_lineprog_file_entry, self.stream)
self['file_entry'].append(operand)
add_entry_old_state(ex_opcode, [operand], is_extended=True)
elif ex_opcode == DW_LNE.set_discriminator:
operand = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
state.discriminator = operand
else:
# Unknown, but need to roll forward the stream because the
# length is specified. Seek forward inst_len - 1 because
# we've already read the extended opcode, which takes part
# in the length.
self.stream.seek(inst_len - 1, os.SEEK_CUR)
else: # 0 < opcode < opcode_base
# Standard opcode
if opcode == DW_LNS.copy:
add_entry_new_state(opcode, [])
elif opcode == DW_LNS.advance_pc:
operand = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
address_addend = (
operand * self.header['minimum_instruction_length'])
state.address += address_addend
add_entry_old_state(opcode, [address_addend])
elif opcode == DW_LNS.advance_line:
operand = struct_parse(self.structs.the_Dwarf_sleb128,
self.stream)
state.line += operand
elif opcode == DW_LNS.set_file:
operand = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
state.file = operand
add_entry_old_state(opcode, [operand])
elif opcode == DW_LNS.set_column:
operand = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
state.column = operand
add_entry_old_state(opcode, [operand])
elif opcode == DW_LNS.negate_stmt:
state.is_stmt = not state.is_stmt
add_entry_old_state(opcode, [])
elif opcode == DW_LNS.set_basic_block:
state.basic_block = True
add_entry_old_state(opcode, [])
elif opcode == DW_LNS.const_add_pc:
adjusted_opcode = 255 - self['opcode_base']
address_addend = ((adjusted_opcode // self['line_range']) *
self['minimum_instruction_length'])
state.address += address_addend
add_entry_old_state(opcode, [address_addend])
elif opcode == DW_LNS.fixed_advance_pc:
operand = struct_parse(self.structs.the_Dwarf_uint16,
self.stream)
state.address += operand
add_entry_old_state(opcode, [operand])
elif opcode == DW_LNS.set_prologue_end:
state.prologue_end = True
add_entry_old_state(opcode, [])
elif opcode == DW_LNS.set_epilogue_begin:
state.epilogue_begin = True
add_entry_old_state(opcode, [])
elif opcode == DW_LNS.set_isa:
operand = struct_parse(self.structs.the_Dwarf_uleb128,
self.stream)
state.isa = operand
add_entry_old_state(opcode, [operand])
else:
dwarf_assert(False, 'Invalid standard line program opcode: %s' % (
opcode,))
offset = self.stream.tell()
return entries
@@ -0,0 +1,422 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/locationlists.py
#
# DWARF location lists section decoding (.debug_loc)
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import os
from typing import IO, TYPE_CHECKING, NamedTuple
from ..common.exceptions import DWARFError
from ..common.utils import struct_parse
from .dwarf_util import _iter_CUs_in_section
if TYPE_CHECKING:
from collections.abc import Callable, Iterator, Mapping
from ..construct.lib.container import Container
from .compileunit import CompileUnit
from .die import DIE, AttributeValue
from .dwarfinfo import DWARFInfo
from .structs import DWARFStructs
from .typeunit import TypeUnit
class LocationExpr(NamedTuple):
loc_expr: list[int]
class LocationEntry(NamedTuple):
entry_offset: int
entry_length: int
begin_offset: int
end_offset: int
loc_expr: list[int]
is_absolute: bool
class BaseAddressEntry(NamedTuple):
entry_offset: int
entry_length: int
base_address: int
class LocationViewPair(NamedTuple):
entry_offset: int
begin: int
end: int
if TYPE_CHECKING:
_Location = LocationExpr | LocationEntry | BaseAddressEntry | LocationViewPair
def _translate_startx_length(e: Container, cu: CompileUnit | TypeUnit) -> LocationEntry:
start_offset: int = cu.dwarfinfo.get_addr(cu, e.start_index)
return LocationEntry(e.entry_offset, e.entry_length, start_offset, start_offset + e.length, e.loc_expr, True)
# Maps parsed entries to the tuples above; LocationViewPair is mapped elsewhere
entry_translate: dict[str, Callable[[Container, CompileUnit | TypeUnit], _Location]] = {
'DW_LLE_base_address' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.entry_length, e.address),
'DW_LLE_offset_pair' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, e.loc_expr, False),
'DW_LLE_start_length' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, e.loc_expr, True),
'DW_LLE_start_end' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, e.loc_expr, True),
'DW_LLE_default_location': lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, -1, -1, e.loc_expr, True),
'DW_LLE_base_addressx' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.index)), # type: ignore[has-type]
'DW_LLE_startx_endx' : lambda e, cu: LocationEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.start_index), cu.dwarfinfo.get_addr(cu, e.end_index), e.loc_expr, True), # type: ignore[has-type]
'DW_LLE_startx_length' : _translate_startx_length
}
class LocationListsPair:
"""For those binaries that contain both a debug_loc and a debug_loclists section,
it holds a LocationLists object for both and forwards API calls to the right one.
"""
def __init__(
self,
streamv4: IO[bytes],
streamv5: IO[bytes],
structs: DWARFStructs,
dwarfinfo: DWARFInfo | None = None,
) -> None:
self._loc = LocationLists(streamv4, structs, 4, dwarfinfo)
self._loclists = LocationLists(streamv5, structs, 5, dwarfinfo)
def get_location_list_at_offset(self, offset: int, die: DIE | None = None) -> list[_Location]:
"""See LocationLists.get_location_list_at_offset().
"""
if die is None:
raise DWARFError("For this binary, \"die\" needs to be provided")
section = self._loclists if die.cu.header.version >= 5 else self._loc
return section.get_location_list_at_offset(offset, die)
def iter_location_lists(self) -> Iterator[BaseAddressEntry | LocationEntry]:
"""Tricky proposition, since the structure of loc and loclists
is not identical. A realistic readelf implementation needs to be aware of both
"""
raise DWARFError("Iterating through two sections is not supported")
def iter_CUs(self) -> Iterator[CompileUnit]:
"""See LocationLists.iter_CUs()
There are no CUs in DWARFv4 sections.
"""
raise DWARFError("Iterating through two sections is not supported")
class LocationLists:
""" A single location list is a Python list consisting of LocationEntry or
BaseAddressEntry objects.
Starting with DWARF5, it may also contain LocationViewPair, but only
if scanning the section, never when requested for a DIE attribute.
The default location entries are returned as LocationEntry with
begin_offset == end_offset == -1
Version determines whether the executable contains a debug_loc
section, or a DWARFv5 style debug_loclists one. Only the 4/5
distinction matters.
Dwarfinfo is only needed for DWARFv5 location entry encodings
that contain references to other sections (e. g. DW_LLE_startx_endx),
and only for location list enumeration.
"""
def __init__(
self,
stream: IO[bytes],
structs: DWARFStructs,
version: int = 4,
dwarfinfo: DWARFInfo | None = None,
) -> None:
self.stream = stream
self.structs = structs
self.dwarfinfo = dwarfinfo
self.version = version
self._max_addr: int = 2 ** (self.structs.address_size * 8) - 1
def get_location_list_at_offset(self, offset: int, die: DIE | None = None) -> list[_Location]:
""" Get a location list at the given offset in the section.
Passing the die is only neccessary in DWARF5+, for decoding
location entry encodings that contain references to other sections.
"""
self.stream.seek(offset, os.SEEK_SET)
if self.version >= 5:
if die is None:
raise DWARFError("For this binary, \"die\" needs to be provided")
return self._parse_location_list_from_stream_v5(die.cu)
return self._parse_location_list_from_stream()
def iter_location_lists(self) -> Iterator[list[_Location]]:
""" Iterates through location lists and view pairs. Returns lists of
LocationEntry, BaseAddressEntry, and LocationViewPair objects.
"""
# The location lists section was never meant for sequential access.
# Location lists are referenced by DIE attributes by offset or by index.
# As of DWARFv5, it may contain, in addition to proper location lists,
# location list view pairs, which are referenced by the nonstandard DW_AT_GNU_locviews
# attribute. A set of locview pairs (which is a couple of ULEB128 values) may preceed
# a location list; the former is referenced by the DW_AT_GNU_locviews attribute, the
# latter - by DW_AT_location (in the same DIE). Binutils' readelf dumps those.
# There is a view pair for each location-type entry in the list.
#
# Also, the section may contain gaps.
#
# Taking a cue from binutils, we would have to scan this section while looking at
# what's in DIEs.
ver5 = self.version >= 5
stream = self.stream
stream.seek(0, os.SEEK_END)
endpos = stream.tell()
stream.seek(0, os.SEEK_SET)
# Need to provide support for DW_AT_GNU_locviews. They are interspersed in
# the locations section, no way to tell where short of checking all DIEs
all_offsets = set() # Set of offsets where either a locview pair set can be found, or a view-less loclist
locviews = dict() # Map of locview offset to the respective loclist offset
cu_map = dict() # Map of loclist offsets to CUs
assert self.dwarfinfo is not None
for cu in self.dwarfinfo.iter_CUs():
cu_ver: int = cu['version']
if (cu_ver >= 5) == ver5:
for die in cu.iter_DIEs():
# A combination of location and locviews means there is a location list
# preceed by several locview pairs
if 'DW_AT_GNU_locviews' in die.attributes:
assert('DW_AT_location' in die.attributes and
LocationParser._attribute_has_loc_list(die.attributes['DW_AT_location'], cu_ver))
views_offset: int = die.attributes['DW_AT_GNU_locviews'].value
list_offset: int = die.attributes['DW_AT_location'].value
locviews[views_offset] = list_offset
cu_map[list_offset] = cu
all_offsets.add(views_offset)
# Scan other attributes for location lists
for key in die.attributes:
attr = die.attributes[key]
if ((key != 'DW_AT_location' or 'DW_AT_GNU_locviews' not in die.attributes) and
LocationParser.attribute_has_location(attr, cu_ver) and
LocationParser._attribute_has_loc_list(attr, cu_ver)):
list_offset = attr.value
all_offsets.add(list_offset)
cu_map[list_offset] = cu
sorted_offsets = sorted(all_offsets)
if ver5:
# Loclists section is organized as an array of CUs, each length prefixed.
# We don't assume that the CUs go in the same order as the ones in info.
offset_index = 0
while stream.tell() < endpos:
# We are at the start of the CU block in the loclists now
cu_header = struct_parse(self.structs.Dwarf_loclists_CU_header, stream)
assert(cu_header.version == 5)
# GNU binutils supports two traversal modes: by offsets in CU header, and sequential.
# We don't have a binary for the former yet. On an off chance that we one day might,
# let's parse the header anyway.
cu_end_offset: int = cu_header.offset_after_length + cu_header.unit_length
# Unit_length includes the header but doesn't include the length
while stream.tell() < cu_end_offset:
# Skip the gap to the next object
next_offset = sorted_offsets[offset_index]
if next_offset == stream.tell(): # At an object, either a loc list or a loc view pair
locview_pairs = self._parse_locview_pairs(locviews)
entries = self._parse_location_list_from_stream_v5(cu_map[stream.tell()])
yield locview_pairs + entries
offset_index += 1
else: # We are at a gap - skip the gap to the next object or to the next CU
if next_offset > cu_end_offset: # Gap at the CU end - the next object is in the next CU
next_offset = cu_end_offset # And implicitly quit the loop within the CU
stream.seek(next_offset, os.SEEK_SET)
else:
for offset in sorted_offsets:
list_offset = locviews.get(offset, offset)
if cu_map[list_offset].header.version < 5:
stream.seek(offset, os.SEEK_SET)
locview_pairs = self._parse_locview_pairs(locviews)
entries = self._parse_location_list_from_stream()
yield locview_pairs + entries
def iter_CUs(self) -> Iterator[CompileUnit]:
"""For DWARF5 returns an array of objects, where each one has an array of offsets
"""
if self.version < 5:
raise DWARFError("CU iteration in loclists is not supported with DWARF<5")
assert self.dwarfinfo is not None
structs = next(self.dwarfinfo.iter_CUs()).structs # Just pick one
return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_loclists_CU_header)
#------ PRIVATE ------#
def _parse_location_list_from_stream(self) -> list[_Location]:
lst: list[_Location] = []
while True:
entry_offset = self.stream.tell()
begin_offset: int = struct_parse(
self.structs.the_Dwarf_target_addr, self.stream)
end_offset: int = struct_parse(
self.structs.the_Dwarf_target_addr, self.stream)
if begin_offset == 0 and end_offset == 0:
# End of list - we're done.
break
elif begin_offset == self._max_addr:
# Base address selection entry
entry_length = self.stream.tell() - entry_offset
lst.append(BaseAddressEntry(entry_offset=entry_offset, entry_length=entry_length, base_address=end_offset))
else:
# Location list entry
expr_len: int = struct_parse(
self.structs.the_Dwarf_uint16, self.stream)
loc_expr: list[int] = [struct_parse(self.structs.the_Dwarf_uint8,
self.stream)
for i in range(expr_len)]
entry_length = self.stream.tell() - entry_offset
lst.append(LocationEntry(
entry_offset=entry_offset,
entry_length=entry_length,
begin_offset=begin_offset,
end_offset=end_offset,
loc_expr=loc_expr,
is_absolute = False))
return lst
def _parse_location_list_from_stream_v5(
self,
cu: CompileUnit | TypeUnit | None = None,
) -> list[_Location]:
""" Returns an array with BaseAddressEntry and LocationEntry.
No terminator entries.
The cu argument is necessary if the section is a
DWARFv5 debug_loclists one, and the target loclist
contains indirect encodings.
"""
return [entry_translate[entry.entry_type](entry, cu) # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
for entry
in struct_parse(self.structs.Dwarf_loclists_entries, self.stream)]
# From V5 style entries to a LocationEntry/BaseAddressEntry
def _translate_entry_v5(self, entry: Container, die: DIE) -> _Location:
off: int = entry.entry_offset
len: int = entry.entry_end_offset - off
type: str = entry.entry_type
if type == 'DW_LLE_base_address':
return BaseAddressEntry(off, len, entry.address)
elif type == 'DW_LLE_offset_pair':
return LocationEntry(off, len, entry.start_offset, entry.end_offset, entry.loc_expr, False)
elif type == 'DW_LLE_start_length':
return LocationEntry(off, len, entry.start_address, entry.start_address + entry.length, entry.loc_expr, True)
elif type == 'DW_LLE_start_end': # No test for this yet, but the format seems straightforward
return LocationEntry(off, len, entry.start_address, entry.end_address, entry.loc_expr, True)
elif type == 'DW_LLE_default_location': # No test for this either, and this is new in the API
return LocationEntry(off, len, -1, -1, entry.loc_expr, True)
elif type in ('DW_LLE_base_addressx', 'DW_LLE_startx_endx', 'DW_LLE_startx_length'):
# We don't have sample binaries for those LLEs. Their proper parsing would
# require knowing the CU context (so that indices can be resolved to code offsets)
raise NotImplementedError("Location list entry type %s is not supported yet" % (type,))
else:
raise DWARFError(False, "Unknown DW_LLE code: %s" % (type,))
# Locviews is the dict, mapping locview offsets to corresponding loclist offsets
def _parse_locview_pairs(self, locviews: Mapping[int, int]) -> list[LocationViewPair]:
stream = self.stream
list_offset: int | None = locviews.get(stream.tell(), None)
pairs: list[LocationViewPair] = []
if list_offset is not None:
while stream.tell() < list_offset:
pair = struct_parse(self.structs.Dwarf_locview_pair, stream)
pairs.append(LocationViewPair(pair.entry_offset, pair.begin, pair.end))
assert(stream.tell() == list_offset)
return pairs
class LocationParser:
""" A parser for location information in DIEs.
Handles both location information contained within the attribute
itself (represented as a LocationExpr object) and references to
location lists in the .debug_loc section (represented as a
list).
"""
def __init__(self, location_lists: LocationLists | LocationListsPair | None) -> None:
self.location_lists = location_lists
@staticmethod
def attribute_has_location(attr: AttributeValue, dwarf_version: int) -> bool:
""" Checks if a DIE attribute contains location information.
"""
return (LocationParser._attribute_is_loclistptr_class(attr) and
(LocationParser._attribute_has_loc_expr(attr, dwarf_version) or
LocationParser._attribute_has_loc_list(attr, dwarf_version)))
def parse_from_attribute(
self,
attr: AttributeValue,
dwarf_version: int,
die: DIE | None = None,
) -> LocationExpr | list[_Location]:
""" Parses a DIE attribute and returns either a LocationExpr or
a list.
"""
if self.attribute_has_location(attr, dwarf_version):
if self._attribute_has_loc_expr(attr, dwarf_version):
return LocationExpr(attr.value)
elif self._attribute_has_loc_list(attr, dwarf_version):
assert self.location_lists is not None
return self.location_lists.get_location_list_at_offset(
attr.value, die)
# We don't yet know if the DIE context will be needed.
# We might get it without a full tree traversal using
# attr.offset as a key, but we assume a good DWARF5
# aware consumer would pass a DIE along.
raise ValueError("Attribute does not have location information")
#------ PRIVATE ------#
@staticmethod
def _attribute_has_loc_expr(attr: AttributeValue, dwarf_version: int) -> bool:
return ((dwarf_version < 4 and attr.form.startswith('DW_FORM_block') and
not attr.name == 'DW_AT_const_value') or
attr.form == 'DW_FORM_exprloc')
@staticmethod
def _attribute_has_loc_list(attr: AttributeValue, dwarf_version: int) -> bool:
return (((dwarf_version < 4 and
attr.form in ('DW_FORM_data1', 'DW_FORM_data2', 'DW_FORM_data4', 'DW_FORM_data8') and
not attr.name == 'DW_AT_const_value') or
attr.form in ('DW_FORM_sec_offset', 'DW_FORM_loclistx')) and
not LocationParser._attribute_is_constant(attr, dwarf_version))
# Starting with DWARF3, DW_AT_data_member_location may contain an integer offset
# instead of a location expression. Need to prevent false positives on attribute_has_location().
# As for DW_AT_upper_bound/DW_AT_count, we've seen it in form DW_FORM_locexpr in a V5 binary. usually it's a constant,
# but the constant sholdn't be misinterpreted as a loclist pointer.
@staticmethod
def _attribute_is_constant(attr: AttributeValue, dwarf_version: int) -> bool:
return (((dwarf_version >= 3 and attr.name == 'DW_AT_data_member_location') or
(attr.name in ('DW_AT_upper_bound', 'DW_AT_count'))) and
attr.form in ('DW_FORM_data1', 'DW_FORM_data2', 'DW_FORM_data4', 'DW_FORM_data8', 'DW_FORM_sdata', 'DW_FORM_udata'))
@staticmethod
def _attribute_is_loclistptr_class(attr: AttributeValue) -> bool:
return (attr.name in ( 'DW_AT_location', 'DW_AT_string_length',
'DW_AT_const_value', 'DW_AT_return_addr',
'DW_AT_data_member_location',
'DW_AT_frame_base', 'DW_AT_segment',
'DW_AT_static_link', 'DW_AT_use_location',
'DW_AT_vtable_elem_location',
'DW_AT_call_value',
'DW_AT_GNU_call_site_value',
'DW_AT_GNU_call_site_target',
'DW_AT_GNU_call_site_data_value',
'DW_AT_call_target',
'DW_AT_call_target_clobbered',
'DW_AT_call_data_location',
'DW_AT_call_data_value',
'DW_AT_upper_bound',
'DW_AT_count'))
@@ -0,0 +1,201 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/namelut.py
#
# DWARF pubtypes/pubnames section decoding (.debug_pubtypes, .debug_pubnames)
#
# Vijay Ramasami (rvijayc@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from collections.abc import Mapping
from functools import cached_property
from typing import IO, TYPE_CHECKING, NamedTuple, TypeVar, overload
from ..common.utils import struct_parse
from ..construct import CString, Struct, If
if TYPE_CHECKING:
from collections.abc import ItemsView, Iterator
from ..construct.lib.container import Container
from .structs import DWARFStructs
_T = TypeVar("_T")
class NameLUTEntry(NamedTuple):
cu_ofs: int
die_ofs: int
class NameLUT(Mapping[str, NameLUTEntry]):
"""
A "Name LUT" holds any of the tables specified by .debug_pubtypes or
.debug_pubnames sections. This is basically a dictionary where the key is
the symbol name (either a public variable, function or a type), and the
value is the tuple (cu_offset, die_offset) corresponding to the variable.
The die_offset is an absolute offset (meaning, it can be used to search the
CU by iterating until a match is obtained).
An ordered dictionary is used to preserve the CU order (i.e, items are
stored on a per-CU basis (as it was originally in the .debug_* section).
Usage:
The NameLUT walks and talks like a dictionary and hence it can be used as
such. Some examples below:
# get the pubnames (a NameLUT from DWARF info).
pubnames = dwarf_info.get_pubnames()
# lookup a variable.
entry1 = pubnames["var_name1"]
entry2 = pubnames.get("var_name2", default=<default_var>)
print(entry2.cu_ofs)
...
# iterate over items.
for (name, entry) in pubnames.items():
# do stuff with name, entry.cu_ofs, entry.die_ofs
# iterate over items on a per-CU basis.
import itertools
for cu_ofs, item_list in itertools.groupby(pubnames.items(),
key = lambda x: x[1].cu_ofs):
# items are now grouped by cu_ofs.
# item_list is an iterator yeilding NameLUTEntry'ies belonging
# to cu_ofs.
# We can parse the CU at cu_offset and use the parsed CU results
# to parse the pubname DIEs in the CU listed by item_list.
for item in item_list:
# work with item which is part of the CU with cu_ofs.
"""
def __init__(self, stream: IO[bytes], size: int, structs: DWARFStructs) -> None:
self._stream = stream
self._size = size
self._structs = structs
def get_entries(self) -> dict[str, NameLUTEntry]:
"""
Returns the parsed NameLUT entries. The returned object is a dictionary
with the symbol name as the key and NameLUTEntry(cu_ofs, die_ofs) as
the value.
This is useful when dealing with very large ELF files with millions of
entries. The returned entries can be pickled to a file and restored by
calling set_entries on subsequent loads.
"""
return self._entries
def set_entries(self, entries: dict[str, NameLUTEntry], cu_headers: list[Container]) -> None:
"""
Set the NameLUT entries from an external source. The input is a
dictionary with the symbol name as the key and NameLUTEntry(cu_ofs,
die_ofs) as the value.
This option is useful when dealing with very large ELF files with
millions of entries. The entries can be parsed once and pickled to a
file and can be restored via this function on subsequent loads.
"""
self._entries = entries
self._cu_headers = cu_headers
def __len__(self) -> int:
"""
Returns the number of entries in the NameLUT.
"""
return len(self._entries)
def __getitem__(self, name: str) -> NameLUTEntry:
"""
Returns a namedtuple - NameLUTEntry(cu_ofs, die_ofs) - that corresponds
to the given symbol name.
"""
return self._entries[name]
def __iter__(self) -> Iterator[str]:
"""
Returns an iterator to the NameLUT dictionary.
"""
return iter(self._entries)
def items(self) -> ItemsView[str, NameLUTEntry]:
"""
Returns the NameLUT dictionary items.
"""
return self._entries.items()
@overload
def get(self, name: str) -> NameLUTEntry | None: ...
@overload
def get(self, name: str, default: NameLUTEntry | _T = ...) -> NameLUTEntry | _T: ...
def get(self, name: str, default: NameLUTEntry | _T | None = None) -> NameLUTEntry | _T | None:
"""
Returns NameLUTEntry(cu_ofs, die_ofs) for the provided symbol name or
None if the symbol does not exist in the corresponding section.
"""
return self._entries.get(name, default)
def get_cu_headers(self) -> list[Container]:
"""
Returns all CU headers. Mainly required for readelf.
"""
return self._cu_headers
@cached_property
def _entries(self) -> dict[str, NameLUTEntry]:
return self.__entries[0]
@cached_property
def _cu_headers(self) -> list[Container]:
return self.__entries[1]
@cached_property
def __entries(self) -> tuple[dict[str, NameLUTEntry], list[Container]]:
"""
Parse the (name, cu_ofs, die_ofs) information from this section.
"""
self._stream.seek(0)
entries: dict[str, NameLUTEntry] = {}
cu_headers: list[Container] = []
offset = 0
# According to 6.1.1. of DWARFv4, each set of names is terminated by
# an offset field containing zero (and no following string). Because
# of sequential parsing, every next entry may be that terminator.
# So, field "name" is conditional.
entry_struct = Struct("Dwarf_offset_name_pair",
self._structs.Dwarf_offset('die_ofs'),
If(lambda ctx: ctx['die_ofs'], CString('name')))
# each run of this loop will fetch one CU worth of entries.
while offset < self._size:
# read the header for this CU.
namelut_hdr = struct_parse(self._structs.Dwarf_nameLUT_header,
self._stream, offset)
cu_headers.append(namelut_hdr)
# compute the next offset.
offset = (offset + namelut_hdr.unit_length +
self._structs.initial_length_field_size())
# before inner loop, latch data that will be used in the inner
# loop to avoid attribute access and other computation.
hdr_cu_ofs = namelut_hdr.debug_info_offset
# while die_ofs of the entry is non-zero (which indicates the end) ...
while True:
entry = struct_parse(entry_struct, self._stream)
# if it is zero, this is the terminating record.
if entry.die_ofs == 0:
break
# add this entry to the look-up dictionary.
entries[entry.name.decode('utf-8')] = NameLUTEntry(
cu_ofs = hdr_cu_ofs,
die_ofs = hdr_cu_ofs + entry.die_ofs)
# return the entries parsed so far.
return (entries, cu_headers)
@@ -0,0 +1,252 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/ranges.py
#
# DWARF ranges section decoding (.debug_ranges)
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
import os
from typing import IO, TYPE_CHECKING, NamedTuple, NoReturn
from ..common.utils import struct_parse
from ..common.exceptions import DWARFError
from .dwarf_util import _iter_CUs_in_section
if TYPE_CHECKING:
from collections.abc import Callable, Iterator
from ..construct.lib.container import Container
from .compileunit import CompileUnit
from .dwarfinfo import DWARFInfo
from .structs import DWARFStructs
class RangeEntry(NamedTuple):
entry_offset: int
entry_length: int
begin_offset: int
end_offset: int
is_absolute: bool
class BaseAddressEntry(NamedTuple):
entry_offset: int
base_address: int
# If we ever see a list with a base entry at the end, there will be an error that entry_length is not a field.
def _translate_startx_length(e: Container, cu: CompileUnit) -> RangeEntry:
start_offset = cu.dwarfinfo.get_addr(cu, e.start_index)
return RangeEntry(e.entry_offset, e.entry_length, start_offset, start_offset + e.length, True)
# Maps parsed entry types to RangeEntry/BaseAddressEntry objects
entry_translate: dict[str, Callable[[Container, CompileUnit], RangeEntry | BaseAddressEntry]] = {
'DW_RLE_base_address' : lambda e, cu: BaseAddressEntry(e.entry_offset, e.address),
'DW_RLE_offset_pair' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_offset, e.end_offset, False),
'DW_RLE_start_end' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.end_address, True),
'DW_RLE_start_length' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, e.start_address, e.start_address + e.length, True),
'DW_RLE_base_addressx': lambda e, cu: BaseAddressEntry(e.entry_offset, cu.dwarfinfo.get_addr(cu, e.index)), # type: ignore[has-type]
'DW_RLE_startx_endx' : lambda e, cu: RangeEntry(e.entry_offset, e.entry_length, cu.dwarfinfo.get_addr(cu, e.start_index), cu.dwarfinfo.get_addr(cu, e.end_index), True), # type: ignore[has-type]
'DW_RLE_startx_length': _translate_startx_length
}
class RangeListsPair:
"""For those binaries that contain both a debug_ranges and a debug_rnglists section,
it holds a RangeLists object for both and forwards API calls to the right one based
on the CU version.
"""
def __init__(
self,
streamv4: IO[bytes],
streamv5: IO[bytes],
structs: DWARFStructs,
dwarfinfo: DWARFInfo | None = None,
) -> None:
self._ranges = RangeLists(streamv4, structs, 4, dwarfinfo)
self._rnglists = RangeLists(streamv5, structs, 5, dwarfinfo)
def get_range_list_at_offset(
self,
offset: int,
cu: CompileUnit | None = None,
) -> list[RangeEntry | BaseAddressEntry]:
"""Forwards the call to either v4 section or v5 one,
depending on DWARF version in the CU.
"""
if cu is None:
raise DWARFError("For this binary, \"cu\" needs to be provided")
section = self._rnglists if cu.header.version >= 5 else self._ranges
return section.get_range_list_at_offset(offset, cu)
def get_range_list_at_offset_ex(self, offset: int) -> Container:
"""Gets an untranslated v5 rangelist from the v5 section.
"""
return self._rnglists.get_range_list_at_offset_ex(offset)
def iter_range_lists(self) -> NoReturn:
"""Tricky proposition, since the structure of ranges and rnglists
is not identical. A realistic readelf implementation needs to be aware of both.
"""
raise DWARFError("Iterating through two sections is not supported")
def iter_CUs(self) -> Iterator[CompileUnit]:
"""See RangeLists.iter_CUs()
CU structure is only present in DWARFv5 rnglists sections. A well written
section dumper should check if one is present.
"""
return self._rnglists.iter_CUs()
def iter_CU_range_lists_ex(self, cu: Container) -> Iterator[CompileUnit]:
"""See RangeLists.iter_CU_range_lists_ex()
CU structure is only present in DWARFv5 rnglists sections. A well written
section dumper should check if one is present.
"""
return self._rnglists.iter_CU_range_lists_ex(cu)
def translate_v5_entry(
self,
entry: Container,
cu: CompileUnit,
) -> RangeEntry | BaseAddressEntry:
"""Forwards a V5 entry translation request to the V5 section
"""
return self._rnglists.translate_v5_entry(entry, cu)
class RangeLists:
""" A single range list is a Python list consisting of RangeEntry or
BaseAddressEntry objects.
Since v0.29, two new parameters - version and dwarfinfo
version is used to distinguish DWARFv5 rnglists section from
the DWARF<=4 ranges section. Only the 4/5 distinction matters.
The dwarfinfo is needed for enumeration, because enumeration
requires scanning the DIEs, because ranges may overlap, even on DWARF<=4
"""
def __init__(
self,
stream: IO[bytes],
structs: DWARFStructs,
version: int,
dwarfinfo: DWARFInfo | None,
) -> None:
self.stream = stream
self.structs = structs
self._max_addr = 2 ** (self.structs.address_size * 8) - 1
self.version = version
self._dwarfinfo = dwarfinfo
def get_range_list_at_offset(
self,
offset: int,
cu: CompileUnit | None = None,
) -> list[RangeEntry | BaseAddressEntry]:
""" Get a range list at the given offset in the section.
The cu argument is necessary if the ranges section is a
DWARFv5 debug_rnglists one, and the target rangelist
contains indirect encodings
"""
self.stream.seek(offset, os.SEEK_SET)
return self._parse_range_list_from_stream(cu)
def get_range_list_at_offset_ex(self, offset: int) -> Container:
"""Get a DWARF v5 range list, addresses and offsets unresolved,
at the given offset in the section
"""
return struct_parse(self.structs.Dwarf_rnglists_entries, self.stream, offset)
def iter_range_lists(self) -> Iterator[list[RangeEntry | BaseAddressEntry]]:
""" Yields all range lists found in the section according to readelf rules.
Scans the DIEs for rangelist offsets, then pulls those.
Returned rangelists are always translated into lists of BaseAddressEntry/RangeEntry objects.
"""
# Rangelists can overlap. That is, one DIE points at the rangelist beginning, and another
# points at the middle of the same. Therefore, enumerating them is not a well defined
# operation - do you count those as two different (but overlapping) ones, or as a single one?
# For debugging utility, you want two. That's what readelf does. For faithfully
# representing the section contents, you want one.
# That was the behaviour of pyelftools 0.28 and below - calling
# parse until the stream end. Leaving aside the question of correctless,
# that's uncompatible with readelf.
ver5 = self.version >= 5
# This maps list offset to CU
cu_map = {die.attributes['DW_AT_ranges'].value : cu
for cu in self._dwarfinfo.iter_CUs() # type: ignore[union-attr] # ty: ignore[unresolved-attribute]
for die in cu.iter_DIEs()
if 'DW_AT_ranges' in die.attributes and (cu['version'] >= 5) == ver5}
all_offsets = list(cu_map.keys())
all_offsets.sort()
for offset in all_offsets:
yield self.get_range_list_at_offset(offset, cu_map[offset])
def iter_CUs(self) -> Iterator[CompileUnit]:
"""For DWARF5 returns an array of objects, where each one has an array of offsets
"""
if self.version < 5:
raise DWARFError("CU iteration in rnglists is not supported with DWARF<5")
assert self._dwarfinfo is not None
structs = next(self._dwarfinfo.iter_CUs()).structs # Just pick one
return _iter_CUs_in_section(self.stream, structs, structs.Dwarf_rnglists_CU_header)
def iter_CU_range_lists_ex(self, cu: Container) -> Iterator[CompileUnit]:
"""For DWARF5, returns untranslated rangelists in the CU, where CU comes from iter_CUs above
"""
stream = self.stream
stream.seek(cu.offset_table_offset + (64 if cu.is64 else 32) * cu.offset_count)
while stream.tell() < cu.offset_after_length + cu.unit_length:
yield struct_parse(self.structs.Dwarf_rnglists_entries, stream)
def translate_v5_entry(
self,
entry: Container,
cu: CompileUnit,
) -> RangeEntry | BaseAddressEntry:
"""Translates entries in a DWARFv5 rangelist from raw parsed format to
a list of BaseAddressEntry/RangeEntry, using the CU
"""
return entry_translate[entry.entry_type](entry, cu)
#------ PRIVATE ------#
def _parse_range_list_from_stream(
self,
cu: CompileUnit | None,
) -> list[RangeEntry | BaseAddressEntry]:
if self.version >= 5:
assert cu is not None
return list(entry_translate[entry.entry_type](entry, cu)
for entry
in struct_parse(self.structs.Dwarf_rnglists_entries, self.stream))
else:
lst: list[RangeEntry | BaseAddressEntry] = []
while True:
entry_offset = self.stream.tell()
begin_offset = struct_parse(
self.structs.the_Dwarf_target_addr, self.stream)
end_offset = struct_parse(
self.structs.the_Dwarf_target_addr, self.stream)
if begin_offset == 0 and end_offset == 0:
# End of list - we're done.
break
elif begin_offset == self._max_addr:
# Base address selection entry
lst.append(BaseAddressEntry(entry_offset=entry_offset, base_address=end_offset))
else:
# Range entry
lst.append(RangeEntry(
entry_offset=entry_offset,
entry_length=self.stream.tell() - entry_offset,
begin_offset=begin_offset,
end_offset=end_offset,
is_absolute=False))
return lst
@@ -0,0 +1,603 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/structs.py
#
# Encapsulation of Construct structs for parsing DWARF, adjusted for correct
# endianness and word-size.
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from typing import IO, TYPE_CHECKING, Any, ClassVar
import elftools.dwarf.enums as e
from ..construct import (
UBInt8, UBInt16, UBInt32, UBInt64, ULInt8, ULInt16, ULInt32, ULInt64,
SBInt8, SBInt16, SBInt32, SBInt64, SLInt8, SLInt16, SLInt32, SLInt64,
Adapter, Struct, ConstructError, If, Enum, Array, PrefixedArray,
CString, Embed, StaticField, IfThenElse, Construct, Rename, String, Switch, Value
)
from ..common.construct_utils import (RepeatUntilExcluding, ULEB128, SLEB128,
StreamOffset, ULInt24, UBInt24)
if TYPE_CHECKING:
from collections.abc import Callable
from typing_extensions import Self # 3.11+
from ..construct.adapters import LengthValueAdapter
from ..construct.lib.container import Container
class DWARFStructs:
""" Exposes Construct structs suitable for parsing information from DWARF
sections. Each compile unit in DWARF info can have its own structs
object. Keep in mind that these structs have to be given a name (by
calling them with a name) before being used for parsing (like other
Construct structs). Those that should be used without a name are marked
by (+).
Accessible attributes (mostly as described in chapter 7 of the DWARF
spec v3):
Dwarf_[u]int{8,16,32,64):
Data chunks of the common sizes
Dwarf_offset:
32-bit or 64-bit word, depending on dwarf_format
Dwarf_length:
32-bit or 64-bit word, depending on dwarf_format
Dwarf_target_addr:
32-bit or 64-bit word, depending on address size
Dwarf_initial_length:
"Initial length field" encoding
section 7.4
Dwarf_{u,s}leb128:
ULEB128 and SLEB128 variable-length encoding
Dwarf_CU_header (+):
Compilation unit header
Dwarf_TU_header (+):
Type unit header
Dwarf_abbrev_declaration (+):
Abbreviation table declaration - doesn't include the initial
code, only the contents.
Dwarf_dw_form (+):
A dictionary mapping 'DW_FORM_*' keys into construct Structs
that parse such forms. These Structs have already been given
dummy names.
Dwarf_lineprog_header (+):
Line program header
Dwarf_lineprog_file_entry (+):
A single file entry in a line program header or instruction
Dwarf_CIE_header (+):
A call-frame CIE
Dwarf_FDE_header (+):
A call-frame FDE
See also the documentation of public methods.
"""
if TYPE_CHECKING:
# type hints for dynamically defined class variables
little_endian: bool
dwarf_format: int
address_size: int
dwarf_version: int
# Cache for structs instances based on creation parameters. Structs
# initialization is expensive and we don't won't to repeat it
# unnecessarily.
_structs_cache: ClassVar[dict[tuple[bool, int, int, int], Self]] = {}
def __new__(
cls,
little_endian: bool,
dwarf_format: int,
address_size: int,
dwarf_version: int = 2,
) -> Self:
""" dwarf_version:
Numeric DWARF version
little_endian:
True if the file is little endian, False if big
dwarf_format:
DWARF Format: 32 or 64-bit (see spec section 7.4)
address_size:
Target machine address size, in bytes (4 or 8). (See spec
section 7.5.1)
"""
key = (little_endian, dwarf_format, address_size, dwarf_version)
if key in cls._structs_cache:
return cls._structs_cache[key]
self = super().__new__(cls)
assert dwarf_format == 32 or dwarf_format == 64
assert address_size == 8 or address_size == 4, str(address_size)
self.little_endian = little_endian
self.dwarf_format = dwarf_format
self.address_size = address_size
self.dwarf_version = dwarf_version
self._create_structs()
cls._structs_cache[key] = self
return self
def initial_length_field_size(self) -> int:
""" Size of an initial length field.
"""
return 4 if self.dwarf_format == 32 else 12
def _create_structs(self) -> None:
if self.little_endian:
self.Dwarf_uint8 = ULInt8
self.Dwarf_uint16 = ULInt16
self.Dwarf_uint24: type[ULInt24 | UBInt24] = ULInt24
self.Dwarf_uint32 = ULInt32
self.Dwarf_uint64 = ULInt64
self.Dwarf_offset = ULInt32 if self.dwarf_format == 32 else ULInt64
self.Dwarf_length = ULInt32 if self.dwarf_format == 32 else ULInt64
self.Dwarf_target_addr = (
ULInt32 if self.address_size == 4 else ULInt64)
self.Dwarf_int8 = SLInt8
self.Dwarf_int16 = SLInt16
self.Dwarf_int32 = SLInt32
self.Dwarf_int64 = SLInt64
else:
self.Dwarf_uint8 = UBInt8
self.Dwarf_uint16 = UBInt16
self.Dwarf_uint24 = UBInt24
self.Dwarf_uint32 = UBInt32
self.Dwarf_uint64 = UBInt64
self.Dwarf_offset = UBInt32 if self.dwarf_format == 32 else UBInt64
self.Dwarf_length = UBInt32 if self.dwarf_format == 32 else UBInt64
self.Dwarf_target_addr = (
UBInt32 if self.address_size == 4 else UBInt64)
self.Dwarf_int8 = SBInt8
self.Dwarf_int16 = SBInt16
self.Dwarf_int32 = SBInt32
self.Dwarf_int64 = SBInt64
# Only instantiate those parsers that are used standalone,
# as opposed to dispatch tables (e. g. forms, opcodes).
# In dispatch tables, they are instantiated already.
# LEB128 parsers are instantiated too, elsewhere.
self.the_Dwarf_offset = self.Dwarf_offset('')
self.the_Dwarf_target_addr = self.Dwarf_target_addr('')
self.the_Dwarf_uint32 = self.Dwarf_uint32('')
self.the_Dwarf_uint16 = self.Dwarf_uint16('')
self.the_Dwarf_uint8 = self.Dwarf_uint8('')
self._create_initial_length()
self._create_leb128()
self._create_cu_header()
self._create_tu_header()
self._create_abbrev_declaration()
self._create_dw_form()
self._create_lineprog_header()
self._create_callframe_entry_headers()
self._create_aranges_header()
self._create_nameLUT_header()
self._create_string_offsets_table_header()
self._create_address_table_header()
self._create_loclists_parsers()
self._create_rnglists_parsers()
self._create_debugsup()
self._create_gnu_debugaltlink()
def _create_initial_length(self) -> None:
def _InitialLength(name: str) -> _InitialLengthAdapter:
# Adapts a Struct that parses forward a full initial length field.
# Only if the first word is the continuation value, the second
# word is parsed from the stream.
return _InitialLengthAdapter(
Struct(name,
self.Dwarf_uint32('first'),
If(lambda ctx: ctx.first == 0xFFFFFFFF,
self.Dwarf_uint64('second'),
elsevalue=None)))
self.Dwarf_initial_length = _InitialLength
def _create_leb128(self) -> None:
self.Dwarf_uleb128 = ULEB128
self.Dwarf_sleb128 = SLEB128
self.the_Dwarf_uleb128 = self.Dwarf_uleb128('')
self.the_Dwarf_sleb128 = self.Dwarf_sleb128('')
def _create_cu_header(self) -> None:
dwarfv4_CU_header = Struct('',
self.Dwarf_offset('debug_abbrev_offset'),
self.Dwarf_uint8('address_size')
)
# DWARFv5 reverses the order of address_size and debug_abbrev_offset.
# DWARFv5 7.5.1.1
dwarfv5_CP_CU_header = Struct('',
self.Dwarf_uint8('address_size'),
self.Dwarf_offset('debug_abbrev_offset')
)
# DWARFv5 7.5.1.2
dwarfv5_SS_CU_header = Struct('',
self.Dwarf_uint8('address_size'),
self.Dwarf_offset('debug_abbrev_offset'),
self.Dwarf_uint64('dwo_id')
)
# DWARFv5 7.5.1.3
dwarfv5_TS_CU_header = Struct('',
self.Dwarf_uint8('address_size'),
self.Dwarf_offset('debug_abbrev_offset'),
self.Dwarf_uint64('type_signature'),
self.Dwarf_offset('type_offset')
)
dwarfv5_CU_header = Struct('',
Enum(self.Dwarf_uint8('unit_type'), **e.ENUM_DW_UT),
Embed(Switch('', lambda ctx: ctx.unit_type,
{
'DW_UT_compile' : dwarfv5_CP_CU_header,
'DW_UT_partial' : dwarfv5_CP_CU_header,
'DW_UT_skeleton' : dwarfv5_SS_CU_header,
'DW_UT_split_compile' : dwarfv5_SS_CU_header,
'DW_UT_type' : dwarfv5_TS_CU_header,
'DW_UT_split_type' : dwarfv5_TS_CU_header,
})))
self.Dwarf_CU_header = Struct('Dwarf_CU_header',
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
IfThenElse('', lambda ctx: ctx['version'] >= 5,
Embed(dwarfv5_CU_header),
Embed(dwarfv4_CU_header),
))
def _create_tu_header(self) -> None:
self.Dwarf_TU_header = Struct('Dwarf_TU_header',
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
self.Dwarf_offset('debug_abbrev_offset'),
self.Dwarf_uint8('address_size'),
self.Dwarf_uint64('signature'),
self.Dwarf_offset('type_offset'))
def _create_abbrev_declaration(self) -> None:
self.Dwarf_abbrev_declaration = Struct('Dwarf_abbrev_entry',
Enum(self.Dwarf_uleb128('tag'), **e.ENUM_DW_TAG),
Enum(self.Dwarf_uint8('children_flag'), **e.ENUM_DW_CHILDREN),
RepeatUntilExcluding(
lambda obj, ctx:
obj.name == 'DW_AT_null' and obj.form == 'DW_FORM_null',
Struct('attr_spec',
Enum(self.Dwarf_uleb128('name'), **e.ENUM_DW_AT),
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM),
If(lambda ctx: ctx['form'] == 'DW_FORM_implicit_const',
self.Dwarf_sleb128('value')))))
def _create_debugsup(self) -> None:
# We don't care about checksums, for now.
self.Dwarf_debugsup = Struct('Elf_debugsup',
self.Dwarf_int16('version'),
self.Dwarf_uint8('is_supplementary'),
CString('sup_filename'))
def _create_gnu_debugaltlink(self) -> None:
self.Dwarf_debugaltlink = Struct('Elf_debugaltlink',
CString("sup_filename"),
String("sup_checksum", length=20))
def _create_dw_form(self) -> None:
self.Dwarf_dw_form = dict(
DW_FORM_addr=self.the_Dwarf_target_addr,
DW_FORM_addrx=self.the_Dwarf_uleb128,
DW_FORM_addrx1=self.the_Dwarf_uint8,
DW_FORM_addrx2=self.the_Dwarf_uint16,
DW_FORM_addrx3=self.Dwarf_uint24(''),
DW_FORM_addrx4=self.the_Dwarf_uint32,
DW_FORM_block1=self._make_block_struct(self.Dwarf_uint8),
DW_FORM_block2=self._make_block_struct(self.Dwarf_uint16),
DW_FORM_block4=self._make_block_struct(self.Dwarf_uint32),
DW_FORM_block=self._make_block_struct(self.Dwarf_uleb128),
# All DW_FORM_data<n> forms are assumed to be unsigned
DW_FORM_data1=self.the_Dwarf_uint8,
DW_FORM_data2=self.the_Dwarf_uint16,
DW_FORM_data4=self.the_Dwarf_uint32,
DW_FORM_data8=self.Dwarf_uint64(''),
DW_FORM_data16=Array(16, self.the_Dwarf_uint8), # Used for hashes and such, not for integers
DW_FORM_sdata=self.the_Dwarf_sleb128,
DW_FORM_udata=self.the_Dwarf_uleb128,
DW_FORM_string=CString(''),
DW_FORM_strp=self.the_Dwarf_offset,
DW_FORM_strp_sup=self.the_Dwarf_offset,
DW_FORM_line_strp=self.the_Dwarf_offset,
DW_FORM_strx=self.the_Dwarf_uleb128,
DW_FORM_strx1=self.the_Dwarf_uint8,
DW_FORM_strx2=self.the_Dwarf_uint16,
DW_FORM_strx3=self.Dwarf_uint24(''),
DW_FORM_strx4=self.Dwarf_uint64(''),
DW_FORM_flag=self.the_Dwarf_uint8,
DW_FORM_ref=self.the_Dwarf_uint32,
DW_FORM_ref1=self.the_Dwarf_uint8,
DW_FORM_ref2=self.the_Dwarf_uint16,
DW_FORM_ref4=self.the_Dwarf_uint32,
DW_FORM_ref_sup4=self.the_Dwarf_uint32,
DW_FORM_ref8=self.Dwarf_uint64(''),
DW_FORM_ref_sup8=self.Dwarf_uint64(''),
DW_FORM_ref_udata=self.the_Dwarf_uleb128,
DW_FORM_ref_addr=self.the_Dwarf_target_addr if self.dwarf_version == 2 else self.the_Dwarf_offset,
DW_FORM_indirect=self.the_Dwarf_uleb128,
# Treated separatedly while parsing, but here so that all forms resolve
DW_FORM_implicit_const=None,
# New forms in DWARFv4
DW_FORM_flag_present = StaticField('', 0),
DW_FORM_sec_offset = self.the_Dwarf_offset,
DW_FORM_exprloc = self._make_block_struct(self.Dwarf_uleb128),
DW_FORM_ref_sig8 = self.Dwarf_uint64(''),
DW_FORM_GNU_strp_alt=self.the_Dwarf_offset,
DW_FORM_GNU_ref_alt=self.the_Dwarf_offset,
DW_AT_GNU_all_call_sites=self.the_Dwarf_uleb128,
# New forms in DWARFv5
DW_FORM_loclistx=self.the_Dwarf_uleb128,
DW_FORM_rnglistx=self.the_Dwarf_uleb128
)
def _create_aranges_header(self) -> None:
self.Dwarf_aranges_header = Struct("Dwarf_aranges_header",
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
self.Dwarf_offset('debug_info_offset'), # a little tbd
self.Dwarf_uint8('address_size'),
self.Dwarf_uint8('segment_size')
)
def _create_nameLUT_header(self) -> None:
self.Dwarf_nameLUT_header = Struct("Dwarf_nameLUT_header",
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
self.Dwarf_offset('debug_info_offset'),
self.Dwarf_length('debug_info_length')
)
def _create_string_offsets_table_header(self) -> None:
self.Dwarf_string_offsets_table_header = Struct(
"Dwarf_string_offets_table_header",
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
self.Dwarf_uint16('padding'),
)
def _create_address_table_header(self) -> None:
self.Dwarf_address_table_header = Struct("Dwarf_address_table_header",
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
self.Dwarf_uint8('address_size'),
self.Dwarf_uint8('segment_selector_size'),
)
def _create_lineprog_header(self) -> None:
# A file entry is terminated by a NULL byte, so we don't want to parse
# past it. Therefore an If is used.
self.Dwarf_lineprog_file_entry = Struct('file_entry',
CString('name'),
If(lambda ctx: bool(ctx.name),
Embed(Struct('',
self.Dwarf_uleb128('dir_index'),
self.Dwarf_uleb128('mtime'),
self.Dwarf_uleb128('length')))))
class FormattedEntry(Construct):
# Generates a parser based on a previously parsed piece,
# similar to deprecared Dynamic.
# Strings are resolved later, since it potentially requires
# looking at another section.
def __init__(self, name: str, structs: DWARFStructs, format_field: str) -> None:
Construct.__init__(self, name)
self.structs = structs
self.format_field = format_field
def _parse(self, stream: IO[bytes], context: Container) -> Any:
# Somewhat tricky technique here, explicitly writing back to the context
if self.format_field + "_parser" in context:
parser = context[self.format_field + "_parser"]
else:
fields = tuple(
Rename(f.content_type, self.structs.Dwarf_dw_form[f.form]) # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
for f in context[self.format_field])
parser = Struct('formatted_entry', *fields)
context[self.format_field + "_parser"] = parser
return parser._parse(stream, context)
ver5 = lambda ctx: ctx.version >= 5
self.Dwarf_lineprog_header = Struct('Dwarf_lineprog_header',
self.Dwarf_initial_length('unit_length'),
self.Dwarf_uint16('version'),
If(ver5,
self.Dwarf_uint8("address_size"),
None),
If(ver5,
self.Dwarf_uint8("segment_selector_size"),
None),
self.Dwarf_offset('header_length'),
self.Dwarf_uint8('minimum_instruction_length'),
If(lambda ctx: ctx.version >= 4,
self.Dwarf_uint8("maximum_operations_per_instruction"),
1),
self.Dwarf_uint8('default_is_stmt'),
self.Dwarf_int8('line_base'),
self.Dwarf_uint8('line_range'),
self.Dwarf_uint8('opcode_base'),
Array(lambda ctx: ctx.opcode_base - 1,
self.Dwarf_uint8('standard_opcode_lengths')),
If(ver5,
PrefixedArray(
Struct('directory_entry_format',
Enum(self.Dwarf_uleb128('content_type'), **e.ENUM_DW_LNCT),
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM)),
self.Dwarf_uint8("directory_entry_format_count"))),
If(ver5, # Name deliberately doesn't match the legacy object, since the format can't be made compatible
PrefixedArray(
FormattedEntry('directories', self, "directory_entry_format"),
self.Dwarf_uleb128('directories_count'))),
If(ver5,
PrefixedArray(
Struct('file_name_entry_format',
Enum(self.Dwarf_uleb128('content_type'), **e.ENUM_DW_LNCT),
Enum(self.Dwarf_uleb128('form'), **e.ENUM_DW_FORM)),
self.Dwarf_uint8("file_name_entry_format_count"))),
If(ver5,
PrefixedArray(
FormattedEntry('file_names', self, "file_name_entry_format"),
self.Dwarf_uleb128('file_names_count'))),
# Legacy directories/files - DWARF < 5 only
If(lambda ctx: ctx.version < 5,
RepeatUntilExcluding(
lambda obj, ctx: obj == b'',
CString('include_directory'))),
If(lambda ctx: ctx.version < 5,
RepeatUntilExcluding(
lambda obj, ctx: not obj.name,
self.Dwarf_lineprog_file_entry)) # array name is file_entry
)
def _create_callframe_entry_headers(self) -> None:
self.Dwarf_CIE_header = Struct('Dwarf_CIE_header',
self.Dwarf_initial_length('length'),
self.Dwarf_offset('CIE_id'),
self.Dwarf_uint8('version'),
CString('augmentation'),
If(lambda ctx: ctx.version >= 4, self.Dwarf_uint8('address_size')),
If(lambda ctx: ctx.version >= 4, self.Dwarf_uint8('segment_size')),
self.Dwarf_uleb128('code_alignment_factor'),
self.Dwarf_sleb128('data_alignment_factor'),
IfThenElse('return_address_register', lambda ctx: ctx.version > 1,
self.Dwarf_uleb128(''),
self.Dwarf_uint8('')))
self.EH_CIE_header = self.Dwarf_CIE_header
# The CIE header was modified in DWARFv4, but the
# CIE header version is driven by the version # in the header
# itself, independent of the DWARF version
# in the CUs.
self.Dwarf_FDE_header = Struct('Dwarf_FDE_header',
self.Dwarf_initial_length('length'),
self.Dwarf_offset('CIE_pointer'),
self.Dwarf_target_addr('initial_location'),
self.Dwarf_target_addr('address_range'))
def _make_block_struct(self, length_field: Callable[[str], Construct]) -> LengthValueAdapter:
""" Create a struct for DW_FORM_block<size>
"""
return PrefixedArray(
subcon=self.Dwarf_uint8('elem'),
length_field=length_field(''))
def _create_loclists_parsers(self) -> None:
""" Create a struct for debug_loclists CU header, DWARFv5, 7,29
"""
self.Dwarf_loclists_CU_header = Struct('Dwarf_loclists_CU_header',
StreamOffset('cu_offset'),
self.Dwarf_initial_length('unit_length'),
Value('is64', lambda ctx: ctx.is64),
StreamOffset('offset_after_length'),
self.Dwarf_uint16('version'),
self.Dwarf_uint8('address_size'),
self.Dwarf_uint8('segment_selector_size'),
self.Dwarf_uint32('offset_count'),
StreamOffset('offset_table_offset'))
cld = self.Dwarf_loclists_counted_location_description = PrefixedArray(self.Dwarf_uint8('loc_expr'), self.the_Dwarf_uleb128)
self.Dwarf_loclists_entries = RepeatUntilExcluding(
lambda obj, ctx: obj.entry_type == 'DW_LLE_end_of_list',
Struct('entry',
StreamOffset('entry_offset'),
Enum(self.Dwarf_uint8('entry_type'), **e.ENUM_DW_LLE),
Embed(Switch('', lambda ctx: ctx.entry_type,
{
'DW_LLE_end_of_list' : Struct('end_of_list'),
'DW_LLE_base_addressx' : Struct('base_addressx', self.Dwarf_uleb128('index')),
'DW_LLE_startx_endx' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('end_index'), cld),
'DW_LLE_startx_length' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('length'), cld),
'DW_LLE_offset_pair' : Struct('startx_endx', self.Dwarf_uleb128('start_offset'), self.Dwarf_uleb128('end_offset'), cld),
'DW_LLE_default_location' : Struct('default_location', cld),
'DW_LLE_base_address' : Struct('base_address', self.Dwarf_target_addr('address')),
'DW_LLE_start_end' : Struct('start_end', self.Dwarf_target_addr('start_address'), self.Dwarf_target_addr('end_address'), cld),
'DW_LLE_start_length' : Struct('start_length', self.Dwarf_target_addr('start_address'), self.Dwarf_uleb128('length'), cld),
})),
StreamOffset('entry_end_offset'),
Value('entry_length', lambda ctx: ctx.entry_end_offset - ctx.entry_offset)))
self.Dwarf_locview_pair = Struct('locview_pair',
StreamOffset('entry_offset'), self.Dwarf_uleb128('begin'), self.Dwarf_uleb128('end'))
def _create_rnglists_parsers(self) -> None:
self.Dwarf_rnglists_CU_header = Struct('Dwarf_rnglists_CU_header',
StreamOffset('cu_offset'),
self.Dwarf_initial_length('unit_length'),
Value('is64', lambda ctx: ctx.is64),
StreamOffset('offset_after_length'),
self.Dwarf_uint16('version'),
self.Dwarf_uint8('address_size'),
self.Dwarf_uint8('segment_selector_size'),
self.Dwarf_uint32('offset_count'),
StreamOffset('offset_table_offset'))
self.Dwarf_rnglists_entries = RepeatUntilExcluding(
lambda obj, ctx: obj.entry_type == 'DW_RLE_end_of_list',
Struct('entry',
StreamOffset('entry_offset'),
Enum(self.Dwarf_uint8('entry_type'), **e.ENUM_DW_RLE),
Embed(Switch('', lambda ctx: ctx.entry_type,
{
'DW_RLE_end_of_list' : Struct('end_of_list'),
'DW_RLE_base_addressx' : Struct('base_addressx', self.Dwarf_uleb128('index')),
'DW_RLE_startx_endx' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('end_index')),
'DW_RLE_startx_length' : Struct('startx_endx', self.Dwarf_uleb128('start_index'), self.Dwarf_uleb128('length')),
'DW_RLE_offset_pair' : Struct('startx_endx', self.Dwarf_uleb128('start_offset'), self.Dwarf_uleb128('end_offset')),
'DW_RLE_base_address' : Struct('base_address', self.Dwarf_target_addr('address')),
'DW_RLE_start_end' : Struct('start_end', self.Dwarf_target_addr('start_address'), self.Dwarf_target_addr('end_address')),
'DW_RLE_start_length' : Struct('start_length', self.Dwarf_target_addr('start_address'), self.Dwarf_uleb128('length'))
})),
StreamOffset('entry_end_offset'),
Value('entry_length', lambda ctx: ctx.entry_end_offset - ctx.entry_offset)))
class _InitialLengthAdapter(Adapter):
""" A standard Construct adapter that expects a sub-construct
as a struct with one or two values (first, second).
"""
def _decode(self, obj: Container, context: Container) -> int:
if obj.first < 0xFFFFFF00:
context['is64'] = False
return obj.first
else:
if obj.first == 0xFFFFFFFF:
context['is64'] = True
return obj.second
else:
raise ConstructError("Failed decoding initial length for %X" % (
obj.first))
@@ -0,0 +1,278 @@
#-------------------------------------------------------------------------------
# elftools: dwarf/typeunit.py
#
# DWARF type unit
#
# Dinkar Khandalekar (contact@dinkar.dev)
# This code is in the public domain
#-------------------------------------------------------------------------------
from __future__ import annotations
from bisect import bisect_right
from functools import cached_property
from typing import TYPE_CHECKING, Any
from .die import DIE
from ..common.utils import dwarf_assert
if TYPE_CHECKING:
from collections.abc import Iterator
from ..construct.lib.container import Container
from .abbrevtable import AbbrevTable
from .dwarfinfo import DWARFInfo
from .structs import DWARFStructs
class TypeUnit:
""" A DWARF type unit (TU).
A type unit contains type definition entries that can be used to
reference to type definition for debugging information entries in
other compilation units and type units. Each type unit must be uniquely
identified by a 64-bit signature. (DWARFv4 section 3.1.3)
Type units are stored in the .debug_types section. This section was
introduced by the DWARFv4 standard (and removed in the DWARFv5 standard;
the underlying type units were relocated to the .debug_info
section - DWARFv5 section 1.4)
Serves as a container and context to DIEs that describe type definitions
referenced from compilation units and other type units.
TU header entries can be accessed as dict keys from this object, i.e.
tu = TypeUnit(...)
tu['version'] # version field of the TU header
To get the top-level DIE describing the type unit, call the
get_top_DIE method.
"""
def __init__(
self,
header: Container,
dwarfinfo: DWARFInfo,
structs: DWARFStructs,
tu_offset: int,
tu_die_offset: int,
) -> None:
""" header:
TU header for this type unit
dwarfinfo:
The DWARFInfo context object which created this one
structs:
A DWARFStructs instance suitable for this type unit
tu_offset:
Offset in the stream to the beginning of this TU (its header)
tu_die_offset:
Offset in the stream of the top DIE of this TU
"""
self.dwarfinfo = dwarfinfo
self.header = header
self.structs = structs
self.tu_offset = tu_offset
self.tu_die_offset = tu_die_offset
# A list of DIEs belonging to this TU.
# This list is lazily constructed as DIEs are iterated over.
self._dielist: list[DIE] = []
# A list of file offsets, corresponding (by index) to the DIEs
# in `self._dielist`. This list exists separately from
# `self._dielist` to make it binary searchable, enabling the
# DIE population strategy used in `iter_DIE_children`.
# Like `self._dielist`, this list is lazily constructed
# as DIEs are iterated over.
self._diemap: list[int] = []
@property
def cu_offset(self) -> int:
"""Simulates the cu_offset attribute required by the DIE by returning the tu_offset instead
"""
return self.tu_offset
@property
def cu_die_offset(self) -> int:
"""Simulates the cu_die_offset attribute required by the DIE by returning the tu_offset instead
"""
return self.tu_die_offset
def dwarf_format(self) -> int:
""" Get the DWARF format (32 or 64) for this TU
"""
return self.structs.dwarf_format
def get_abbrev_table(self) -> AbbrevTable:
""" Get the abbreviation table (AbbrevTable object) for this TU
"""
return self._abbrev_table
@cached_property
def _abbrev_table(self) -> AbbrevTable:
return self.dwarfinfo.get_abbrev_table(self['debug_abbrev_offset'])
def get_top_DIE(self) -> DIE:
""" Get the top DIE (which is DW_TAG_type_unit entry) of this TU
"""
# Note that a top DIE always has minimal offset and is therefore
# at the beginning of our lists, so no bisect is required.
if self._diemap:
return self._dielist[0]
assert self.dwarfinfo.debug_types_sec is not None
top = DIE(
cu=self,
stream=self.dwarfinfo.debug_types_sec.stream,
offset=self.tu_die_offset)
self._dielist.insert(0, top)
self._diemap.insert(0, self.tu_die_offset)
top._translate_indirect_attributes() # Can't translate indirect attributes until the top DIE has been parsed to the end
return top
def has_top_DIE(self) -> bool:
""" Returns whether the top DIE in this TU has already been parsed and cached.
No parsing on demand!
"""
return bool(self._diemap)
@property
def size(self) -> int:
return self['unit_length'] + self.structs.initial_length_field_size()
def iter_DIEs(self) -> Iterator[DIE]:
""" Iterate over all the DIEs in the TU, in order of their appearance.
Note that null DIEs will also be returned.
"""
return self._iter_DIE_subtree(self.get_top_DIE())
def iter_DIE_children(self, die: DIE) -> Iterator[DIE]:
""" Given a DIE, yields either its children, without null DIE list
terminator, or nothing, if that DIE has no children.
The null DIE terminator is saved in that DIE when iteration ended.
"""
if not die.has_children:
return
# `cur_offset` tracks the stream offset of the next DIE to yield
# as we iterate over our children,
cur_offset = die.offset + die.size
while True:
child = self._get_cached_DIE(cur_offset)
child.set_parent(die)
if child.is_null():
die._terminator = child
return
yield child
if not child.has_children:
cur_offset += child.size
elif "DW_AT_sibling" in child.attributes:
sibling = child.attributes["DW_AT_sibling"]
if sibling.form in ('DW_FORM_ref1', 'DW_FORM_ref2',
'DW_FORM_ref4', 'DW_FORM_ref8',
'DW_FORM_ref', 'DW_FORM_ref_udata'):
cur_offset = sibling.value + self.tu_offset
elif sibling.form == 'DW_FORM_ref_addr':
cur_offset = sibling.value
else:
raise NotImplementedError('sibling in form %s' % sibling.form)
else:
# If no DW_AT_sibling attribute is provided by the producer
# then the whole child subtree must be parsed to find its next
# sibling. There is one zero byte representing null DIE
# terminating children list. It is used to locate child subtree
# bounds.
# If children are not parsed yet, this instruction will manage
# to recursive call of this function which will result in
# setting of `_terminator` attribute of the `child`.
if child._terminator is None:
for _ in self.iter_DIE_children(child):
pass
assert child._terminator is not None
cur_offset = child._terminator.offset + child._terminator.size
def get_DIE_from_refaddr(self, refaddr: int) -> DIE:
""" Obtain a DIE contained in this CU from a reference.
refaddr:
The offset into the .debug_info section, which must be
contained in this CU or a DWARFError will be raised.
When using a reference class attribute with a form that is
relative to the compile unit, add unit add the compile unit's
.cu_addr before calling this function.
"""
# All DIEs are after the cu header and within the unit
dwarf_assert(
self.cu_die_offset <= refaddr < self.cu_offset + self.size,
'refaddr %s not in DIE range of CU %s' % (refaddr, self.cu_offset))
return self._get_cached_DIE(refaddr)
#------ PRIVATE ------#
def __getitem__(self, name: str) -> Any:
""" Implement dict-like access to header entries
"""
return self.header[name]
def _iter_DIE_subtree(self, die: DIE) -> Iterator[DIE]:
""" Given a DIE, this yields it with its subtree including null DIEs
(child list terminators).
"""
# If the die is an imported unit, replace it with what it refers to if
# we can
if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
die = die.get_DIE_from_attribute('DW_AT_import')
yield die
if die.has_children:
for c in die.iter_children():
yield from die.cu._iter_DIE_subtree(c)
assert die._terminator is not None
yield die._terminator
def _get_cached_DIE(self, offset: int) -> DIE:
""" Given a DIE offset, look it up in the cache. If not present,
parse the DIE and insert it into the cache.
offset:
The offset of the DIE in the debug_types section to retrieve.
The stream reference is copied from the top DIE. The top die will
also be parsed and cached if needed.
See also get_DIE_from_refaddr(self, refaddr).
"""
# The top die must be in the cache if any DIE is in the cache.
# The stream is the same for all DIEs in this TU, so populate
# the top DIE and obtain a reference to its stream.
top_die_stream = self.get_top_DIE().stream
# `offset` is the offset in the stream of the DIE we want to return.
# The map is maintined as a parallel array to the list. We call
# bisect each time to ensure new DIEs are inserted in the correct
# order within both `self._dielist` and `self._diemap`.
i = bisect_right(self._diemap, offset)
# Note that `self._diemap` cannot be empty because a the top DIE
# was inserted by the call to .get_top_DIE(). Also it has the minimal
# offset, so the bisect_right insert point will always be at least 1.
if offset == self._diemap[i - 1]:
die = self._dielist[i - 1]
else:
die = DIE(cu=self, stream=top_die_stream, offset=offset)
self._dielist.insert(i, die)
self._diemap.insert(i, offset)
return die