Skip to content

Commit

Permalink
Support for .gnu_debuglink DWARF5 Section (#525)
Browse files Browse the repository at this point in the history
* feat: add support for DWARF5 .gnu_debuglink section

* test: update tests to make them compliant with new DWARFInfo constructor

* test: add test for gnu_debuglink section
  • Loading branch information
io-no authored Sep 26, 2024
1 parent c03e7e6 commit 9b5a554
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 7 deletions.
10 changes: 8 additions & 2 deletions elftools/dwarf/dwarfinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ def __init__(self,
debug_loclists_sec,
debug_rnglists_sec,
debug_sup_sec,
gnu_debugaltlink_sec
gnu_debugaltlink_sec,
gnu_debuglink_sec
):
""" config:
A DwarfConfig object
Expand Down Expand Up @@ -110,6 +111,7 @@ def __init__(self,
self.debug_rnglists_sec = debug_rnglists_sec
self.debug_sup_sec = debug_sup_sec
self.gnu_debugaltlink_sec = gnu_debugaltlink_sec
self.gnu_debuglink_sec = gnu_debuglink_sec

# Sets the supplementary_dwarfinfo to None. Client code can set this
# to something else, typically a DWARFInfo file read from an ELFFile
Expand Down Expand Up @@ -562,7 +564,7 @@ def replace_value(data, content_type, replacer):

def parse_debugsupinfo(self):
"""
Extract a filename from either .debug_sup or .gnu_debualtlink sections.
Extract a filename from .debug_sup, .gnu_debualtlink sections, or .gnu_debuglink.
"""
if self.debug_sup_sec is not None:
self.debug_sup_sec.stream.seek(0)
Expand All @@ -573,5 +575,9 @@ def parse_debugsupinfo(self):
self.gnu_debugaltlink_sec.stream.seek(0)
suplink = self.structs.Dwarf_debugaltlink.parse_stream(self.gnu_debugaltlink_sec.stream)
return suplink.sup_filename
if self.gnu_debuglink_sec is not None:
self.gnu_debuglink_sec.stream.seek(0)
suplink = self.structs.Dwarf_debuglink.parse_stream(self.gnu_debuglink_sec.stream)
return suplink.sup_filename
return None

13 changes: 13 additions & 0 deletions elftools/dwarf/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def _create_structs(self):

self._create_debugsup()
self._create_gnu_debugaltlink()
self._create_gnu_debuglink()

def _create_initial_length(self):
def _InitialLength(name):
Expand Down Expand Up @@ -261,6 +262,18 @@ def _create_gnu_debugaltlink(self):
CString("sup_filename"),
String("sup_checksum", length=20))

def _create_gnu_debuglink(self):
self.Dwarf_debuglink = Struct('Elf_debuglink',
CString("sup_filename"),
Switch('', lambda ctx: (len(ctx.sup_filename) % 4),
{
0: String("sup_padding", length=3),
1: String("sup_padding", length=2),
2: String("sup_padding", length=1),
3: String("sup_padding", length=0),
}),
String("sup_checksum", length=4))

def _create_dw_form(self):
self.Dwarf_dw_form = dict(
DW_FORM_addr=self.the_Dwarf_target_addr,
Expand Down
9 changes: 5 additions & 4 deletions elftools/elf/elffile.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
'.debug_pubnames', '.debug_addr',
'.debug_str_offsets', '.debug_line_str',
'.debug_loclists', '.debug_rnglists',
'.debug_sup', '.gnu_debugaltlink')
'.debug_sup', '.gnu_debugaltlink', '.gnu_debuglink')

compressed = bool(self.get_section_by_name('.zdebug_info'))
if compressed:
Expand All @@ -284,7 +284,7 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
debug_loc_sec_name, debug_ranges_sec_name, debug_pubtypes_name,
debug_pubnames_name, debug_addr_name, debug_str_offsets_name,
debug_line_str_name, debug_loclists_sec_name, debug_rnglists_sec_name,
debug_sup_name, gnu_debugaltlink_name, eh_frame_sec_name) = section_names
debug_sup_name, gnu_debugaltlink_name, gnu_debuglink, eh_frame_sec_name) = section_names

debug_sections = {}
for secname in section_names:
Expand Down Expand Up @@ -325,7 +325,8 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
debug_loclists_sec=debug_sections[debug_loclists_sec_name],
debug_rnglists_sec=debug_sections[debug_rnglists_sec_name],
debug_sup_sec=debug_sections[debug_sup_name],
gnu_debugaltlink_sec=debug_sections[gnu_debugaltlink_name]
gnu_debugaltlink_sec=debug_sections[gnu_debugaltlink_name],
gnu_debuglink_sec=debug_sections[gnu_debuglink]
)
if follow_links:
dwarfinfo.supplementary_dwarfinfo = self.get_supplementary_dwarfinfo(dwarfinfo)
Expand All @@ -335,7 +336,7 @@ def get_dwarf_info(self, relocate_dwarf_sections=True, follow_links=True):
def get_supplementary_dwarfinfo(self, dwarfinfo):
"""
Read supplementary dwarfinfo, from either the standared .debug_sup
section or the GNU proprietary .gnu_debugaltlink.
section, the GNU proprietary .gnu_debugaltlink, or .gnu_debuglink.
"""
supfilepath = dwarfinfo.parse_debugsupinfo()
if supfilepath is not None and self.stream_loader is not None:
Expand Down
77 changes: 77 additions & 0 deletions test/test_debuglink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#------------------------------------------------------------------------------
# elftools tests
#
# Gabriele Digregorio - Io_no
# This code is in the public domain
#------------------------------------------------------------------------------

from elftools.elf.elffile import ELFFile
import unittest


class TestDebuglink(unittest.TestCase):
""" This test verifies that the .gnu_debuglink section is followed and parsed correctly.
The test file contains a .gnu_debuglink section that points to a debug file
containing DWARF info.
We verify that the subprograms are correctly retrieved from the debug file.
"""

def stream_loader(self, external_filename: str) -> 'IO[bytes]':
"""
This function takes an external filename to load a supplementary object file,
and returns a stream suitable for creating a new ELFFile.
Args:
external_filename (str): The relative file path to load.
Returns:
stream (IO[bytes]): A stream suitable for creating a new ELFFile.
"""
self.assertEqual(external_filename, b'debuglink.debug')
stream = open(b'test/testfiles_for_unittests/' + external_filename, 'rb')
return stream

def subprograms_from_debuglink(self, elf: ELFFile) -> dict[str, (int, int)]:
"""Returns a dictionary containing the subprograms of the specified ELF file from the linked
debug file.
Args:
elf (ELFFile): The ELF file.
Returns:
dict: A dictionary containing the subprograms of the specified ELF file.
"""
subprograms = {}

# Retrieve the subprograms from the DWARF info
dwarf_info = elf.get_dwarf_info(follow_links=True, relocate_dwarf_sections=True)

if dwarf_info.supplementary_dwarfinfo:
for CU in dwarf_info.supplementary_dwarfinfo.iter_CUs():
for DIE in CU.iter_DIEs():
if DIE.tag == 'DW_TAG_subprogram':
attributes = DIE.attributes
lowpc_attr = attributes.get('DW_AT_low_pc')
highpc_attr = attributes.get('DW_AT_high_pc')
name_attr = attributes.get('DW_AT_name')
if not lowpc_attr or not highpc_attr or not name_attr:
continue
lowpc = lowpc_attr.value
if highpc_attr.form == 'DW_FORM_addr':
# highpc is an absolute address
size = highpc_attr.value - lowpc
elif highpc_attr.form in {'DW_FORM_data2','DW_FORM_data4',
'DW_FORM_data8', 'DW_FORM_data1',
'DW_FORM_udata'}:
# highpc is an offset from lowpc
size = highpc_attr.value
name = name_attr.value
subprograms[name] = (lowpc, size)
return subprograms

def test_debuglink(self):
with open('test/testfiles_for_unittests/debuglink', "rb") as elf_file:
elf = ELFFile(elf_file, stream_loader=self.stream_loader)
subprograms = self.subprograms_from_debuglink(elf)
self.assertEqual(subprograms, {b'main': (0x1161, 0x52), b'addNumbers': (0x1149, 0x18)})

if __name__ == '__main__':
unittest.main()
3 changes: 2 additions & 1 deletion test/test_refaddr_bitness.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def test_main(self):
debug_loclists_sec = None,
debug_rnglists_sec = None,
debug_sup_sec = None,
gnu_debugaltlink_sec = None
gnu_debugaltlink_sec = None,
gnu_debuglink_sec = None
)

CUs = [cu for cu in di.iter_CUs()]
Expand Down
Binary file added test/testfiles_for_unittests/debuglink
Binary file not shown.
14 changes: 14 additions & 0 deletions test/testfiles_for_unittests/debuglink.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <stdio.h>

int addNumbers(int a, int b) {
return a + b;
}

int main() {
int num1 = 7;
int num2 = 3;
int sum = addNumbers(num1, num2);

printf("Sum of %d and %d is %d\n", num1, num2, sum);
return 0;
}
Binary file added test/testfiles_for_unittests/debuglink.debug
Binary file not shown.
4 changes: 4 additions & 0 deletions test/testfiles_for_unittests/debuglink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
gcc -g -o debuglink debuglink.c
objcopy --only-keep-debug debuglink debuglink.debug
strip --strip-all debuglink
objcopy --add-gnu-debuglink=debuglink.debug debuglink

0 comments on commit 9b5a554

Please sign in to comment.