1#------------------------------------------------------------------------------
2# elftools: elf/gnuversions.py
3#
4# ELF sections
5#
6# Yann Rouillard (yann@pleiades.fr.eu.org)
7# This code is in the public domain
8#------------------------------------------------------------------------------
9from ..construct import CString
10from ..common.utils import struct_parse, elf_assert
11from .sections import Section, Symbol
12
13
14class Version(object):
15    """ Version object - representing a version definition or dependency
16        entry from a "Version Needed" or a "Version Dependency" table section.
17
18        This kind of entry contains a pointer to an array of auxiliary entries
19        that store the information about version names or dependencies.
20        These entries are not stored in this object and should be accessed
21        through the appropriate method of a section object which will return
22        an iterator of VersionAuxiliary objects.
23
24        Similarly to Section objects, allows dictionary-like access to
25        verdef/verneed entry
26    """
27    def __init__(self, entry, name=None):
28        self.entry = entry
29        self.name = name
30
31    def __getitem__(self, name):
32        """ Implement dict-like access to entry
33        """
34        return self.entry[name]
35
36
37class VersionAuxiliary(object):
38    """ Version Auxiliary object - representing an auxiliary entry of a version
39        definition or dependency entry
40
41        Similarly to Section objects, allows dictionary-like access to the
42        verdaux/vernaux entry
43    """
44    def __init__(self, entry, name):
45        self.entry = entry
46        self.name = name
47
48    def __getitem__(self, name):
49        """ Implement dict-like access to entries
50        """
51        return self.entry[name]
52
53
54class GNUVersionSection(Section):
55    """ Common ancestor class for ELF SUNW|GNU Version Needed/Dependency
56        sections class which contains shareable code
57    """
58
59    def __init__(self, header, name, elffile, stringtable,
60                 field_prefix, version_struct, version_auxiliaries_struct):
61        super(GNUVersionSection, self).__init__(header, name, elffile)
62        self.stringtable = stringtable
63        self.field_prefix = field_prefix
64        self.version_struct = version_struct
65        self.version_auxiliaries_struct = version_auxiliaries_struct
66
67    def num_versions(self):
68        """ Number of version entries in the section
69        """
70        return self['sh_info']
71
72    def _field_name(self, name, auxiliary=False):
73        """ Return the real field's name of version or a version auxiliary
74            entry
75        """
76        middle = 'a_' if auxiliary else '_'
77        return self.field_prefix + middle + name
78
79    def _iter_version_auxiliaries(self, entry_offset, count):
80        """ Yield all auxiliary entries of a version entry
81        """
82        name_field = self._field_name('name', auxiliary=True)
83        next_field = self._field_name('next', auxiliary=True)
84
85        for _ in range(count):
86            entry = struct_parse(
87                        self.version_auxiliaries_struct,
88                        self.stream,
89                        stream_pos=entry_offset)
90
91            name = self.stringtable.get_string(entry[name_field])
92            version_aux = VersionAuxiliary(entry, name)
93            yield version_aux
94
95            entry_offset += entry[next_field]
96
97    def iter_versions(self):
98        """ Yield all the version entries in the section
99            Each time it returns the main version structure
100            and an iterator to walk through its auxiliaries entries
101        """
102        aux_field = self._field_name('aux')
103        count_field = self._field_name('cnt')
104        next_field = self._field_name('next')
105
106        entry_offset = self['sh_offset']
107        for _ in range(self.num_versions()):
108            entry = struct_parse(
109                self.version_struct,
110                self.stream,
111                stream_pos=entry_offset)
112
113            elf_assert(entry[count_field] > 0,
114                'Expected number of version auxiliary entries (%s) to be > 0'
115                'for the following version entry: %s' % (
116                    count_field, str(entry)))
117
118            version = Version(entry)
119            aux_entries_offset = entry_offset + entry[aux_field]
120            version_auxiliaries_iter = self._iter_version_auxiliaries(
121                    aux_entries_offset, entry[count_field])
122
123            yield version, version_auxiliaries_iter
124
125            entry_offset += entry[next_field]
126
127
128class GNUVerNeedSection(GNUVersionSection):
129    """ ELF SUNW or GNU Version Needed table section.
130        Has an associated StringTableSection that's passed in the constructor.
131    """
132    def __init__(self, header, name, elffile, stringtable):
133        super(GNUVerNeedSection, self).__init__(
134                header, name, elffile, stringtable, 'vn',
135                elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux)
136        self._has_indexes = None
137
138    def has_indexes(self):
139        """ Return True if at least one version definition entry has an index
140            that is stored in the vna_other field.
141            This information is used for symbol versioning
142        """
143        if self._has_indexes is None:
144            self._has_indexes = False
145            for _, vernaux_iter in self.iter_versions():
146                for vernaux in vernaux_iter:
147                    if vernaux['vna_other']:
148                        self._has_indexes = True
149                        break
150
151        return self._has_indexes
152
153    def iter_versions(self):
154        for verneed, vernaux in super(GNUVerNeedSection, self).iter_versions():
155            verneed.name = self.stringtable.get_string(verneed['vn_file'])
156            yield verneed, vernaux
157
158    def get_version(self, index):
159        """ Get the version information located at index #n in the table
160            Return boths the verneed structure and the vernaux structure
161            that contains the name of the version
162        """
163        for verneed, vernaux_iter in self.iter_versions():
164            for vernaux in vernaux_iter:
165                if vernaux['vna_other'] == index:
166                    return verneed, vernaux
167
168        return None
169
170
171class GNUVerDefSection(GNUVersionSection):
172    """ ELF SUNW or GNU Version Definition table section.
173        Has an associated StringTableSection that's passed in the constructor.
174    """
175    def __init__(self, header, name, elffile, stringtable):
176        super(GNUVerDefSection, self).__init__(
177                header, name, elffile, stringtable, 'vd',
178                elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux)
179
180    def get_version(self, index):
181        """ Get the version information located at index #n in the table
182            Return boths the verdef structure and an iterator to retrieve
183            both the version names and dependencies in the form of
184            verdaux entries
185        """
186        for verdef, verdaux_iter in self.iter_versions():
187            if verdef['vd_ndx'] == index:
188                return verdef, verdaux_iter
189
190        return None
191
192
193class GNUVerSymSection(Section):
194    """ ELF SUNW or GNU Versym table section.
195        Has an associated SymbolTableSection that's passed in the constructor.
196    """
197    def __init__(self, header, name, elffile, symboltable):
198        super(GNUVerSymSection, self).__init__(header, name, elffile)
199        self.symboltable = symboltable
200
201    def num_symbols(self):
202        """ Number of symbols in the table
203        """
204        return self['sh_size'] // self['sh_entsize']
205
206    def get_symbol(self, n):
207        """ Get the symbol at index #n from the table (Symbol object)
208            It begins at 1 and not 0 since the first entry is used to
209            store the current version of the syminfo table
210        """
211        # Grab the symbol's entry from the stream
212        entry_offset = self['sh_offset'] + n * self['sh_entsize']
213        entry = struct_parse(
214            self.structs.Elf_Versym,
215            self.stream,
216            stream_pos=entry_offset)
217        # Find the symbol name in the associated symbol table
218        name = self.symboltable.get_symbol(n).name
219        return Symbol(entry, name)
220
221    def iter_symbols(self):
222        """ Yield all the symbols in the table
223        """
224        for i in range(self.num_symbols()):
225            yield self.get_symbol(i)
226