1#!/usr/bin/env python3 2# 3# Copyright 2013 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7 8import argparse 9import collections 10import os 11import re 12import sys 13 14from pylib import constants 15from pylib.constants import host_paths 16 17# pylint: disable=wrong-import-order 18# Uses symbol.py from third_party/android_platform, not python's. 19with host_paths.SysPath( 20 host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, 21 position=0): 22 import symbol 23 24 25_RE_ASAN = re.compile( 26 r""" 27 (?P<prefix>.*?) 28 (?P<pos>\#\S*?) # position of the call in stack. 29 # escape the char "#" due to the VERBOSE flag. 30 \s+(\S*?)\s+ 31 \( # match the char "(". 32 (?P<lib>.*?) # library path. 33 \+0[xX](?P<addr>.*?) # address of the symbol in hex. 34 # the prefix "0x" is skipped. 35 \) # match the char ")". 36 """, re.VERBOSE) 37 38# This named tuple models a parsed Asan log line. 39AsanParsedLine = collections.namedtuple('AsanParsedLine', 40 'prefix,library,pos,rel_address') 41 42# This named tuple models an Asan log line. 'raw' is the raw content 43# while 'parsed' is None or an AsanParsedLine instance. 44AsanLogLine = collections.namedtuple('AsanLogLine', 'raw,parsed') 45 46def _ParseAsanLogLine(line): 47 """Parse line into corresponding AsanParsedLine value, if any, or None.""" 48 m = re.match(_RE_ASAN, line) 49 if not m: 50 return None 51 return AsanParsedLine(prefix=m.group('prefix'), 52 library=m.group('lib'), 53 pos=m.group('pos'), 54 rel_address=int(m.group('addr'), 16)) 55 56 57def _FindASanLibraries(): 58 asan_lib_dir = os.path.join(host_paths.DIR_SOURCE_ROOT, 59 'third_party', 'llvm-build', 60 'Release+Asserts', 'lib') 61 asan_libs = [] 62 for src_dir, _, files in os.walk(asan_lib_dir): 63 asan_libs += [os.path.relpath(os.path.join(src_dir, f)) 64 for f in files 65 if f.endswith('.so')] 66 return asan_libs 67 68 69def _TranslateLibPath(library, asan_libs): 70 for asan_lib in asan_libs: 71 if os.path.basename(library) == os.path.basename(asan_lib): 72 return '/' + asan_lib 73 # pylint: disable=no-member 74 return symbol.TranslateLibPath(library) 75 76 77def _PrintSymbolized(asan_input, arch): 78 """Print symbolized logcat output for Asan symbols. 79 80 Args: 81 asan_input: list of input lines. 82 arch: Target CPU architecture. 83 """ 84 asan_libs = _FindASanLibraries() 85 86 # Maps library -> [ AsanParsedLine... ] 87 libraries = collections.defaultdict(list) 88 89 asan_log_lines = [] 90 for line in asan_input: 91 line = line.rstrip() 92 parsed = _ParseAsanLogLine(line) 93 if parsed: 94 libraries[parsed.library].append(parsed) 95 asan_log_lines.append(AsanLogLine(raw=line, parsed=parsed)) 96 97 # Maps library -> { address -> [(symbol, location, obj_sym_with_offset)...] } 98 all_symbols = collections.defaultdict(dict) 99 100 for library, items in libraries.items(): 101 libname = _TranslateLibPath(library, asan_libs) 102 lib_relative_addrs = set(i.rel_address for i in items) 103 # pylint: disable=no-member 104 symbols_by_library = symbol.SymbolInformationForSet(libname, 105 lib_relative_addrs, 106 True, 107 cpu_arch=arch) 108 if symbols_by_library: 109 all_symbols[library] = symbols_by_library 110 111 for log_line in asan_log_lines: 112 m = log_line.parsed 113 if (m and m.library in all_symbols and 114 m.rel_address in all_symbols[m.library]): 115 # NOTE: all_symbols[lib][address] is a never-emtpy list of tuples. 116 # NOTE: The documentation for SymbolInformationForSet() indicates 117 # that usually one wants to display the last list item, not the first. 118 # The code below takes the first, is this the best choice here? 119 s = all_symbols[m.library][m.rel_address][0] 120 symbol_name = s[0] 121 symbol_location = s[1] 122 print('%s%s %s %s @ \'%s\'' % 123 (m.prefix, m.pos, hex(m.rel_address), symbol_name, symbol_location)) 124 else: 125 print(log_line.raw) 126 127 128def main(): 129 parser = argparse.ArgumentParser() 130 parser.add_argument('-l', 131 '--logcat', 132 help='File containing adb logcat output with ASan ' 133 'stacks. Use stdin if not specified.') 134 parser.add_argument('--output-directory', 135 help='Path to the root build directory.') 136 parser.add_argument('--arch', default='arm', help='CPU architecture name') 137 args = parser.parse_args() 138 139 if args.output_directory: 140 constants.SetOutputDirectory(args.output_directory) 141 # Do an up-front test that the output directory is known. 142 constants.CheckOutputDirectory() 143 144 if args.logcat: 145 asan_input = open(args.logcat, 'r') 146 else: 147 asan_input = sys.stdin 148 149 _PrintSymbolized(asan_input.readlines(), args.arch) 150 151 152if __name__ == "__main__": 153 sys.exit(main()) 154