1#!/usr/bin/env python 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import argparse 19import gzip 20import json 21import os 22import zipfile 23 24 25class LsdumpError(Exception): 26 """The exception raised by ParseLsdumpFile.""" 27 pass 28 29 30class AttrDict(dict): 31 """A dictionary with attribute accessors.""" 32 33 def __getattr__(self, key): 34 """Returns self[key].""" 35 try: 36 return self[key] 37 except KeyError: 38 raise AttributeError('Failed to get attribute: %s' % key) 39 40 def __setattr__(self, key, value): 41 """Assigns value to self[key].""" 42 self[key] = value 43 44 45def _GetTypeSymbol(record_type): 46 """Gets the mangled name of a record type. 47 48 Before Android R, unique_id was mangled name starting with "_ZTS". 49 linker_set_key was unmangled. 50 Since Android R, unique_id has been removed, and linker_set_key has 51 been changed to mangled name starting with "_ZTI". 52 53 Args: 54 record_type: An AttrDict, a record type in lsdump. 55 56 Returns: 57 A string, the mangled name starting with "_ZTI". 58 """ 59 if "unique_id" in record_type: 60 return record_type["unique_id"].replace("_ZTS", "_ZTI", 1) 61 return record_type["linker_set_key"] 62 63 64def _OpenFileOrGzipped(file_name): 65 """Opens a file that is either in gzip or uncompressed format. 66 67 If file_name ends with '.gz' then return gzip.open(file_name, 'rb'), 68 else return open(file_name, 'rb'). 69 70 Args: 71 file_name: The file name to open. 72 73 Returns: 74 A file object. 75 76 Raises: 77 IOError if fails to open. 78 """ 79 if file_name.endswith('.gz'): 80 return gzip.open(file_name, 'rb') 81 return open(file_name, 'rb') 82 83 84def _ConsumeOffset(tok, beg=0): 85 """Consumes a <offset-number> in a thunk symbol.""" 86 pos = tok.find('_', beg) + 1 87 return tok[:pos], tok[pos:] 88 89 90def _ConsumeCallOffset(tok): 91 """Consumes a <call-offset> in a thunk symbol.""" 92 if tok[:1] == 'h': 93 lhs, rhs = _ConsumeOffset(tok, 1) 94 elif tok[:1] == 'v': 95 lhs, rhs = _ConsumeOffset(tok, 1) 96 lhs2, rhs = _ConsumeOffset(rhs) 97 if lhs and lhs2: 98 lhs = lhs + lhs2 99 else: 100 lhs, rhs = '', tok 101 else: 102 lhs, rhs = '', tok 103 return lhs, rhs 104 105 106def _FindThunkTarget(name): 107 """Finds thunk symbol's target function. 108 109 <thunk-symbol> ::= _ZT <call-offset> <base-encoding> 110 | _ZTc <call-offset> <call-offset> <base-encoding> 111 <call-offset> ::= h <nv-offset> 112 | v <v-offset> 113 <nv-offset> ::= <offset-number> _ 114 <v-offset> ::= <offset-number> _ <offset-number> _ 115 116 Args: 117 name: A string, the symbol name to resolve. 118 119 Returns: 120 A string, symbol name of the nominal target function. 121 The input symbol name if it is not a thunk symbol. 122 """ 123 if name.startswith('_ZTh') or name.startswith('_ZTv'): 124 lhs, rhs = _ConsumeCallOffset(name[len('_ZT'):]) 125 if lhs: 126 return '_Z' + rhs 127 if name.startswith('_ZTc'): 128 lhs, rhs = _ConsumeCallOffset(name[len('_ZTc'):]) 129 lhs2, rhs = _ConsumeCallOffset(rhs) 130 if lhs and lhs2: 131 return '_Z' + rhs 132 return name 133 134 135def _FilterElfFunctions(lsdump): 136 """Finds exported functions and thunks in lsdump. 137 138 Args: 139 lsdump: An AttrDict object containing the lsdump. 140 141 Yields: 142 The AttrDict objects in lsdump.elf_functions. 143 """ 144 functions = {function.linker_set_key for function in lsdump.functions} 145 146 for elf_function in lsdump.elf_functions: 147 if elf_function.name in functions: 148 yield elf_function 149 elif _FindThunkTarget(elf_function.name) in functions: 150 yield elf_function 151 152 153def _FilterElfObjects(lsdump): 154 """Finds exported variables, type info, and vtables in lsdump. 155 156 Args: 157 lsdump: An AttrDict object containing the lsdump. 158 159 Yields: 160 The AttrDict objects in lsdump.elf_objects. 161 """ 162 global_vars = {global_var.linker_set_key 163 for global_var in lsdump.global_vars} 164 record_names = {_GetTypeSymbol(record_type)[len('_ZTI'):] 165 for record_type in lsdump.record_types} 166 167 for elf_object in lsdump.elf_objects: 168 name = elf_object.name 169 if name in global_vars: 170 yield elf_object 171 elif (name[:len('_ZTI')] in {'_ZTV', '_ZTT', '_ZTI', '_ZTS'} and 172 name[len('_ZTI'):] in record_names): 173 yield elf_object 174 175 176def _ParseSymbolsFromLsdump(lsdump, output_dump): 177 """Parses symbols from an lsdump. 178 179 Args: 180 lsdump: An AttrDict object containing the lsdump. 181 output_dump: An AttrDict object containing the output. 182 """ 183 output_dump.elf_functions = list(_FilterElfFunctions(lsdump)) 184 output_dump.elf_objects = list(_FilterElfObjects(lsdump)) 185 186 187def _ParseVtablesFromLsdump(lsdump, output_dump): 188 """Parses vtables from an lsdump. 189 190 Args: 191 lsdump: An AttrDict object containing the lsdump. 192 output_dump: An AttrDict object containing the output. 193 """ 194 vtable_symbols = {elf_object.name for elf_object in lsdump.elf_objects 195 if elf_object.name.startswith('_ZTV')} 196 197 output_dump.record_types = [] 198 for lsdump_record_type in lsdump.record_types: 199 type_symbol = _GetTypeSymbol(lsdump_record_type) 200 vtable_symbol = '_ZTV' + type_symbol[len('_ZTI'):] 201 if vtable_symbol not in vtable_symbols: 202 continue 203 record_type = AttrDict() 204 record_type.linker_set_key = type_symbol 205 record_type.vtable_components = lsdump_record_type.vtable_components 206 output_dump.record_types.append(record_type) 207 208 209def _ParseLsdumpFileObject(lsdump_file): 210 """Converts an lsdump file to a dump object. 211 212 Args: 213 lsdump_file: The file object of the input lsdump. 214 215 Returns: 216 The AttrDict object converted from the lsdump file. 217 218 Raises: 219 LsdumpError if fails to create the dump file. 220 """ 221 try: 222 lsdump = json.load(lsdump_file, object_hook=AttrDict) 223 except ValueError: 224 raise LsdumpError(e) 225 226 try: 227 output_dump = AttrDict() 228 _ParseVtablesFromLsdump(lsdump, output_dump) 229 _ParseSymbolsFromLsdump(lsdump, output_dump) 230 return output_dump 231 except AttributeError as e: 232 raise LsdumpError(e) 233 234 235def _ParseLsdumpZipFile(lsdump_zip, output_zip): 236 """Converts zipped lsdump files to the dump files for ABI test.""" 237 for name in lsdump_zip.namelist(): 238 if name.endswith(".lsdump"): 239 with lsdump_zip.open(name, mode="r") as lsdump_file: 240 output_dump = _ParseLsdumpFileObject(lsdump_file) 241 output_str = json.dumps(output_dump, indent=1, 242 separators=(',', ':')) 243 output_zip.writestr(name, output_str) 244 245 246def ParseLsdumpFile(input_path, output_path): 247 """Converts an lsdump file to a dump file for the ABI test. 248 249 Args: 250 input_path: The path to the (gzipped) lsdump file. 251 output_path: The path to the output dump file. 252 253 Raises: 254 LsdumpError if fails to create the dump file. 255 """ 256 abs_output_path = os.path.abspath(output_path) 257 abs_output_dir = os.path.dirname(abs_output_path) 258 259 try: 260 if abs_output_dir and not os.path.exists(abs_output_dir): 261 os.makedirs(abs_output_dir) 262 263 with _OpenFileOrGzipped(input_path) as lsdump_file: 264 output_dump = _ParseLsdumpFileObject(lsdump_file) 265 with open(output_path, 'wb') as output_file: 266 json.dump(output_dump, output_file, 267 indent=1, separators=(',', ':')) 268 except (IOError, OSError) as e: 269 raise LsdumpError(e) 270 271 272def main(): 273 arg_parser = argparse.ArgumentParser( 274 description='This script converts an lsdump file to a dump file for ' 275 'the ABI test in VTS.') 276 arg_parser.add_argument('input_path', 277 help='input lsdump file path.') 278 arg_parser.add_argument('output_path', 279 help='output dump file path.') 280 args = arg_parser.parse_args() 281 282 # TODO(b/150663999): Remove this when the build system is able to process 283 # the large file group. 284 if zipfile.is_zipfile(args.input_path): 285 with zipfile.ZipFile(args.input_path, mode='r') as input_zip: 286 # The zip will be added to a Python package. It is not necessary 287 # to reduce the file size. 288 with zipfile.ZipFile(args.output_path, mode='w', 289 compression=zipfile.ZIP_STORED) as output_zip: 290 _ParseLsdumpZipFile(input_zip, output_zip) 291 exit(0) 292 293 try: 294 ParseLsdumpFile(args.input_path, args.output_path) 295 except LsdumpError as e: 296 print(e) 297 exit(1) 298 299 300if __name__ == '__main__': 301 main() 302