music with stepper motors
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
13 KiB

5 years ago
#-------------------------------------------------------------------------------
# elftools: dwarf/dwarfinfo.py
#
# DWARFInfo - Main class for accessing DWARF debug information
#
# Eli Bendersky (eliben@gmail.com)
# This code is in the public domain
#-------------------------------------------------------------------------------
from collections import namedtuple
from ..common.exceptions import DWARFError
from ..common.utils import (struct_parse, dwarf_assert,
parse_cstring_from_stream)
from .structs import DWARFStructs
from .compileunit import CompileUnit
from .abbrevtable import AbbrevTable
from .lineprogram import LineProgram
from .callframe import CallFrameInfo
from .locationlists import LocationLists
from .ranges import RangeLists
from .aranges import ARanges
from .namelut import NameLUT
# Describes a debug section
#
# stream: a stream object containing the data of this section
# name: section name in the container file
# global_offset: the global offset of the section in its container file
# size: the size of the section's data, in bytes
# address: the virtual address for the section's data
#
# 'name' and 'global_offset' are for descriptional purposes only and
# aren't strictly required for the DWARF parsing to work. 'address' is required
# to properly decode the special '.eh_frame' format.
#
DebugSectionDescriptor = namedtuple('DebugSectionDescriptor',
'stream name global_offset size address')
# Some configuration parameters for the DWARF reader. This exists to allow
# DWARFInfo to be independent from any specific file format/container.
#
# little_endian:
# boolean flag specifying whether the data in the file is little endian
#
# machine_arch:
# Machine architecture as a string. For example 'x86' or 'x64'
#
# default_address_size:
# The default address size for the container file (sizeof pointer, in bytes)
#
DwarfConfig = namedtuple('DwarfConfig',
'little_endian machine_arch default_address_size')
class DWARFInfo(object):
""" Acts also as a "context" to other major objects, bridging between
various parts of the debug infromation.
"""
def __init__(self,
config,
debug_info_sec,
debug_aranges_sec,
debug_abbrev_sec,
debug_frame_sec,
eh_frame_sec,
debug_str_sec,
debug_loc_sec,
debug_ranges_sec,
debug_line_sec,
debug_pubtypes_sec,
debug_pubnames_sec):
""" config:
A DwarfConfig object
debug_*_sec:
DebugSectionDescriptor for a section. Pass None for sections
that don't exist. These arguments are best given with
keyword syntax.
"""
self.config = config
self.debug_info_sec = debug_info_sec
self.debug_aranges_sec = debug_aranges_sec
self.debug_abbrev_sec = debug_abbrev_sec
self.debug_frame_sec = debug_frame_sec
self.eh_frame_sec = eh_frame_sec
self.debug_str_sec = debug_str_sec
self.debug_loc_sec = debug_loc_sec
self.debug_ranges_sec = debug_ranges_sec
self.debug_line_sec = debug_line_sec
self.debug_pubtypes_sec = debug_pubtypes_sec
self.debug_pubnames_sec = debug_pubnames_sec
# 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
# to default values.
self.structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=32,
address_size=self.config.default_address_size)
# Cache for abbrev tables: a dict keyed by offset
self._abbrevtable_cache = {}
@property
def has_debug_info(self):
""" Return whether this contains debug information.
It can be not the case when the ELF only contains .eh_frame, which is
encoded DWARF but not actually for debugging.
"""
return bool(self.debug_info_sec)
def iter_CUs(self):
""" Yield all the compile units (CompileUnit objects) in the debug info
"""
return self._parse_CUs_iter()
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).
"""
dwarf_assert(
offset < self.debug_abbrev_sec.size,
"Offset '0x%x' to abbrev table out of section bounds" % offset)
if offset not in self._abbrevtable_cache:
self._abbrevtable_cache[offset] = AbbrevTable(
structs=self.structs,
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.
If the CU doesn't point to a line program, return None.
"""
# The line program is pointed to by the DW_AT_stmt_list attribute of
# the top DIE of a CU.
top_DIE = CU.get_top_DIE()
if 'DW_AT_stmt_list' in top_DIE.attributes:
return self._parse_line_program_at_offset(
top_DIE.attributes['DW_AT_stmt_list'].value, CU.structs)
else:
return None
def has_CFI(self):
""" Does this dwarf info have a dwarf_frame CFI section?
"""
return self.debug_frame_sec is not None
def CFI_entries(self):
""" Get a list of dwarf_frame CFI entries from the .debug_frame section.
"""
cfi = CallFrameInfo(
stream=self.debug_frame_sec.stream,
size=self.debug_frame_sec.size,
address=self.debug_frame_sec.address,
base_structs=self.structs)
return cfi.get_entries()
def has_EH_CFI(self):
""" Does this dwarf info have a eh_frame CFI section?
"""
return self.eh_frame_sec is not None
def EH_CFI_entries(self):
""" Get a list of eh_frame CFI entries from the .eh_frame section.
"""
cfi = CallFrameInfo(
stream=self.eh_frame_sec.stream,
size=self.eh_frame_sec.size,
address=self.eh_frame_sec.address,
base_structs=self.structs,
for_eh_frame=True)
return cfi.get_entries()
def get_pubtypes(self):
"""
Returns a NameLUT object that contains information read from the
.debug_pubtypes section in the ELF file.
NameLUT is essentially a dictionary containing the CU/DIE offsets of
each symbol. See the NameLUT doc string for more details.
"""
if self.debug_pubtypes_sec:
return NameLUT(self.debug_pubtypes_sec.stream,
self.debug_pubtypes_sec.size,
self.structs)
else:
return None
def get_pubnames(self):
"""
Returns a NameLUT object that contains information read from the
.debug_pubnames section in the ELF file.
NameLUT is essentially a dictionary containing the CU/DIE offsets of
each symbol. See the NameLUT doc string for more details.
"""
if self.debug_pubnames_sec:
return NameLUT(self.debug_pubnames_sec.stream,
self.debug_pubnames_sec.size,
self.structs)
else:
return None
def get_aranges(self):
""" Get an ARanges object representing the .debug_aranges section of
the DWARF data, or None if the section doesn't exist
"""
if self.debug_aranges_sec:
return ARanges(self.debug_aranges_sec.stream,
self.debug_aranges_sec.size,
self.structs)
else:
return None
def location_lists(self):
""" Get a LocationLists object representing the .debug_loc section of
the DWARF data, or None if this section doesn't exist.
"""
if self.debug_loc_sec:
return LocationLists(self.debug_loc_sec.stream, self.structs)
else:
return None
def range_lists(self):
""" Get a RangeLists object representing the .debug_ranges section of
the DWARF data, or None if this section doesn't exist.
"""
if self.debug_ranges_sec:
return RangeLists(self.debug_ranges_sec.stream, self.structs)
else:
return None
#------ PRIVATE ------#
def _parse_CUs_iter(self):
""" Parse CU entries from debug_info. Yield CUs in order of appearance.
"""
if self.debug_info_sec is None:
return
offset = 0
while offset < self.debug_info_sec.size:
cu = self._parse_CU_at_offset(offset)
# Compute the offset of the next CU in the section. The unit_length
# field of the CU header contains its size not including the length
# field itself.
offset = ( offset +
cu['unit_length'] +
cu.structs.initial_length_field_size())
yield cu
def _parse_CU_at_offset(self, offset):
""" Parse and return a CU at the given offset in the debug_info stream.
"""
# Section 7.4 (32-bit and 64-bit DWARF Formats) of the DWARF spec v3
# states that the first 32-bit word of the CU header determines
# whether the CU is represented with 32-bit or 64-bit DWARF format.
#
# So we peek at the first word in the CU header to determine its
# dwarf format. Based on it, we then create a new DWARFStructs
# instance suitable for this CU and use it to parse the rest.
#
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
# find out address_size is actually 8, we just create a new structs
# object for this CU.
#
cu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=4)
cu_header = struct_parse(
cu_structs.Dwarf_CU_header, self.debug_info_sec.stream, offset)
if cu_header['address_size'] == 8:
cu_structs = DWARFStructs(
little_endian=self.config.little_endian,
dwarf_format=dwarf_format,
address_size=8)
cu_die_offset = self.debug_info_sec.stream.tell()
dwarf_assert(
self._is_supported_version(cu_header['version']),
"Expected supported DWARF version. Got '%s'" % cu_header['version'])
return CompileUnit(
header=cu_header,
dwarfinfo=self,
structs=cu_structs,
cu_offset=offset,
cu_die_offset=cu_die_offset)
def _is_supported_version(self, version):
""" DWARF version supported by this parser
"""
return 2 <= version <= 4
def _parse_line_program_at_offset(self, debug_line_offset, structs):
""" Given an offset to the .debug_line section, parse the line program
starting at this offset in the section and return it.
structs is the DWARFStructs object used to do this parsing.
"""
lineprog_header = struct_parse(
structs.Dwarf_lineprog_header,
self.debug_line_sec.stream,
debug_line_offset)
# Calculate the offset to the next line program (see DWARF 6.2.4)
end_offset = ( debug_line_offset + lineprog_header['unit_length'] +
structs.initial_length_field_size())
return LineProgram(
header=lineprog_header,
stream=self.debug_line_sec.stream,
structs=structs,
program_start_offset=self.debug_line_sec.stream.tell(),
program_end_offset=end_offset)