From 76a7327765bdbde0c0af5c915e1d610a8117de8c Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Fri, 9 Dec 2011 16:53:18 +0200 Subject: [PATCH] starting skeleton implementation of call frame parsing --- elftools/dwarf/callframe.py | 35 +++++++++++++++++++++++++++++++++ elftools/dwarf/dwarfinfo.py | 24 ++++++++++++----------- elftools/dwarf/lineprogram.py | 3 ++- elftools/dwarf/structs.py | 24 ++++++++++++++++++++++- elftools/elf/elffile.py | 37 ++++++++++++++++++----------------- 5 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 elftools/dwarf/callframe.py diff --git a/elftools/dwarf/callframe.py b/elftools/dwarf/callframe.py new file mode 100644 index 0000000..280caa4 --- /dev/null +++ b/elftools/dwarf/callframe.py @@ -0,0 +1,35 @@ +#------------------------------------------------------------------------------- +# elftools: dwarf/callframe.py +# +# DWARF call frame information +# +# Eli Bendersky (eliben@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +from ..common.utils import (struct_parse) +from .structs import DWARFStructs + + +class CallFrameInfo(object): + def __init__(self, stream, size, base_structs): + self.stream = stream + self.size = size + self.base_structs = base_structs + + def _parse_entries(self): + offset = 0 + while offset < self.size: + entry_length = struct_parse( + self.base_structs.Dwarf_uint32(''), self.stream, offset) + dwarf_format = 64 if entry_length == 0xFFFFFFFF else 32 + + entry_structs = DWARFStructs( + little_endian=self.base_structs.little_endian, + dwarf_format=dwarf_format, + address_size=self.base_structs.address_size) + + # ZZZ: it will be easier to split entry reading: + # header: what comes before the instructions + # the instructions are parsed separately (their length is computed + # from the length and the tell() after parsing the header) + diff --git a/elftools/dwarf/dwarfinfo.py b/elftools/dwarf/dwarfinfo.py index e0c9fd0..807e707 100644 --- a/elftools/dwarf/dwarfinfo.py +++ b/elftools/dwarf/dwarfinfo.py @@ -36,7 +36,7 @@ DebugSectionDescriptor = namedtuple('DebugSectionDescriptor', # # machine_arch: # Machine architecture as a string. For example 'x86' or 'x64' -# +# # DwarfConfig = namedtuple('DwarfConfig', 'little_endian machine_arch') @@ -50,6 +50,7 @@ class DWARFInfo(object): config, debug_info_sec, debug_abbrev_sec, + debug_frame_sec, debug_str_sec, debug_line_sec): """ config: @@ -61,6 +62,7 @@ class DWARFInfo(object): self.config = config self.debug_info_sec = debug_info_sec self.debug_abbrev_sec = debug_abbrev_sec + self.debug_frame_sec = debug_frame_sec self.debug_str_sec = debug_str_sec self.debug_line_sec = debug_line_sec @@ -71,13 +73,13 @@ class DWARFInfo(object): little_endian=self.config.little_endian, dwarf_format=32, address_size=4) - + # A list of CUs. Populated lazily when they're actually requested. self._CUs = None - + # Cache for abbrev tables: a dict keyed by offset self._abbrevtable_cache = {} - + def iter_CUs(self): """ Yield all the compile units (CompileUnit objects) in the debug info """ @@ -88,12 +90,12 @@ class DWARFInfo(object): def get_abbrev_table(self, offset): """ 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). """ @@ -106,13 +108,13 @@ class DWARFInfo(object): stream=self.debug_abbrev_sec.stream, offset=offset) return self._abbrevtable_cache[offset] - + def get_string_from_table(self, offset): """ Obtain a string from the string table section, given an offset relative to the section. """ return parse_cstring_from_stream(self.debug_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. @@ -126,9 +128,9 @@ class DWARFInfo(object): top_DIE.attributes['DW_AT_stmt_list'].value, CU.structs) else: return None - + #------ PRIVATE ------# - + def _parse_CUs(self): """ Parse CU entries from debug_info. """ @@ -146,7 +148,7 @@ class DWARFInfo(object): initial_length = struct_parse( self.structs.Dwarf_uint32(''), self.debug_info_sec.stream, offset) dwarf_format = 64 if initial_length == 0xFFFFFFFF else 32 - + # At this point we still haven't read the whole header, so we don't # know the address_size. Therefore, we're going to create structs # with a default address_size=4. If, after parsing the header, we diff --git a/elftools/dwarf/lineprogram.py b/elftools/dwarf/lineprogram.py index 8f8485c..361511f 100644 --- a/elftools/dwarf/lineprogram.py +++ b/elftools/dwarf/lineprogram.py @@ -105,7 +105,8 @@ class LineProgram(object): 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. + state. The extra information is mainly for the purposes of display + with readelf and debugging. """ if self._decoded_entries is None: self._decoded_entries = self._decode_line_program() diff --git a/elftools/dwarf/structs.py b/elftools/dwarf/structs.py index 2c00325..d25178c 100644 --- a/elftools/dwarf/structs.py +++ b/elftools/dwarf/structs.py @@ -63,6 +63,12 @@ class DWARFStructs(object): 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. """ def __init__(self, little_endian, dwarf_format, address_size): @@ -217,7 +223,23 @@ class DWARFStructs(object): lambda obj, ctx: len(obj.name) == 0, self.Dwarf_lineprog_file_entry), ) - + + def _create_callframe_entry_headers(self): + self.Dwarf_CIE_header = Struct('Dwarf_CIE_header', + self.Dwarf_initial_length('length'), + self.Dwarf_offset('CIE_id'), + self.Dwarf_uint8('version'), + CString('augmentation'), + self.Dwarf_uleb128('code_alignment_factor'), + self.Dwarf_sleb128('data_alignment_factor'), + self.Dwarf_uleb128('return_address_register')) + + 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): """ Create a struct for DW_FORM_block """ diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index 0e3530d..9fd2634 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py @@ -96,20 +96,20 @@ class ELFFile(object): """ segment_header = self._get_segment_header(n) return self._make_segment(segment_header) - + def iter_segments(self): """ Yield all the segments in the file """ for i in range(self.num_segments()): yield self.get_segment(i) - + def has_dwarf_info(self): """ Check whether this file appears to have debugging information. We assume that if it has the debug_info section, it has all theother required sections as well. """ return bool(self.get_section_by_name('.debug_info')) - + def get_dwarf_info(self, relocate_dwarf_sections=True): """ Return a DWARFInfo object representing the debugging information in this file. @@ -119,27 +119,28 @@ class ELFFile(object): """ # Expect that has_dwarf_info was called, so at least .debug_info is # present. Check also the presence of other must-have debug sections. + # Sections that aren't found will be passed as None to DWARFInfo. # debug_sections = {} for secname in ('.debug_info', '.debug_abbrev', '.debug_str', - '.debug_line'): + '.debug_line', '.debug_frame'): section = self.get_section_by_name(secname) - elf_assert( - section is not None, - "Expected to find DWARF section '%s' in the file" % ( - secname)) - debug_sections[secname] = self._read_dwarf_section( - section, - relocate_dwarf_sections) - + if section is None: + debug_sections[secname] = None + else: + debug_sections[secname] = self._read_dwarf_section( + section, + relocate_dwarf_sections) + return DWARFInfo( config=DwarfConfig( little_endian=self.little_endian, machine_arch=self.get_machine_arch()), debug_info_sec=debug_sections['.debug_info'], debug_abbrev_sec=debug_sections['.debug_abbrev'], + debug_frame_sec=debug_sections['.debug_frame'], debug_str_sec=debug_sections['.debug_str'], - debug_line_sec=debug_sections['.debug_line']) + debug_line_sec=debug_sections['.debug_line']) def get_machine_arch(self): if self['e_machine'] == 'EM_X86_64': @@ -148,14 +149,14 @@ class ELFFile(object): return 'x86' else: return '' - + #-------------------------------- PRIVATE --------------------------------# - + def __getitem__(self, name): """ Implement dict-like access to header entries """ return self.header[name] - + def _identify_file(self): """ Verify the ELF file and identify its class and endianness. """ @@ -166,7 +167,7 @@ class ELFFile(object): self.stream.seek(0) magic = self.stream.read(4) elf_assert(magic == '\x7fELF', 'Magic number does not match') - + ei_class = self.stream.read(1) if ei_class == '\x01': self.elfclass = 32 @@ -174,7 +175,7 @@ class ELFFile(object): self.elfclass = 64 else: raise ELFError('Invalid EI_CLASS %s' % repr(ei_class)) - + ei_data = self.stream.read(1) if ei_data == '\x01': self.little_endian = True -- 2.30.2