added support for version definition, version dependency and version symbol sections...
authorYann Rouillard <yann@pleiades.fr.eu.org>
Mon, 27 May 2013 20:44:28 +0000 (22:44 +0200)
committerYann Rouillard <yann@pleiades.fr.eu.org>
Mon, 27 May 2013 20:44:28 +0000 (22:44 +0200)
elftools/elf/constants.py
elftools/elf/descriptions.py
elftools/elf/elffile.py
elftools/elf/enums.py
elftools/elf/sections.py
elftools/elf/structs.py
scripts/readelf.py
test/run_readelf_tests.py

index 8387c0fbefd373f2c259222785d360cb6fdfd052..85b4c20ff3f15e104cb78fc330b96011ec6bde91 100644 (file)
@@ -62,3 +62,8 @@ class SUNW_SYMINFO_FLAGS(object):
     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 
index c1cda6a0dd6faf60f6e9098115d81910c6da8339..f760dfb2c35b8e246f2fad4073a6701fa817331d 100644 (file)
@@ -9,7 +9,7 @@
 from .enums import (
     ENUM_D_TAG, ENUM_E_VERSION, ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64
     )
-from .constants import P_FLAGS, SH_FLAGS, SUNW_SYMINFO_FLAGS
+from .constants import P_FLAGS, SH_FLAGS, SUNW_SYMINFO_FLAGS, VER_FLAGS
 from ..common.py3compat import iteritems
 
 
@@ -103,6 +103,18 @@ def describe_syminfo_flags(x):
 def describe_symbol_boundto(x):
     return _DESCR_SYMINFO_BOUNDTO.get(x, '%3s' % x)
 
+def describe_ver_flags(x):
+    s = ''
+    for flag in (
+            VER_FLAGS.VER_FLG_WEAK,
+            VER_FLAGS.VER_FLG_BASE,
+            VER_FLAGS.VER_FLG_INFO):
+        if x & flag:
+            if s:
+                s += ' | '
+            s += _DESCR_VER_FLAGS[flag]
+    return s
+
 #-------------------------------------------------------------------------------
 _unknown = '<unknown>'
 
@@ -275,6 +287,13 @@ _DESCR_SYMINFO_BOUNDTO = dict(
     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))
 
index ccb7e562a220009357cbb0c58d849726739a3fbc..67b39afb4cc51f71b40b5a5f5e6d4e4d9fa8e1e5 100644 (file)
@@ -13,7 +13,9 @@ from ..construct import ConstructError
 from .structs import ELFStructs
 from .sections import (
         Section, StringTableSection, SymbolTableSection,
-        SUNWSyminfoTableSection, NullSection)
+        SUNWSyminfoTableSection, VerneedTableSection, 
+        VerdefTableSection, VersymTableSection,
+        NullSection)
 from .dynamic import DynamicSection, DynamicSegment
 from .relocation import RelocationSection, RelocationHandler
 from .segments import Segment, InterpSegment
@@ -248,6 +250,12 @@ class ELFFile(object):
             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_verneed_table_section(section_header, name)
+        elif sectype == 'SHT_GNU_verdef':
+            return self._make_verdef_table_section(section_header, name)
+        elif sectype == 'SHT_GNU_versym':
+            return self._make_versym_table_section(section_header, name)
         elif sectype in ('SHT_REL', 'SHT_RELA'):
             return RelocationSection(
                 section_header, name, self.stream, self)
@@ -276,6 +284,36 @@ class ELFFile(object):
             elffile=self,
             symboltable=strtab_section)
 
