* Recognize DW_FORM_ref_udata as a reference type.
References to other DIEs can also be implemented with a form
DW_FORM_ref_udata, for using the ULEB128 encoding
* Add support for DWARF supplementary object files.
if PY3:
import io
from pathlib import Path
+
StringIO = io.StringIO
BytesIO = io.BytesIO
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', 'DW_FORM_ref_udata'):
cur_offset = sibling.value + self.cu_offset
elif sibling.form == 'DW_FORM_ref_addr':
cur_offset = sibling.value
""" Given a DIE, this yields it with its subtree including null DIEs
(child list terminators).
"""
+ # If the die is an imported unit, replace it with what it refers to if
+ # we can
+ if die.tag == 'DW_TAG_imported_unit' and self.dwarfinfo.supplementary_dwarfinfo:
+ die = die.get_DIE_from_attribute('DW_AT_import')
yield die
if die.has_children:
for c in die.iter_children():
- for d in self._iter_DIE_subtree(c):
+ for d in die.cu._iter_DIE_subtree(c):
yield d
yield die._terminator
"""
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_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'):
elif attr.form in ('DW_FORM_ref_sig8'):
# Implement search type units for matching signature
raise NotImplementedError('%s (type unit by signature)' % attr.form)
- elif attr.form in ('DW_FORM_ref_sup4', 'DW_FORM_ref_sup8'):
+ 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)
elif form == 'DW_FORM_line_strp':
with preserve_stream_pos(self.stream):
value = self.dwarfinfo.get_string_from_linetable(raw_value)
+ elif form in ('DW_FORM_GNU_strp_alt', 'DW_FORM_strp_sup'):
+ if self.dwarfinfo.supplementary_dwarfinfo:
+ return self.dwarfinfo.supplementary_dwarfinfo.get_string_from_table(raw_value)
+ else:
+ value = raw_value
elif form == 'DW_FORM_flag':
value = not raw_value == 0
elif form == 'DW_FORM_flag_present':
debug_str_offsets_sec,
debug_line_str_sec,
debug_loclists_sec,
- debug_rnglists_sec): # Not parsed for now
+ debug_rnglists_sec,
+ debug_sup_sec,
+ gnu_debugaltlink_sec
+ ):
""" config:
A DwarfConfig object
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
+
+ # Sets the supplementary_dwarfinfo to None. Client code can set this
+ # to something else, typically a DWARFInfo file read from an ELFFile
+ # which path is stored in the debug_sup_sec or gnu_debugaltlink_sec.
+ self.supplementary_dwarfinfo = None
# This is the DWARFStructs the context uses, so it doesn't depend on
# DWARF format and address_size (these are determined per CU) - set them
replace_value(data, field.content_type, self.get_string_from_linetable)
elif field.form == 'DW_FORM_strp':
replace_value(data, field.content_type, self.get_string_from_table)
+ elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_GNU_strp_alt'):
+ if self.supplementary_dwarfinfo:
+ replace_value(data, field.content_type, self.supplementary_dwarfinfo.get_string_fromtable)
+ else:
+ replace_value(data, field.content_type, lambda x: str(x))
elif field.form in ('DW_FORM_strp_sup', 'DW_FORM_strx', 'DW_FORM_strx1', 'DW_FORM_strx2', 'DW_FORM_strx3', 'DW_FORM_strx4'):
raise NotImplementedError()
program_start_offset=self.debug_line_sec.stream.tell(),
program_end_offset=end_offset)
+ def parse_debugsupinfo(self):
+ """
+ Extract a filename from either .debug_sup or .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
+ return None
+
SBInt8, SBInt16, SBInt32, SBInt64, SLInt8, SLInt16, SLInt32, SLInt64,
Adapter, Struct, ConstructError, If, Enum, Array, PrefixedArray,
CString, Embed, StaticField, IfThenElse, Construct, Rename, Sequence,
- Switch, Value
+ String, Switch, Value
)
from ..common.construct_utils import (RepeatUntilExcluding, ULEB128, SLEB128,
StreamOffset)
self._create_loclists_parsers()
self._create_rnglists_parsers()
+ self._create_debugsup()
+ self._create_gnu_debugaltlink()
+
def _create_initial_length(self):
def _InitialLength(name):
# Adapts a Struct that parses forward a full initial length field.
If(lambda ctx: ctx['form'] == 'DW_FORM_implicit_const',
self.Dwarf_sleb128('value')))))
+ def _create_debugsup(self):
+ # We don't care about checksums, for now.
+ self.Dwarf_debugsup = Struct('Elf_debugsup',
+ self.Dwarf_int16('version'),
+ self.Dwarf_uint8('is_supplementary'),
+ CString('sup_filename'))
+
+ def _create_gnu_debugaltlink(self):
+ self.Dwarf_debugaltlink = Struct('Elf_debugaltlink',
+ CString("sup_filename"),
+ String("sup_checksum", length=20))
+
def _create_dw_form(self):
self.Dwarf_dw_form = dict(
DW_FORM_addr=self.Dwarf_target_addr(''),
DW_FORM_string=CString(''),
DW_FORM_strp=self.Dwarf_offset(''),
+ DW_FORM_strp_sup=self.Dwarf_offset(''),
DW_FORM_line_strp=self.Dwarf_offset(''),
DW_FORM_strx1=self.Dwarf_uint8(''),
DW_FORM_strx2=self.Dwarf_uint16(''),
DW_FORM_ref1=self.Dwarf_uint8(''),
DW_FORM_ref2=self.Dwarf_uint16(''),
DW_FORM_ref4=self.Dwarf_uint32(''),
+ DW_FORM_ref_sup4=self.Dwarf_uint32(''),
DW_FORM_ref8=self.Dwarf_uint64(''),
+ DW_FORM_ref_sup8=self.Dwarf_uint64(''),
DW_FORM_ref_udata=self.Dwarf_uleb128(''),
DW_FORM_ref_addr=self.Dwarf_target_addr('') if self.dwarf_version == 2 else self.Dwarf_offset(''),
# This code is in the public domain
#-------------------------------------------------------------------------------
import io
+import os
import struct
import zlib
""" Creation: the constructor accepts a stream (file-like object) with the
contents of an ELF file.
+ Optionally, a stream_loader function can be passed as the second
+ argument. This stream_loader function takes a relative file path to
+ load a supplementary object file, and returns a stream suitable for
+ creating a new ELFFile. Currently, the only such relative file path is
+ obtained from the supplementary object files.
+
Accessible attributes:
stream:
e_ident_raw:
the raw e_ident field of the header
"""
- def __init__(self, stream):
+ def __init__(self, stream, stream_loader=None):
self.stream = stream
self._identify_file()
self.structs = ELFStructs(
self._section_header_stringtable = \
self._get_section_header_stringtable()
self._section_name_map = None
+ self.stream_loader = stream_loader
+
+ @classmethod
+ def load_from_path(cls, path):
+ """Takes a path to a file on the local filesystem, and returns an
+ ELFFile from it, setting up a correct stream_loader relative to the
+ original file.
+ """
+ base_directory = os.path.dirname(path)
+ def loader(elf_path):
+ # FIXME: use actual path instead of str/bytes
+ if not os.path.isabs(elf_path):
+ elf_path = os.path.join(base_directory,
+ elf_path)
+ return open(elf_path, 'rb')
+ stream = open(path, 'rb')
+ return ELFFile(stream, loader)
def num_sections(self):
""" Number of sections in the file
self.get_section_by_name('.zdebug_info') or
self.get_section_by_name('.eh_frame'))
- def get_dwarf_info(self, relocate_dwarf_sections=True):
+ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
""" Return a DWARFInfo object representing the debugging information in
this file.
If relocate_dwarf_sections is True, relocations for DWARF sections
are looked up and applied.
+
+ If follow_links is True, we will try to load the supplementary
+ object file (if any), and use it to resolve references and imports.
"""
# Expect that has_dwarf_info was called, so at least .debug_info is
# present.
'.debug_loc', '.debug_ranges', '.debug_pubtypes',
'.debug_pubnames', '.debug_addr',
'.debug_str_offsets', '.debug_line_str',
- '.debug_loclists', '.debug_rnglists')
-
+ '.debug_loclists', '.debug_rnglists',
+ '.debug_sup', '.gnu_debugaltlink')
compressed = bool(self.get_section_by_name('.zdebug_info'))
if compressed:
debug_loc_sec_name, debug_ranges_sec_name, debug_pubtypes_name,
debug_pubnames_name, debug_addr_name, debug_str_offsets_name,
debug_line_str_name, debug_loclists_sec_name, debug_rnglists_sec_name,
- eh_frame_sec_name) = section_names
+ debug_sup_name, gnu_debugaltlink_name, eh_frame_sec_name) = section_names
debug_sections = {}
for secname in section_names:
dwarf_section = self._decompress_dwarf_section(dwarf_section)
debug_sections[secname] = dwarf_section
- return DWARFInfo(
+ # Lookup if we have any of the .gnu_debugaltlink (GNU proprietary
+ # implementation) or .debug_sup sections, referencing a supplementary
+ # DWARF file
+
+ dwarfinfo = DWARFInfo(
config=DwarfConfig(
little_endian=self.little_endian,
default_address_size=self.elfclass // 8,
debug_str_offsets_sec=debug_sections[debug_str_offsets_name],
debug_line_str_sec=debug_sections[debug_line_str_name],
debug_loclists_sec=debug_sections[debug_loclists_sec_name],
- debug_rnglists_sec=debug_sections[debug_rnglists_sec_name]
+ debug_rnglists_sec=debug_sections[debug_rnglists_sec_name],
+ debug_sup_sec=debug_sections[debug_sup_name],
+ gnu_debugaltlink_sec=debug_sections[gnu_debugaltlink_name]
)
+ if follow_links:
+ dwarfinfo.supplementary_dwarfinfo = self.get_supplementary_dwarfinfo(dwarfinfo)
+ return dwarfinfo
+
+
+ def get_supplementary_dwarfinfo(self, dwarfinfo):
+ """
+ Read supplementary dwarfinfo, from either the standared .debug_sup
+ section or the GNU proprietary .gnu_debugaltlink.
+ """
+ supfilepath = dwarfinfo.parse_debugsupinfo()
+ if supfilepath is not None and self.stream_loader is not None:
+ stream = self.stream_loader(supfilepath)
+ supelffile = ELFFile(stream)
+ dwarf_info = supelffile.get_dwarf_info()
+ stream.close()
+ return dwarf_info
+ return None
+
def has_ehabi_info(self):
""" Check whether this file appears to have arm exception handler index table.
)
return section._replace(stream=uncompressed_stream, size=size)
+
+ def close(self):
+ self.stream.close()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
self.Elf_word('abi_tiny'),
)
+ def _create_gnu_debugaltlink(self):
+ self.Elf_debugaltlink = Struct('Elf_debugaltlink',
+ CString("sup_filename"),
+ String("sup_checksum", length=20))
+
def _create_gnu_property(self):
# Structure of GNU property notes is documented in
# https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
debug_str_offsets_sec=None,
debug_line_str_sec=None,
debug_loclists_sec = None,
- debug_rnglists_sec = None
+ debug_rnglists_sec = None,
+ debug_sup_sec = None,
+ gnu_debugaltlink_sec = None
)
CUs = [cu for cu in di.iter_CUs()]
--- /dev/null
+# The test_gnudebugaltlink* and test_debugsup* files have been generated as
+# follows:
+# $ cat test_sup.c
+# int main(int argc, char** argv)
+# {
+# return argc;
+# }
+#
+# $ gcc test_sup.c -o test_debugsup1
+# $ gcc test_sup.c -o test_debugsup2
+# $ dwz test_debugsup1 test_debugsup2 -m test_debugsup.common --dwarf-5
+#
+# $ gcc test_sup.c -o test_gnudebugaltlink1
+# $ gcc test_sup.c -o test_gnudebugaltlink2
+# $ dwz test_gnudebugaltlink1 test_gnudebugaltlink2 -m test_gnudebugaltlink.common
+
+import unittest
+import os
+
+from elftools.elf.elffile import ELFFile
+
+class TestDWARFSupplementaryObjects(unittest.TestCase):
+
+ def test_gnudebugaltlink_no_followlinks(self):
+ path = os.path.join('test', 'testfiles_for_unittests',
+ 'test_gnudebugaltlink1')
+ with open(path, 'rb') as f:
+ elffile = ELFFile(f)
+ # Check that we don't have a supplementary_dwarfinfo
+ dwarfinfo = elffile.get_dwarf_info(follow_links=False)
+ self.assertIsNone(dwarfinfo.supplementary_dwarfinfo)
+ # Check that imported units are present
+ self.assertTrue(any(die.tag == 'DW_TAG_imported_unit'
+ for cu in dwarfinfo.iter_CUs()
+ for die in cu.iter_DIEs()))
+ # Check that DW_FORM_GNU_strp_alt keep their raw_value.
+ for cu in dwarfinfo.iter_CUs():
+ for die in cu.iter_DIEs():
+ attrs = die.attributes
+ if ('DW_AT_name' in attrs and
+ attrs['DW_AT_name'].form == 'DW_FORM_GNU_strp_alt'):
+ self.assertEqual(attrs['DW_AT_name'].value,
+ attrs['DW_AT_name'].raw_value)
+
+ def test_gnudebugaltlink_followlinks(self):
+ base_dir = os.path.join(b'test', b'testfiles_for_unittests')
+ path = os.path.join(base_dir, b'test_gnudebugaltlink1')
+ with ELFFile.load_from_path(path) as elffile:
+ # Check that we do have a supplementary_dwarfinfo
+ dwarfinfo = elffile.get_dwarf_info()
+ self.assertIsNotNone(dwarfinfo.supplementary_dwarfinfo)
+ # Check that imported units are replaced by what they refer to.
+ self.assertTrue(all(die.tag != 'DW_TAG_imported_unit'
+ for cu in dwarfinfo.iter_CUs()
+ for die in cu.iter_DIEs()))
+ # Check that DW_FORM_GNU_strp_alt get a proper reference
+ for cu in dwarfinfo.iter_CUs():
+ for die in cu.iter_DIEs():
+ attrs = die.attributes
+ if ('DW_AT_name' in attrs and attrs['DW_AT_name'].form ==
+ 'DW_FORM_GNU_strp_alt'):
+ self.assertIsInstance(attrs['DW_AT_name'].value, bytes)
+
+ def test_debugsup_no_followlinks(self):
+ path = os.path.join('test', 'testfiles_for_unittests',
+ 'test_debugsup1')
+ with ELFFile.load_from_path(path) as elffile:
+ # Check that we don't have a supplementary_dwarfinfo
+ dwarfinfo = elffile.get_dwarf_info(follow_links=False)
+ self.assertIsNone(dwarfinfo.supplementary_dwarfinfo)
+ # Check that imported units are present
+ self.assertTrue(any(die.tag == 'DW_TAG_imported_unit'
+ for cu in dwarfinfo.iter_CUs()
+ for die in cu.iter_DIEs()))
+ # Check that DW_FORM_GNU_strp_alt keep their raw_value.
+ for cu in dwarfinfo.iter_CUs():
+ for die in cu.iter_DIEs():
+ attrs = die.attributes
+ if ('DW_AT_name' in attrs and
+ attrs['DW_AT_name'].form == 'DW_FORM_strp_sup'):
+ self.assertEqual(attrs['DW_AT_name'].value,
+ attrs['DW_AT_name'].raw_value)
+
+ def test_debugsup_followlinks(self):
+ base_dir = os.path.join(b'test', b'testfiles_for_unittests')
+ path = os.path.join(base_dir, b'test_debugsup1')
+ with ELFFile.load_from_path(path) as elffile:
+ # Check that we do have a supplementary_dwarfinfo
+ dwarfinfo = elffile.get_dwarf_info()
+ self.assertIsNotNone(dwarfinfo.supplementary_dwarfinfo)
+ # Check that imported units are replaced by what they refer to.
+ self.assertTrue(all(die.tag != 'DW_TAG_imported_unit'
+ for cu in dwarfinfo.iter_CUs()
+ for die in cu.iter_DIEs()))
+ # Check that DW_FORM_GNU_strp_alt get a proper reference
+ for cu in dwarfinfo.iter_CUs():
+ for die in cu.iter_DIEs():
+ attrs = die.attributes
+ if ('DW_AT_name' in attrs and attrs['DW_AT_name'].form ==
+ 'DW_FORM_strp_sup'):
+ self.assertIsInstance(attrs['DW_AT_name'].value, bytes)