Merge remote-tracking branch 'upstream/master' into bbb Conflicts: elftools/elf/dynamic.py Resolved by using upstream's verbatim copy because vapier's change is now in upstream. Change-Id: I6c597d4ef0a876d26736f96360cff95c072e8005
diff --git a/.gitignore b/.gitignore index 0d20b64..603cba7 100644 --- a/.gitignore +++ b/.gitignore
@@ -1 +1,11 @@ *.pyc +.coverage +.tox +htmlcov +tags +build +dist +MANIFEST +*.sublime-workspace + +
diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 4e7e920..0000000 --- a/.hgignore +++ /dev/null
@@ -1,13 +0,0 @@ -syntax: glob - -*.pyc -.coverage -.tox -htmlcov -tags -build -dist -MANIFEST -*.sublime-workspace - -
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c2eaf23 --- /dev/null +++ b/.travis.yml
@@ -0,0 +1,8 @@ +language: python +python: + - "2.7" + - "3.2" + - "3.3" + - "3.4" +script: python test/all_tests.py +
diff --git a/CHANGES b/CHANGES index 54e772c..926dbe0 100644 --- a/CHANGES +++ b/CHANGES
@@ -1,7 +1,20 @@ Changelog ========= -+ Version 0.22 (17.04.2013) ++ Version 0.22 (30.03.2014) + + - pyelftools repository moved to https://github.com/eliben/pyelftools + - Support for version sections - contributed by Yann Rouillard. + - Better ARM support (including AArch64) - contributed by Dobromir Stefanov. + - Added some initial support for parsing Solaris OpenCSW ELF files + (contributed by Yann Rouillard). + - Added some initial support for DWARF4 (as generated by gcc 4.8) + and DWARF generated by recent versions of Clang (3.3). + - Added the get_full_path utility method to DIEs that have an associated + file name / path (based on pull request #16 by Shaheed Haque). + - Set up Travis CI integration. + ++ Version 0.21 (17.04.2013) - Added new example: dwarf_decode_address - decode function name and file & line information from an address. @@ -22,4 +35,3 @@ on Windows. + Version 0.10 - Initial public release (06.01.2012) -
diff --git a/README.rst b/README.rst index 264b898..d668f3b 100644 --- a/README.rst +++ b/README.rst
@@ -3,7 +3,7 @@ **pyelftools** is a pure-Python library for parsing and analyzing ELF files and DWARF debugging information. See the -`User's guide <https://bitbucket.org/eliben/pyelftools/wiki/Userguide>`_ +`User's guide <https://github.com/eliben/pyelftools/wiki/User's-guide>`_ for more details. Pre-requisites @@ -12,7 +12,7 @@ As a user of **pyelftools**, one only needs Python to run. It works with Python versions 2.6, 2.7 and 3.x (x >= 2). For hacking on **pyelftools** the requirements are a bit more strict, please see the -`hacking guide <https://bitbucket.org/eliben/pyelftools/wiki/Hacking>`_. +`hacking guide <https://github.com/eliben/pyelftools/wiki/Hacking-guide>`_. Installing ---------- @@ -23,21 +23,22 @@ Alternatively, you can download the source distribution for the most recent and historic versions from the *Downloads* tab on the `pyelftools project page -<https://bitbucket.org/eliben/pyelftools>`_ (by going to *Tags*). Then, you can +<https://github.com/eliben/pyelftools>`_ (by going to *Tags*). Then, you can install from source, as usual:: > python setup.py install Since **pyelftools** is a work in progress, it's recommended to have the most -recent version of the code. This can be done by downloading the ``tip`` tag -("trunk") from *Downloads* or just cloning the Mercurial repository. +recent version of the code. This can be done by downloading the `master zip +file <https://github.com/eliben/pyelftools/archive/master.zip>`_ or just +cloning the Git repository. How to use it? -------------- **pyelftools** is a regular Python library: you import and invoke it from your own code. For a detailed usage guide and links to examples, please consult the -`user's guide <https://bitbucket.org/eliben/pyelftools/wiki/Userguide>`_. +`user's guide <https://github.com/eliben/pyelftools/wiki/User's-guide>`_. License ------- @@ -45,6 +46,12 @@ **pyelftools** is open source software. Its code is in the public domain. See the ``LICENSE`` file for more details. +CI Status +--------- +**pyelftools** has automatic testing enabled through the convenient +`Travis CI project <https://travis-ci.org>`_. Here is the latest build status: - +.. image:: https://travis-ci.org/eliben/pyelftools.png?branch=master + :align: center + :target: https://travis-ci.org/eliben/pyelftools
diff --git a/TODO b/TODO index bc6ffdc..9028044 100644 --- a/TODO +++ b/TODO
@@ -4,7 +4,15 @@ * Update elftools/__init__.py * Update setup.py * Update CHANGES -* Tag in hg +* Tag in git (v0.xx) + +construct +--------- + +The construct seems to be maintained again - they also backported my Python 3 +fixes. Theoretically, I can remove construct from pyelftools and use it as a +dependency instead. I don't really have time to play with this now, but may +do so in the future. Distribution ------------ @@ -14,16 +22,11 @@ Preparing a new release ----------------------- -* Run readelf tests with up-to-date readelf (from the new binutils) -* Run all tests with Python 2.7 before packaging, on Linux +* Run 'tox' tests (with '-r' to create new venvs) * Make sure new version was updated everywhere appropriate -* Packaging done on Linux * Run ``python setup.py build sdist`` (no 'upload' yet) * Untar the created ``dist/pyelftools-x.y.tar.gz`` and make sure everything looks ok -* Runt 'tox' tests (with '-r' to create new venvs) * Now build with upload to send it to PyPi * Test with pip install from some new virtualenv -* If everything is OK, upload the distribution to BitBucket as well * The older download can be kept alive for a couple of days -
diff --git a/elftools/__init__.py b/elftools/__init__.py index 90be2eb..8a3e56c 100644 --- a/elftools/__init__.py +++ b/elftools/__init__.py
@@ -4,5 +4,4 @@ # Eli Bendersky (eliben@gmail.com) # This code is in the public domain #------------------------------------------------------------------------------- -__version__ = '0.21' - +__version__ = '0.22'
diff --git a/elftools/common/utils.py b/elftools/common/utils.py index 7bd1d5b..a2c9edb 100644 --- a/elftools/common/utils.py +++ b/elftools/common/utils.py
@@ -31,8 +31,8 @@ stream.seek(stream_pos) return struct.parse_stream(stream) except ConstructError as e: - raise ELFParseError(e.message) - + raise ELFParseError(str(e)) + def parse_cstring_from_stream(stream, stream_pos=None): """ Parse a C-string from the given stream. The string is returned without @@ -78,11 +78,10 @@ @contextmanager def preserve_stream_pos(stream): """ Usage: - - # stream has some position FOO (return value of stream.tell()) - with preserve_stream_pos(stream): - # do stuff that manipulates the stream - # stream still has position FOO + # stream has some position FOO (return value of stream.tell()) + with preserve_stream_pos(stream): + # do stuff that manipulates the stream + # stream still has position FOO """ saved_pos = stream.tell() yield
diff --git a/elftools/construct/README b/elftools/construct/README index 0d76f7f..7f9e141 100644 --- a/elftools/construct/README +++ b/elftools/construct/README
@@ -2,6 +2,9 @@ data. This is my fork of construct 2, with some modifications for Python 3 and bug fixes. The construct website is http://construct.readthedocs.org +pyelftools carries construct around because construct has been abandoned for +a long time and didn't get bugfixes; it also didn't work with Python 3. + LICENSE is the original license.
diff --git a/elftools/dwarf/callframe.py b/elftools/dwarf/callframe.py index 5b35af6..264adb8 100644 --- a/elftools/dwarf/callframe.py +++ b/elftools/dwarf/callframe.py
@@ -26,13 +26,13 @@ Eventually, each entry gets its own structs based on the initial length field it starts with. The address_size, however, is taken from base_structs. This appears to be a limitation of the DWARFv3 - standard, fixed in v4 (where an address_size field exists for each - CFI. A discussion I had on dwarf-discuss confirms this. - Currently for base_structs I simply use the elfclass of the - containing file, but more sophisticated methods are used by - libdwarf and others, such as guessing which CU contains which FDEs - (based on their address ranges) and taking the address_size from - those CUs. + standard, fixed in v4. + A discussion I had on dwarf-discuss confirms this. + So for DWARFv4 we'll take the address size from the CIE header, + but for earlier versions will use the elfclass of the containing + file; more sophisticated methods are used by libdwarf and others, + such as guessing which CU contains which FDEs (based on their + address ranges) and taking the address_size from those CUs. """ def __init__(self, stream, size, base_structs): self.stream = stream @@ -99,6 +99,14 @@ header = struct_parse( header_struct, self.stream, offset) + # If this is DWARF version 4 or later, we can have a more precise + # address size, read from the CIE header. + if entry_structs.dwarf_version >= 4: + entry_structs = DWARFStructs( + little_endian=entry_structs.little_endian, + dwarf_format=entry_structs.dwarf_format, + address_size=header.address_size) + # For convenience, compute the end offset for this entry end_offset = ( offset + header.length +
diff --git a/elftools/dwarf/descriptions.py b/elftools/dwarf/descriptions.py index 987e0d0..ca00cd1 100644 --- a/elftools/dwarf/descriptions.py +++ b/elftools/dwarf/descriptions.py
@@ -82,7 +82,7 @@ 'DW_CFA_advance_loc4', 'DW_CFA_advance_loc'): _assert_FDE_instruction(instr) factored_offset = instr.args[0] * cie['code_alignment_factor'] - s += ' %s: %s to %08x\n' % ( + s += ' %s: %s to %016x\n' % ( name, factored_offset, factored_offset + pc) pc += factored_offset elif name in ( 'DW_CFA_remember_state', 'DW_CFA_restore_state', @@ -197,6 +197,12 @@ """ return '1' if attr.value else '0' +def _describe_attr_present(attr, die, section_offset): + """ Some forms may simply mean that an attribute is present, + without providing any value. + """ + return '1' + def _describe_attr_block(attr, die, section_offset): s = '%s byte block: ' % len(attr.value) s += ' '.join('%x' % item for item in attr.value) + ' ' @@ -227,6 +233,9 @@ DW_FORM_block2=_describe_attr_block, DW_FORM_block4=_describe_attr_block, DW_FORM_block=_describe_attr_block, + DW_FORM_flag_present=_describe_attr_present, + DW_FORM_exprloc=_describe_attr_block, + DW_FORM_ref_sig8=_describe_attr_ref, )
diff --git a/elftools/dwarf/die.py b/elftools/dwarf/die.py index cb5d050..86830fc 100644 --- a/elftools/dwarf/die.py +++ b/elftools/dwarf/die.py
@@ -7,9 +7,12 @@ # This code is in the public domain #------------------------------------------------------------------------------- from collections import namedtuple +import os -from ..common.py3compat import OrderedDict +from ..common.exceptions import DWARFError +from ..common.py3compat import OrderedDict, bytes2str, iteritems from ..common.utils import struct_parse, preserve_stream_pos +from .enums import DW_FORM_raw2name # AttributeValue - describes an attribute value in the DIE: @@ -99,6 +102,20 @@ """ return self._parent + def get_full_path(self): + """ Return the full path filename for the DIE. + + The filename is the join of 'DW_AT_comp_dir' and 'DW_AT_name', + either of which may be missing in practice. Note that its value is + usually a string taken from the .debug_string section and the + returned value will be a string. + """ + comp_dir_attr = self.attributes.get('DW_AT_comp_dir', None) + comp_dir = bytes2str(comp_dir_attr.value) if comp_dir_attr else '' + fname_attr = self.attributes.get('DW_AT_name', None) + fname = bytes2str(fname_attr.value) if fname_attr else '' + return os.path.join(comp_dir, fname) + def iter_children(self): """ Yield all children of this DIE """ @@ -128,7 +145,7 @@ def __repr__(self): s = 'DIE %s, size=%s, has_chidren=%s\n' % ( self.tag, self.size, self.has_children) - for attrname, attrval in self.attributes.iteritems(): + for attrname, attrval in iteritems(self.attributes): s += ' |%-18s: %s\n' % (attrname, attrval) return s @@ -187,9 +204,15 @@ elif form == 'DW_FORM_flag': value = not raw_value == 0 elif form == 'DW_FORM_indirect': - form = raw_value + try: + form = DW_FORM_raw2name[raw_value] + except KeyError as err: + raise DWARFError( + 'Found DW_FORM_indirect with unknown raw_value=' + + str(raw_value)) + raw_value = struct_parse( - structs.Dwarf_dw_form[form], self.stream) + self.cu.structs.Dwarf_dw_form[form], self.stream) # Let's hope this doesn't get too deep :-) return self._translate_attr_value(form, raw_value) else:
diff --git a/elftools/dwarf/dwarfinfo.py b/elftools/dwarf/dwarfinfo.py index 9aa2f52..1995fc8 100644 --- a/elftools/dwarf/dwarfinfo.py +++ b/elftools/dwarf/dwarfinfo.py
@@ -21,7 +21,7 @@ # 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 @@ -30,7 +30,7 @@ # 'name' and 'global_offset' are for descriptional purposes only and # aren't strictly required for the DWARF parsing to work. # -DebugSectionDescriptor = namedtuple('DebugSectionDescriptor', +DebugSectionDescriptor = namedtuple('DebugSectionDescriptor', 'stream name global_offset size') @@ -51,7 +51,7 @@ class DWARFInfo(object): - """ Acts also as a "context" to other major objects, bridging between + """ Acts also as a "context" to other major objects, bridging between various parts of the debug infromation. """ def __init__(self, @@ -59,6 +59,7 @@ debug_info_sec, debug_abbrev_sec, debug_frame_sec, + eh_frame_sec, debug_str_sec, debug_loc_sec, debug_ranges_sec, @@ -68,19 +69,20 @@ debug_*_sec: DebugSectionDescriptor for a section. Pass None for sections - that don't exist. These arguments are best given with + that don't exist. These arguments are best given with keyword syntax. """ 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.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 - # This is the DWARFStructs the context uses, so it doesn't depend on + # 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( @@ -119,7 +121,7 @@ return self._abbrevtable_cache[offset] def get_string_from_table(self, offset): - """ Obtain a string from the string table section, given an 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) @@ -139,12 +141,12 @@ return None def has_CFI(self): - """ Does this dwarf info has a CFI section? + """ 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 CFI entries from the .debug_frame section. + """ Get a list of dwarf_frame CFI entries from the .debug_frame section. """ cfi = CallFrameInfo( stream=self.debug_frame_sec.stream, @@ -152,17 +154,37 @@ 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, + base_structs=self.structs) + return cfi.get_entries() + 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. """ - return LocationLists(self.debug_loc_sec.stream, self.structs) + 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. """ - return RangeLists(self.debug_ranges_sec.stream, self.structs) + if self.debug_ranges_sec: + return RangeLists(self.debug_ranges_sec.stream, self.structs) + else: + return None #------ PRIVATE ------# @@ -175,18 +197,18 @@ # 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'] + + 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 + # 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. @@ -205,15 +227,15 @@ 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) - + address_size=8) + cu_die_offset = self.debug_info_sec.stream.tell() dwarf_assert( self._is_supported_version(cu_header['version']), @@ -224,11 +246,11 @@ 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 <= 3 + 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
diff --git a/elftools/dwarf/enums.py b/elftools/dwarf/enums.py index 1338725..d576dff 100644 --- a/elftools/dwarf/enums.py +++ b/elftools/dwarf/enums.py
@@ -7,6 +7,7 @@ # This code is in the public domain #------------------------------------------------------------------------------- from ..construct import Pass +from ..common.py3compat import iteritems ENUM_DW_TAG = dict( @@ -52,10 +53,12 @@ DW_TAG_namelist_items = 0x2c, DW_TAG_packed_type = 0x2d, DW_TAG_subprogram = 0x2e, - DW_TAG_template_type_parameter = 0x2f, + + # The DWARF standard defines these as _parameter, not _param, but we + # maintain compatibility with readelf. DW_TAG_template_type_param = 0x2f, - DW_TAG_template_value_parameter = 0x30, DW_TAG_template_value_param = 0x30, + DW_TAG_thrown_type = 0x31, DW_TAG_try_block = 0x32, DW_TAG_variant_part = 0x33, @@ -185,7 +188,9 @@ DW_AT_main_subprogram = 0x6a, DW_AT_data_bit_offset = 0x6b, DW_AT_const_expr = 0x6c, - + DW_AT_enum_class = 0x6d, + DW_AT_linkage_name = 0x6e, + DW_AT_MIPS_fde = 0x2001, DW_AT_MIPS_loop_begin = 0x2002, DW_AT_MIPS_tail_loop_begin = 0x2003, @@ -197,8 +202,46 @@ DW_AT_MIPS_abstract_name = 0x2009, DW_AT_MIPS_clone_origin = 0x200a, DW_AT_MIPS_has_inlines = 0x200b, + DW_AT_MIPS_stride_byte = 0x200c, + DW_AT_MIPS_stride_elem = 0x200d, + DW_AT_MIPS_ptr_dopetype = 0x200e, + DW_AT_MIPS_allocatable_dopetype = 0x200f, + DW_AT_MIPS_assumed_shape_dopetype = 0x2010, + DW_AT_MIPS_assumed_size = 0x2011, - _default_ = Pass, + DW_AT_sf_names = 0x2101, + DW_AT_src_info = 0x2102, + DW_AT_mac_info = 0x2103, + DW_AT_src_coords = 0x2104, + DW_AT_body_begin = 0x2105, + DW_AT_body_end = 0x2106, + DW_AT_GNU_vector = 0x2107, + DW_AT_GNU_template_name = 0x2110, + + DW_AT_GNU_call_site_value = 0x2111, + DW_AT_GNU_call_site_data_value = 0x2112, + DW_AT_GNU_call_site_target = 0x2113, + DW_AT_GNU_call_site_target_clobbered = 0x2114, + DW_AT_GNU_tail_call = 0x2115, + DW_AT_GNU_all_tail_call_sites = 0x2116, + DW_AT_GNU_all_call_sites = 0x2117, + DW_AT_GNU_all_source_call_sites = 0x2118, + + DW_AT_APPLE_optimized = 0x3fe1, + DW_AT_APPLE_flags = 0x3fe2, + DW_AT_APPLE_isa = 0x3fe3, + DW_AT_APPLE_block = 0x3fe4, + DW_AT_APPLE_major_runtime_vers = 0x3fe5, + DW_AT_APPLE_runtime_class = 0x3fe6, + DW_AT_APPLE_omit_frame_ptr = 0x3fe7, + DW_AT_APPLE_property_name = 0x3fe8, + DW_AT_APPLE_property_getter = 0x3fe9, + DW_AT_APPLE_property_setter = 0x3fea, + DW_AT_APPLE_property_attribute = 0x3feb, + DW_AT_APPLE_objc_complete_type = 0x3fec, + DW_AT_APPLE_property = 0x3fed, + + _default_ = Pass, ) @@ -225,7 +268,16 @@ DW_FORM_ref8 = 0x14, DW_FORM_ref_udata = 0x15, DW_FORM_indirect = 0x16, + DW_FORM_sec_offset = 0x17, + DW_FORM_exprloc = 0x18, + DW_FORM_flag_present = 0x19, + DW_FORM_ref_sig8 = 0x20, + DW_FORM_GNU_strp_alt = 0x1f21, + DW_FORM_GNU_ref_alt = 0x1f20, _default_ = Pass, ) +# Inverse mapping for ENUM_DW_FORM +DW_FORM_raw2name = dict((v, k) for k, v in iteritems(ENUM_DW_FORM)) +
diff --git a/elftools/dwarf/lineprogram.py b/elftools/dwarf/lineprogram.py index ee5193e..810e603 100644 --- a/elftools/dwarf/lineprogram.py +++ b/elftools/dwarf/lineprogram.py
@@ -215,10 +215,10 @@ add_entry_old_state(opcode, [operand]) elif opcode == DW_LNS_negate_stmt: state.is_stmt = not state.is_stmt - add_entry_old_state(opcode, [operand]) + add_entry_old_state(opcode, []) elif opcode == DW_LNS_set_basic_block: state.basic_block = True - add_entry_old_state(opcode, [operand]) + add_entry_old_state(opcode, []) elif opcode == DW_LNS_const_add_pc: adjusted_opcode = 255 - self['opcode_base'] address_addend = ((adjusted_opcode // self['line_range']) *
diff --git a/elftools/dwarf/structs.py b/elftools/dwarf/structs.py index cfb2515..39e4815 100644 --- a/elftools/dwarf/structs.py +++ b/elftools/dwarf/structs.py
@@ -11,7 +11,7 @@ UBInt8, UBInt16, UBInt32, UBInt64, ULInt8, ULInt16, ULInt32, ULInt64, SBInt8, SBInt16, SBInt32, SBInt64, SLInt8, SLInt16, SLInt32, SLInt64, Adapter, Struct, ConstructError, If, RepeatUntil, Field, Rename, Enum, - Array, PrefixedArray, CString, Embed, + Array, PrefixedArray, CString, Embed, StaticField ) from ..common.construct_utils import RepeatUntilExcluding @@ -19,39 +19,39 @@ class DWARFStructs(object): - """ Exposes Construct structs suitable for parsing information from DWARF + """ Exposes Construct structs suitable for parsing information from DWARF sections. Each compile unit in DWARF info can have its own structs - object. Keep in mind that these structs have to be given a name (by + object. Keep in mind that these structs have to be given a name (by calling them with a name) before being used for parsing (like other Construct structs). Those that should be used without a name are marked by (+). - + Accessible attributes (mostly as described in chapter 7 of the DWARF spec v3): - + Dwarf_[u]int{8,16,32,64): Data chunks of the common sizes - + Dwarf_offset: 32-bit or 64-bit word, depending on dwarf_format - + Dwarf_target_addr: 32-bit or 64-bit word, depending on address size - + Dwarf_initial_length: "Initial length field" encoding section 7.4 - + Dwarf_{u,s}leb128: ULEB128 and SLEB128 variable-length encoding - + Dwarf_CU_header (+): Compilation unit header - + Dwarf_abbrev_declaration (+): Abbreviation table declaration - doesn't include the initial code, only the contents. - + Dwarf_dw_form (+): A dictionary mapping 'DW_FORM_*' keys into construct Structs that parse such forms. These Structs have already been given @@ -62,7 +62,7 @@ Dwarf_lineprog_file_entry (+): A single file entry in a line program header or instruction - + Dwarf_CIE_header (+): A call-frame CIE @@ -71,22 +71,27 @@ See also the documentation of public methods. """ - def __init__(self, little_endian, dwarf_format, address_size): - """ little_endian: + def __init__(self, + little_endian, dwarf_format, address_size, dwarf_version=2): + """ dwarf_version: + Numeric DWARF version + + little_endian: True if the file is little endian, False if big - + dwarf_format: DWARF Format: 32 or 64-bit (see spec section 7.4) - + address_size: - Target machine address size, in bytes (4 or 8). (See spec + Target machine address size, in bytes (4 or 8). (See spec section 7.5.1) """ assert dwarf_format == 32 or dwarf_format == 64 assert address_size == 8 or address_size == 4 self.little_endian = little_endian - self.dwarf_format = dwarf_format + self.dwarf_format = dwarf_format self.address_size = address_size + self.dwarf_version = dwarf_version self._create_structs() def initial_length_field_size(self): @@ -131,7 +136,7 @@ def _create_initial_length(self): def _InitialLength(name): # Adapts a Struct that parses forward a full initial length field. - # Only if the first word is the continuation value, the second + # Only if the first word is the continuation value, the second # word is parsed from the stream. # return _InitialLengthAdapter( @@ -152,13 +157,13 @@ self.Dwarf_uint16('version'), self.Dwarf_offset('debug_abbrev_offset'), self.Dwarf_uint8('address_size')) - + def _create_abbrev_declaration(self): self.Dwarf_abbrev_declaration = Struct('Dwarf_abbrev_entry', Enum(self.Dwarf_uleb128('tag'), **ENUM_DW_TAG), Enum(self.Dwarf_uint8('children_flag'), **ENUM_DW_CHILDREN), RepeatUntilExcluding( - lambda obj, ctx: + lambda obj, ctx: obj.name == 'DW_AT_null' and obj.form == 'DW_FORM_null', Struct('attr_spec', Enum(self.Dwarf_uleb128('name'), **ENUM_DW_AT), @@ -167,12 +172,12 @@ def _create_dw_form(self): self.Dwarf_dw_form = dict( DW_FORM_addr=self.Dwarf_target_addr(''), - + DW_FORM_block1=self._make_block_struct(self.Dwarf_uint8), DW_FORM_block2=self._make_block_struct(self.Dwarf_uint16), DW_FORM_block4=self._make_block_struct(self.Dwarf_uint32), DW_FORM_block=self._make_block_struct(self.Dwarf_uleb128), - + # All DW_FORM_data<n> forms are assumed to be unsigned DW_FORM_data1=self.Dwarf_uint8(''), DW_FORM_data2=self.Dwarf_uint16(''), @@ -180,19 +185,29 @@ DW_FORM_data8=self.Dwarf_uint64(''), DW_FORM_sdata=self.Dwarf_sleb128(''), DW_FORM_udata=self.Dwarf_uleb128(''), - + DW_FORM_string=CString(''), DW_FORM_strp=self.Dwarf_offset(''), DW_FORM_flag=self.Dwarf_uint8(''), - + DW_FORM_ref1=self.Dwarf_uint8(''), DW_FORM_ref2=self.Dwarf_uint16(''), DW_FORM_ref4=self.Dwarf_uint32(''), DW_FORM_ref8=self.Dwarf_uint64(''), DW_FORM_ref_udata=self.Dwarf_uleb128(''), DW_FORM_ref_addr=self.Dwarf_offset(''), - + DW_FORM_indirect=self.Dwarf_uleb128(''), + + # New forms in DWARFv4 + DW_FORM_flag_present = StaticField('', 0), + DW_FORM_sec_offset = self.Dwarf_offset(''), + DW_FORM_exprloc = self._make_block_struct(self.Dwarf_uleb128), + DW_FORM_ref_sig8 = self.Dwarf_offset(''), + + DW_FORM_GNU_strp_alt=self.Dwarf_offset(''), + DW_FORM_GNU_ref_alt=self.Dwarf_offset(''), + DW_AT_GNU_all_call_sites=self.Dwarf_uleb128(''), ) def _create_lineprog_header(self): @@ -215,7 +230,7 @@ self.Dwarf_int8('line_base'), self.Dwarf_uint8('line_range'), self.Dwarf_uint8('opcode_base'), - Array(lambda ctx: ctx['opcode_base'] - 1, + Array(lambda ctx: ctx['opcode_base'] - 1, self.Dwarf_uint8('standard_opcode_lengths')), RepeatUntilExcluding( lambda obj, ctx: obj == b'', @@ -226,14 +241,27 @@ ) 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')) + # The CIE header was modified in DWARFv4. + if self.dwarf_version == 4: + 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_uint8('address_size'), + self.Dwarf_uint8('segment_size'), + self.Dwarf_uleb128('code_alignment_factor'), + self.Dwarf_sleb128('data_alignment_factor'), + self.Dwarf_uleb128('return_address_register')) + else: + 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'), @@ -242,7 +270,7 @@ self.Dwarf_target_addr('address_range')) def _make_block_struct(self, length_field): - """ Create a struct for DW_FORM_block<size> + """ Create a struct for DW_FORM_block<size> """ return PrefixedArray( subcon=self.Dwarf_uint8('elem'),
diff --git a/elftools/elf/constants.py b/elftools/elf/constants.py index df75e16..b41f35a 100644 --- a/elftools/elf/constants.py +++ b/elftools/elf/constants.py
@@ -6,6 +6,26 @@ # Eli Bendersky (eliben@gmail.com) # This code is in the public domain #------------------------------------------------------------------------------- + +class E_FLAGS(object): + """ Flag values for the e_flags field of the ELF header + """ + EF_ARM_EABIMASK=0xFF000000 + EF_ARM_EABI_VER1=0x01000000 + EF_ARM_EABI_VER2=0x02000000 + EF_ARM_EABI_VER3=0x03000000 + EF_ARM_EABI_VER4=0x04000000 + EF_ARM_EABI_VER5=0x05000000 + EF_ARM_GCCMASK=0x00400FFF + EF_ARM_HASENTRY=0x02 + EF_ARM_SYMSARESORTED=0x04 + EF_ARM_DYNSYMSUSESEGIDX=0x8 + EF_ARM_MAPSYMSFIRST=0x10 + EF_ARM_LE8=0x00400000 + EF_ARM_BE8=0x00800000 + EF_ARM_ABI_FLOAT_SOFT=0x00000200 + EF_ARM_ABI_FLOAT_HARD=0x00000400 + class SHN_INDICES(object): """ Special section indices """ @@ -45,3 +65,25 @@ PF_MASKOS=0x00FF0000 PF_MASKPROC=0xFF000000 + +# symbol info flags for entries +# in the .SUNW_syminfo section +class SUNW_SYMINFO_FLAGS(object): + """ Flags for the si_flags field of entries + in the .SUNW_syminfo section + """ + SYMINFO_FLG_DIRECT=0x1 + SYMINFO_FLG_FILTER=0x2 + SYMINFO_FLG_COPY=0x4 + SYMINFO_FLG_LAZYLOAD=0x8 + SYMINFO_FLG_DIRECTBIND=0x10 + SYMINFO_FLG_NOEXTDIRECT=0x20 + SYMINFO_FLG_AUXILIARY=0x40 + SYMINFO_FLG_INTERPOSE=0x80 + SYMINFO_FLG_CAP=0x100 + SYMINFO_FLG_DEFERRED=0x200 + +class VER_FLAGS(object): + VER_FLG_BASE=0x1 + VER_FLG_WEAK=0x2 + VER_FLG_INFO=0x4
diff --git a/elftools/elf/descriptions.py b/elftools/elf/descriptions.py index df81000..6d108c2 100644 --- a/elftools/elf/descriptions.py +++ b/elftools/elf/descriptions.py
@@ -7,9 +7,9 @@ # This code is in the public domain #------------------------------------------------------------------------------- from .enums import ( - ENUM_D_TAG, ENUM_E_VERSION, ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64 - ) -from .constants import P_FLAGS, SH_FLAGS + ENUM_D_TAG, ENUM_E_VERSION, ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, + ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64) +from .constants import P_FLAGS, SH_FLAGS, SUNW_SYMINFO_FLAGS, VER_FLAGS from ..common.py3compat import iteritems @@ -77,6 +77,10 @@ return _DESCR_RELOC_TYPE_i386.get(x, _unknown) elif arch == 'x64': return _DESCR_RELOC_TYPE_x64.get(x, _unknown) + elif arch == 'ARM': + return _DESCR_RELOC_TYPE_ARM.get(x, _unknown) + elif arch == 'AArch64': + return _DESCR_RELOC_TYPE_AARCH64.get(x, _unknown) else: return 'unrecognized: %-7x' % (x & 0xFFFFFFFF) @@ -84,6 +88,28 @@ return _DESCR_D_TAG.get(x, _unknown) +def describe_syminfo_flags(x): + return ''.join(_DESCR_SYMINFO_FLAGS[flag] for flag in ( + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_CAP, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECT, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_FILTER, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_AUXILIARY, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECTBIND, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_COPY, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_LAZYLOAD, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_NOEXTDIRECT, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_INTERPOSE, + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DEFERRED) if x & flag) + +def describe_symbol_boundto(x): + return _DESCR_SYMINFO_BOUNDTO.get(x, '%3s' % x) + +def describe_ver_flags(x): + return ' | '.join(_DESCR_VER_FLAGS[flag] for flag in ( + VER_FLAGS.VER_FLG_WEAK, + VER_FLAGS.VER_FLG_BASE, + VER_FLAGS.VER_FLG_INFO) if x & flag) + #------------------------------------------------------------------------------- _unknown = '<unknown>' @@ -143,6 +169,9 @@ EM_IA_64='Intel IA-64', EM_X86_64='Advanced Micro Devices X86-64', EM_AVR='Atmel AVR 8-bit microcontroller', + EM_ARM='ARM', + EM_AARCH64='AArch64', + EM_BLAFKIN='Analog Devices Blackfin', RESERVED='RESERVED', ) @@ -157,6 +186,11 @@ PT_GNU_EH_FRAME='GNU_EH_FRAME', PT_GNU_STACK='GNU_STACK', PT_GNU_RELRO='GNU_RELRO', + PT_ARM_ARCHEXT='ARM_ARCHEXT', + PT_ARM_EXIDX='ARM_EXIDX', + PT_ARM_UNWIND='ARM_UNWIND', + PT_AARCH64_ARCHEXT='AARCH64_ARCHEXT', + PT_AARCH64_UNWIND='AARCH64_UNWIND', ) _DESCR_P_FLAGS = { @@ -188,6 +222,10 @@ SHT_GNU_verneed='VERNEED', SHT_GNU_versym='VERSYM', SHT_GNU_LIBLIST='GNU_LIBLIST', + SHT_ARM_EXIDX='ARM_EXIDX', + SHT_ARM_PREEMPTMAP='ARM_PREEMPTMAP', + SHT_ARM_ATTRIBUTES='ARM_ATTRIBUTES', + SHT_ARM_DEBUGOVERLAY='ARM_DEBUGOVERLAY', ) _DESCR_SH_FLAGS = { @@ -227,7 +265,10 @@ STV_DEFAULT='DEFAULT', STV_INTERNAL='INTERNAL', STV_HIDDEN='HIDDEN', - STD_PROTECTED='PROTECTED', + STV_PROTECTED='PROTECTED', + STV_EXPORTED='EXPORTED', + STV_SINGLETON='SINGLETON', + STV_ELIMINATE='ELIMINATE', ) _DESCR_ST_SHNDX = dict( @@ -236,11 +277,44 @@ SHN_COMMON='COM', ) +_DESCR_SYMINFO_FLAGS = { + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECT: 'D', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECTBIND: 'B', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_COPY: 'C', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_LAZYLOAD: 'L', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_NOEXTDIRECT: 'N', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_AUXILIARY: 'A', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_FILTER: 'F', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_INTERPOSE: 'I', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_CAP: 'S', + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DEFERRED: 'P', +} + +_DESCR_SYMINFO_BOUNDTO = dict( + SYMINFO_BT_SELF='<self>', + SYMINFO_BT_PARENT='<parent>', + SYMINFO_BT_NONE='', + SYMINFO_BT_EXTERN='<extern>', +) + +_DESCR_VER_FLAGS = { + 0: '', + VER_FLAGS.VER_FLG_BASE: 'BASE', + VER_FLAGS.VER_FLG_WEAK: 'WEAK', + VER_FLAGS.VER_FLG_INFO: 'INFO', +} + _DESCR_RELOC_TYPE_i386 = dict( (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_i386)) _DESCR_RELOC_TYPE_x64 = dict( (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_x64)) +_DESCR_RELOC_TYPE_ARM = dict( + (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_ARM)) + +_DESCR_RELOC_TYPE_AARCH64 = dict( + (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_AARCH64)) + _DESCR_D_TAG = dict( (v, k) for k, v in iteritems(ENUM_D_TAG))
diff --git a/elftools/elf/dynamic.py b/elftools/elf/dynamic.py index 824cb78..9f985c2 100644 --- a/elftools/elf/dynamic.py +++ b/elftools/elf/dynamic.py
@@ -10,10 +10,9 @@ from .sections import Section from .segments import Segment +from ..common.exceptions import ELFError from ..common.utils import struct_parse, parse_cstring_from_stream -from .enums import ENUM_D_TAG - class _DynamicStringTable(object): """ Bare string table based on values found via ELF dynamic tags and @@ -40,13 +39,16 @@ value of DT_SONAME (fetched from the dynamic symbol table). """ _HANDLED_TAGS = frozenset( - ['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME']) + ['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME', + 'DT_SUNW_FILTER']) - def __init__(self, entry, dynstr): + def __init__(self, entry, stringtable): + if stringtable is None: + raise ELFError('Creating DynamicTag without string table') self.entry = entry - if entry.d_tag in self._HANDLED_TAGS and dynstr: + if entry.d_tag in self._HANDLED_TAGS: setattr(self, entry.d_tag[3:].lower(), - dynstr.get_string(self.entry.d_val)) + stringtable.get_string(self.entry.d_val)) def __getitem__(self, name): """ Implement dict-like access to entries @@ -67,24 +69,25 @@ class Dynamic(object): """ Shared functionality between dynamic sections and segments. """ - def __init__(self, stream, elffile, position): + def __init__(self, stream, elffile, stringtable, position): self._stream = stream self._elffile = elffile self._elfstructs = elffile.structs self._num_tags = -1 self._offset = position self._tagsize = self._elfstructs.Elf_Dyn.sizeof() - self.__string_table = None - @property - def _string_table(self): + # Do not access this directly yourself; use _get_stringtable() instead. + self._stringtable = stringtable + + def _get_stringtable(self): """ Return a string table for looking up dynamic tag related strings. - This won't be a "full" string table object, but will at least support - the get_string() function. + This won't be a "full" string table object, but will at least + support the get_string() function. """ - if self.__string_table: - return self.__string_table + if self._stringtable: + return self._stringtable # If the ELF has stripped its section table (which is unusual, but # perfectly valid), we need to use the dynamic tags to locate the @@ -96,19 +99,15 @@ # If we found a dynamic string table, locate the offset in the file # by using the program headers. if strtab: - for segment in self._elffile.iter_segments(): - if (strtab >= segment['p_vaddr'] and - strtab < segment['p_vaddr'] + segment['p_filesz']): - self.__string_table = _DynamicStringTable( - self._stream, - segment['p_offset'] + (strtab - segment['p_vaddr'])) - return self.__string_table + table_offset = next(self._elffile.address_offsets(strtab), None) + if table_offset is not None: + self._stringtable = _DynamicStringTable(self._stream, table_offset) + return self._stringtable # That didn't work for some reason. Let's use the section header # even though this ELF is super weird. - self.__string_table = self._elffile.get_section_by_name(b'.dynstr') - - return self.__string_table + self._stringtable = self._elffile.get_section_by_name(b'.dynstr') + return self._stringtable def _iter_tags(self, type=None): """ Yield all raw tags (limit to |type| if specified) @@ -124,7 +123,7 @@ """ Yield all tags (limit to |type| if specified) """ for tag in self._iter_tags(type=type): - yield DynamicTag(tag, self._string_table) + yield DynamicTag(tag, self._get_stringtable()) def _get_tag(self, n): """ Get the raw tag at index #n from the file @@ -138,7 +137,7 @@ def get_tag(self, n): """ Get the tag at index #n from the file (DynamicTag object) """ - return DynamicTag(self._get_tag(n), self._string_table) + return DynamicTag(self._get_tag(n), self._get_stringtable()) def num_tags(self): """ Number of dynamic tags in the file @@ -158,12 +157,25 @@ """ def __init__(self, header, name, stream, elffile): Section.__init__(self, header, name, stream) - Dynamic.__init__(self, stream, elffile, self['sh_offset']) + stringtable = elffile.get_section(header['sh_link']) + Dynamic.__init__(self, stream, elffile, stringtable, self['sh_offset']) class DynamicSegment(Segment, Dynamic): """ ELF dynamic table segment. Knows how to process the list of tags. """ def __init__(self, header, stream, elffile): + # The string table section to be used to resolve string names in + # the dynamic tag array is the one pointed at by the sh_link field + # of the dynamic section header. + # So we must look for the dynamic section contained in the dynamic + # segment, we do so by searching for the dynamic section whose content + # is located at the same offset as the dynamic segment + stringtable = None + for section in elffile.iter_sections(): + if (isinstance(section, DynamicSection) and + section['sh_offset'] == header['p_offset']): + stringtable = elffile.get_section(section['sh_link']) + break Segment.__init__(self, header, stream) - Dynamic.__init__(self, stream, elffile, self['p_offset']) + Dynamic.__init__(self, stream, elffile, stringtable, self['p_offset'])
diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index 1d8d6de..15aa07e 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py
@@ -12,11 +12,14 @@ from ..construct import ConstructError from .structs import ELFStructs from .sections import ( - Section, StringTableSection, SymbolTableSection, NullSection) + Section, StringTableSection, SymbolTableSection, + SUNWSyminfoTableSection, NullSection) from .dynamic import DynamicSection, DynamicSegment from .relocation import RelocationSection, RelocationHandler +from .gnuversions import ( + GNUVerNeedSection, GNUVerDefSection, + GNUVerSymSection) from .segments import Segment, InterpSegment -from .enums import ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64 from ..dwarf.dwarfinfo import DWARFInfo, DebugSectionDescriptor, DwarfConfig @@ -105,6 +108,18 @@ for i in range(self.num_segments()): yield self.get_segment(i) + def address_offsets(self, start, size=1): + """ Yield a file offset for each ELF segment containing a memory region. + + A memory region is defined by the range [start...start+size). The + offset of the region is yielded. + """ + end = start + size + for seg in self.iter_segments(): + if (start >= seg['p_vaddr'] and + end <= seg['p_vaddr'] + seg['p_filesz']): + yield start - seg['p_vaddr'] + seg['p_offset'] + 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 @@ -125,24 +140,26 @@ # debug_sections = {} for secname in (b'.debug_info', b'.debug_abbrev', b'.debug_str', - b'.debug_line', b'.debug_frame', b'.debug_loc', - b'.debug_ranges'): + b'.debug_line', b'.debug_frame', + b'.debug_loc', b'.debug_ranges'): section = self.get_section_by_name(secname) if section is None: debug_sections[secname] = None else: debug_sections[secname] = self._read_dwarf_section( - section, - relocate_dwarf_sections) + section, + relocate_dwarf_sections) return DWARFInfo( config=DwarfConfig( little_endian=self.little_endian, - default_address_size=self.elfclass / 8, + default_address_size=self.elfclass // 8, machine_arch=self.get_machine_arch()), debug_info_sec=debug_sections[b'.debug_info'], debug_abbrev_sec=debug_sections[b'.debug_abbrev'], debug_frame_sec=debug_sections[b'.debug_frame'], + # TODO(eliben): reading of eh_frame is not hooked up yet + eh_frame_sec=None, debug_str_sec=debug_sections[b'.debug_str'], debug_loc_sec=debug_sections[b'.debug_loc'], debug_ranges_sec=debug_sections[b'.debug_ranges'], @@ -158,6 +175,8 @@ return 'x86' elif self['e_machine'] == 'EM_ARM': return 'ARM' + elif self['e_machine'] == 'EM_AARCH64': + return 'AArch64' else: return '<unknown>' @@ -241,8 +260,16 @@ return StringTableSection(section_header, name, self.stream) elif sectype == 'SHT_NULL': return NullSection(section_header, name, self.stream) - elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM'): + elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM', 'SHT_SUNW_LDYNSYM'): return self._make_symbol_table_section(section_header, name) + elif sectype == 'SHT_SUNW_syminfo': + return self._make_sunwsyminfo_table_section(section_header, name) + elif sectype == 'SHT_GNU_verneed': + return self._make_gnu_verneed_section(section_header, name) + elif sectype == 'SHT_GNU_verdef': + return self._make_gnu_verdef_section(section_header, name) + elif sectype == 'SHT_GNU_versym': + return self._make_gnu_versym_section(section_header, name) elif sectype in ('SHT_REL', 'SHT_RELA'): return RelocationSection( section_header, name, self.stream, self) @@ -261,6 +288,46 @@ elffile=self, stringtable=strtab_section) + def _make_sunwsyminfo_table_section(self, section_header, name): + """ Create a SUNWSyminfoTableSection + """ + linked_strtab_index = section_header['sh_link'] + strtab_section = self.get_section(linked_strtab_index) + return SUNWSyminfoTableSection( + section_header, name, self.stream, + elffile=self, + symboltable=strtab_section) + + def _make_gnu_verneed_section(self, section_header, name): + """ Create a GNUVerNeedSection + """ + linked_strtab_index = section_header['sh_link'] + strtab_section = self.get_section(linked_strtab_index) + return GNUVerNeedSection( + section_header, name, self.stream, + elffile=self, + stringtable=strtab_section) + + def _make_gnu_verdef_section(self, section_header, name): + """ Create a GNUVerDefSection + """ + linked_strtab_index = section_header['sh_link'] + strtab_section = self.get_section(linked_strtab_index) + return GNUVerDefSection( + section_header, name, self.stream, + elffile=self, + stringtable=strtab_section) + + def _make_gnu_versym_section(self, section_header, name): + """ Create a GNUVerSymSection + """ + linked_strtab_index = section_header['sh_link'] + strtab_section = self.get_section(linked_strtab_index) + return GNUVerSymSection( + section_header, name, self.stream, + elffile=self, + symboltable=strtab_section) + def _get_segment_header(self, n): """ Find the header of segment #n, parse it and return the struct """
diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py index aaa6c71..4aa449a 100644 --- a/elftools/elf/enums.py +++ b/elftools/elf/enums.py
@@ -47,6 +47,7 @@ ELFOSABI_OPENVMS=13, ELFOSABI_NSK=14, ELFOSABI_AROS=15, + ELFOSABI_ARM_AEABI=64, ELFOSABI_ARM=97, ELFOSABI_STANDALONE=255, _default_=Pass, @@ -159,6 +160,7 @@ EM_ARCA=109, EM_UNICORE=110, EM_L10M=180, + EM_AARCH64=183, _default_=Pass, ) @@ -184,14 +186,20 @@ SHT_NUM=19, SHT_LOOS=0x60000000, SHT_GNU_HASH=0x6ffffff6, - SHT_GNU_verdef=0x6ffffffd, - SHT_GNU_verneed=0x6ffffffe, - SHT_GNU_versym=0x6fffffff, + SHT_GNU_verdef=0x6ffffffd, # also SHT_SUNW_verdef + SHT_GNU_verneed=0x6ffffffe, # also SHT_SUNW_verneed + SHT_GNU_versym=0x6fffffff, # also SHT_SUNW_versym SHT_LOPROC=0x70000000, SHT_HIPROC=0x7fffffff, SHT_LOUSER=0x80000000, SHT_HIUSER=0xffffffff, SHT_AMD64_UNWIND=0x70000001, + SHT_SUNW_LDYNSYM=0x6ffffff3, + SHT_SUNW_syminfo=0x6ffffffc, + SHT_ARM_EXIDX=0x70000001, + SHT_ARM_PREEMPTMAP=0x70000002, + SHT_ARM_ATTRIBUTES=0x70000003, + SHT_ARM_DEBUGOVERLAY=0x70000004, _default_=Pass, ) @@ -211,6 +219,11 @@ PT_GNU_EH_FRAME=0x6474e550, PT_GNU_STACK=0x6474e551, PT_GNU_RELRO=0x6474e552, + PT_ARM_ARCHEXT=0x70000000, + PT_ARM_EXIDX=0x70000001, + PT_ARM_UNWIND=0x70000001, + PT_AARCH64_ARCHEXT=0x70000000, + PT_AARCH64_UNWIND=0x70000001, _default_=Pass, ) @@ -252,6 +265,9 @@ STV_INTERNAL=1, STV_HIDDEN=2, STV_PROTECTED=3, + STV_EXPORTED=4, + STV_SINGLETON=5, + STV_ELIMINATE=6, _default_=Pass, ) @@ -301,6 +317,24 @@ DT_PREINIT_ARRAYSZ=33, DT_NUM=34, DT_LOOS=0x6000000d, + DT_SUNW_AUXILIARY=0x6000000d, + DT_SUNW_RTLDINF=0x6000000e, + DT_SUNW_FILTER=0x6000000f, + DT_SUNW_CAP=0x60000010, + DT_SUNW_SYMTAB=0x60000011, + DT_SUNW_SYMSZ=0x60000012, + DT_SUNW_ENCODING=0x60000013, + DT_SUNW_SORTENT=0x60000013, + DT_SUNW_SYMSORT=0x60000014, + DT_SUNW_SYMSORTSZ=0x60000015, + DT_SUNW_TLSSORT=0x60000016, + DT_SUNW_TLSSORTSZ=0x60000017, + DT_SUNW_CAPINFO=0x60000018, + DT_SUNW_STRPAD=0x60000019, + DT_SUNW_CAPCHAIN=0x6000001a, + DT_SUNW_LDMACH=0x6000001b, + DT_SUNW_CAPCHAINENT=0x6000001d, + DT_SUNW_CAPCHAINSZ=0x6000001f, DT_HIOS=0x6ffff000, DT_LOPROC=0x70000000, DT_HIPROC=0x7fffffff, @@ -428,3 +462,271 @@ _default_=Pass, ) +# Sunw Syminfo Bound To special values +ENUM_SUNW_SYMINFO_BOUNDTO = dict( + SYMINFO_BT_SELF=0xffff, + SYMINFO_BT_PARENT=0xfffe, + SYMINFO_BT_NONE=0xfffd, + SYMINFO_BT_EXTERN=0xfffc, + _default_=Pass, +) + +# Versym section, version dependency index +ENUM_VERSYM = dict( + VER_NDX_LOCAL=0, + VER_NDX_GLOBAL=1, + VER_NDX_LORESERVE=0xff00, + VER_NDX_ELIMINATE=0xff01, + _default_=Pass, +) +# Sunw Syminfo Bound To special values +ENUM_SUNW_SYMINFO_BOUNDTO = dict( + SYMINFO_BT_SELF=0xffff, + SYMINFO_BT_PARENT=0xfffe, + SYMINFO_BT_NONE=0xfffd, + SYMINFO_BT_EXTERN=0xfffc, + _default_=Pass, +) + +ENUM_RELOC_TYPE_ARM = dict( + R_ARM_NONE=0, + R_ARM_PC24=1, + R_ARM_ABS32=2, + R_ARM_REL32=3, + R_ARM_LDR_PC_G0=4, + R_ARM_ABS16=5, + R_ARM_ABS12=6, + R_ARM_THM_ABS5=7, + R_ARM_ABS8=8, + R_ARM_SBREL32=9, + R_ARM_THM_CALL=10, + R_ARM_THM_PC8=11, + R_ARM_BREL_ADJ=12, + R_ARM_SWI24=13, + R_ARM_THM_SWI8=14, + R_ARM_XPC25=15, + R_ARM_THM_XPC22=16, + R_ARM_TLS_DTPMOD32=17, + R_ARM_TLS_DTPOFF32=18, + R_ARM_TLS_TPOFF32=19, + R_ARM_COPY=20, + R_ARM_GLOB_DAT=21, + R_ARM_JUMP_SLOT=22, + R_ARM_RELATIVE=23, + R_ARM_GOTOFF32=24, + R_ARM_BASE_PREL=25, + R_ARM_GOT_BREL=26, + R_ARM_PLT32=27, + R_ARM_CALL=28, + R_ARM_JUMP24=29, + R_ARM_THM_JUMP24=30, + R_ARM_BASE_ABS=31, + R_ARM_ALU_PCREL_7_0=32, + R_ARM_ALU_PCREL_15_8=33, + R_ARM_ALU_PCREL_23_15=34, + R_ARM_LDR_SBREL_11_0_NC=35, + R_ARM_ALU_SBREL_19_12_NC=36, + R_ARM_ALU_SBREL_27_20_CK=37, + R_ARM_TARGET1=38, + R_ARM_SBREL31=39, + R_ARM_V4BX=40, + R_ARM_TARGET2=41, + R_ARM_PREL31=42, + R_ARM_MOVW_ABS_NC=43, + R_ARM_MOVT_ABS=44, + R_ARM_MOVW_PREL_NC=45, + R_ARM_MOVT_PREL=46, + R_ARM_THM_MOVW_ABS_NC=47, + R_ARM_THM_MOVT_ABS=48, + R_ARM_THM_MOVW_PREL_NC=49, + R_ARM_THM_MOVT_PREL=50, + R_ARM_THM_JUMP19=51, + R_ARM_THM_JUMP6=52, + R_ARM_THM_ALU_PREL_11_0=53, + R_ARM_THM_PC12=54, + R_ARM_ABS32_NOI=55, + R_ARM_REL32_NOI=56, + R_ARM_ALU_PC_G0_NC=57, + R_ARM_ALU_PC_G0=58, + R_ARM_ALU_PC_G1_NC=59, + R_ARM_ALU_PC_G1=60, + R_ARM_ALU_PC_G2=61, + R_ARM_LDR_PC_G1=62, + R_ARM_LDR_PC_G2=63, + R_ARM_LDRS_PC_G0=64, + R_ARM_LDRS_PC_G1=65, + R_ARM_LDRS_PC_G2=66, + R_ARM_LDC_PC_G0=67, + R_ARM_LDC_PC_G1=68, + R_ARM_LDC_PC_G2=69, + R_ARM_ALU_SB_G0_NC=70, + R_ARM_ALU_SB_G0=71, + R_ARM_ALU_SB_G1_NC=72, + R_ARM_ALU_SB_G1=73, + R_ARM_ALU_SB_G2=74, + R_ARM_LDR_SB_G0=75, + R_ARM_LDR_SB_G1=76, + R_ARM_LDR_SB_G2=77, + R_ARM_LDRS_SB_G0=78, + R_ARM_LDRS_SB_G1=79, + R_ARM_LDRS_SB_G2=80, + R_ARM_LDC_SB_G0=81, + R_ARM_LDC_SB_G1=82, + R_ARM_LDC_SB_G2=83, + R_ARM_MOVW_BREL_NC=84, + R_ARM_MOVT_BREL=85, + R_ARM_MOVW_BREL=86, + R_ARM_THM_MOVW_BREL_NC=87, + R_ARM_THM_MOVT_BREL=88, + R_ARM_THM_MOVW_BREL=89, + R_ARM_PLT32_ABS=94, + R_ARM_GOT_ABS=95, + R_ARM_GOT_PREL=96, + R_ARM_GOT_BREL12=97, + R_ARM_GOTOFF12=98, + R_ARM_GOTRELAX=99, + R_ARM_GNU_VTENTRY=100, + R_ARM_GNU_VTINHERIT=101, + R_ARM_THM_JUMP11=102, + R_ARM_THM_JUMP8=103, + R_ARM_TLS_GD32=104, + R_ARM_TLS_LDM32=105, + R_ARM_TLS_LDO32=106, + R_ARM_TLS_IE32=107, + R_ARM_TLS_LE32=108, + R_ARM_TLS_LDO12=109, + R_ARM_TLS_LE12=110, + R_ARM_TLS_IE12GP=111, + R_ARM_PRIVATE_0=112, + R_ARM_PRIVATE_1=113, + R_ARM_PRIVATE_2=114, + R_ARM_PRIVATE_3=115, + R_ARM_PRIVATE_4=116, + R_ARM_PRIVATE_5=117, + R_ARM_PRIVATE_6=118, + R_ARM_PRIVATE_7=119, + R_ARM_PRIVATE_8=120, + R_ARM_PRIVATE_9=121, + R_ARM_PRIVATE_10=122, + R_ARM_PRIVATE_11=123, + R_ARM_PRIVATE_12=124, + R_ARM_PRIVATE_13=125, + R_ARM_PRIVATE_14=126, + R_ARM_PRIVATE_15=127, + R_ARM_ME_TOO=128, + R_ARM_THM_TLS_DESCSEQ16=129, + R_ARM_THM_TLS_DESCSEQ32=130, + R_ARM_THM_GOT_BREL12=131, + R_ARM_IRELATIVE=140, +) + +ENUM_RELOC_TYPE_AARCH64 = dict( + R_AARCH64_NONE=256, + R_AARCH64_ABS64=257, + R_AARCH64_ABS32=258, + R_AARCH64_ABS16=259, + R_AARCH64_PREL64=260, + R_AARCH64_PREL32=261, + R_AARCH64_PREL16=262, + R_AARCH64_MOVW_UABS_G0=263, + R_AARCH64_MOVW_UABS_G0_NC=264, + R_AARCH64_MOVW_UABS_G1=265, + R_AARCH64_MOVW_UABS_G1_NC=266, + R_AARCH64_MOVW_UABS_G2=267, + R_AARCH64_MOVW_UABS_G2_NC=268, + R_AARCH64_MOVW_UABS_G3=269, + R_AARCH64_MOVW_SABS_G0=270, + R_AARCH64_MOVW_SABS_G1=271, + R_AARCH64_MOVW_SABS_G2=272, + R_AARCH64_LD_PREL_LO19=273, + R_AARCH64_ADR_PREL_LO21=274, + R_AARCH64_ADR_PREL_PG_HI21=275, + R_AARCH64_ADR_PREL_PG_HI21_NC=276, + R_AARCH64_ADD_ABS_LO12_NC=277, + R_AARCH64_LDST8_ABS_LO12_NC=278, + R_AARCH64_TSTBR14=279, + R_AARCH64_CONDBR19=280, + R_AARCH64_JUMP26=282, + R_AARCH64_CALL26=283, + R_AARCH64_LDST16_ABS_LO12_NC=284, + R_AARCH64_LDST32_ABS_LO12_NC=285, + R_AARCH64_LDST64_ABS_LO12_NC=286, + R_AARCH64_MOVW_PREL_G0=287, + R_AARCH64_MOVW_PREL_G0_NC=288, + R_AARCH64_MOVW_PREL_G1=289, + R_AARCH64_MOVW_PREL_G1_NC=290, + R_AARCH64_MOVW_PREL_G2=291, + R_AARCH64_MOVW_PREL_G2_NC=292, + R_AARCH64_MOVW_PREL_G3=293, + R_AARCH64_MOVW_GOTOFF_G0=300, + R_AARCH64_MOVW_GOTOFF_G0_NC=301, + R_AARCH64_MOVW_GOTOFF_G1=302, + R_AARCH64_MOVW_GOTOFF_G1_NC=303, + R_AARCH64_MOVW_GOTOFF_G2=304, + R_AARCH64_MOVW_GOTOFF_G2_NC=305, + R_AARCH64_MOVW_GOTOFF_G3=306, + R_AARCH64_GOTREL64=307, + R_AARCH64_GOTREL32=308, + R_AARCH64_GOT_LD_PREL19=309, + R_AARCH64_LD64_GOTOFF_LO15=310, + R_AARCH64_ADR_GOT_PAGE=311, + R_AARCH64_LD64_GOT_LO12_NC=312, + R_AARCH64_TLSGD_ADR_PREL21=512, + R_AARCH64_TLSGD_ADR_PAGE21=513, + R_AARCH64_TLSGD_ADD_LO12_NC=514, + R_AARCH64_TLSGD_MOVW_G1=515, + R_AARCH64_TLSGD_MOVW_G0_NC=516, + R_AARCH64_TLSLD_ADR_PREL21=517, + R_AARCH64_TLSLD_ADR_PAGE21=518, + R_AARCH64_TLSLD_ADD_LO12_NC=519, + R_AARCH64_TLSLD_MOVW_G1=520, + R_AARCH64_TLSLD_MOVW_G0_NC=521, + R_AARCH64_TLSLD_LD_PREL19=522, + R_AARCH64_TLSLD_MOVW_DTPREL_G2=523, + R_AARCH64_TLSLD_MOVW_DTPREL_G1=524, + R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC=525, + R_AARCH64_TLSLD_MOVW_DTPREL_G0=526, + R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC=527, + R_AARCH64_TLSLD_ADD_DTPREL_HI12=528, + R_AARCH64_TLSLD_ADD_DTPREL_LO12=529, + R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC=530, + R_AARCH64_TLSLD_LDST8_DTPREL_LO12=531, + R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC=532, + R_AARCH64_TLSLD_LDST16_DTPREL_LO12=533, + R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC=534, + R_AARCH64_TLSLD_LDST32_DTPREL_LO12=535, + R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC=536, + R_AARCH64_TLSLD_LDST64_DTPREL_LO12=537, + R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC=538, + R_AARCH64_TLSIE_MOVW_GOTTPREL_G1=539, + R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC=540, + R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21=541, + R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC=542, + R_AARCH64_TLSIE_LD_GOTTPREL_PREL19=543, + R_AARCH64_TLSLE_MOVW_TPREL_G2=544, + R_AARCH64_TLSLE_MOVW_TPREL_G1=545, + R_AARCH64_TLSLE_MOVW_TPREL_G1_NC=546, + R_AARCH64_TLSLE_MOVW_TPREL_G0=547, + R_AARCH64_TLSLE_MOVW_TPREL_G0_NC=548, + R_AARCH64_TLSLE_ADD_TPREL_HI12=549, + R_AARCH64_TLSLE_ADD_TPREL_LO12=550, + R_AARCH64_TLSLE_ADD_TPREL_LO12_NC=551, + R_AARCH64_TLSLE_LDST8_TPREL_LO12=552, + R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC=553, + R_AARCH64_TLSLE_LDST16_TPREL_LO12=554, + R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC=555, + R_AARCH64_TLSLE_LDST32_TPREL_LO12=556, + R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC=557, + R_AARCH64_TLSLE_LDST64_TPREL_LO12=558, + R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC=559, + R_AARCH64_COPY=1024, + R_AARCH64_GLOB_DAT=1025, + R_AARCH64_JUMP_SLOT=1026, + R_AARCH64_RELATIVE=1027, + R_AARCH64_TLS_DTPREL64=1028, + R_AARCH64_TLS_DTPMOD64=1029, + R_AARCH64_TLS_TPREL64=1030, + R_AARCH64_TLS_DTPREL32=1031, + R_AARCH64_TLS_DTPMOD32=1032, + R_AARCH64_TLS_TPREL32=1033, +)
diff --git a/elftools/elf/gnuversions.py b/elftools/elf/gnuversions.py new file mode 100644 index 0000000..4a4473f --- /dev/null +++ b/elftools/elf/gnuversions.py
@@ -0,0 +1,228 @@ +#------------------------------------------------------------------------------ +# elftools: elf/gnuversions.py +# +# ELF sections +# +# Yann Rouillard (yann@pleiades.fr.eu.org) +# This code is in the public domain +#------------------------------------------------------------------------------ +from ..construct import CString +from ..common.utils import struct_parse, elf_assert +from .sections import Section, Symbol + + +class Version(object): + """ Version object - representing a version definition or dependency + entry from a "Version Needed" or a "Version Dependency" table section. + + This kind of entry contains a pointer to an array of auxiliary entries + that store the information about version names or dependencies. + These entries are not stored in this object and should be accessed + through the appropriate method of a section object which will return + an iterator of VersionAuxiliary objects. + + Similarly to Section objects, allows dictionary-like access to + verdef/verneed entry + """ + def __init__(self, entry, name=None): + self.entry = entry + self.name = name + + def __getitem__(self, name): + """ Implement dict-like access to entry + """ + return self.entry[name] + + +class VersionAuxiliary(object): + """ Version Auxiliary object - representing an auxiliary entry of a version + definition or dependency entry + + Similarly to Section objects, allows dictionary-like access to the + verdaux/vernaux entry + """ + def __init__(self, entry, name): + self.entry = entry + self.name = name + + def __getitem__(self, name): + """ Implement dict-like access to entries + """ + return self.entry[name] + + +class GNUVersionSection(Section): + """ Common ancestor class for ELF SUNW|GNU Version Needed/Dependency + sections class which contains shareable code + """ + + def __init__(self, header, name, stream, elffile, stringtable, + field_prefix, version_struct, version_auxiliaries_struct): + super(GNUVersionSection, self).__init__(header, name, stream) + self.elffile = elffile + self.stringtable = stringtable + self.field_prefix = field_prefix + self.version_struct = version_struct + self.version_auxiliaries_struct = version_auxiliaries_struct + + def num_versions(self): + """ Number of version entries in the section + """ + return self['sh_info'] + + def _field_name(self, name, auxiliary=False): + """ Return the real field's name of version or a version auxiliary + entry + """ + middle = 'a_' if auxiliary else '_' + return self.field_prefix + middle + name + + def _iter_version_auxiliaries(self, entry_offset, count): + """ Yield all auxiliary entries of a version entry + """ + name_field = self._field_name('name', auxiliary=True) + next_field = self._field_name('next', auxiliary=True) + + for _ in range(count): + entry = struct_parse( + self.version_auxiliaries_struct, + self.stream, + stream_pos=entry_offset) + + name = self.stringtable.get_string(entry[name_field]) + version_aux = VersionAuxiliary(entry, name) + yield version_aux + + entry_offset += entry[next_field] + + def iter_versions(self): + """ Yield all the version entries in the section + Each time it returns the main version structure + and an iterator to walk through its auxiliaries entries + """ + aux_field = self._field_name('aux') + count_field = self._field_name('cnt') + next_field = self._field_name('next') + + entry_offset = self['sh_offset'] + for _ in range(self.num_versions()): + entry = struct_parse( + self.version_struct, + self.stream, + stream_pos=entry_offset) + + elf_assert(entry[count_field] > 0, + 'Expected number of version auxiliary entries (%s) to be > 0' + 'for the following version entry: %s' % ( + count_field, str(entry))) + + version = Version(entry) + aux_entries_offset = entry_offset + entry[aux_field] + version_auxiliaries_iter = self._iter_version_auxiliaries( + aux_entries_offset, entry[count_field]) + + yield version, version_auxiliaries_iter + + entry_offset += entry[next_field] + + +class GNUVerNeedSection(GNUVersionSection): + """ ELF SUNW or GNU Version Needed table section. + Has an associated StringTableSection that's passed in the constructor. + """ + def __init__(self, header, name, stream, elffile, stringtable): + super(GNUVerNeedSection, self).__init__( + header, name, stream, elffile, stringtable, 'vn', + elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux) + self._has_indexes = None + + def has_indexes(self): + """ Return True if at least one version definition entry has an index + that is stored in the vna_other field. + This information is used for symbol versioning + """ + if self._has_indexes is None: + self._has_indexes = False + for _, vernaux_iter in self.iter_versions(): + for vernaux in vernaux_iter: + if vernaux['vna_other']: + self._has_indexes = True + break + + return self._has_indexes + + def iter_versions(self): + for verneed, vernaux in super(GNUVerNeedSection, self).iter_versions(): + verneed.name = self.stringtable.get_string(verneed['vn_file']) + yield verneed, vernaux + + def get_version(self, index): + """ Get the version information located at index #n in the table + Return boths the verneed structure and the vernaux structure + that contains the name of the version + """ + for verneed, vernaux_iter in self.iter_versions(): + for vernaux in vernaux_iter: + if vernaux['vna_other'] == index: + return verneed, vernaux + + return None + + +class GNUVerDefSection(GNUVersionSection): + """ ELF SUNW or GNU Version Definition table section. + Has an associated StringTableSection that's passed in the constructor. + """ + def __init__(self, header, name, stream, elffile, stringtable): + super(GNUVerDefSection, self).__init__( + header, name, stream, elffile, stringtable, 'vd', + elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux) + + def get_version(self, index): + """ Get the version information located at index #n in the table + Return boths the verdef structure and an iterator to retrieve + both the version names and dependencies in the form of + verdaux entries + """ + for verdef, verdaux_iter in self.iter_versions(): + if verdef['vd_ndx'] == index: + return verdef, verdaux_iter + + return None + + +class GNUVerSymSection(Section): + """ ELF SUNW or GNU Versym table section. + Has an associated SymbolTableSection that's passed in the constructor. + """ + def __init__(self, header, name, stream, elffile, symboltable): + super(GNUVerSymSection, self).__init__(header, name, stream) + self.elffile = elffile + self.elfstructs = self.elffile.structs + self.symboltable = symboltable + + def num_symbols(self): + """ Number of symbols in the table + """ + return self['sh_size'] // self['sh_entsize'] + + def get_symbol(self, n): + """ Get the symbol at index #n from the table (Symbol object) + It begins at 1 and not 0 since the first entry is used to + store the current version of the syminfo table + """ + # Grab the symbol's entry from the stream + entry_offset = self['sh_offset'] + n * self['sh_entsize'] + entry = struct_parse( + self.elfstructs.Elf_Versym, + self.stream, + stream_pos=entry_offset) + # Find the symbol name in the associated symbol table + name = self.symboltable.get_symbol(n).name + return Symbol(entry, name) + + def iter_symbols(self): + """ Yield all the symbols in the table + """ + for i in range(self.num_symbols()): + yield self.get_symbol(i)
diff --git a/elftools/elf/relocation.py b/elftools/elf/relocation.py index 7c2b74c..176f7c5 100644 --- a/elftools/elf/relocation.py +++ b/elftools/elf/relocation.py
@@ -178,6 +178,11 @@ addend=reloc['r_addend'] if recipe.has_addend else 0) # 3. Write the relocated value back into the stream stream.seek(reloc['r_offset']) + + # Make sure the relocated value fits back by wrapping it around. This + # looks like a problem, but it seems to be the way this is done in + # binutils too. + relocated_value = relocated_value % (2 ** (recipe.bytesize * 8)) value_struct.build_stream(relocated_value, stream) # Relocations are represented by "recipes". Each recipe specifies: @@ -202,6 +207,9 @@ def _reloc_calc_sym_plus_addend(value, sym_value, offset, addend=0): return sym_value + addend + def _reloc_calc_sym_plus_addend_pcrel(value, sym_value, offset, addend=0): + return sym_value + addend - offset + _RELOCATION_RECIPES_X86 = { ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=False, calc_func=_reloc_calc_identity), @@ -218,6 +226,9 @@ bytesize=8, has_addend=True, calc_func=_reloc_calc_identity), ENUM_RELOC_TYPE_x64['R_X86_64_64']: _RELOCATION_RECIPE_TYPE( bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend), + ENUM_RELOC_TYPE_x64['R_X86_64_PC32']: _RELOCATION_RECIPE_TYPE( + bytesize=8, has_addend=True, + calc_func=_reloc_calc_sym_plus_addend_pcrel), ENUM_RELOC_TYPE_x64['R_X86_64_32']: _RELOCATION_RECIPE_TYPE( bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend), ENUM_RELOC_TYPE_x64['R_X86_64_32S']: _RELOCATION_RECIPE_TYPE(
diff --git a/elftools/elf/sections.py b/elftools/elf/sections.py index 518c857..1380d6b 100644 --- a/elftools/elf/sections.py +++ b/elftools/elf/sections.py
@@ -6,14 +6,13 @@ # Eli Bendersky (eliben@gmail.com) # This code is in the public domain #------------------------------------------------------------------------------- -from ..construct import CString from ..common.utils import struct_parse, elf_assert, parse_cstring_from_stream class Section(object): """ Base class for ELF sections. Also used for all sections types that have no special functionality. - + Allows dictionary-like access to the section header. For example: > sec = Section(...) > sec['sh_type'] # section type @@ -22,7 +21,7 @@ self.header = header self.name = name self.stream = stream - + def data(self): """ The section data from the file. """ @@ -33,7 +32,7 @@ """ Is this a null section? """ return False - + def __getitem__(self, name): """ Implement dict-like access to header entries """ @@ -41,6 +40,8 @@ def __eq__(self, other): return self.header == other.header + def __hash__(self): + return hash(self.header) class NullSection(Section): @@ -51,14 +52,14 @@ def is_null(self): return True - + class StringTableSection(Section): """ ELF string table section. """ def __init__(self, header, name, stream): super(StringTableSection, self).__init__(header, name, stream) - + def get_string(self, offset): """ Get the string stored at the given offset in this string table. """ @@ -77,15 +78,15 @@ self.elfstructs = self.elffile.structs self.stringtable = stringtable elf_assert(self['sh_entsize'] > 0, - 'Expected entry size of section %s to be > 0' % name) + 'Expected entry size of section %r to be > 0' % name) elf_assert(self['sh_size'] % self['sh_entsize'] == 0, - 'Expected section size to be a multiple of entry size in section %s' % name) + 'Expected section size to be a multiple of entry size in section %r' % name) def num_symbols(self): """ Number of symbols in the table """ return self['sh_size'] // self['sh_entsize'] - + def get_symbol(self, n): """ Get the symbol at index #n from the table (Symbol object) """ @@ -123,3 +124,38 @@ return self.entry[name] +class SUNWSyminfoTableSection(Section): + """ ELF .SUNW Syminfo table section. + Has an associated SymbolTableSection that's passed in the constructor. + """ + def __init__(self, header, name, stream, elffile, symboltable): + super(SUNWSyminfoTableSection, self).__init__(header, name, stream) + self.elffile = elffile + self.elfstructs = self.elffile.structs + self.symboltable = symboltable + + def num_symbols(self): + """ Number of symbols in the table + """ + return self['sh_size'] // self['sh_entsize'] - 1 + + def get_symbol(self, n): + """ Get the symbol at index #n from the table (Symbol object). + It begins at 1 and not 0 since the first entry is used to + store the current version of the syminfo table. + """ + # Grab the symbol's entry from the stream + entry_offset = self['sh_offset'] + n * self['sh_entsize'] + entry = struct_parse( + self.elfstructs.Elf_Sunw_Syminfo, + self.stream, + stream_pos=entry_offset) + # Find the symbol name in the associated symbol table + name = self.symboltable.get_symbol(n).name + return Symbol(entry, name) + + def iter_symbols(self): + """ Yield all the symbols in the table + """ + for i in range(1, self.num_symbols() + 1): + yield self.get_symbol(i)
diff --git a/elftools/elf/structs.py b/elftools/elf/structs.py index 08567de..0862400 100644 --- a/elftools/elf/structs.py +++ b/elftools/elf/structs.py
@@ -19,20 +19,20 @@ class ELFStructs(object): """ Accessible attributes: - + Elf_{byte|half|word|word64|addr|offset|sword|xword|xsword}: - Data chunks, as specified by the ELF standard, adjusted for + Data chunks, as specified by the ELF standard, adjusted for correct endianness and word-size. Elf_Ehdr: ELF file header - + Elf_Phdr: Program header - + Elf_Shdr: Section header - + Elf_Sym: Symbol table entry @@ -42,9 +42,9 @@ def __init__(self, little_endian=True, elfclass=32): assert elfclass == 32 or elfclass == 64 self.little_endian = little_endian - self.elfclass = elfclass + self.elfclass = elfclass self._create_structs() - + def _create_structs(self): if self.little_endian: self.Elf_byte = ULInt8 @@ -66,14 +66,18 @@ self.Elf_sword = SBInt32 self.Elf_xword = UBInt32 if self.elfclass == 32 else UBInt64 self.Elf_sxword = SBInt32 if self.elfclass == 32 else SBInt64 - + self._create_ehdr() self._create_phdr() self._create_shdr() self._create_sym() self._create_rel() self._create_dyn() - + self._create_sunw_syminfo() + self._create_gnu_verneed() + self._create_gnu_verdef() + self._create_gnu_versym() + def _create_ehdr(self): self.Elf_Ehdr = Struct('Elf_Ehdr', Struct('e_ident', @@ -99,7 +103,7 @@ self.Elf_half('e_shnum'), self.Elf_half('e_shstrndx'), ) - + def _create_phdr(self): if self.elfclass == 32: self.Elf_Phdr = Struct('Elf_Phdr', @@ -122,8 +126,8 @@ self.Elf_xword('p_filesz'), self.Elf_xword('p_memsz'), self.Elf_xword('p_align'), - ) - + ) + def _create_shdr(self): self.Elf_Shdr = Struct('Elf_Shdr', self.Elf_word('sh_name'), @@ -137,7 +141,7 @@ self.Elf_xword('sh_addralign'), self.Elf_xword('sh_entsize'), ) - + def _create_rel(self): # r_info is also taken apart into r_info_sym and r_info_type. # This is done in Value to avoid endianity issues while parsing. @@ -203,5 +207,50 @@ self.Elf_xword('st_size'), ) + def _create_sunw_syminfo(self): + self.Elf_Sunw_Syminfo = Struct('Elf_Sunw_Syminfo', + Enum(self.Elf_half('si_boundto'), **ENUM_SUNW_SYMINFO_BOUNDTO), + self.Elf_half('si_flags'), + ) + def _create_gnu_verneed(self): + # Structure of "version needed" entries is documented in + # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format + self.Elf_Verneed = Struct('Elf_Verneed', + self.Elf_half('vn_version'), + self.Elf_half('vn_cnt'), + self.Elf_word('vn_file'), + self.Elf_word('vn_aux'), + self.Elf_word('vn_next'), + ) + self.Elf_Vernaux = Struct('Elf_Vernaux', + self.Elf_word('vna_hash'), + self.Elf_half('vna_flags'), + self.Elf_half('vna_other'), + self.Elf_word('vna_name'), + self.Elf_word('vna_next'), + ) + def _create_gnu_verdef(self): + # Structure off "version definition" entries are documented in + # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format + self.Elf_Verdef = Struct('Elf_Verdef', + self.Elf_half('vd_version'), + self.Elf_half('vd_flags'), + self.Elf_half('vd_ndx'), + self.Elf_half('vd_cnt'), + self.Elf_word('vd_hash'), + self.Elf_word('vd_aux'), + self.Elf_word('vd_next'), + ) + self.Elf_Verdaux = Struct('Elf_Verdaux', + self.Elf_word('vda_name'), + self.Elf_word('vda_next'), + ) + + def _create_gnu_versym(self): + # Structure off "version symbol" entries are documented in + # Oracle "Linker and Libraries Guide", Chapter 7 Object File Format + self.Elf_Versym = Struct('Elf_Versym', + Enum(self.Elf_half('ndx'), **ENUM_VERSYM), + )
diff --git a/examples/dwarf_die_tree.py b/examples/dwarf_die_tree.py index 9dcb6b6..ef108c3 100644 --- a/examples/dwarf_die_tree.py +++ b/examples/dwarf_die_tree.py
@@ -14,7 +14,6 @@ # examples/ dir of the source distribution. sys.path[0:0] = ['.', '..'] -from elftools.common.py3compat import bytes2str from elftools.elf.elffile import ELFFile @@ -44,15 +43,8 @@ top_DIE = CU.get_top_DIE() print(' Top DIE with tag=%s' % top_DIE.tag) - # Each DIE holds an OrderedDict of attributes, mapping names to - # values. Values are represented by AttributeValue objects in - # elftools/dwarf/die.py - # We're interested in the DW_AT_name attribute. Note that its value - # is usually a string taken from the .debug_string section. This - # is done transparently by the library, and such a value will be - # simply given as a string. - name_attr = top_DIE.attributes['DW_AT_name'] - print(' name=%s' % bytes2str(name_attr.value)) + # We're interested in the filename... + print(' name=%s' % top_DIE.get_full_path()) # Display DIEs recursively starting with top_DIE die_info_rec(top_DIE)
diff --git a/examples/dwarf_range_lists.py b/examples/dwarf_range_lists.py index fced6a6..6e8998d 100644 --- a/examples/dwarf_range_lists.py +++ b/examples/dwarf_range_lists.py
@@ -37,6 +37,9 @@ # The range lists are extracted by DWARFInfo from the .debug_ranges # section, and returned here as a RangeLists object. range_lists = dwarfinfo.range_lists() + if range_lists is None: + print(' file has no .debug_ranges section') + return for CU in dwarfinfo.iter_CUs(): # DWARFInfo allows to iterate over the compile units contained in
diff --git a/examples/examine_dwarf_info.py b/examples/examine_dwarf_info.py index 1aa28c6..0e1619e 100644 --- a/examples/examine_dwarf_info.py +++ b/examples/examine_dwarf_info.py
@@ -13,7 +13,6 @@ # examples/ dir of the source distribution. sys.path[0:0] = ['.', '..'] -from elftools.common.py3compat import bytes2str from elftools.elf.elffile import ELFFile @@ -43,15 +42,8 @@ top_DIE = CU.get_top_DIE() print(' Top DIE with tag=%s' % top_DIE.tag) - # Each DIE holds an OrderedDict of attributes, mapping names to - # values. Values are represented by AttributeValue objects in - # elftools/dwarf/die.py - # We're interested in the DW_AT_name attribute. Note that its value - # is usually a string taken from the .debug_str section. This - # is done transparently by the library, and such a value will be - # simply given as a string. - name_attr = top_DIE.attributes['DW_AT_name'] - print(' name=%s' % bytes2str(name_attr.value)) + # We're interested in the filename... + print(' name=%s' % top_DIE.get_full_path()) if __name__ == '__main__': for filename in sys.argv[1:]:
diff --git a/examples/reference_output/dwarf_die_tree.out b/examples/reference_output/dwarf_die_tree.out index 143cbbb..4a81a8e 100644 --- a/examples/reference_output/dwarf_die_tree.out +++ b/examples/reference_output/dwarf_die_tree.out
@@ -1,11 +1,11 @@ Processing file: ./examples/sample_exe64.elf Found a compile unit at offset 0, length 115 Top DIE with tag=DW_TAG_compile_unit - name=../sysdeps/x86_64/elf/start.S + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/../sysdeps/x86_64/elf/start.S DIE tag=DW_TAG_compile_unit Found a compile unit at offset 119, length 135 Top DIE with tag=DW_TAG_compile_unit - name=init.c + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/init.c DIE tag=DW_TAG_compile_unit DIE tag=DW_TAG_base_type DIE tag=DW_TAG_base_type @@ -21,7 +21,7 @@ DIE tag=DW_TAG_const_type Found a compile unit at offset 258, length 156 Top DIE with tag=DW_TAG_compile_unit - name=z.c + name=/tmp/ebenders/z.c DIE tag=DW_TAG_compile_unit DIE tag=DW_TAG_subprogram DIE tag=DW_TAG_formal_parameter @@ -33,7 +33,7 @@ DIE tag=DW_TAG_variable Found a compile unit at offset 418, length 300 Top DIE with tag=DW_TAG_compile_unit - name=elf-init.c + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/elf-init.c DIE tag=DW_TAG_compile_unit DIE tag=DW_TAG_base_type DIE tag=DW_TAG_typedef
diff --git a/examples/reference_output/examine_dwarf_info.out b/examples/reference_output/examine_dwarf_info.out index 968be29..5114626 100644 --- a/examples/reference_output/examine_dwarf_info.out +++ b/examples/reference_output/examine_dwarf_info.out
@@ -1,13 +1,13 @@ Processing file: ./examples/sample_exe64.elf Found a compile unit at offset 0, length 115 Top DIE with tag=DW_TAG_compile_unit - name=../sysdeps/x86_64/elf/start.S + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/../sysdeps/x86_64/elf/start.S Found a compile unit at offset 119, length 135 Top DIE with tag=DW_TAG_compile_unit - name=init.c + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/init.c Found a compile unit at offset 258, length 156 Top DIE with tag=DW_TAG_compile_unit - name=z.c + name=/tmp/ebenders/z.c Found a compile unit at offset 418, length 300 Top DIE with tag=DW_TAG_compile_unit - name=elf-init.c + name=/usr/src/packages/BUILD/glibc-2.11.1/csu/elf-init.c
diff --git a/scripts/readelf.py b/scripts/readelf.py index 720106d..8179c01 100755 --- a/scripts/readelf.py +++ b/scripts/readelf.py
@@ -25,6 +25,10 @@ from elftools.elf.enums import ENUM_D_TAG from elftools.elf.segments import InterpSegment from elftools.elf.sections import SymbolTableSection +from elftools.elf.gnuversions import ( + GNUVerSymSection, GNUVerDefSection, + GNUVerNeedSection, + ) from elftools.elf.relocation import RelocationSection from elftools.elf.descriptions import ( describe_ei_class, describe_ei_data, describe_ei_version, @@ -33,7 +37,9 @@ describe_sh_type, describe_sh_flags, describe_symbol_type, describe_symbol_bind, describe_symbol_visibility, describe_symbol_shndx, describe_reloc_type, describe_dyn_tag, + describe_ver_flags, ) +from elftools.elf.constants import E_FLAGS from elftools.dwarf.dwarfinfo import DWARFInfo from elftools.dwarf.descriptions import ( describe_reg_name, describe_attr_value, set_global_machine_arch, @@ -61,6 +67,8 @@ # Lazily initialized if a debug dump is requested self._dwarfinfo = None + self._versioninfo = None + def display_file_header(self): """ Display the ELF file header """ @@ -94,8 +102,9 @@ self._emit(' Start of section headers: %s' % header['e_shoff']) self._emitline(' (bytes into file)') - self._emitline(' Flags: %s' % - self._format_hex(header['e_flags'])) + self._emitline(' Flags: %s%s' % + (self._format_hex(header['e_flags']), + self.decode_flags(header['e_flags']))) self._emitline(' Size of this header: %s (bytes)' % header['e_ehsize']) self._emitline(' Size of program headers: %s (bytes)' % @@ -109,6 +118,17 @@ self._emitline(' Section header string table index: %s' % header['e_shstrndx']) + def decode_flags(self, flags): + description = "" + if self.elffile['e_machine'] == "EM_ARM": + if flags & E_FLAGS.EF_ARM_HASENTRY: + description += ", has entry point" + + version = flags & E_FLAGS.EF_ARM_EABIMASK + if version == E_FLAGS.EF_ARM_EABI_VER5: + description += ", Version5 EABI" + return description + def display_program_headers(self, show_heading=True): """ Display the ELF program headers. If show_heading is True, displays the heading for this information @@ -254,6 +274,8 @@ def display_symbol_tables(self): """ Display the symbol tables contained in the file """ + self._init_versioninfo() + for section in self.elffile.iter_sections(): if not isinstance(section, SymbolTableSection): continue @@ -272,24 +294,47 @@ self._emitline(' Num: Value Size Type Bind Vis Ndx Name') for nsym, symbol in enumerate(section.iter_symbols()): + + version_info = '' + # readelf doesn't display version info for Solaris versioning + if (section['sh_type'] == 'SHT_DYNSYM' and + self._versioninfo['type'] == 'GNU'): + version = self._symbol_version(nsym) + if (version['name'] != bytes2str(symbol.name) and + version['index'] not in ('VER_NDX_LOCAL', + 'VER_NDX_GLOBAL')): + if version['filename']: + # external symbol + version_info = '@%(name)s (%(index)i)' % version + else: + # internal symbol + if version['hidden']: + version_info = '@%(name)s' % version + else: + version_info = '@@%(name)s' % version + # symbol names are truncated to 25 chars, similarly to readelf - self._emitline('%6d: %s %5d %-7s %-6s %-7s %4s %.25s' % ( + self._emitline('%6d: %s %5d %-7s %-6s %-7s %4s %.25s%s' % ( nsym, - self._format_hex(symbol['st_value'], fullhex=True, lead0x=False), + self._format_hex( + symbol['st_value'], fullhex=True, lead0x=False), symbol['st_size'], describe_symbol_type(symbol['st_info']['type']), describe_symbol_bind(symbol['st_info']['bind']), describe_symbol_visibility(symbol['st_other']['visibility']), describe_symbol_shndx(symbol['st_shndx']), - bytes2str(symbol.name))) + bytes2str(symbol.name), + version_info)) def display_dynamic_tags(self): """ Display the dynamic tags contained in the file """ + has_dynamic_sections = False for section in self.elffile.iter_sections(): if not isinstance(section, DynamicSection): continue + has_dynamic_sections = True self._emitline("\nDynamic section at offset %s contains %s entries:" % ( self._format_hex(section['sh_offset']), section.num_tags())) @@ -305,11 +350,9 @@ parsed = 'Library runpath: [%s]' % bytes2str(tag.runpath) elif tag.entry.d_tag == 'DT_SONAME': parsed = 'Library soname: [%s]' % bytes2str(tag.soname) - elif (tag.entry.d_tag.endswith('SZ') or - tag.entry.d_tag.endswith('ENT')): + elif tag.entry.d_tag.endswith(('SZ', 'ENT')): parsed = '%i (bytes)' % tag['d_val'] - elif (tag.entry.d_tag.endswith('NUM') or - tag.entry.d_tag.endswith('COUNT')): + elif tag.entry.d_tag.endswith(('NUM', 'COUNT')): parsed = '%i' % tag['d_val'] elif tag.entry.d_tag == 'DT_PLTREL': s = describe_dyn_tag(tag.entry.d_val) @@ -325,6 +368,10 @@ padding, '(%s)' % (tag.entry.d_tag[3:],), parsed)) + if not has_dynamic_sections: + # readelf only prints this if there is at least one segment + if self.elffile.num_segments(): + self._emitline("\nThere is no dynamic section in this file.") def display_relocations(self): """ Display the relocations contained in the file @@ -384,6 +431,111 @@ if not has_relocation_sections: self._emitline('\nThere are no relocations in this file.') + def display_version_info(self): + """ Display the version info contained in the file + """ + self._init_versioninfo() + + if not self._versioninfo['type']: + self._emitline("\nNo version information found in this file.") + return + + for section in self.elffile.iter_sections(): + if isinstance(section, GNUVerSymSection): + self._print_version_section_header( + section, 'Version symbols', lead0x=False) + + num_symbols = section.num_symbols() + + # Symbol version info are printed four by four entries + for idx_by_4 in range(0, num_symbols, 4): + + self._emit(' %03x:' % idx_by_4) + + for idx in range(idx_by_4, min(idx_by_4 + 4, num_symbols)): + + symbol_version = self._symbol_version(idx) + if symbol_version['index'] == 'VER_NDX_LOCAL': + version_index = 0 + version_name = '(*local*)' + elif symbol_version['index'] == 'VER_NDX_GLOBAL': + version_index = 1 + version_name = '(*global*)' + else: + version_index = symbol_version['index'] + version_name = '(%(name)s)' % symbol_version + + visibility = 'h' if symbol_version['hidden'] else ' ' + + self._emit('%4x%s%-13s' % ( + version_index, visibility, version_name)) + + self._emitline() + + elif isinstance(section, GNUVerDefSection): + self._print_version_section_header( + section, 'Version definition', indent=2) + + offset = 0 + for verdef, verdaux_iter in section.iter_versions(): + verdaux = next(verdaux_iter) + + name = verdaux.name + if verdef['vd_flags']: + flags = describe_ver_flags(verdef['vd_flags']) + # Mimic exactly the readelf output + flags += ' ' + else: + flags = 'none' + + self._emitline(' %s: Rev: %i Flags: %s Index: %i' + ' Cnt: %i Name: %s' % ( + self._format_hex(offset, fieldsize=6, + alternate=True), + verdef['vd_version'], flags, verdef['vd_ndx'], + verdef['vd_cnt'], bytes2str(name))) + + verdaux_offset = ( + offset + verdef['vd_aux'] + verdaux['vda_next']) + for idx, verdaux in enumerate(verdaux_iter, start=1): + self._emitline(' %s: Parent %i: %s' % + (self._format_hex(verdaux_offset, fieldsize=4), + idx, bytes2str(verdaux.name))) + verdaux_offset += verdaux['vda_next'] + + offset += verdef['vd_next'] + + elif isinstance(section, GNUVerNeedSection): + self._print_version_section_header(section, 'Version needs') + + offset = 0 + for verneed, verneed_iter in section.iter_versions(): + + self._emitline(' %s: Version: %i File: %s Cnt: %i' % ( + self._format_hex(offset, fieldsize=6, + alternate=True), + verneed['vn_version'], bytes2str(verneed.name), + verneed['vn_cnt'])) + + vernaux_offset = offset + verneed['vn_aux'] + for idx, vernaux in enumerate(verneed_iter, start=1): + if vernaux['vna_flags']: + flags = describe_ver_flags(vernaux['vna_flags']) + # Mimic exactly the readelf output + flags += ' ' + else: + flags = 'none' + + self._emitline( + ' %s: Name: %s Flags: %s Version: %i' % ( + self._format_hex(vernaux_offset, fieldsize=4), + bytes2str(vernaux.name), flags, + vernaux['vna_other'])) + + vernaux_offset += vernaux['vna_next'] + + offset += verneed['vn_next'] + def display_hex_dump(self, section_spec): """ Display a hex dump of a section. section_spec is either a section number or a name. @@ -486,7 +638,8 @@ else: self._emitline('debug dump not yet supported for "%s"' % dump_what) - def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True): + def _format_hex(self, addr, fieldsize=None, fullhex=False, lead0x=True, + alternate=False): """ Format an address into a hexadecimal string. fieldsize: @@ -501,7 +654,20 @@ lead0x: If True, leading 0x is added + + alternate: + If True, override lead0x to emulate the alternate + hexadecimal form specified in format string with the # + character: only non-zero values are prefixed with 0x. + This form is used by readelf. """ + if alternate: + if addr == 0: + lead0x = False + else: + lead0x = True + fieldsize -= 2 + s = '0x' if lead0x else '' if fullhex: fieldsize = 8 if self.elffile.elfclass == 32 else 16 @@ -511,6 +677,97 @@ field = '%' + '0%sx' % fieldsize return s + field % addr + def _print_version_section_header(self, version_section, name, lead0x=True, + indent=1): + """ Print a section header of one version related section (versym, + verneed or verdef) with some options to accomodate readelf + little differences between each header (e.g. indentation + and 0x prefixing). + """ + if hasattr(version_section, 'num_versions'): + num_entries = version_section.num_versions() + else: + num_entries = version_section.num_symbols() + + self._emitline("\n%s section '%s' contains %s entries:" % + (name, bytes2str(version_section.name), num_entries)) + self._emitline('%sAddr: %s Offset: %s Link: %i (%s)' % ( + ' ' * indent, + self._format_hex( + version_section['sh_addr'], fieldsize=16, lead0x=lead0x), + self._format_hex( + version_section['sh_offset'], fieldsize=6, lead0x=True), + version_section['sh_link'], + bytes2str( + self.elffile.get_section(version_section['sh_link']).name) + ) + ) + + def _init_versioninfo(self): + """ Search and initialize informations about version related sections + and the kind of versioning used (GNU or Solaris). + """ + if self._versioninfo is not None: + return + + self._versioninfo = {'versym': None, 'verdef': None, + 'verneed': None, 'type': None} + + for section in self.elffile.iter_sections(): + if isinstance(section, GNUVerSymSection): + self._versioninfo['versym'] = section + elif isinstance(section, GNUVerDefSection): + self._versioninfo['verdef'] = section + elif isinstance(section, GNUVerNeedSection): + self._versioninfo['verneed'] = section + elif isinstance(section, DynamicSection): + for tag in section.iter_tags(): + if tag['d_tag'] == 'DT_VERSYM': + self._versioninfo['type'] = 'GNU' + break + + if not self._versioninfo['type'] and ( + self._versioninfo['verneed'] or self._versioninfo['verdef']): + self._versioninfo['type'] = 'Solaris' + + def _symbol_version(self, nsym): + """ Return a dict containing information on the + or None if no version information is available + """ + self._init_versioninfo() + + symbol_version = dict.fromkeys(('index', 'name', 'filename', 'hidden')) + + if (not self._versioninfo['versym'] or + nsym >= self._versioninfo['versym'].num_symbols()): + return None + + symbol = self._versioninfo['versym'].get_symbol(nsym) + index = symbol.entry['ndx'] + if not index in ('VER_NDX_LOCAL', 'VER_NDX_GLOBAL'): + index = int(index) + + if self._versioninfo['type'] == 'GNU': + # In GNU versioning mode, the highest bit is used to + # store wether the symbol is hidden or not + if index & 0x8000: + index &= ~0x8000 + symbol_version['hidden'] = True + + if (self._versioninfo['verdef'] and + index <= self._versioninfo['verdef'].num_versions()): + _, verdaux_iter = \ + self._versioninfo['verdef'].get_version(index) + symbol_version['name'] = bytes2str(next(verdaux_iter).name) + else: + verneed, vernaux = \ + self._versioninfo['verneed'].get_version(index) + symbol_version['name'] = bytes2str(vernaux.name) + symbol_version['filename'] = bytes2str(verneed.name) + + symbol_version['index'] = index + return symbol_version + def _section_from_spec(self, spec): """ Retrieve a section given a "spec" (either number or name). Return None if no such section exists in the file. @@ -664,8 +921,10 @@ for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): - self._emitline('\n%08x %08x %08x CIE' % ( - entry.offset, entry['length'], entry['CIE_id'])) + self._emitline('\n%08x %s %s CIE' % ( + entry.offset, + self._format_hex(entry['length'], fullhex=True, lead0x=False), + self._format_hex(entry['CIE_id'], fullhex=True, lead0x=False))) self._emitline(' Version: %d' % entry['version']) self._emitline(' Augmentation: "%s"' % bytes2str(entry['augmentation'])) self._emitline(' Code alignment factor: %u' % entry['code_alignment_factor']) @@ -673,13 +932,15 @@ self._emitline(' Return address column: %d' % entry['return_address_register']) self._emitline() else: # FDE - self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( + self._emitline('\n%08x %s %s FDE cie=%08x pc=%s..%s' % ( entry.offset, - entry['length'], - entry['CIE_pointer'], + self._format_hex(entry['length'], fullhex=True, lead0x=False), + self._format_hex(entry['CIE_pointer'], fullhex=True, lead0x=False), entry.cie.offset, - entry['initial_location'], - entry['initial_location'] + entry['address_range'])) + self._format_hex(entry['initial_location'], fullhex=True, lead0x=False), + self._format_hex( + entry['initial_location'] + entry['address_range'], + fullhex=True, lead0x=False))) self._emit(describe_CFI_instructions(entry)) self._emitline() @@ -694,23 +955,24 @@ for entry in self._dwarfinfo.CFI_entries(): if isinstance(entry, CIE): - self._emitline('\n%08x %08x %08x CIE "%s" cf=%d df=%d ra=%d' % ( + self._emitline('\n%08x %s %s CIE "%s" cf=%d df=%d ra=%d' % ( entry.offset, - entry['length'], - entry['CIE_id'], + self._format_hex(entry['length'], fullhex=True, lead0x=False), + self._format_hex(entry['CIE_id'], fullhex=True, lead0x=False), bytes2str(entry['augmentation']), entry['code_alignment_factor'], entry['data_alignment_factor'], entry['return_address_register'])) ra_regnum = entry['return_address_register'] else: # FDE - self._emitline('\n%08x %08x %08x FDE cie=%08x pc=%08x..%08x' % ( + self._emitline('\n%08x %s %s FDE cie=%08x pc=%s..%s' % ( entry.offset, - entry['length'], - entry['CIE_pointer'], + self._format_hex(entry['length'], fullhex=True, lead0x=False), + self._format_hex(entry['CIE_pointer'], fullhex=True, lead0x=False), entry.cie.offset, - entry['initial_location'], - entry['initial_location'] + entry['address_range'])) + self._format_hex(entry['initial_location'], fullhex=True, lead0x=False), + self._format_hex(entry['initial_location'] + entry['address_range'], + fullhex=True, lead0x=False))) ra_regnum = entry.cie['return_address_register'] # Print the heading row for the decoded table @@ -802,6 +1064,9 @@ optparser.add_option('-p', '--string-dump', action='store', dest='show_string_dump', metavar='<number|name>', help='Dump the contents of section <number|name> as strings') + optparser.add_option('-V', '--version-info', + action='store_true', dest='show_version_info', + help='Display the version sections (if present)') optparser.add_option('--debug-dump', action='store', dest='debug_dump_what', metavar='<what>', help=( @@ -838,6 +1103,8 @@ readelf.display_symbol_tables() if options.show_relocs: readelf.display_relocations() + if options.show_version_info: + readelf.display_version_info() if options.show_hex_dump: readelf.display_hex_dump(options.show_hex_dump) if options.show_string_dump:
diff --git a/setup.py b/setup.py index c8d54b1..3fed12d 100644 --- a/setup.py +++ b/setup.py
@@ -24,11 +24,11 @@ description='Library for analyzing ELF files and DWARF debugging information', long_description=description, license='Public domain', - version='0.21', + version='0.22', author='Eli Bendersky', maintainer='Eli Bendersky', author_email='eliben@gmail.com', - url='https://bitbucket.org/eliben/pyelftools', + url='https://github.com/eliben/pyelftools', platforms='Cross Platform', classifiers = [ 'Programming Language :: Python :: 2', @@ -44,7 +44,5 @@ 'elftools.construct', 'elftools.construct.lib', ], - scripts=['scripts/readelf.py'], + scripts=['scripts/readelf.py'] ) - -
diff --git a/test/all_tests.py b/test/all_tests.py new file mode 100755 index 0000000..4cb8e3c --- /dev/null +++ b/test/all_tests.py
@@ -0,0 +1,28 @@ +#!/usr/bin/env python +#------------------------------------------------------------------------------- +# test/all_tests.py +# +# Run all pyelftools tests. +# +# Eli Bendersky (eliben@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +from __future__ import print_function +import subprocess, sys +from utils import is_in_rootdir + +def run_test_script(path): + cmd = [sys.executable, path] + print("Running '%s'" % ' '.join(cmd)) + subprocess.check_call(cmd) + +def main(): + if not is_in_rootdir(): + testlog.error('Error: Please run me from the root dir of pyelftools!') + return 1 + run_test_script('test/run_all_unittests.py') + run_test_script('test/run_examples_test.py') + run_test_script('test/run_readelf_tests.py') + +if __name__ == '__main__': + sys.exit(main())
diff --git a/test/external_tools/README.txt b/test/external_tools/README.txt index a6c496a..3380cce 100644 --- a/test/external_tools/README.txt +++ b/test/external_tools/README.txt
@@ -1,2 +1,4 @@ Some utilities that use libelf to create synthetic ELF files +Also, readelf picked up from a built binutils. Run it with --version to version +details. The binary is built on a 64-bit Ubuntu machine.
diff --git a/test/external_tools/readelf b/test/external_tools/readelf new file mode 100755 index 0000000..fb5d91a --- /dev/null +++ b/test/external_tools/readelf Binary files differ
diff --git a/test/run_all_unittests.py b/test/run_all_unittests.py index f7291bc..70a57d6 100755 --- a/test/run_all_unittests.py +++ b/test/run_all_unittests.py
@@ -9,16 +9,25 @@ #------------------------------------------------------------------------------- from __future__ import print_function +import os, sys + try: import unittest2 as unittest except ImportError: import unittest -if __name__ == '__main__': - import os +def main(): if not os.path.isdir('test'): print('!! Please execute from the root directory of pyelftools') + return 1 else: tests = unittest.TestLoader().discover('test', 'test*.py', 'test') - unittest.TextTestRunner().run(tests) + result = unittest.TextTestRunner().run(tests) + if result.wasSuccessful(): + return 0 + else: + return 1 + +if __name__ == '__main__': + sys.exit(main())
diff --git a/test/run_readelf_tests.py b/test/run_readelf_tests.py index 0c542d8..2c1d5f3 100755 --- a/test/run_readelf_tests.py +++ b/test/run_readelf_tests.py
@@ -23,14 +23,10 @@ testlog.setLevel(logging.DEBUG) testlog.addHandler(logging.StreamHandler(sys.stdout)) -# Set the path for calling readelf. By default this is the system readelf. -# The first assignment to READELF_PATH reflects the binutils version I used -# to test the current pyelftools with. -# Alas, binutils's readelf changes its output slightly even between minor -# releases so a lot of bogus differences can occur; this is why an exact version -# is specified to reproduce the tests. -# -READELF_PATH = '/home/eliben/test/binutils-2.23.52/binutils/readelf' +# Set the path for calling readelf. We carry our own version of readelf around, +# because binutils tend to change its output even between daily builds of the +# same minor release and keeping track is a headache. +READELF_PATH = 'test/external_tools/readelf' if not os.path.exists(READELF_PATH): READELF_PATH = 'readelf' @@ -50,7 +46,7 @@ success = True testlog.info("Test file '%s'" % filename) for option in [ - '-e', '-d', '-s', '-r', '-x.text', '-p.shstrtab', + '-e', '-d', '-s', '-r', '-x.text', '-p.shstrtab', '-V', '--debug-dump=info', '--debug-dump=decodedline', '--debug-dump=frames', '--debug-dump=frames-interp']: if verbose: testlog.info("..option='%s'" % option) @@ -90,9 +86,9 @@ Note: this function contains some rather horrible hacks to ignore differences which are not important for the verification of pyelftools. This is due to some intricacies of binutils's readelf which pyelftools - doesn't currently implement, or silly inconsistencies in the output of - readelf, which I was reluctant to replicate. - Read the documentation for more details. + doesn't currently implement, features that binutils doesn't support, + or silly inconsistencies in the output of readelf, which I was reluctant + to replicate. Read the documentation for more details. """ def prepare_lines(s): return [line for line in s.lower().splitlines() if line.strip() != ''] @@ -125,8 +121,19 @@ # Compare ignoring whitespace lines1_parts = lines1[i].split() lines2_parts = lines2[i].split() + if ''.join(lines1_parts) != ''.join(lines2_parts): ok = False + + try: + # Ignore difference in precision of hex representation in the + # last part (i.e. 008f3b vs 8f3b) + if (''.join(lines1_parts[:-1]) == ''.join(lines2_parts[:-1]) and + int(lines1_parts[-1], 16) == int(lines2_parts[-1], 16)): + ok = True + except ValueError: + pass + sm = SequenceMatcher() sm.set_seqs(lines1[i], lines2[i]) changes = sm.get_opcodes() @@ -146,14 +153,17 @@ elif 'os/abi' in lines1[i]: if 'unix - gnu' in lines1[i] and 'unix - linux' in lines2[i]: ok = True + elif ( 'unknown at value' in lines1[i] and + 'dw_at_apple' in lines2[i]): + ok = True else: for s in ('t (tls)', 'l (large)'): if s in lines1[i] or s in lines2[i]: ok = True break if not ok: - errmsg = 'Mismatch on line #%s:\n>>%s<<\n>>%s<<\n' % ( - i, lines1[i], lines2[i]) + errmsg = 'Mismatch on line #%s:\n>>%s<<\n>>%s<<\n (%r)' % ( + i, lines1[i], lines2[i], changes) return False, errmsg return True, '' @@ -183,7 +193,7 @@ if len(args) > 0: filenames = args else: - filenames = list(discover_testfiles('test/testfiles')) + filenames = list(discover_testfiles('test/testfiles_for_readelf')) success = True for filename in filenames:
diff --git a/test/test_arm_support.py b/test/test_arm_support.py index b2b0ab2..6493663 100644 --- a/test/test_arm_support.py +++ b/test/test_arm_support.py
@@ -15,7 +15,7 @@ class TestARMSupport(unittest.TestCase): def test_hello(self): - with open(os.path.join('test', 'testfiles', + with open(os.path.join('test', 'testfiles_for_unittests', 'simple_gcc.elf.arm'), 'rb') as f: elf = ELFFile(f) self.assertEqual(elf.get_machine_arch(), 'ARM') @@ -25,6 +25,20 @@ self.assertEqual(elf.num_sections(), 14) self.assertEqual(elf.num_segments(), 2) + def test_DWARF_indirect_forms(self): + # This file uses a lot of DW_FORM_indirect, and is also an ARM ELF + # with non-trivial DWARF info. + # So this is a simple sanity check that we can successfully parse it + # and extract the expected amount of CUs. + with open(os.path.join('test', 'testfiles_for_unittests', + 'arm_with_form_indirect.elf'), 'rb') as f: + elffile = ELFFile(f) + self.assertTrue(elffile.has_dwarf_info()) + + dwarfinfo = elffile.get_dwarf_info() + all_CUs = list(dwarfinfo.iter_CUs()) + self.assertEqual(len(all_CUs), 9) + if __name__ == '__main__': unittest.main()
diff --git a/test/test_double_dynstr_section.py b/test/test_double_dynstr_section.py new file mode 100644 index 0000000..3078554 --- /dev/null +++ b/test/test_double_dynstr_section.py
@@ -0,0 +1,61 @@ +#------------------------------------------------------------------------------ +# elftools tests +# +# Yann Rouillard (yann@pleiades.fr.eu.org) +# This code is in the public domain +#------------------------------------------------------------------------------ +try: + import unittest2 as unittest +except ImportError: + import unittest +import os + +from utils import setup_syspath; setup_syspath() +from elftools.elf.elffile import ELFFile +from elftools.elf.dynamic import DynamicSection, DynamicTag + + +class TestDoubleDynstrSections(unittest.TestCase): + """ This test make sure than dynamic tags + are properly analyzed when two .dynstr + sections are present in an elf file + """ + + reference_data = [ + b'libz.so.1', + b'libc.so.6', + b'lib_versioned.so.1', + ] + + def _test_double_dynstr_section_generic(self, testfile): + + with open(os.path.join('test', 'testfiles_for_unittests', testfile), + 'rb') as f: + elf = ELFFile(f) + for section in elf.iter_sections(): + if isinstance(section, DynamicSection): + d_tags = [getattr(x, x.entry.d_tag[3:].lower()) + for x in section.iter_tags() + if x.entry.d_tag in DynamicTag._HANDLED_TAGS] + self.assertListEqual( + TestDoubleDynstrSections.reference_data, + d_tags) + return + self.fail('No dynamic section found !!') + + + def test_double_dynstr_section(self): + """ First test with the good dynstr section first + """ + self._test_double_dynstr_section_generic( + 'lib_with_two_dynstr_sections.so.1.elf') + + def test_double_dynstr_section_reverse(self): + """ Second test with the good dynstr section last + """ + self._test_double_dynstr_section_generic( + 'lib_with_two_dynstr_sections_reversed.so.1.elf') + + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_dwarf_range_lists.py b/test/test_dwarf_range_lists.py new file mode 100644 index 0000000..81dab9a --- /dev/null +++ b/test/test_dwarf_range_lists.py
@@ -0,0 +1,34 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Eli Bendersky (eliben@gmail.com), Santhosh Kumar Mani (santhoshmani@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +try: + import unittest2 as unittest +except ImportError: + import unittest +import os + +from utils import setup_syspath; setup_syspath() +from elftools.elf.elffile import ELFFile + +class TestRangeLists(unittest.TestCase): + # Test the absence of .debug_ranges section + def test_range_list_absence(self): + with open(os.path.join('test', 'testfiles_for_unittests', + 'arm_with_form_indirect.elf'), 'rb') as f: + elffile = ELFFile(f) + self.assertTrue(elffile.has_dwarf_info()) + self.assertIsNone(elffile.get_dwarf_info().range_lists()) + + # Test the presence of .debug_ranges section + def test_range_list_presence(self): + with open(os.path.join('test', 'testfiles_for_unittests', + 'sample_exe64.elf'), 'rb') as f: + elffile = ELFFile(f) + self.assertTrue(elffile.has_dwarf_info()) + self.assertIsNotNone(elffile.get_dwarf_info().range_lists()) + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_dynamic.py b/test/test_dynamic.py new file mode 100644 index 0000000..f25feba --- /dev/null +++ b/test/test_dynamic.py
@@ -0,0 +1,51 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Eli Bendersky (eliben@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +try: + import unittest2 as unittest +except ImportError: + import unittest +import os + +from utils import setup_syspath +setup_syspath() +from elftools.elf.elffile import ELFFile +from elftools.common.exceptions import ELFError +from elftools.elf.dynamic import DynamicTag + + +class TestDynamicTag(unittest.TestCase): + """Tests for the DynamicTag class.""" + + def test_requires_stringtable(self): + with self.assertRaises(ELFError): + dt = DynamicTag('', None) + + +class TestDynamic(unittest.TestCase): + """Tests for the Dynamic class.""" + + def test_missing_sections(self): + """Verify we can get dynamic strings w/out section headers""" + + libs = [] + with open(os.path.join('test', 'testfiles_for_unittests', + 'aarch64_super_stripped.elf'), 'rb') as f: + elf = ELFFile(f) + for segment in elf.iter_segments(): + if segment.header.p_type != 'PT_DYNAMIC': + continue + + for t in segment.iter_tags(): + if t.entry.d_tag == 'DT_NEEDED': + libs.append(t.needed.decode('utf-8')) + + exp = ['libc.so.6'] + self.assertEqual(libs, exp) + + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_elffile.py b/test/test_elffile.py new file mode 100644 index 0000000..5c7b2b8 --- /dev/null +++ b/test/test_elffile.py
@@ -0,0 +1,50 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Eli Bendersky (eliben@gmail.com) +# This code is in the public domain +#------------------------------------------------------------------------------- +try: + import unittest2 as unittest +except ImportError: + import unittest + +from utils import setup_syspath; setup_syspath() +from elftools.elf.elffile import ELFFile + +class TestMap(unittest.TestCase): + def test_address_offsets(self): + class MockELF(ELFFile): + __init__ = object.__init__ + def iter_segments(self): + return iter(( + dict(p_vaddr=0x10200, p_filesz=0x200, p_offset=0x100), + dict(p_vaddr=0x10100, p_filesz=0x100, p_offset=0x400), + )) + + elf = MockELF() + + self.assertEqual(tuple(elf.address_offsets(0x10100)), (0x400,)) + self.assertEqual(tuple(elf.address_offsets(0x10120)), (0x420,)) + self.assertEqual(tuple(elf.address_offsets(0x101FF)), (0x4FF,)) + self.assertEqual(tuple(elf.address_offsets(0x10200)), (0x100,)) + self.assertEqual(tuple(elf.address_offsets(0x100FF)), ()) + self.assertEqual(tuple(elf.address_offsets(0x10400)), ()) + + self.assertEqual( + tuple(elf.address_offsets(0x10100, 0x100)), (0x400,)) + self.assertEqual(tuple(elf.address_offsets(0x10100, 4)), (0x400,)) + self.assertEqual(tuple(elf.address_offsets(0x10120, 4)), (0x420,)) + self.assertEqual(tuple(elf.address_offsets(0x101FC, 4)), (0x4FC,)) + self.assertEqual(tuple(elf.address_offsets(0x10200, 4)), (0x100,)) + self.assertEqual(tuple(elf.address_offsets(0x10100, 0x200)), ()) + self.assertEqual(tuple(elf.address_offsets(0x10000, 0x800)), ()) + self.assertEqual(tuple(elf.address_offsets(0x100FC, 4)), ()) + self.assertEqual(tuple(elf.address_offsets(0x100FE, 4)), ()) + self.assertEqual(tuple(elf.address_offsets(0x101FE, 4)), ()) + self.assertEqual(tuple(elf.address_offsets(0x103FE, 4)), ()) + self.assertEqual(tuple(elf.address_offsets(0x10400, 4)), ()) + + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_gnuversions.py b/test/test_gnuversions.py new file mode 100644 index 0000000..8c1786a --- /dev/null +++ b/test/test_gnuversions.py
@@ -0,0 +1,160 @@ +#------------------------------------------------------------------------------ +# elftools tests +# +# Yann Rouillard (yann@pleiades.fr.eu.org) +# This code is in the public domain +#------------------------------------------------------------------------------ +try: + import unittest2 as unittest +except ImportError: + import unittest +import os + +from utils import setup_syspath +setup_syspath() +from elftools.elf.elffile import ELFFile +from elftools.elf.constants import VER_FLAGS +from elftools.elf.gnuversions import ( + GNUVerNeedSection, GNUVerDefSection, + GNUVerSymSection) + + +class TestSymbolVersioning(unittest.TestCase): + + versym_reference_data = [ + {'name': b'', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'_ITM_deregisterTMCloneTable', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'puts', 'ndx': 5}, + {'name': b'strlcat', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'__stack_chk_fail', 'ndx': 6}, + {'name': b'__gmon_start__', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'gzoffset', 'ndx': 7}, + {'name': b'_Jv_RegisterClasses', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'_ITM_registerTMCloneTable', 'ndx': 'VER_NDX_LOCAL'}, + {'name': b'__cxa_finalize', 'ndx': 5}, + {'name': b'_edata', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'VER_1.0', 'ndx': 2}, + {'name': b'function1_ver1_1', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'_end', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'function1', 'ndx': 4 | 0x8000}, + {'name': b'__bss_start', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'function1', 'ndx': 2}, + {'name': b'VER_1.1', 'ndx': 3}, + {'name': b'_init', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'function1_ver1_0', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'_fini', 'ndx': 'VER_NDX_GLOBAL'}, + {'name': b'VER_1.2', 'ndx': 4}, + {'name': b'function2', 'ndx': 3}, + ] + + def test_versym_section(self): + + reference_data = TestSymbolVersioning.versym_reference_data + + with open(os.path.join('test', 'testfiles_for_unittests', + 'lib_versioned64.so.1.elf'), 'rb') as f: + elf = ELFFile(f) + versym_section = None + for section in elf.iter_sections(): + if isinstance(section, GNUVerSymSection): + versym_section = section + break + + self.assertIsNotNone(versym_section) + + for versym, ref_versym in zip(section.iter_symbols(), + reference_data): + self.assertEqual(versym.name, ref_versym['name']) + self.assertEqual(versym['ndx'], ref_versym['ndx']) + + verneed_reference_data = [ + {'name': b'libz.so.1', 'vn_version': 1, 'vn_cnt': 1, + 'vernaux': [ + {'name': b'ZLIB_1.2.3.5', 'vna_flags': 0, 'vna_other': 7}]}, + {'name': b'libc.so.6', 'vn_version': 1, 'vn_cnt': 2, + 'vernaux': [ + {'name': b'GLIBC_2.4', 'vna_flags': 0, 'vna_other': 6}, + {'name': b'GLIBC_2.2.5', 'vna_flags': 0, 'vna_other': 5}]}, + ] + + def test_verneed_section(self): + + reference_data = TestSymbolVersioning.verneed_reference_data + + with open(os.path.join('test', 'testfiles_for_unittests', + 'lib_versioned64.so.1.elf'), 'rb') as f: + elf = ELFFile(f) + verneed_section = None + for section in elf.iter_sections(): + if isinstance(section, GNUVerNeedSection): + verneed_section = section + break + + self.assertIsNotNone(verneed_section) + + for (verneed, vernaux_iter), ref_verneed in zip( + section.iter_versions(), reference_data): + + self.assertEqual(verneed.name, ref_verneed['name']) + self.assertEqual(verneed['vn_cnt'], ref_verneed['vn_cnt']) + self.assertEqual(verneed['vn_version'], + ref_verneed['vn_version']) + + for vernaux, ref_vernaux in zip( + vernaux_iter, ref_verneed['vernaux']): + + self.assertEqual(vernaux.name, ref_vernaux['name']) + self.assertEqual(vernaux['vna_flags'], + ref_vernaux['vna_flags']) + self.assertEqual(vernaux['vna_other'], + ref_vernaux['vna_other']) + + verdef_reference_data = [ + {'vd_ndx': 1, 'vd_version': 1, 'vd_flags': VER_FLAGS.VER_FLG_BASE, + 'vd_cnt': 1, + 'verdaux': [ + {'name': b'lib_versioned.so.1'}]}, + {'vd_ndx': 2, 'vd_version': 1, 'vd_flags': 0, 'vd_cnt': 1, + 'verdaux': [ + {'name': b'VER_1.0'}]}, + {'vd_ndx': 3, 'vd_version': 1, 'vd_flags': 0, 'vd_cnt': 2, + 'verdaux': [ + {'name': b'VER_1.1'}, + {'name': b'VER_1.0'}]}, + {'vd_ndx': 4, 'vd_version': 1, 'vd_flags': 0, 'vd_cnt': 2, + 'verdaux': [ + {'name': b'VER_1.2'}, + {'name': b'VER_1.1'}]}, + ] + + def test_verdef_section(self): + + reference_data = TestSymbolVersioning.verdef_reference_data + + with open(os.path.join('test', 'testfiles_for_unittests', + 'lib_versioned64.so.1.elf'), 'rb') as f: + elf = ELFFile(f) + verneed_section = None + for section in elf.iter_sections(): + if isinstance(section, GNUVerDefSection): + verdef_section = section + break + + self.assertIsNotNone(verdef_section) + + for (verdef, verdaux_iter), ref_verdef in zip( + section.iter_versions(), reference_data): + + self.assertEqual(verdef['vd_ndx'], ref_verdef['vd_ndx']) + self.assertEqual(verdef['vd_version'], + ref_verdef['vd_version']) + self.assertEqual(verdef['vd_flags'], ref_verdef['vd_flags']) + self.assertEqual(verdef['vd_cnt'], ref_verdef['vd_cnt']) + + for verdaux, ref_verdaux in zip( + verdaux_iter, ref_verdef['verdaux']): + self.assertEqual(verdaux.name, ref_verdaux['name']) + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_solaris_support.py b/test/test_solaris_support.py new file mode 100644 index 0000000..d243fe9 --- /dev/null +++ b/test/test_solaris_support.py
@@ -0,0 +1,87 @@ +#------------------------------------------------------------------------------- +# elftools tests +# +# Yann Rouillard (yann@pleiades.fr.eu.org) +# This code is in the public domain +#------------------------------------------------------------------------------- +try: + import unittest2 as unittest +except ImportError: + import unittest +import os +import copy + +from utils import setup_syspath; setup_syspath() +from elftools.elf.elffile import ELFFile +from elftools.elf.constants import SUNW_SYMINFO_FLAGS + + +class TestSolarisSupport(unittest.TestCase): + + def _test_SUNW_syminfo_section_generic(self, testfile): + with open(os.path.join('test', 'testfiles_for_unittests', + testfile), 'rb') as f: + elf = ELFFile(f) + syminfo_section = elf.get_section_by_name(b'.SUNW_syminfo') + self.assertIsNotNone(syminfo_section) + + # The test files were compiled against libc.so.1 with + # direct binding, hence the libc symbols used + # (exit, atexit and _exit) have the direct binding flags + # in the syminfo table. + # We check that this is properly detected. + exit_symbols = [s for s in syminfo_section.iter_symbols() + if b'exit' in s.name] + self.assertNotEqual(len(exit_symbols), 0) + + for symbol in exit_symbols: + # libc.so.1 has the index 0 in the dynamic table + self.assertEqual(symbol['si_boundto'], 0) + self.assertEqual(symbol['si_flags'], + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECT | + SUNW_SYMINFO_FLAGS.SYMINFO_FLG_DIRECTBIND) + + def test_SUNW_syminfo_section_x86(self): + self._test_SUNW_syminfo_section_generic('exe_solaris32_cc.elf') + + def test_SUNW_syminfo_section_x64(self): + self._test_SUNW_syminfo_section_generic('exe_solaris64_cc.elf') + + def test_SUNW_syminfo_section_sparc32(self): + self._test_SUNW_syminfo_section_generic('exe_solaris32_cc.sparc.elf') + + def test_SUNW_syminfo_section_sparc64(self): + self._test_SUNW_syminfo_section_generic('exe_solaris64_cc.sparc.elf') + + ldsynsym_reference_data = [b'', b'exe_solaris32.elf', b'crti.s', b'crt1.o', + b'crt1.s', b'fsr.s', b'values-Xa.c', + b'exe_solaris64.elf.c', b'crtn.s'] + + def _test_SUNW_ldynsym_section_generic(self, testfile, reference_data): + with open(os.path.join('test', 'testfiles_for_unittests', + testfile), 'rb') as f: + elf = ELFFile(f) + ldynsym_section = elf.get_section_by_name(b'.SUNW_ldynsym') + self.assertIsNotNone(ldynsym_section) + + for symbol, ref_symbol_name in zip( + ldynsym_section.iter_symbols(), reference_data): + + self.assertEqual(symbol.name, ref_symbol_name) + + def test_SUNW_ldynsym_section_x86(self): + reference_data = TestSolarisSupport.ldsynsym_reference_data + self._test_SUNW_ldynsym_section_generic('exe_solaris32_cc.elf', + reference_data) + + def test_SUNW_ldynsym_section_x64(self): + reference_data = copy.deepcopy( + TestSolarisSupport.ldsynsym_reference_data) + reference_data[1] = b'exe_solaris64.elf' + reference_data[3] = b'crt1x.o' + reference_data[5] = b'fsrx.s' + self._test_SUNW_ldynsym_section_generic('exe_solaris64_cc.elf', + reference_data) + +if __name__ == '__main__': + unittest.main()
diff --git a/test/test_utils.py b/test/test_utils.py index 314b6b5..b74f2e5 100644 --- a/test/test_utils.py +++ b/test/test_utils.py
@@ -45,17 +45,17 @@ self.assertEqual(parse_cstring_from_stream(sio, 2348), text[2348:5000]) -class Test_preserve_stream_pos(object): +class Test_preserve_stream_pos(unittest.TestCase): def test_basic(self): - sio = BytesIO('abcdef') + sio = BytesIO(b'abcdef') with preserve_stream_pos(sio): sio.seek(4) - self.assertEqual(stream.tell(), 0) + self.assertEqual(sio.tell(), 0) sio.seek(5) with preserve_stream_pos(sio): sio.seek(0) - self.assertEqual(stream.tell(), 5) + self.assertEqual(sio.tell(), 5) if __name__ == '__main__':
diff --git a/test/testfiles_for_readelf/clang33-simple.o b/test/testfiles_for_readelf/clang33-simple.o new file mode 100644 index 0000000..d64d2ef --- /dev/null +++ b/test/testfiles_for_readelf/clang33-simple.o Binary files differ
diff --git a/test/testfiles/exe_simple32.elf b/test/testfiles_for_readelf/exe_simple32.elf similarity index 100% rename from test/testfiles/exe_simple32.elf rename to test/testfiles_for_readelf/exe_simple32.elf Binary files differ
diff --git a/test/testfiles/exe_simple64.elf b/test/testfiles_for_readelf/exe_simple64.elf similarity index 100% rename from test/testfiles/exe_simple64.elf rename to test/testfiles_for_readelf/exe_simple64.elf Binary files differ
diff --git a/test/testfiles/exe_stripped64.elf b/test/testfiles_for_readelf/exe_stripped64.elf similarity index 100% rename from test/testfiles/exe_stripped64.elf rename to test/testfiles_for_readelf/exe_stripped64.elf Binary files differ
diff --git a/test/testfiles_for_readelf/gcc48-simple.o b/test/testfiles_for_readelf/gcc48-simple.o new file mode 100644 index 0000000..851a21a --- /dev/null +++ b/test/testfiles_for_readelf/gcc48-simple.o Binary files differ
diff --git a/test/testfiles_for_readelf/gcc48-simple.src.c b/test/testfiles_for_readelf/gcc48-simple.src.c new file mode 100644 index 0000000..23849b3 --- /dev/null +++ b/test/testfiles_for_readelf/gcc48-simple.src.c
@@ -0,0 +1,19 @@ +/* Generated by compiling with gcc 4.8 as follows: +** +** gcc-4.8 -O0 -g -fno-dwarf2-cfi-asm -c dwarf4_simple.c -o gcc48-simple. +** +** Note: -fno-dwarf2-cfi-asm to tell gcc to generate .dwarf_frames as well +** as the .eh_frames it generates by default. +** +*/ + +extern int bar(int); +extern int baz(int); + +int foo(int v) { + int x = bar(v); + int i; + for (i = 0; i < v; ++i) + x += bar(i) + bar(v) * baz(i); + return x; +}
diff --git a/test/testfiles/libelf0_8_13_32bit.so.elf b/test/testfiles_for_readelf/libelf0_8_13_32bit.so.elf similarity index 100% rename from test/testfiles/libelf0_8_13_32bit.so.elf rename to test/testfiles_for_readelf/libelf0_8_13_32bit.so.elf Binary files differ
diff --git a/test/testfiles/obj_simple32.o.elf b/test/testfiles_for_readelf/obj_simple32.o.elf similarity index 100% rename from test/testfiles/obj_simple32.o.elf rename to test/testfiles_for_readelf/obj_simple32.o.elf Binary files differ
diff --git a/test/testfiles/penalty_32_gcc.o.elf b/test/testfiles_for_readelf/penalty_32_gcc.o.elf similarity index 100% rename from test/testfiles/penalty_32_gcc.o.elf rename to test/testfiles_for_readelf/penalty_32_gcc.o.elf Binary files differ
diff --git a/test/testfiles/penalty_64_clang.o.elf b/test/testfiles_for_readelf/penalty_64_clang.o.elf similarity index 100% rename from test/testfiles/penalty_64_clang.o.elf rename to test/testfiles_for_readelf/penalty_64_clang.o.elf Binary files differ
diff --git a/test/testfiles/penalty_64_gcc.o.elf b/test/testfiles_for_readelf/penalty_64_gcc.o.elf similarity index 100% rename from test/testfiles/penalty_64_gcc.o.elf rename to test/testfiles_for_readelf/penalty_64_gcc.o.elf Binary files differ
diff --git a/test/testfiles_for_readelf/reloc_aarch64_gcc.o.elf b/test/testfiles_for_readelf/reloc_aarch64_gcc.o.elf new file mode 100644 index 0000000..25eb90c --- /dev/null +++ b/test/testfiles_for_readelf/reloc_aarch64_gcc.o.elf Binary files differ
diff --git a/test/testfiles_for_readelf/simple_aarch64_gcc.o.elf b/test/testfiles_for_readelf/simple_aarch64_gcc.o.elf new file mode 100644 index 0000000..9a60f83 --- /dev/null +++ b/test/testfiles_for_readelf/simple_aarch64_gcc.o.elf Binary files differ
diff --git a/test/testfiles/simple_gcc.elf.arm b/test/testfiles_for_readelf/simple_arm_gcc.o.elf similarity index 100% copy from test/testfiles/simple_gcc.elf.arm copy to test/testfiles_for_readelf/simple_arm_gcc.o.elf Binary files differ
diff --git a/test/testfiles/update32.o.elf b/test/testfiles_for_readelf/update32.o.elf similarity index 100% rename from test/testfiles/update32.o.elf rename to test/testfiles_for_readelf/update32.o.elf Binary files differ
diff --git a/test/testfiles_for_unittests/aarch64_super_stripped.elf b/test/testfiles_for_unittests/aarch64_super_stripped.elf new file mode 100755 index 0000000..0e5c2c4 --- /dev/null +++ b/test/testfiles_for_unittests/aarch64_super_stripped.elf Binary files differ
diff --git a/test/testfiles_for_unittests/arm_with_form_indirect.elf b/test/testfiles_for_unittests/arm_with_form_indirect.elf new file mode 100644 index 0000000..ff0a3ba --- /dev/null +++ b/test/testfiles_for_unittests/arm_with_form_indirect.elf Binary files differ
diff --git a/test/testfiles_for_unittests/exe_solaris32_cc.elf b/test/testfiles_for_unittests/exe_solaris32_cc.elf new file mode 100644 index 0000000..51b925c --- /dev/null +++ b/test/testfiles_for_unittests/exe_solaris32_cc.elf Binary files differ
diff --git a/test/testfiles_for_unittests/exe_solaris32_cc.sparc.elf b/test/testfiles_for_unittests/exe_solaris32_cc.sparc.elf new file mode 100644 index 0000000..7e879ef --- /dev/null +++ b/test/testfiles_for_unittests/exe_solaris32_cc.sparc.elf Binary files differ
diff --git a/test/testfiles_for_unittests/exe_solaris64_cc.elf b/test/testfiles_for_unittests/exe_solaris64_cc.elf new file mode 100644 index 0000000..b6bad65 --- /dev/null +++ b/test/testfiles_for_unittests/exe_solaris64_cc.elf Binary files differ
diff --git a/test/testfiles_for_unittests/exe_solaris64_cc.sparc.elf b/test/testfiles_for_unittests/exe_solaris64_cc.sparc.elf new file mode 100644 index 0000000..b9e4a17 --- /dev/null +++ b/test/testfiles_for_unittests/exe_solaris64_cc.sparc.elf Binary files differ
diff --git a/test/testfiles_for_unittests/lib_versioned64.so.1.elf b/test/testfiles_for_unittests/lib_versioned64.so.1.elf new file mode 100644 index 0000000..09edd94 --- /dev/null +++ b/test/testfiles_for_unittests/lib_versioned64.so.1.elf Binary files differ
diff --git a/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf b/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf new file mode 100755 index 0000000..661950b --- /dev/null +++ b/test/testfiles_for_unittests/lib_with_two_dynstr_sections.so.1.elf Binary files differ
diff --git a/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf b/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf new file mode 100755 index 0000000..8d07de1 --- /dev/null +++ b/test/testfiles_for_unittests/lib_with_two_dynstr_sections_reversed.so.1.elf Binary files differ
diff --git a/test/testfiles/exe_simple64.elf b/test/testfiles_for_unittests/sample_exe64.elf similarity index 100% copy from test/testfiles/exe_simple64.elf copy to test/testfiles_for_unittests/sample_exe64.elf Binary files differ
diff --git a/test/testfiles/simple_gcc.elf.arm b/test/testfiles_for_unittests/simple_gcc.elf.arm similarity index 100% rename from test/testfiles/simple_gcc.elf.arm rename to test/testfiles_for_unittests/simple_gcc.elf.arm Binary files differ
diff --git a/test/utils.py b/test/utils.py index 908cfcc..ff58678 100644 --- a/test/utils.py +++ b/test/utils.py
@@ -19,7 +19,7 @@ sys.path.insert(0, '.') -def run_exe(exe_path, args): +def run_exe(exe_path, args=[]): """ Runs the given executable as a subprocess, given the list of arguments. Captures its return code (rc) and stdout and returns a pair: rc, stdout_str
diff --git a/tox.ini b/tox.ini index 1409ccb..600ca1c 100644 --- a/tox.ini +++ b/tox.ini
@@ -1,5 +1,5 @@ [tox] -envlist = py27,py32 +envlist = py27,py33 [testenv] commands = @@ -10,4 +10,3 @@ [testenv:py26] deps = unittest2 -