+    def _make_verneed_table_section(self, section_header, name):
+        """ Create a VerneedTableSection
+        """
+        linked_strtab_index = section_header['sh_link']
+        strtab_section = self.get_section(linked_strtab_index)
+        return VerneedTableSection(
+            section_header, name, self.stream,
+            elffile=self,
+            stringtable=strtab_section)
+
+    def _make_verdef_table_section(self, section_header, name):
+        """ Create a VerdefTableSection
+        """
+        linked_strtab_index = section_header['sh_link']
+        strtab_section = self.get_section(linked_strtab_index)
+        return VerdefTableSection(
+            section_header, name, self.stream,
+            elffile=self,
+            stringtable=strtab_section)
+
+    def _make_versym_table_section(self, section_header, name):
+        """ Create a VersymTableSection
+        """
+        linked_strtab_index = section_header['sh_link']
+        strtab_section = self.get_section(linked_strtab_index)
+        return VersymTableSection(
+            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
         """
index 121c287be2154cd67ee394f61c429a0173da1018..5fd823877e8e5b7abaaf74c7662ed3d03ef41c9c 100644 (file)
@@ -184,9 +184,9 @@ ENUM_SH_TYPE = dict(
     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,
@@ -456,3 +456,20 @@ ENUM_SUNW_SYMINFO_BOUNDTO = dict(
     _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,
+)
+
index ce6245057ee798380755e0e60c426bc144897d15..853414469a27cd363e7a4b2181949d1745bc006e 100644 (file)
@@ -158,3 +158,245 @@ class SUNWSyminfoTableSection(Section):
         """
         for i in range(1, self.num_symbols() + 1):
             yield self.get_symbol(i)
+
+
+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 VerneedTableSection(Section):
+    """ 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(VerneedTableSection, self).__init__(header, name, stream)
+        self.elffile = elffile
+        self.elfstructs = self.elffile.structs
+        self.stringtable = stringtable
+        self._has_indexes = None
+
+    def num_versions(self):
+        """ Number of version dependency in the table
+        """
+        return self['sh_info']
+
+    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 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
+
+
+    def _iter_version_auxiliaries(self, entry_offset, count):
+        """ Yield all auxiliary entries of a version dependency
+        """
+        for _ in range(count): 
+            entry = struct_parse(
+                        self.elfstructs.Elf_Vernaux,
+                        self.stream,
+                        stream_pos=entry_offset)
+
+            name = self.stringtable.get_string(entry['vna_name'])
+            version_aux = VersionAuxiliary(entry, name)
+            yield version_aux
+
+            if not entry['vna_next']:
+                break
+
+            entry_offset += entry['vna_next']
+        
+
+    def iter_versions(self):
+        """ Yield all the version dependencies entries in the table 
+            Each time it returns the main version dependency structure
+            and an iterator to walk through its auxiliaries entries
+        """
+        entry_offset = self['sh_offset']
+        for _ in range(self.num_versions()):
+            entry = struct_parse(
+                self.elfstructs.Elf_Verneed,
+                self.stream,
+                stream_pos=entry_offset)
+
+            name = self.stringtable.get_string(entry['vn_file'])
+            elf_assert(entry['vn_cnt'] > 0,
+                'Expected number of version names to be > 0 for version definition %s' % name)
+
+            verneed = Version(entry, name)
+            aux_entries_offset = entry_offset + entry['vn_aux']
+            vernaux_iter = self._iter_version_auxiliaries(aux_entries_offset,
+                                                          entry['vn_cnt'])
+            yield verneed, vernaux_iter
+
+            if not entry['vn_next']:
+                break
+
+            entry_offset += entry['vn_next'] 
+
+
+class VerdefTableSection(Section):
+    """ 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(VerdefTableSection, self).__init__(header, name, stream)
+        self.elffile = elffile
+        self.elfstructs = self.elffile.structs
+        self.stringtable = stringtable
+
+    def num_versions(self):
+        """ Number of version definitions in the table
+        """
+        return self['sh_info']
+        
+    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
+
+    def _iter_version_auxiliaries(self, entry_offset, count):
+        """ Yield all auxiliary entries of a version definition
+        """
+        for _ in range(count):
+            entry = struct_parse(
+                        self.elfstructs.Elf_Verdaux,
+                        self.stream,
+                        stream_pos=entry_offset)
+
+            name = self.stringtable.get_string(entry['vda_name'])
+            vernaux = VersionAuxiliary(entry, name)
+            yield vernaux
+
+            if not entry['vda_next']:
+                break
+
+            entry_offset += entry['vda_next']
+        
+
+    def iter_versions(self):
+        """ Yield all the version definition entries in the table 
+            Each time it returns the main version definition structure
+            and an iterator to walk through its auxiliaries entries
+        """
+        entry_offset = self['sh_offset']
+        for _ in range(self.num_versions()):
+            entry = struct_parse(
+                self.elfstructs.Elf_Verdef,
+                self.stream,
+                stream_pos=entry_offset)
+
+            elf_assert(entry['vd_cnt'] > 0,
+                'Expected number of version names to be > 0'
+                'for version definition at index %i' % entry['vd_ndx'])
+
+            verdef = Version(entry)
+            aux_entries_offset = entry_offset + entry['vd_aux']
+            verdaux_iter = self._iter_version_auxiliaries(aux_entries_offset,
+                                                          entry['vd_cnt'])
+            yield verdef, verdaux_iter
+
+            if not entry['vd_next']:
+                break
+
+            entry_offset += entry['vd_next'] 
+
+
+class VersymTableSection(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(VersymTableSection, 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)
+
index 72d07fab7fd21e6b109297afb2ce320f186695b4..fba25031bd26e2c6bf3310cd7b68eb967cff2fad 100644 (file)
@@ -74,6 +74,9 @@ class ELFStructs(object):
         self._create_rel()
         self._create_dyn()
         self._create_sunw_syminfo()
+        self._create_version_needed()
+        self._create_version_definition()
+        self._create_version_symbol()
 
     def _create_ehdr(self):
         self.Elf_Ehdr = Struct('Elf_Ehdr',
@@ -209,3 +212,45 @@ class ELFStructs(object):
             Enum(self.Elf_half('si_boundto'), **ENUM_SUNW_SYMINFO_BOUNDTO),
             self.Elf_half('si_flags'),
         )
+
+    def _create_version_needed(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_version_definition(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_version_symbol(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),
+        )
index 720106d5d5532381b1b6a701e22a0d35618fb4d4..b437a5f5154742fd7ca9dc155a41651e45d9ff73 100755 (executable)
@@ -24,7 +24,10 @@ from elftools.elf.elffile import ELFFile
 from elftools.elf.dynamic import DynamicSection, DynamicSegment
 from elftools.elf.enums import ENUM_D_TAG
 from elftools.elf.segments import InterpSegment
-from elftools.elf.sections import SymbolTableSection
+from elftools.elf.sections import (
+    SymbolTableSection, VersymTableSection,
+    VerdefTableSection, VerneedTableSection,
+    )
 from elftools.elf.relocation import RelocationSection
 from elftools.elf.descriptions import (
     describe_ei_class, describe_ei_data, describe_ei_version,
@@ -33,6 +36,7 @@ from elftools.elf.descriptions import (
     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.dwarf.dwarfinfo import DWARFInfo
 from elftools.dwarf.descriptions import (
@@ -61,6 +65,9 @@ class ReadElf(object):
         # Lazily initialized if a debug dump is requested
         self._dwarfinfo = None
 
+        self._versioninfo = None
+
+
     def display_file_header(self):
         """ Display the ELF file header
         """
@@ -254,6 +261,8 @@ class ReadElf(object):
     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
@@ -263,6 +272,7 @@ class ReadElf(object):
                     bytes2str(section.name)))
                 continue
 
+
             self._emitline("\nSymbol table '%s' contains %s entries:" % (
                 bytes2str(section.name), section.num_symbols()))
 
@@ -272,8 +282,27 @@ class ReadElf(object):
                 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),
                     symbol['st_size'],
@@ -281,7 +310,8 @@ class ReadElf(object):
                     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
@@ -384,6 +414,114 @@ class ReadElf(object):
         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, VersymTableSection):
+
+                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, VerdefTableSection):
+
+                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, VerneedTableSection):
+
+                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 +624,8 @@ class ReadElf(object):
         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 +640,20 @@ class ReadElf(object):
 
             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 +663,95 @@ class ReadElf(object):
             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, VersymTableSection):
+                self._versioninfo['versym'] = section
+            elif isinstance(section, VerdefTableSection):
+                self._versioninfo['verdef'] = section
+            elif isinstance(section, VerneedTableSection):
+                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.
@@ -802,6 +1043,9 @@ def main(stream=None):
     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 +1082,8 @@ def main(stream=None):
                 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:
index bd15fff4ae91244cdfde5bd3bbe9c65c3929920f..57ab19725672e3a37f09381064aa09e61ff4d1a9 100755 (executable)
@@ -52,7 +52,8 @@ def run_test_on_file(filename, verbose=False):
     for option in [
             '-e', '-d', '-s', '-r', '-x.text', '-p.shstrtab',
             '--debug-dump=info', '--debug-dump=decodedline',
-            '--debug-dump=frames', '--debug-dump=frames-interp']:
+            '--debug-dump=frames', '--debug-dump=frames-interp',
+            '-V']:
         if verbose: testlog.info("..option='%s'" % option)
         # stdouts will be a 2-element list: output of readelf and output
         # of scripts/readelf.py