blob: 97913ab84191ed7e8d87219a50b702b28762815b [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2013 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.
"""stack symbolizes native crash dumps."""
import getopt
import glob
import os
import re
import sys
import stack_core
import subprocess
import symbol
_DEFAULT_SYMROOT = '/tmp/symbols'
_DEFAULT_BUILD_DIR = 'out/android_Debug'
_DEFAULT_NDK_DIR = 'third_party/android_tools/ndk'
def PrintUsage():
"""Print usage and exit with error."""
# pylint: disable-msg=C6310
print
print " usage: " + sys.argv[0] + " [options] [FILE]"
print
print " --symbols-dir=path"
print " the path to a symbols dir, this is generally for system level"
print " symbols"
print
print " --build-dir=path"
print " the path to a directory containing Mojo symbols (can be"
print " absolute or relative to src), such as =out/android_Debug"
print " --ndk-dir=path"
print " the path to a directory containing Android NDK"
print
print " --symbols-zip=path"
print " the path to a symbols zip file, such as =dream-symbols-12345.zip"
print
print " --more-info"
print " --less-info"
print " Change the level of detail in the output."
print " --more-info is slower and more verbose, but more functions will"
print " be fully qualified with namespace/classname and have full"
print " argument information. Also, the 'stack data' section will be"
print " printed."
print
print " --arch=arm|arm64|x64|x86|mips"
print " the target architecture"
print
print " FILE should contain a stack trace in it somewhere"
print " the tool will find that and re-print it with"
print " source files and line numbers. If you don't"
print " pass FILE, or if file is -, it reads from"
print " stdin."
print
# pylint: enable-msg=C6310
sys.exit(1)
def UnzipSymbols(symbolfile, symdir=None):
"""Unzips a file to _DEFAULT_SYMROOT and returns the unzipped location.
Args:
symbolfile: The .zip file to unzip
symdir: Optional temporary directory to use for extraction
Returns:
A tuple containing (the directory into which the zip file was unzipped,
the path to the "symbols" directory in the unzipped file). To clean
up, the caller can delete the first element of the tuple.
Raises:
SymbolDownloadException: When the unzip fails.
"""
if not symdir:
symdir = "%s/%s" % (_DEFAULT_SYMROOT, hash(symbolfile))
if not os.path.exists(symdir):
os.makedirs(symdir)
print "extracting %s..." % symbolfile
saveddir = os.getcwd()
os.chdir(symdir)
try:
unzipcode = subprocess.call(["unzip", "-qq", "-o", symbolfile])
if unzipcode > 0:
os.remove(symbolfile)
raise SymbolDownloadException("failed to extract symbol files (%s)."
% symbolfile)
finally:
os.chdir(saveddir)
android_symbols = glob.glob("%s/out/target/product/*/symbols" % symdir)
if android_symbols:
return (symdir, android_symbols[0])
else:
# This is a zip of Chrome symbols, so symbol.CHROME_SYMBOLS_DIR needs to be
# updated to point here.
symbol.CHROME_SYMBOLS_DIR = symdir
return (symdir, symdir)
def GetBasenameFromMojoApp(url):
"""Used by GetSymbolMapping() to extract the basename from the location the
mojo app was downloaded from. The location is a URL, e.g.
http://foo/bar/x.so."""
index = url.rfind('/')
return url[(index + 1):] if index != -1 else url
def GetSymboledNameForMojoApp(path):
"""Used by GetSymbolMapping to get the non-stripped library name for an
installed mojo app."""
# e.g. tracing.mojo -> libtracing_library.so
name, ext = os.path.splitext(path)
if ext != '.mojo':
return path
return 'lib%s_library.so' % name
def GetSymbolMapping(lines):
"""Returns a mapping (dictionary) from download file to .so."""
regex = re.compile('Caching mojo app (\S+) at (\S+)')
mappings = {}
for line in lines:
result = regex.search(line)
if result:
url = GetBasenameFromMojoApp(result.group(1))
mappings[result.group(2)] = GetSymboledNameForMojoApp(url)
return mappings
def _LowestAncestorContainingRelpath(dir_path, relpath):
"""Returns the lowest ancestor dir of |dir_path| that contains |relpath|.
"""
cur_dir_path = os.path.abspath(dir_path)
while True:
if os.path.exists(os.path.join(cur_dir_path, relpath)):
return cur_dir_path
next_dir_path = os.path.dirname(cur_dir_path)
if next_dir_path != cur_dir_path:
cur_dir_path = next_dir_path
else:
return None
def _GuessDir(relpath):
"""Returns absolute path to location |relpath| in the lowest ancestor of this
file that contains it."""
lowest_ancestor = _LowestAncestorContainingRelpath(
os.path.dirname(__file__), relpath)
if not lowest_ancestor:
return None
return os.path.join(lowest_ancestor, relpath)
def main():
try:
options, arguments = getopt.getopt(sys.argv[1:], "",
["more-info",
"less-info",
"build-dir=",
"ndk-dir=",
"symbols-dir=",
"symbols-zip=",
"arch=",
"help"])
except getopt.GetoptError, unused_error:
PrintUsage()
zip_arg = None
more_info = False
for option, value in options:
if option == "--help":
PrintUsage()
elif option == "--symbols-dir":
symbol.SYMBOLS_DIR = os.path.expanduser(value)
elif option == "--symbols-zip":
zip_arg = os.path.expanduser(value)
elif option == "--arch":
symbol.ARCH = value
elif option == "--build-dir":
symbol.BUILD_DIR = value
elif option == "--ndk-dir":
symbol.NDK_DIR = value
elif option == "--more-info":
more_info = True
elif option == "--less-info":
more_info = False
if not symbol.BUILD_DIR:
guess = _GuessDir(_DEFAULT_BUILD_DIR)
if not guess:
print "Couldn't find the build directory, please pass --build-dir."
return 1
print "Inferring the build directory path as " + guess
symbol.BUILD_DIR = guess
if not symbol.NDK_DIR:
guess = _GuessDir(_DEFAULT_NDK_DIR)
if not guess:
print "Couldn't find the Android NDK, please pass --ndk-dir."
return 1
print "Inferring the Android NDK path as " + guess
symbol.NDK_DIR = guess
if len(arguments) > 1:
PrintUsage()
if not arguments or arguments[0] == "-":
print "Reading native crash info from stdin"
f = sys.stdin
else:
print "Searching for native crashes in %s" % arguments[0]
f = open(arguments[0], "r")
lines = f.readlines()
f.close()
rootdir = None
if zip_arg:
rootdir, symbol.SYMBOLS_DIR = UnzipSymbols(zip_arg)
if symbol.SYMBOLS_DIR:
print "Reading Android symbols from", symbol.SYMBOLS_DIR
print "Reading Mojo symbols from", symbol.BUILD_DIR
stack_core.ConvertTrace(lines, more_info, GetSymbolMapping(lines))
if rootdir:
# be a good citizen and clean up...os.rmdir and os.removedirs() don't work
cmd = "rm -rf \"%s\"" % rootdir
print "\ncleaning up (%s)" % cmd
os.system(cmd)
if __name__ == "__main__":
main()
# vi: ts=2 sw=2