Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for .gnu_debuglink DWARF5 Section #525

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -161,6 +161,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 @@ -247,6 +248,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.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 @@ -250,7 +250,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 @@ -264,7 +264,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 @@ -305,7 +305,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 @@ -315,7 +316,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