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