asan_symbolize.py 5.1 KB

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