blob: 4cbe87936a2f73475abcdc47913898fb23697a0a [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Symbolizes stack traces from logcat.
See https://developer.android.com/ndk/guides/ndk-stack for more information.
"""
from __future__ import print_function
import argparse
import os
import re
import subprocess
import sys
def find_llvm_symbolizer():
"""Finds the NDK llvm-symbolizer(1) binary.
Returns: An absolute path to llvm-symbolizer(1)."""
# ndk-stack is installed to $NDK/prebuilt/<platform>/bin, so from
# `~/Downloads/android-ndk-r18/prebuilt/linux-x86_64/bin/ndk-stack`...
# ...get `/usr/enh/Downloads/android-ndk-r18/`:
ndk_bin = os.path.dirname(os.path.realpath(__file__))
ndk_root = os.path.abspath(os.path.join(ndk_bin, '../../..'))
# ...get `linux-x86_64`:
arch = os.path.basename(os.path.abspath(os.path.join(ndk_bin, '../')))
# And from there construct the llvm-symbolizer path.
llvm_bin = os.path.join(ndk_root, 'toolchains', 'llvm', 'prebuilt', arch,
'bin')
return os.path.join(llvm_bin, 'llvm-symbolizer')
def main():
""""Program entry point."""
parser = argparse.ArgumentParser(
description='Symbolizes Android crashes.',
epilog='See <https://developer.android.com/ndk/guides/ndk-stack>.')
parser.add_argument(
'-sym',
'--sym',
dest='symbol_dir',
required=True, # TODO: default to '.'?
help='directory containing unstripped .so files')
parser.add_argument(
'-i',
'-dump',
'--dump',
dest='input',
default='-',
type=argparse.FileType('r'),
help='input filename')
args = parser.parse_args()
if not os.path.exists(args.symbol_dir):
sys.exit('{} does not exist!\n'.format(args.symbol_dir))
cmd = [
find_llvm_symbolizer(), '--demangle', '--functions=linkage',
'--inlining=true', '--use-symbol-table=true'
]
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
banner = '*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***'
in_crash = False
saw_frame = False
for line in args.input:
line = line.rstrip()
if not in_crash:
if banner in line:
in_crash = True
saw_frame = False
print('********** Crash dump: **********')
continue
for tag in ['Build fingerprint:', 'Abort message:']:
if tag in line:
print(line[line.find(tag):])
continue
# See Backtrace::FormatFrameData in libbacktrace.
# We're deliberately very loose because NDK users are likely to be
# looking at crashes on ancient OS releases.
# TODO: support asan stacks too?
m = re.match('.* +(#[0-9]+) +pc ([0-9a-f]+) +(([^ ]+).*)', line)
if m:
saw_frame = True
frame = m.group(1)
pc = m.group(2)
tail = m.group(3)
lib = m.group(4)
lib_path = os.path.join(args.symbol_dir, os.path.basename(lib))
out_line = '%s 0x%s %s' % (frame, pc, tail)
indent = (out_line.find('(') + 1) * ' '
print(out_line)
if os.path.exists(lib_path):
print('"%s" 0x%s' % (lib_path, pc), file=proc.stdin)
while True:
symbolizer_output = proc.stdout.readline().rstrip()
if not symbolizer_output:
break
# TODO: rewrite file names based on a source path?
print('%s%s' % (indent, symbolizer_output))
elif saw_frame:
in_crash = False
print('Crash dump is completed\n')
if __name__ == "__main__":
main()