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