| #!src/build/run_python |
| |
| # Copyright 2014 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. |
| |
| """Analyzes the crash dump. |
| Usage: |
| |
| $ src/build/crash_analyzer.py |
| out/target/nacl_i686/intermediates/bionic_test/bionic_test.results.1.tmp |
| |
| Note that this is also integrated in launch_chrome.py. |
| """ |
| |
| import argparse |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| from src.build import build_common |
| from src.build import toolchain |
| from src.build.build_options import OPTIONS |
| |
| |
| class CrashAnalyzer(object): |
| def __init__(self, is_annotating=False): |
| self._text_segments = [] |
| self._crash_addr = None |
| # A map from basename to path of binaries. Loaded lazily when |
| # crash is found. |
| self._binary_map = None |
| self._is_annotating = is_annotating |
| |
| def handle_line(self, line): |
| if self._is_annotating: |
| sys.stdout.write(line) |
| |
| line = line.strip() |
| matched = re.match( |
| r'linker: Loaded text: 0x([0-9a-fA-F]+)-0x([0-9a-fA-F]+) (.*)', line) |
| if matched: |
| start_addr = int(matched.group(1), 16) |
| end_addr = int(matched.group(2), 16) |
| binary_name = matched.group(3) |
| self._text_segments.append((binary_name, start_addr, end_addr)) |
| return False |
| |
| if self._is_annotating: |
| addr_reg = r'.*0x([0-9a-fA-F]+)' |
| else: |
| addr_reg = r'\*\* Signal \d+ from untrusted code: pc=([0-9a-fA-F]+)' |
| |
| matched = re.match(addr_reg, line) |
| if matched: |
| self._crash_addr = int(matched.group(1), 16) |
| # Convert 64bit address to 32bit for x86-64. |
| self._crash_addr &= (1 << 32) - 1 |
| return True |
| |
| return False |
| |
| def init_binary_map(self): |
| if self._binary_map: |
| return |
| |
| self._binary_map = {} |
| for dirpath, dirnames, filenames in ( |
| os.walk(build_common.get_load_library_path())): |
| for filename in filenames: |
| if re.match(r'arc_[^/]*\.nexe$', filename): |
| name = '/lib/main.nexe' |
| print 'Used %s as main.nexe' % filename |
| else: |
| name = os.path.basename(filename) |
| if name in self._binary_map: |
| raise Exception('Duplicated binary: ' + name) |
| self._binary_map[name] = os.path.join(dirpath, filename) |
| |
| def get_crash_report(self): |
| assert self._crash_addr is not None |
| for binary_name, start_addr, end_addr in self._text_segments: |
| if start_addr > self._crash_addr or self._crash_addr >= end_addr: |
| continue |
| |
| addr = self._crash_addr |
| # For PIC or PIE, we need to subtract the load bias. |
| if binary_name.endswith('.so') or OPTIONS.is_bare_metal_build(): |
| addr -= start_addr |
| |
| if os.path.exists(binary_name): |
| binary_filename = binary_name |
| else: |
| self.init_binary_map() |
| if binary_name not in self._binary_map: |
| return '%s %x (binary file not found)\n' % (binary_name, addr) |
| binary_filename = self._binary_map[binary_name] |
| |
| pipe = subprocess.Popen([toolchain.get_tool(OPTIONS.target(), |
| 'addr2line'), |
| '-e', binary_filename], |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| addr2line_result = pipe.communicate('%x\n' % addr)[0] |
| |
| # We can always get clean result using 32 byte aligned start |
| # address as NaCl binary does never overlap 32 byte boundary. |
| objdump_start_addr = (addr & ~31) - 32 |
| objdump_end_addr = addr + 64 |
| pipe = subprocess.Popen([toolchain.get_tool(OPTIONS.target(), |
| 'objdump'), |
| '-SC', binary_filename, |
| '--start-address', '0x%x' % objdump_start_addr, |
| '--stop-address', '0x%x' % objdump_end_addr], |
| stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| objdump_result = pipe.communicate('%x\n' % addr)[0] |
| |
| if self._is_annotating: |
| report = ('[[ %s 0x%x %s ]]' % |
| (binary_filename, addr, addr2line_result.strip())) |
| # The result of objdump is too verbose for annotation. |
| else: |
| report = '%s 0x%x\n' % (binary_filename, addr) |
| report += addr2line_result |
| report += objdump_result |
| |
| return report |
| return 'Failed to retrieve a crash report\n' |
| |
| |
| def _parse_args(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--annotate', action='store_true', |
| help='All hex values which start with 0x will be ' |
| 'annotated.') |
| parser.add_argument('target_files', nargs='*') |
| return parser.parse_args() |
| |
| |
| def main(): |
| OPTIONS.parse_configure_file() |
| args = _parse_args() |
| for arg in args.target_files: |
| with open(arg) as f: |
| analyzer = CrashAnalyzer(is_annotating=args.annotate) |
| for line in f: |
| if analyzer.handle_line(line): |
| print analyzer.get_crash_report() |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |