Improve DWARF 5 compatibility. (#400)
authorRonan Dunklau <ronan@dunklau.fr>
Tue, 10 May 2022 13:56:32 +0000 (15:56 +0200)
committerGitHub <noreply@github.com>
Tue, 10 May 2022 13:56:32 +0000 (06:56 -0700)
* Add support DW_FORM_implicit_const

* Add support for DW_FORM_line_strp

* Add new tests for DW_FORM_implicit_const and DW_FORM_linestrp.

elftools/dwarf/die.py
elftools/dwarf/dwarfinfo.py
elftools/dwarf/structs.py
elftools/elf/elffile.py
test/test_dwarf_v5_forms.py [new file with mode: 0644]
test/test_refaddr_bitness.py
test/testfiles_for_unittests/dwarf_v5_forms.debug [new file with mode: 0755]

index dd9d5925802477521f7f799ee986a81c9355e3cf..810cef4937e0947be4a12a8a07f25462dce78ef6 100755 (executable)
@@ -237,11 +237,18 @@ class DIE(object):
 
         # Guided by the attributes listed in the abbreviation declaration, parse
         # values from the stream.
-        for name, form in abbrev_decl.iter_attr_specs():
+        for spec in abbrev_decl['attr_spec']:
+            form = spec.form
+            name = spec.name
             attr_offset = self.stream.tell()
-            raw_value = struct_parse(structs.Dwarf_dw_form[form], self.stream)
-
-            value = self._translate_attr_value(form, raw_value)
+            # 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
+            else:
+                raw_value = struct_parse(structs.Dwarf_dw_form[form], self.stream)
+                value = self._translate_attr_value(form, raw_value)
             self.attributes[name] = AttributeValue(
                 name=name,
                 form=form,
@@ -258,6 +265,9 @@ class DIE(object):
         if form == 'DW_FORM_strp':
             with preserve_stream_pos(self.stream):
                 value = self.dwarfinfo.get_string_from_table(raw_value)
+        elif form == 'DW_FORM_line_strp':
+            with preserve_stream_pos(self.stream):
+                value = self.dwarfinfo.get_string_from_linetable(raw_value)
         elif form == 'DW_FORM_flag':
             value = not raw_value == 0
         elif form == 'DW_FORM_flag_present':
index 48c4bb504a18ca54e408c34c44a287817048df15..a300e7b9a07476c6096e9c50240536ce2fce2355 100644 (file)
@@ -73,7 +73,8 @@ class DWARFInfo(object):
             debug_pubtypes_sec,
             debug_pubnames_sec,
             debug_addr_sec,
-            debug_str_offsets_sec):
+            debug_str_offsets_sec,
+            debug_line_str_sec):
         """ config:
                 A DwarfConfig object
 
@@ -92,6 +93,7 @@ class DWARFInfo(object):
         self.debug_loc_sec = debug_loc_sec
         self.debug_ranges_sec = debug_ranges_sec
         self.debug_line_sec = debug_line_sec
+        self.debug_line_str_sec = debug_line_str_sec
         self.debug_pubtypes_sec = debug_pubtypes_sec
         self.debug_pubnames_sec = debug_pubnames_sec
 
@@ -231,6 +233,12 @@ class DWARFInfo(object):
         """
         return parse_cstring_from_stream(self.debug_str_sec.stream, offset)
 
