| #!/usr/bin/env python3 |
| # |
| # Copyright 2013 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| |
| import argparse |
| import collections |
| import os |
| import re |
| import sys |
| |
| from pylib import constants |
| from pylib.constants import host_paths |
| |
| # pylint: disable=wrong-import-order |
| # Uses symbol.py from third_party/android_platform, not python's. |
| with host_paths.SysPath( |
| host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, |
| position=0): |
| import symbol |
| |
| |
| _RE_ASAN = re.compile( |
| r""" |
| (?P<prefix>.*?) |
| (?P<pos>\#\S*?) # position of the call in stack. |
| # escape the char "#" due to the VERBOSE flag. |
| \s+(\S*?)\s+ |
| \( # match the char "(". |
| (?P<lib>.*?) # library path. |
| \+0[xX](?P<addr>.*?) # address of the symbol in hex. |
| # the prefix "0x" is skipped. |
| \) # match the char ")". |
| """, re.VERBOSE) |
| |
| # This named tuple models a parsed Asan log line. |
| AsanParsedLine = collections.namedtuple('AsanParsedLine', |
| 'prefix,library,pos,rel_address') |
| |
| # This named tuple models an Asan log line. 'raw' is the raw content |
| # while 'parsed' is None or an AsanParsedLine instance. |
| AsanLogLine = collections.namedtuple('AsanLogLine', 'raw,parsed') |
| |
| def _ParseAsanLogLine(line): |
| """Parse line into corresponding AsanParsedLine value, if any, or None.""" |
| m = re.match(_RE_ASAN, line) |
| if not m: |
| return None |
| return AsanParsedLine(prefix=m.group('prefix'), |
| library=m.group('lib'), |
| pos=m.group('pos'), |
| rel_address=int(m.group('addr'), 16)) |
| |
| |
| def _FindASanLibraries(): |
| asan_lib_dir = os.path.join(host_paths.DIR_SOURCE_ROOT, |
| 'third_party', 'llvm-build', |
| 'Release+Asserts', 'lib') |
| asan_libs = [] |
| for src_dir, _, files in os.walk(asan_lib_dir): |
| asan_libs += [os.path.relpath(os.path.join(src_dir, f)) |
| for f in files |
| if f.endswith('.so')] |
| return asan_libs |
| |
| |
| def _TranslateLibPath(library, asan_libs): |
| for asan_lib in asan_libs: |
| if os.path.basename(library) == os.path.basename(asan_lib): |
| return '/' + asan_lib |
| # pylint: disable=no-member |
| return symbol.TranslateLibPath(library) |
| |
| |
| def _PrintSymbolized(asan_input, arch): |
| """Print symbolized logcat output for Asan symbols. |
| |
| Args: |
| asan_input: list of input lines. |
| arch: Target CPU architecture. |
| """ |
| asan_libs = _FindASanLibraries() |
| |
| # Maps library -> [ AsanParsedLine... ] |
| libraries = collections.defaultdict(list) |
| |
| asan_log_lines = [] |
| for line in asan_input: |
| line = line.rstrip() |
| parsed = _ParseAsanLogLine(line) |
| if parsed: |
| libraries[parsed.library].append(parsed) |
| asan_log_lines.append(AsanLogLine(raw=line, parsed=parsed)) |
| |
| # Maps library -> { address -> [(symbol, location, obj_sym_with_offset)...] } |
| all_symbols = collections.defaultdict(dict) |
| |
| for library, items in libraries.items(): |
| libname = _TranslateLibPath(library, asan_libs) |
| lib_relative_addrs = set(i.rel_address for i in items) |
| # pylint: disable=no-member |
| symbols_by_library = symbol.SymbolInformationForSet(libname, |
| lib_relative_addrs, |
| True, |
| cpu_arch=arch) |
| if symbols_by_library: |
| all_symbols[library] = symbols_by_library |
| |
| for log_line in asan_log_lines: |
| m = log_line.parsed |
| if (m and m.library in all_symbols and |
| m.rel_address in all_symbols[m.library]): |
| # NOTE: all_symbols[lib][address] is a never-emtpy list of tuples. |
| # NOTE: The documentation for SymbolInformationForSet() indicates |
| # that usually one wants to display the last list item, not the first. |
| # The code below takes the first, is this the best choice here? |
| s = all_symbols[m.library][m.rel_address][0] |
| symbol_name = s[0] |
| symbol_location = s[1] |
| print('%s%s %s %s @ \'%s\'' % |
| (m.prefix, m.pos, hex(m.rel_address), symbol_name, symbol_location)) |
| else: |
| print(log_line.raw) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('-l', |
| '--logcat', |
| help='File containing adb logcat output with ASan ' |
| 'stacks. Use stdin if not specified.') |
| parser.add_argument('--output-directory', |
| help='Path to the root build directory.') |
| parser.add_argument('--arch', default='arm', help='CPU architecture name') |
| args = parser.parse_args() |
| |
| if args.output_directory: |
| constants.SetOutputDirectory(args.output_directory) |
| # Do an up-front test that the output directory is known. |
| constants.CheckOutputDirectory() |
| |
| if args.logcat: |
| asan_input = open(args.logcat, 'r') |
| else: |
| asan_input = sys.stdin |
| |
| _PrintSymbolized(asan_input.readlines(), args.arch) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |