123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- #!/usr/bin/env python3
- #
- # Copyright 2013 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- from __future__ import print_function
- 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())
|