+    def get_string_from_linetable(self, offset):
+        """ Obtain a string from the string table section, given an offset
+            relative to the section.
+        """
+        return parse_cstring_from_stream(self.debug_line_str_sec.stream, offset)
+
     def line_program_for_CU(self, CU):
         """ Given a CU object, fetch the line program it points to from the
             .debug_line section.
index 16f29f6341b08a3598b1fe079a839a9c838e893c..79e0d8f230ac10648b6d2161eae3bd0bb31cce8e 100644 (file)
@@ -182,7 +182,9 @@ class DWARFStructs(object):
                     obj.name == 'DW_AT_null' and obj.form == 'DW_FORM_null',
                 Struct('attr_spec',
                     Enum(self.Dwarf_uleb128('name'), **ENUM_DW_AT),
-                    Enum(self.Dwarf_uleb128('form'), **ENUM_DW_FORM))))
+                    Enum(self.Dwarf_uleb128('form'), **ENUM_DW_FORM),
+                    If(lambda ctx: ctx['form'] == 'DW_FORM_implicit_const',
+                        self.Dwarf_sleb128('value')))))
 
     def _create_dw_form(self):
         self.Dwarf_dw_form = dict(
@@ -208,6 +210,7 @@ class DWARFStructs(object):
 
             DW_FORM_string=CString(''),
             DW_FORM_strp=self.Dwarf_offset(''),
+            DW_FORM_line_strp=self.Dwarf_offset(''),
             DW_FORM_strx1=self.Dwarf_uint8(''),
             DW_FORM_strx2=self.Dwarf_uint16(''),
             # DW_FORM_strx3=self.Dwarf_uint24(''),  # TODO
index 244841a2e4cbf1abc24bbc4b219fdd93c5d9c7b3..10367ad75a7ab8dbb57651a80456fc55924a5b5f 100644 (file)
@@ -217,7 +217,9 @@ class ELFFile(object):
         section_names = ('.debug_info', '.debug_aranges', '.debug_abbrev',
                          '.debug_str', '.debug_line', '.debug_frame',
                          '.debug_loc', '.debug_ranges', '.debug_pubtypes',
-                         '.debug_pubnames', '.debug_addr', '.debug_str_offsets')
+                         '.debug_pubnames', '.debug_addr',
+                         '.debug_str_offsets', '.debug_line_str')
+
 
         compressed = bool(self.get_section_by_name('.zdebug_info'))
         if compressed:
@@ -230,7 +232,7 @@ class ELFFile(object):
          debug_str_sec_name, debug_line_sec_name, debug_frame_sec_name,
          debug_loc_sec_name, debug_ranges_sec_name, debug_pubtypes_name,
          debug_pubnames_name, debug_addr_name, debug_str_offsets_name,
-         eh_frame_sec_name) = section_names
+         debug_line_str_name, eh_frame_sec_name) = section_names
 
         debug_sections = {}
         for secname in section_names:
@@ -263,6 +265,7 @@ class ELFFile(object):
                 debug_pubnames_sec=debug_sections[debug_pubnames_name],
                 debug_addr_sec=debug_sections[debug_addr_name],
                 debug_str_offsets_sec=debug_sections[debug_str_offsets_name],
+                debug_line_str_sec=debug_sections[debug_line_str_name]
                 )
 
     def has_ehabi_info(self):
diff --git a/test/test_dwarf_v5_forms.py b/test/test_dwarf_v5_forms.py
new file mode 100644 (file)
index 0000000..efae53c
--- /dev/null
@@ -0,0 +1,52 @@
+# The dwarf_v5_forms.debug file was generated as follows, using gcc 11.2.0 on
+# an x86_64 machine.
+# $ cat dwarf_v5_forms.c
+# int main();
+# {
+#         char ** val;
+#         return 0;
+# }
+# $ gcc -O0 -gdwarf-5 dwarf_v5_forms.c -o dwarf_v5_forms.debug
+# $ strip --only-keep-debug dwarf_v5_forms.debug
+import unittest
+import os
+
+
+from elftools.elf.elffile import ELFFile
+
+class TestDWARFV5_forms(unittest.TestCase):
+
+    def test_DW_FORM_implicit_const(self):
+        path = os.path.join('test', 'testfiles_for_unittests',
+                            'dwarf_v5_forms.debug')
+        with open(path, 'rb') as f:
+            elffile = ELFFile(f)
+            dwarfinfo = elffile.get_dwarf_info()
+            # File is very small, so load all DIEs.
+            dies = []
+            for cu in dwarfinfo.iter_CUs():
+                dies.extend(cu.iter_DIEs())
+            # Locate the "var" DIE.
+            for die in dies:
+                # There should be only one
+                if (die.tag == "DW_TAG_variable" and
+                    die.attributes["DW_AT_name"].value == b'val'):
+                        # In the dwarfinfo, it's type is sized using a
+                        # DW_FORM_implicit_const: check it is parsed correctly
+                        break
+            dietype = cu.get_DIE_from_refaddr(die.attributes["DW_AT_type"].value)
+            byte_size_attr = dietype.attributes["DW_AT_byte_size"]
+            self.assertEqual(byte_size_attr.form, "DW_FORM_implicit_const")
+            self.assertEqual(byte_size_attr.value, 8)
+
+    def test_DW_FORM_linestrp(self):
+        path = os.path.join('test', 'testfiles_for_unittests',
+                            'dwarf_v5_forms.debug')
+        with open(path, 'rb') as f:
+            elffile = ELFFile(f)
+            dwarfinfo = elffile.get_dwarf_info()
+            cu = next(dwarfinfo.iter_CUs())
+            top_die = cu.get_top_DIE()
+            name_attr = top_die.attributes["DW_AT_name"]
+            self.assertEqual(name_attr.form, "DW_FORM_line_strp")
+            self.assertEqual(name_attr.value, b"dwarf_v5_forms.c")
index b9e88747fce954e8534a5c301ff626fcf22a427c..ea01db64da0fd2939a182bee3835833b136de7d0 100644 (file)
@@ -46,6 +46,7 @@ class TestRefAddrOnDWARFv2With64BitTarget(unittest.TestCase):
             debug_pubnames_sec = None,
             debug_addr_sec=None,
             debug_str_offsets_sec=None,
+            debug_line_str_sec=None,
         )
 
         CUs = [cu for cu in di.iter_CUs()]
diff --git a/test/testfiles_for_unittests/dwarf_v5_forms.debug b/test/testfiles_for_unittests/dwarf_v5_forms.debug
new file mode 100755 (executable)
index 0000000..4f6a402
Binary files /dev/null and b/test/testfiles_for_unittests/dwarf_v5_forms.debug differ