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