• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2017 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import os
18import struct
19
20from vts.utils.python.library import elf_consts
21
22
23class ElfError(Exception):
24    """The exception raised by ElfParser."""
25    pass
26
27
28class ElfParser(object):
29    """The class reads information from an ELF file.
30
31    Attributes:
32        _file: The ELF file object.
33        _file_size: Size of the ELF.
34        bitness: Bitness of the ELF.
35        _address_size: Size of address or offset in the ELF.
36        _offsets: Offset of each entry in the ELF.
37        _seek_read_address: The function to read an address or offset entry
38                            from the ELF.
39        _sh_offset: Offset of section header table in the file.
40        _sh_size: Size of section header table entry.
41        _sh_count: Number of section header table entries.
42        _sh_strtab_index: Index of the section that contains section names.
43        _section_headers: List of SectionHeader objects read from the ELF.
44    """
45
46    class SectionHeader(object):
47        """Contains section header entries as attributes.
48
49        Attributes:
50            name_offset: Offset in the section header string table.
51            type: Type of the section.
52            address: The virtual memory address where the section is loaded.
53            offset: The offset of the section in the ELF file.
54            size: Size of the section.
55            entry_size: Size of each entry in the section.
56        """
57
58        def __init__(self, elf, offset):
59            """Loads a section header from ELF file.
60
61            Args:
62                elf: The instance of ElfParser.
63                offset: The starting offset of the section header.
64            """
65            self.name_offset = elf._SeekRead32(
66                    offset + elf._offsets.SECTION_NAME_OFFSET)
67            self.type = elf._SeekRead32(
68                    offset + elf._offsets.SECTION_TYPE)
69            self.address = elf._seek_read_address(
70                    offset + elf._offsets.SECTION_ADDRESS)
71            self.offset = elf._seek_read_address(
72                    offset + elf._offsets.SECTION_OFFSET)
73            self.size = elf._seek_read_address(
74                    offset + elf._offsets.SECTION_SIZE)
75            self.entry_size = elf._seek_read_address(
76                    offset + elf._offsets.SECTION_ENTRY_SIZE)
77
78    def __init__(self, file_path):
79        """Creates a parser to open and read an ELF file.
80
81        Args:
82            file_path: The path to the ELF.
83
84        Raises:
85            ElfError if the file is not a valid ELF.
86        """
87        try:
88            self._file = open(file_path, "rb")
89        except IOError as e:
90            raise ElfError(e)
91        try:
92            self._LoadElfHeader()
93            self._section_headers = [
94                    self.SectionHeader(self, self._sh_offset + i * self._sh_size)
95                    for i in range(self._sh_count)]
96        except:
97            self._file.close()
98            raise
99
100    def __del__(self):
101        """Closes the ELF file."""
102        self.Close()
103
104    def Close(self):
105        """Closes the ELF file."""
106        if hasattr(self, "_file"):
107            self._file.close()
108
109    def _SeekRead(self, offset, read_size):
110        """Reads a byte string at specific offset in the file.
111
112        Args:
113            offset: An integer, the offset from the beginning of the file.
114            read_size: An integer, number of bytes to read.
115
116        Returns:
117            A byte string which is the file content.
118
119        Raises:
120            ElfError if fails to seek and read.
121        """
122        if offset + read_size > self._file_size:
123            raise ElfError("Read beyond end of file.")
124        try:
125            self._file.seek(offset)
126            return self._file.read(read_size)
127        except IOError as e:
128            raise ElfError(e)
129
130    def _SeekRead8(self, offset):
131        """Reads an 1-byte integer from file."""
132        return struct.unpack("B", self._SeekRead(offset, 1))[0]
133
134    def _SeekRead16(self, offset):
135        """Reads a 2-byte integer from file."""
136        return struct.unpack("H", self._SeekRead(offset, 2))[0]
137
138    def _SeekRead32(self, offset):
139        """Reads a 4-byte integer from file."""
140        return struct.unpack("I", self._SeekRead(offset, 4))[0]
141
142    def _SeekRead64(self, offset):
143        """Reads an 8-byte integer from file."""
144        return struct.unpack("Q", self._SeekRead(offset, 8))[0]
145
146    def _SeekReadString(self, offset):
147        """Reads a null-terminated string starting from specific offset.
148
149        Args:
150            offset: The offset from the beginning of the file.
151
152        Returns:
153            A byte string, excluding the null character.
154        """
155        ret = ""
156        buf_size = 16
157        self._file.seek(offset)
158        while True:
159            try:
160                buf = self._file.read(buf_size)
161            except IOError as e:
162                raise ElfError(e)
163            end_index = buf.find('\0')
164            if end_index < 0:
165                ret += buf
166            else:
167                ret += buf[:end_index]
168                return ret
169            if len(buf) != buf_size:
170                raise ElfError("Null-terminated string reaches end of file.")
171
172    def _LoadElfHeader(self):
173        """Loads ELF header and initializes attributes."""
174        try:
175            self._file_size = os.fstat(self._file.fileno()).st_size
176        except OSError as e:
177            raise ElfError(e)
178
179        magic = self._SeekRead(elf_consts.MAGIC_OFFSET, 4)
180        if magic != elf_consts.MAGIC_BYTES:
181            raise ElfError("Wrong magic bytes.")
182        bitness = self._SeekRead8(elf_consts.BITNESS_OFFSET)
183        if bitness == elf_consts.BITNESS_32:
184            self.bitness = 32
185            self._address_size = 4
186            self._offsets = elf_consts.ElfOffsets32
187            self._seek_read_address = self._SeekRead32
188        elif bitness == elf_consts.BITNESS_64:
189            self.bitness = 64
190            self._address_size = 8
191            self._offsets = elf_consts.ElfOffsets64
192            self._seek_read_address = self._SeekRead64
193        else:
194            raise ElfError("Wrong bitness value.")
195
196        self._sh_offset = self._seek_read_address(
197                self._offsets.SECTION_HEADER_OFFSET)
198        self._sh_size = self._SeekRead16(self._offsets.SECTION_HEADER_SIZE)
199        self._sh_count = self._SeekRead16(self._offsets.SECTION_HEADER_COUNT)
200        self._sh_strtab_index = self._SeekRead16(
201                self._offsets.SECTION_HEADER_STRTAB_INDEX)
202        if self._sh_strtab_index >= self._sh_count:
203            raise ElfError("Wrong section header string table index.")
204
205    def _LoadSectionName(self, sh):
206        """Reads the name of a section.
207
208        Args:
209            sh: An instance of SectionHeader.
210
211        Returns:
212            A string, the name of the section.
213        """
214        strtab = self._section_headers[self._sh_strtab_index]
215        return self._SeekReadString(strtab.offset + sh.name_offset)
216
217    def _LoadDtNeeded(self, offset):
218        """Reads DT_NEEDED entries from dynamic section.
219
220        Args:
221            offset: The offset of the dynamic section from the beginning of
222                    the file.
223
224        Returns:
225            A list of strings, the names of libraries.
226        """
227        strtab_address = None
228        name_offsets = []
229        while True:
230            tag = self._seek_read_address(offset)
231            offset += self._address_size
232            value = self._seek_read_address(offset)
233            offset += self._address_size
234
235            if tag == elf_consts.DT_NULL:
236                break
237            if tag == elf_consts.DT_NEEDED:
238                name_offsets.append(value)
239            if tag == elf_consts.DT_STRTAB:
240                strtab_address = value
241
242        if strtab_address is None:
243            raise ElfError("Cannot find string table offset in dynamic section.")
244
245        try:
246            strtab_offset = next(x.offset for x in self._section_headers
247                                 if x.address == strtab_address)
248        except StopIteration:
249            raise ElfError("Cannot find dynamic string table.")
250
251        names = [self._SeekReadString(strtab_offset + x)
252                 for x in name_offsets]
253        return names
254
255    def ListDependencies(self):
256        """Lists the shared libraries that the ELF depends on.
257
258        Returns:
259            A list of strings, the names of the depended libraries.
260        """
261        deps = []
262        for sh in self._section_headers:
263            if sh.type == elf_consts.SHT_DYNAMIC:
264                deps.extend(self._LoadDtNeeded(sh.offset))
265        return deps
266
267    def ListGlobalDynamicSymbols(self):
268        """Lists the dynamic symbols defined in the ELF.
269
270        Returns:
271            A list of strings, the names of the symbols.
272        """
273        dynstr = None
274        dynsym = None
275        for sh in self._section_headers:
276            name = self._LoadSectionName(sh)
277            if name == elf_consts.DYNSYM:
278                dynsym = sh
279            elif name == elf_consts.DYNSTR:
280                dynstr = sh
281        if not dynsym or not dynstr or dynsym.size == 0:
282            raise ElfError("Cannot find dynamic symbol table.")
283
284        sym_names = []
285        for offset in range(
286                dynsym.offset, dynsym.offset + dynsym.size, dynsym.entry_size):
287            sym_info = self._SeekRead8(offset + self._offsets.SYMBOL_INFO)
288            if (sym_info & 0xf) == elf_consts.SYMBOL_NOTYPE:
289                continue
290            if sym_info >> 4 not in (elf_consts.SYMBOL_BINDING_GLOBAL,
291                                     elf_consts.SYMBOL_BINDING_WEAK):
292                continue
293            sym_sh_index = self._SeekRead16(
294                    offset + self._offsets.SYMBOL_SECTION_INDEX)
295            if sym_sh_index == elf_consts.SHN_UNDEFINED:
296                continue
297            name_offset = self._SeekRead32(offset + self._offsets.SYMBOL_NAME)
298            sym_names.append(self._SeekReadString(dynstr.offset + name_offset))
299        return sym_names
300