#!/usr/bin/python # # Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0(the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Use addr2line to interpret tombstone contents The defaults should work if this is run after running lunch. """ from __future__ import print_function import argparse import collections import functools import multiprocessing import os import re import subprocess import sys # Patterns of things that we might want to match. patterns = [ (re.compile('(.* pc )([0-9a-f]+) +([^ ]+) .*'), 1, 3, 2), (re.compile('(.*)#[0-9]+ 0x[0-9a-f]+ +\((.*)\+0x([0-9a-f]+)\)'), 1, 2, 3)] LookupInfo = collections.namedtuple('LookupInfo', ['line_number', 'details', 'file_name']) def lookup_addr(args, object_path, address): try: if object_path[0] == os.path.sep: object_path = object_path[1:] parms = [args.addr2line, '-e', os.path.join(args.symbols, object_path), address] details = subprocess.check_output(parms).strip().split(':') return LookupInfo( line_number=details[-1], details=details, file_name=':'.join(details[:-1])) except subprocess.CalledProcessError: return None def simple_match(line, info, indent, out_file): print('{} // From {}:{}'.format( line, info.file_name, info.line_number), file=out_file) def source_match(line, info, indent, out_file): source = '' try: with open(info.file_name, 'r') as f: for i in range(int(info.line_number.split(' ')[0])): source = f.readline() # Fall back to the simple formatter on any error except Exception: simple_match(line, info, indent, out_file) return print(line, file=out_file) print('{}// From {}:{}'.format( ' ' * indent, info.file_name, info.line_number), file=out_file) print('{} {}'.format(' ' * indent, ' '.join(source.strip().split())), file=out_file) def process(in_file, out_file, args): for line in in_file: line = line.rstrip() groups = None for p in patterns: groups = p[0].match(line) if groups: break info = None if groups is not None: info = lookup_addr(args, groups.group(p[2]), groups.group(p[3])) if info is None: print(line, file=out_file) continue if args.source: source_match(line, info, len(groups.group(p[1])), out_file) else: simple_match(line, info, len(groups.group(p[1])), out_file) def process_file(path, args): with open(path + args.suffix, 'w') as out_file: with open(path, 'r') as in_file: process(in_file, out_file, args) def common_arg_parser(): parser = argparse.ArgumentParser(description= 'Add line information to a tombstone') parser.add_argument('--addr2line', type=str, help='Path to addr2line', default=os.path.join( os.environ.get('ANDROID_TOOLCHAIN', ''), 'x86_64-linux-android-addr2line')) parser.add_argument('files', metavar='FILE', type=str, nargs='+', help='a list of files to process') parser.add_argument('--jobs', type=int, default=32, help='Number of parallel jobs to run') parser.add_argument('--source', default=False, action='store_true', help='Attempt to print the source') parser.add_argument('--suffix', type=str, default='.txt', help='Suffix to add to the processed file') return parser def process_all(args): multiprocessing.Pool(32).map(functools.partial(process_file, args=args), args.files) if __name__ == '__main__': parser = common_arg_parser() parser.add_argument('--symbols', type=str, help='Path to the symbols', default=os.path.join( os.environ.get('ANDROID_PRODUCT_OUT', ''), 'symbols')) process_all(parser.parse_args())