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 22 23 24class LsdumpError(Exception): 25 """The exception raised by ParseLsdumpFile.""" 26 pass 27 28 29class AttrDict(dict): 30 """A dictionary with attribute accessors.""" 31 32 def __getattr__(self, key): 33 """Returns self[key].""" 34 try: 35 return self[key] 36 except KeyError: 37 raise AttributeError('Failed to get attribute: %s' % key) 38 39 def __setattr__(self, key, value): 40 """Assigns value to self[key].""" 41 self[key] = value 42 43 44def _OpenFileOrGzipped(file_name): 45 """Opens a file that is either in gzip or uncompressed format. 46 47 If file_name ends with '.gz' then return gzip.open(file_name, 'rb'), 48 else return open(file_name, 'rb'). 49 50 Args: 51 file_name: The file name to open. 52 53 Returns: 54 A file object. 55 56 Raises: 57 IOError if fails to open. 58 """ 59 if file_name.endswith('.gz'): 60 return gzip.open(file_name, 'rb') 61 return open(file_name, 'rb') 62 63 64def _ConsumeOffset(tok, beg=0): 65 """Consumes a <offset-number> in a thunk symbol.""" 66 pos = tok.find('_', beg) + 1 67 return tok[:pos], tok[pos:] 68 69 70def _ConsumeCallOffset(tok): 71 """Consumes a <call-offset> in a thunk symbol.""" 72 if tok[:1] == 'h': 73 lhs, rhs = _ConsumeOffset(tok, 1) 74 elif tok[:1] == 'v': 75 lhs, rhs = _ConsumeOffset(tok, 1) 76 lhs2, rhs = _ConsumeOffset(rhs) 77 if lhs and lhs2: 78 lhs = lhs + lhs2 79 else: 80 lhs, rhs = '', tok 81 else: 82 lhs, rhs = '', tok 83 return lhs, rhs 84 85 86def _FindThunkTarget(name): 87 """Finds thunk symbol's target function. 88 89 <thunk-symbol> ::= _ZT <call-offset> <base-encoding> 90 | _ZTc <call-offset> <call-offset> <base-encoding> 91 <call-offset> ::= h <nv-offset> 92 | v <v-offset> 93 <nv-offset> ::= <offset-number> _ 94 <v-offset> ::= <offset-number> _ <offset-number> _ 95 96 Args: 97 name: A string, the symbol name to resolve. 98 99 Returns: 100 A string, symbol name of the nominal target function. 101 The input symbol name if it is not a thunk symbol. 102 """ 103 if name.startswith('_ZTh') or name.startswith('_ZTv'): 104 lhs, rhs = _ConsumeCallOffset(name[len('_ZT'):]) 105 if lhs: 106 return '_Z' + rhs 107 if name.startswith('_ZTc'): 108 lhs, rhs = _ConsumeCallOffset(name[len('_ZTc'):]) 109 lhs2, rhs = _ConsumeCallOffset(rhs) 110 if lhs and lhs2: 111 return '_Z' + rhs 112 return name 113 114 115def _FilterElfFunctions(lsdump): 116 """Finds exported functions and thunks in lsdump. 117 118 Args: 119 lsdump: An AttrDict object containing the lsdump. 120 121 Yields: 122 The AttrDict objects in lsdump.elf_functions. 123 """ 124 functions = {function.linker_set_key for function in lsdump.functions} 125 126 for elf_function in lsdump.elf_functions: 127 if elf_function.name in functions: 128 yield elf_function 129 elif _FindThunkTarget(elf_function.name) in functions: 130 yield elf_function 131 132 133def _FilterElfObjects(lsdump): 134 """Finds exported variables, type info, and vtables in lsdump. 135 136 Args: 137 lsdump: An AttrDict object containing the lsdump. 138 139 Yields: 140 The AttrDict objects in lsdump.elf_objects. 141 """ 142 global_vars = {global_var.linker_set_key 143 for global_var in lsdump.global_vars} 144 record_names = {record_type.unique_id[len('_ZTS'):] 145 for record_type in lsdump.record_types} 146 147 for elf_object in lsdump.elf_objects: 148 name = elf_object.name 149 if name in global_vars: 150 yield elf_object 151 elif (name[:len('_ZTS')] in {'_ZTV', '_ZTT', '_ZTI', '_ZTS'} and 152 name[len('_ZTS'):] in record_names): 153 yield elf_object 154 155 156def _ParseSymbolsFromLsdump(lsdump, output_dump): 157 """Parses symbols from an lsdump. 158 159 Args: 160 lsdump: An AttrDict object containing the lsdump. 161 output_dump: An AttrDict object containing the output. 162 """ 163 output_dump.elf_functions = list(_FilterElfFunctions(lsdump)) 164 output_dump.elf_objects = list(_FilterElfObjects(lsdump)) 165 166 167def _ParseVtablesFromLsdump(lsdump, output_dump): 168 """Parses vtables from an lsdump. 169 170 Args: 171 lsdump: An AttrDict object containing the lsdump. 172 output_dump: An AttrDict object containing the output. 173 """ 174 vtable_symbols = {elf_object.name for elf_object in lsdump.elf_objects 175 if elf_object.name.startswith('_ZTV')} 176 177 output_dump.record_types = [] 178 for lsdump_record_type in lsdump.record_types: 179 type_symbol = lsdump_record_type.unique_id 180 vtable_symbol = '_ZTV' + type_symbol[len('_ZTS'):] 181 if vtable_symbol not in vtable_symbols: 182 continue 183 record_type = AttrDict() 184 record_type.unique_id = lsdump_record_type.unique_id 185 record_type.vtable_components = lsdump_record_type.vtable_components 186 output_dump.record_types.append(record_type) 187 188 189def ParseLsdumpFile(input_path, output_path): 190 """Converts an lsdump file to a dump file for the ABI test. 191 192 Args: 193 input_path: The path to the (gzipped) lsdump file. 194 output_path: The path to the output dump file. 195 196 Raises: 197 LsdumpError if fails to create the dump file. 198 """ 199 try: 200 with _OpenFileOrGzipped(input_path) as lsdump_file: 201 lsdump = json.load(lsdump_file, object_hook=AttrDict) 202 except (IOError, ValueError) as e: 203 raise LsdumpError(e) 204 205 try: 206 output_dump = AttrDict() 207 _ParseVtablesFromLsdump(lsdump, output_dump) 208 _ParseSymbolsFromLsdump(lsdump, output_dump) 209 except AttributeError as e: 210 raise LsdumpError(e) 211 212 abs_output_path = os.path.abspath(output_path) 213 abs_output_dir = os.path.dirname(abs_output_path) 214 215 try: 216 if abs_output_dir and not os.path.exists(abs_output_dir): 217 os.makedirs(abs_output_dir) 218 with open(output_path, 'wb') as output_file: 219 json.dump(output_dump, output_file, 220 indent=1, separators=(',', ':')) 221 except (IOError, OSError) as e: 222 raise LsdumpError(e) 223 224 225def main(): 226 arg_parser = argparse.ArgumentParser( 227 description='This script converts an lsdump file to a dump file for ' 228 'the ABI test in VTS.') 229 arg_parser.add_argument('input_path', 230 help='input lsdump file path.') 231 arg_parser.add_argument('output_path', 232 help='output dump file path.') 233 args = arg_parser.parse_args() 234 235 try: 236 ParseLsdumpFile(args.input_path, args.output_path) 237 except LsdumpError as e: 238 print(e) 239 exit(1) 240 241 242if __name__ == '__main__': 243 main() 244