Clean up the stack trace symbolization tool.
[cherry-picked from internal master] Change-Id: Idba672b1c12d17b790c7901514f90b7f14f7256c
This commit is contained in:
380
scripts/stack
380
scripts/stack
@@ -1,18 +1,25 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2006 Google Inc. All Rights Reserved.
|
||||
# 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 getpass
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib
|
||||
|
||||
import stack_core
|
||||
import symbol
|
||||
|
||||
|
||||
@@ -22,17 +29,8 @@ def PrintUsage():
|
||||
print
|
||||
print " usage: " + sys.argv[0] + " [options] [FILE]"
|
||||
print
|
||||
print " --symbols-dir=path"
|
||||
print " the path to a symbols dir, such as =/tmp/out/target/product/dream/symbols"
|
||||
print
|
||||
print " --symbols-zip=path"
|
||||
print " the path to a symbols zip file, such as =dream-symbols-12345.zip"
|
||||
print
|
||||
print " --auto"
|
||||
print " attempt to:"
|
||||
print " 1) automatically find the build number in the crash"
|
||||
print " 2) if it's an official build, download the symbols "
|
||||
print " from the build server, and use them"
|
||||
print " --arch=arm|x86"
|
||||
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"
|
||||
@@ -44,347 +42,23 @@ def PrintUsage():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class SSOCookie(object):
|
||||
"""Creates a cookie file so we can download files from the build server."""
|
||||
|
||||
def __init__(self, cookiename=".sso.cookie", keep=False):
|
||||
self.sso_server = "login.corp.google.com"
|
||||
self.name = cookiename
|
||||
self.keeper = keep
|
||||
if not os.path.exists(self.name):
|
||||
user = os.environ["USER"]
|
||||
print "\n%s, to access the symbols, please enter your LDAP " % user,
|
||||
sys.stdout.flush()
|
||||
password = getpass.getpass()
|
||||
params = urllib.urlencode({"u": user, "pw": password})
|
||||
url = "https://%s/login?ssoformat=CORP_SSO" % self.sso_server
|
||||
# login to SSO
|
||||
curlcmd = ["/usr/bin/curl",
|
||||
"--cookie", self.name,
|
||||
"--cookie-jar", self.name,
|
||||
"--silent",
|
||||
"--location",
|
||||
"--data", params,
|
||||
"--output", "/dev/null",
|
||||
url]
|
||||
subprocess.check_call(curlcmd)
|
||||
if os.path.exists(self.name):
|
||||
os.chmod(self.name, 0600)
|
||||
else:
|
||||
print "Could not log in to SSO"
|
||||
sys.exit(1)
|
||||
|
||||
def __del__(self):
|
||||
"""Clean up."""
|
||||
if not self.keeper:
|
||||
os.remove(self.name)
|
||||
|
||||
|
||||
class NoBuildIDException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def FindBuildFingerprint(lines):
|
||||
"""Searches the given file (array of lines) for the build fingerprint."""
|
||||
fingerprint_regex = re.compile("^.*Build fingerprint:\s'(?P<fingerprint>.*)'")
|
||||
for line in lines:
|
||||
fingerprint_search = fingerprint_regex.match(line.strip())
|
||||
if fingerprint_search:
|
||||
return fingerprint_search.group("fingerprint")
|
||||
|
||||
return None # didn't find the fingerprint string, so return none
|
||||
|
||||
|
||||
class SymbolDownloadException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
DEFAULT_SYMROOT = "/tmp/symbols"
|
||||
|
||||
|
||||
def DownloadSymbols(fingerprint, cookie):
|
||||
"""Attempts to download the symbols from the build server.
|
||||
|
||||
If successful, extracts them, and returns the path.
|
||||
|
||||
Args:
|
||||
fingerprint: build fingerprint from the input stack trace
|
||||
cookie: SSOCookie
|
||||
|
||||
Returns:
|
||||
tuple (None, None) if no fingerprint is provided. Otherwise
|
||||
tuple (root directory, symbols directory).
|
||||
|
||||
Raises:
|
||||
SymbolDownloadException: Problem downloading symbols for fingerprint
|
||||
"""
|
||||
if fingerprint is None:
|
||||
return (None, None)
|
||||
symdir = "%s/%s" % (DEFAULT_SYMROOT, hash(fingerprint))
|
||||
if not os.path.exists(symdir):
|
||||
os.makedirs(symdir)
|
||||
# build server figures out the branch based on the CL
|
||||
params = {
|
||||
"op": "GET-SYMBOLS-LINK",
|
||||
"fingerprint": fingerprint,
|
||||
}
|
||||
print "url: http://android-build/buildbot-update?" + urllib.urlencode(params)
|
||||
url = urllib.urlopen("http://android-build/buildbot-update?",
|
||||
urllib.urlencode(params)).readlines()[0]
|
||||
if not url:
|
||||
raise SymbolDownloadException("Build server down? Failed to find syms...")
|
||||
|
||||
regex_str = (r"(?P<base_url>http\:\/\/android-build\/builds\/.*\/[0-9]+)"
|
||||
r"(?P<img>.*)")
|
||||
url_regex = re.compile(regex_str)
|
||||
url_match = url_regex.match(url)
|
||||
if url_match is None:
|
||||
raise SymbolDownloadException("Unexpected results from build server URL...")
|
||||
|
||||
base_url = url_match.group("base_url")
|
||||
img = url_match.group("img")
|
||||
symbolfile = img.replace("-img-", "-symbols-")
|
||||
symurl = base_url + symbolfile
|
||||
localsyms = symdir + symbolfile
|
||||
|
||||
if not os.path.exists(localsyms):
|
||||
print "downloading %s ..." % symurl
|
||||
curlcmd = ["/usr/bin/curl",
|
||||
"--cookie", cookie.name,
|
||||
"--silent",
|
||||
"--location",
|
||||
"--write-out", "%{http_code}",
|
||||
"--output", localsyms,
|
||||
symurl]
|
||||
p = subprocess.Popen(curlcmd,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
code = p.stdout.read()
|
||||
err = p.stderr.read()
|
||||
if err:
|
||||
raise SymbolDownloadException("stderr from curl download: %s" % err)
|
||||
if code != "200":
|
||||
raise SymbolDownloadException("Faied to download %s" % symurl)
|
||||
else:
|
||||
print "using existing cache for symbols"
|
||||
|
||||
return UnzipSymbols(localsyms, symdir)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
return (symdir, glob.glob("%s/out/target/product/*/symbols" % symdir)[0])
|
||||
|
||||
|
||||
def PrintTraceLines(trace_lines):
|
||||
"""Print back trace."""
|
||||
maxlen = max(map(lambda tl: len(tl[1]), trace_lines))
|
||||
print
|
||||
print "Stack Trace:"
|
||||
print " RELADDR " + "FUNCTION".ljust(maxlen) + " FILE:LINE"
|
||||
for tl in trace_lines:
|
||||
(addr, symbol_with_offset, location) = tl
|
||||
print " %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location)
|
||||
return
|
||||
|
||||
|
||||
def PrintValueLines(value_lines):
|
||||
"""Print stack data values."""
|
||||
print
|
||||
print "Stack Data:"
|
||||
print " ADDR VALUE FILE:LINE/FUNCTION"
|
||||
for vl in value_lines:
|
||||
(addr, value, symbol_with_offset, location) = vl
|
||||
print " " + addr + " " + value + " " + location
|
||||
if location:
|
||||
print " " + symbol_with_offset
|
||||
return
|
||||
|
||||
UNKNOWN = "<unknown>"
|
||||
HEAP = "[heap]"
|
||||
STACK = "[stack]"
|
||||
|
||||
|
||||
def ConvertTrace(lines):
|
||||
"""Convert strings containing native crash to a stack."""
|
||||
process_info_line = re.compile("(pid: [0-9]+, tid: [0-9]+.*)")
|
||||
signal_line = re.compile("(signal [0-9]+ \(.*\).*)")
|
||||
register_line = re.compile("(([ ]*[0-9a-z]{2} [0-9a-f]{8}){4})")
|
||||
thread_line = re.compile("(.*)(\-\-\- ){15}\-\-\-")
|
||||
# Note taht both trace and value line matching allow for variable amounts of
|
||||
# whitespace (e.g. \t). This is because the we want to allow for the stack
|
||||
# tool to operate on AndroidFeedback provided system logs. AndroidFeedback
|
||||
# strips out double spaces that are found in tombsone files and logcat output.
|
||||
#
|
||||
# Examples of matched trace lines include lines from tombstone files like:
|
||||
# #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so
|
||||
# Or lines from AndroidFeedback crash report system logs like:
|
||||
# 03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so
|
||||
# Please note the spacing differences.
|
||||
trace_line = re.compile("(.*)\#([0-9]+)[ \t]+(..)[ \t]+([0-9a-f]{8})[ \t]+([^\r\n \t]*)( \((.*)\))?") # pylint: disable-msg=C6310
|
||||
# Examples of matched value lines include:
|
||||
# bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so
|
||||
# 03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so
|
||||
# Again, note the spacing differences.
|
||||
value_line = re.compile("(.*)([0-9a-f]{8})[ \t]+([0-9a-f]{8})[ \t]+([^\r\n \t]*)")
|
||||
# Lines from 'code around' sections of the output will be matched before
|
||||
# value lines because otheriwse the 'code around' sections will be confused as
|
||||
# value lines.
|
||||
#
|
||||
# Examples include:
|
||||
# 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
|
||||
# 03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
|
||||
code_line = re.compile("(.*)[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[ \r\n]") # pylint: disable-msg=C6310
|
||||
|
||||
trace_lines = []
|
||||
value_lines = []
|
||||
|
||||
for ln in lines:
|
||||
# AndroidFeedback adds zero width spaces into its crash reports. These
|
||||
# should be removed or the regular expresssions will fail to match.
|
||||
line = unicode(ln, errors='ignore')
|
||||
header = process_info_line.search(line)
|
||||
if header:
|
||||
print header.group(1)
|
||||
continue
|
||||
header = signal_line.search(line)
|
||||
if header:
|
||||
print header.group(1)
|
||||
continue
|
||||
header = register_line.search(line)
|
||||
if header:
|
||||
print header.group(1)
|
||||
continue
|
||||
if trace_line.match(line):
|
||||
match = trace_line.match(line)
|
||||
(unused_0, unused_1, unused_2,
|
||||
code_addr, area, symbol_present, symbol_name) = match.groups()
|
||||
|
||||
if area == UNKNOWN or area == HEAP or area == STACK:
|
||||
trace_lines.append((code_addr, area, area))
|
||||
else:
|
||||
# If a calls b which further calls c and c is inlined to b, we want to
|
||||
# display "a -> b -> c" in the stack trace instead of just "a -> c"
|
||||
(source_symbol,
|
||||
source_location,
|
||||
object_symbol_with_offset) = symbol.SymbolInformation(area, code_addr)
|
||||
if not source_symbol:
|
||||
if symbol_present:
|
||||
source_symbol = symbol.CallCppFilt(symbol_name)
|
||||
else:
|
||||
source_symbol = UNKNOWN
|
||||
if not source_location:
|
||||
source_location = area
|
||||
if not object_symbol_with_offset:
|
||||
object_symbol_with_offset = source_symbol
|
||||
if not object_symbol_with_offset.startswith(source_symbol):
|
||||
trace_lines.append(("v------>", source_symbol, source_location))
|
||||
trace_lines.append((code_addr,
|
||||
object_symbol_with_offset,
|
||||
source_location))
|
||||
else:
|
||||
trace_lines.append((code_addr,
|
||||
object_symbol_with_offset,
|
||||
source_location))
|
||||
if code_line.match(line):
|
||||
# Code lines should be ignored. If this were exluded the 'code around'
|
||||
# sections would trigger value_line matches.
|
||||
continue;
|
||||
if value_line.match(line):
|
||||
match = value_line.match(line)
|
||||
(unused_, addr, value, area) = match.groups()
|
||||
if area == UNKNOWN or area == HEAP or area == STACK or not area:
|
||||
value_lines.append((addr, value, area, ""))
|
||||
else:
|
||||
(source_symbol,
|
||||
source_location,
|
||||
object_symbol_with_offset) = symbol.SymbolInformation(area, value)
|
||||
if not source_location:
|
||||
source_location = ""
|
||||
if not object_symbol_with_offset:
|
||||
object_symbol_with_offset = UNKNOWN
|
||||
value_lines.append((addr,
|
||||
value,
|
||||
object_symbol_with_offset,
|
||||
source_location))
|
||||
header = thread_line.search(line)
|
||||
if header:
|
||||
if trace_lines:
|
||||
PrintTraceLines(trace_lines)
|
||||
|
||||
if value_lines:
|
||||
PrintValueLines(value_lines)
|
||||
trace_lines = []
|
||||
value_lines = []
|
||||
print
|
||||
print "-----------------------------------------------------\n"
|
||||
|
||||
if trace_lines:
|
||||
PrintTraceLines(trace_lines)
|
||||
|
||||
if value_lines:
|
||||
PrintValueLines(value_lines)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
options, arguments = getopt.getopt(sys.argv[1:], "",
|
||||
["auto",
|
||||
"symbols-dir=",
|
||||
"symbols-zip=",
|
||||
["arch=",
|
||||
"help"])
|
||||
except getopt.GetoptError, unused_error:
|
||||
PrintUsage()
|
||||
|
||||
zip_arg = None
|
||||
auto = False
|
||||
fingerprint = None
|
||||
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 == "--auto":
|
||||
auto = True
|
||||
elif option == "--arch":
|
||||
symbol.ARCH = value
|
||||
|
||||
if len(arguments) > 1:
|
||||
PrintUsage()
|
||||
|
||||
if auto:
|
||||
cookie = SSOCookie(".symbols.cookie")
|
||||
|
||||
if not arguments or arguments[0] == "-":
|
||||
print "Reading native crash info from stdin"
|
||||
f = sys.stdin
|
||||
@@ -395,22 +69,8 @@ def main():
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
|
||||
rootdir = None
|
||||
if auto:
|
||||
fingerprint = FindBuildFingerprint(lines)
|
||||
print "fingerprint:", fingerprint
|
||||
rootdir, symbol.SYMBOLS_DIR = DownloadSymbols(fingerprint, cookie)
|
||||
elif zip_arg:
|
||||
rootdir, symbol.SYMBOLS_DIR = UnzipSymbols(zip_arg)
|
||||
|
||||
print "Reading symbols from", symbol.SYMBOLS_DIR
|
||||
ConvertTrace(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)
|
||||
stack_core.ConvertTrace(lines)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
196
scripts/stack_core.py
Executable file
196
scripts/stack_core.py
Executable file
@@ -0,0 +1,196 @@
|
||||
#!/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 re
|
||||
|
||||
import symbol
|
||||
|
||||
def PrintTraceLines(trace_lines):
|
||||
"""Print back trace."""
|
||||
maxlen = max(map(lambda tl: len(tl[1]), trace_lines))
|
||||
print
|
||||
print "Stack Trace:"
|
||||
print " RELADDR " + "FUNCTION".ljust(maxlen) + " FILE:LINE"
|
||||
for tl in trace_lines:
|
||||
(addr, symbol_with_offset, location) = tl
|
||||
print " %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location)
|
||||
return
|
||||
|
||||
|
||||
def PrintValueLines(value_lines):
|
||||
"""Print stack data values."""
|
||||
maxlen = max(map(lambda tl: len(tl[2]), value_lines))
|
||||
print
|
||||
print "Stack Data:"
|
||||
print " ADDR VALUE " + "FUNCTION".ljust(maxlen) + " FILE:LINE"
|
||||
for vl in value_lines:
|
||||
(addr, value, symbol_with_offset, location) = vl
|
||||
print " %8s %8s %s %s" % (addr, value, symbol_with_offset.ljust(maxlen), location)
|
||||
return
|
||||
|
||||
UNKNOWN = "<unknown>"
|
||||
HEAP = "[heap]"
|
||||
STACK = "[stack]"
|
||||
|
||||
|
||||
def PrintOutput(trace_lines, value_lines):
|
||||
if trace_lines:
|
||||
PrintTraceLines(trace_lines)
|
||||
if value_lines:
|
||||
PrintValueLines(value_lines)
|
||||
|
||||
def PrintDivider():
|
||||
print
|
||||
print "-----------------------------------------------------\n"
|
||||
|
||||
def ConvertTrace(lines):
|
||||
"""Convert strings containing native crash to a stack."""
|
||||
process_info_line = re.compile("(pid: [0-9]+, tid: [0-9]+.*)")
|
||||
signal_line = re.compile("(signal [0-9]+ \(.*\).*)")
|
||||
register_line = re.compile("(([ ]*[0-9a-z]{2} [0-9a-f]{8}){4})")
|
||||
thread_line = re.compile("(.*)(\-\-\- ){15}\-\-\-")
|
||||
dalvik_jni_thread_line = re.compile("(\".*\" prio=[0-9]+ tid=[0-9]+ NATIVE.*)")
|
||||
dalvik_native_thread_line = re.compile("(\".*\" sysTid=[0-9]+ nice=[0-9]+.*)")
|
||||
# Note that both trace and value line matching allow for variable amounts of
|
||||
# whitespace (e.g. \t). This is because the we want to allow for the stack
|
||||
# tool to operate on AndroidFeedback provided system logs. AndroidFeedback
|
||||
# strips out double spaces that are found in tombsone files and logcat output.
|
||||
#
|
||||
# Examples of matched trace lines include lines from tombstone files like:
|
||||
# #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so
|
||||
# #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so (symbol)
|
||||
# Or lines from AndroidFeedback crash report system logs like:
|
||||
# 03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so
|
||||
# Please note the spacing differences.
|
||||
trace_line = re.compile("(.*)\#([0-9]+)[ \t]+(..)[ \t]+([0-9a-f]{8})[ \t]+([^\r\n \t]*)( \((.*)\))?") # pylint: disable-msg=C6310
|
||||
# Examples of matched value lines include:
|
||||
# bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so
|
||||
# bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so (symbol)
|
||||
# 03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so
|
||||
# Again, note the spacing differences.
|
||||
value_line = re.compile("(.*)([0-9a-f]{8})[ \t]+([0-9a-f]{8})[ \t]+([^\r\n \t]*)( \((.*)\))?")
|
||||
# Lines from 'code around' sections of the output will be matched before
|
||||
# value lines because otheriwse the 'code around' sections will be confused as
|
||||
# value lines.
|
||||
#
|
||||
# Examples include:
|
||||
# 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
|
||||
# 03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
|
||||
code_line = re.compile("(.*)[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[a-f0-9]{8}[ \t]*[ \r\n]") # pylint: disable-msg=C6310
|
||||
|
||||
trace_lines = []
|
||||
value_lines = []
|
||||
last_frame = -1
|
||||
|
||||
for ln in lines:
|
||||
# AndroidFeedback adds zero width spaces into its crash reports. These
|
||||
# should be removed or the regular expresssions will fail to match.
|
||||
line = unicode(ln, errors='ignore')
|
||||
process_header = process_info_line.search(line)
|
||||
signal_header = signal_line.search(line)
|
||||
register_header = register_line.search(line)
|
||||
thread_header = thread_line.search(line)
|
||||
dalvik_jni_thread_header = dalvik_jni_thread_line.search(line)
|
||||
dalvik_native_thread_header = dalvik_native_thread_line.search(line)
|
||||
if process_header or signal_header or register_header or thread_header \
|
||||
or dalvik_jni_thread_header or dalvik_native_thread_header:
|
||||
if trace_lines or value_lines:
|
||||
PrintOutput(trace_lines, value_lines)
|
||||
PrintDivider()
|
||||
trace_lines = []
|
||||
value_lines = []
|
||||
last_frame = -1
|
||||
if process_header:
|
||||
print process_header.group(1)
|
||||
if signal_header:
|
||||
print signal_header.group(1)
|
||||
if register_header:
|
||||
print register_header.group(1)
|
||||
if thread_header:
|
||||
print thread_header.group(1)
|
||||
if dalvik_jni_thread_header:
|
||||
print dalvik_jni_thread_header.group(1)
|
||||
if dalvik_native_thread_header:
|
||||
print dalvik_native_thread_header.group(1)
|
||||
continue
|
||||
if trace_line.match(line):
|
||||
match = trace_line.match(line)
|
||||
(unused_0, frame, unused_1,
|
||||
code_addr, area, symbol_present, symbol_name) = match.groups()
|
||||
|
||||
if frame <= last_frame and (trace_lines or value_lines):
|
||||
PrintOutput(trace_lines, value_lines)
|
||||
PrintDivider()
|
||||
trace_lines = []
|
||||
value_lines = []
|
||||
last_frame = frame
|
||||
|
||||
if area == UNKNOWN or area == HEAP or area == STACK:
|
||||
trace_lines.append((code_addr, "", area))
|
||||
else:
|
||||
# If a calls b which further calls c and c is inlined to b, we want to
|
||||
# display "a -> b -> c" in the stack trace instead of just "a -> c"
|
||||
info = symbol.SymbolInformation(area, code_addr)
|
||||
nest_count = len(info) - 1
|
||||
for (source_symbol, source_location, object_symbol_with_offset) in info:
|
||||
if not source_symbol:
|
||||
if symbol_present:
|
||||
source_symbol = symbol.CallCppFilt(symbol_name)
|
||||
else:
|
||||
source_symbol = UNKNOWN
|
||||
if not source_location:
|
||||
source_location = area
|
||||
if nest_count > 0:
|
||||
nest_count = nest_count - 1
|
||||
trace_lines.append(("v------>", source_symbol, source_location))
|
||||
else:
|
||||
if not object_symbol_with_offset:
|
||||
object_symbol_with_offset = source_symbol
|
||||
trace_lines.append((code_addr,
|
||||
object_symbol_with_offset,
|
||||
source_location))
|
||||
if code_line.match(line):
|
||||
# Code lines should be ignored. If this were exluded the 'code around'
|
||||
# sections would trigger value_line matches.
|
||||
continue;
|
||||
if value_line.match(line):
|
||||
match = value_line.match(line)
|
||||
(unused_, addr, value, area, symbol_present, symbol_name) = match.groups()
|
||||
if area == UNKNOWN or area == HEAP or area == STACK or not area:
|
||||
value_lines.append((addr, value, "", area))
|
||||
else:
|
||||
info = symbol.SymbolInformation(area, value)
|
||||
(source_symbol, source_location, object_symbol_with_offset) = info.pop()
|
||||
if not source_symbol:
|
||||
if symbol_present:
|
||||
source_symbol = symbol.CallCppFilt(symbol_name)
|
||||
else:
|
||||
source_symbol = UNKNOWN
|
||||
if not source_location:
|
||||
source_location = area
|
||||
if not object_symbol_with_offset:
|
||||
object_symbol_with_offset = source_symbol
|
||||
value_lines.append((addr,
|
||||
value,
|
||||
object_symbol_with_offset,
|
||||
source_location))
|
||||
|
||||
PrintOutput(trace_lines, value_lines)
|
||||
|
||||
|
||||
# vi: ts=2 sw=2
|
||||
@@ -1,6 +1,18 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2006 Google Inc. All Rights Reserved.
|
||||
# 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.
|
||||
|
||||
"""Module for looking up symbolic debugging information.
|
||||
|
||||
@@ -29,6 +41,10 @@ def FindSymbolsDir():
|
||||
|
||||
SYMBOLS_DIR = FindSymbolsDir()
|
||||
|
||||
ARCH = "arm"
|
||||
|
||||
TOOLCHAIN_INFO = None
|
||||
|
||||
def Uname():
|
||||
"""'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
|
||||
uname = os.uname()[0]
|
||||
@@ -44,9 +60,9 @@ def Uname():
|
||||
def ToolPath(tool, toolchain_info=None):
|
||||
"""Return a full qualified path to the specified tool"""
|
||||
if not toolchain_info:
|
||||
toolchain_info = TOOLCHAIN_INFO
|
||||
(label, target) = toolchain_info
|
||||
return os.path.join(ANDROID_BUILD_TOP, "prebuilt", Uname(), "toolchain", label, "bin",
|
||||
toolchain_info = FindToolchain()
|
||||
(label, platform, target) = toolchain_info
|
||||
return os.path.join(ANDROID_BUILD_TOP, "prebuilts/gcc", Uname(), platform, label, "bin",
|
||||
target + "-" + tool)
|
||||
|
||||
def FindToolchain():
|
||||
@@ -58,26 +74,32 @@ def FindToolchain():
|
||||
Returns:
|
||||
A pair of strings containing toolchain label and target prefix.
|
||||
"""
|
||||
global TOOLCHAIN_INFO
|
||||
if TOOLCHAIN_INFO is not None:
|
||||
return TOOLCHAIN_INFO
|
||||
|
||||
## Known toolchains, newer ones in the front.
|
||||
if ARCH == "arm":
|
||||
gcc_version = os.environ["TARGET_GCC_VERSION"]
|
||||
known_toolchains = [
|
||||
("arm-linux-androideabi-4.4.x", "arm-linux-androideabi"),
|
||||
("arm-eabi-4.4.3", "arm-eabi"),
|
||||
("arm-eabi-4.4.0", "arm-eabi"),
|
||||
("arm-eabi-4.3.1", "arm-eabi"),
|
||||
("arm-eabi-4.2.1", "arm-eabi")
|
||||
("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi"),
|
||||
]
|
||||
elif ARCH =="x86":
|
||||
known_toolchains = [
|
||||
("i686-android-linux-4.4.3", "x86", "i686-android-linux")
|
||||
]
|
||||
else:
|
||||
known_toolchains = []
|
||||
|
||||
# Look for addr2line to check for valid toolchain path.
|
||||
for (label, target) in known_toolchains:
|
||||
toolchain_info = (label, target);
|
||||
for (label, platform, target) in known_toolchains:
|
||||
toolchain_info = (label, platform, target);
|
||||
if os.path.exists(ToolPath("addr2line", toolchain_info)):
|
||||
TOOLCHAIN_INFO = toolchain_info
|
||||
return toolchain_info
|
||||
|
||||
raise Exception("Could not find tool chain")
|
||||
|
||||
TOOLCHAIN_INFO = FindToolchain()
|
||||
|
||||
def SymbolInformation(lib, addr):
|
||||
"""Look up symbol information about an address.
|
||||
|
||||
@@ -86,20 +108,19 @@ def SymbolInformation(lib, addr):
|
||||
addr: string hexidecimal address
|
||||
|
||||
Returns:
|
||||
For a given library and address, return tuple of: (source_symbol,
|
||||
source_location, object_symbol_with_offset) the values may be None
|
||||
if the information was unavailable.
|
||||
A list of the form [(source_symbol, source_location,
|
||||
object_symbol_with_offset)].
|
||||
|
||||
source_symbol may not be a prefix of object_symbol_with_offset if
|
||||
the source function was inlined in the object code of another
|
||||
function.
|
||||
If the function has been inlined then the list may contain
|
||||
more than one element with the symbols for the most deeply
|
||||
nested inlined location appearing first. The list is
|
||||
always non-empty, even if no information is available.
|
||||
|
||||
usually you want to display the object_symbol_with_offset and
|
||||
source_location, the source_symbol is only useful to show if the
|
||||
address was from an inlined function.
|
||||
Usually you want to display the source_location and
|
||||
object_symbol_with_offset from the last element in the list.
|
||||
"""
|
||||
info = SymbolInformationForSet(lib, set([addr]))
|
||||
return (info and info.get(addr)) or (None, None, None)
|
||||
return (info and info.get(addr)) or [(None, None, None)]
|
||||
|
||||
|
||||
def SymbolInformationForSet(lib, unique_addrs):
|
||||
@@ -110,17 +131,17 @@ def SymbolInformationForSet(lib, unique_addrs):
|
||||
unique_addrs: set of hexidecimal addresses
|
||||
|
||||
Returns:
|
||||
For a given library and set of addresses, returns a dictionary of the form
|
||||
{addr: (source_symbol, source_location, object_symbol_with_offset)}. The
|
||||
values may be None if the information was unavailable.
|
||||
A dictionary of the form {addr: [(source_symbol, source_location,
|
||||
object_symbol_with_offset)]} where each address has a list of
|
||||
associated symbols and locations. The list is always non-empty.
|
||||
|
||||
For a given address, source_symbol may not be a prefix of
|
||||
object_symbol_with_offset if the source function was inlined in the
|
||||
object code of another function.
|
||||
If the function has been inlined then the list may contain
|
||||
more than one element with the symbols for the most deeply
|
||||
nested inlined location appearing first. The list is
|
||||
always non-empty, even if no information is available.
|
||||
|
||||
Usually you want to display the object_symbol_with_offset and
|
||||
source_location; the source_symbol is only useful to show if the
|
||||
address was from an inlined function.
|
||||
Usually you want to display the source_location and
|
||||
object_symbol_with_offset from the last element in the list.
|
||||
"""
|
||||
if not lib:
|
||||
return None
|
||||
@@ -135,14 +156,17 @@ def SymbolInformationForSet(lib, unique_addrs):
|
||||
|
||||
result = {}
|
||||
for addr in unique_addrs:
|
||||
(source_symbol, source_location) = addr_to_line.get(addr, (None, None))
|
||||
source_info = addr_to_line.get(addr)
|
||||
if not source_info:
|
||||
source_info = [(None, None)]
|
||||
if addr in addr_to_objdump:
|
||||
(object_symbol, object_offset) = addr_to_objdump.get(addr)
|
||||
object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
|
||||
object_offset)
|
||||
else:
|
||||
object_symbol_with_offset = None
|
||||
result[addr] = (source_symbol, source_location, object_symbol_with_offset)
|
||||
result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
|
||||
for (source_symbol, source_location) in source_info]
|
||||
|
||||
return result
|
||||
|
||||
@@ -155,8 +179,13 @@ def CallAddr2LineForSet(lib, unique_addrs):
|
||||
unique_addrs: set of string hexidecimal addresses look up.
|
||||
|
||||
Returns:
|
||||
A dictionary of the form {addr: (symbol, file:line)}. The values may
|
||||
be (None, None) if the address could not be looked up.
|
||||
A dictionary of the form {addr: [(symbol, file:line)]} where
|
||||
each address has a list of associated symbols and locations
|
||||
or an empty list if no symbol information was found.
|
||||
|
||||
If the function has been inlined then the list may contain
|
||||
more than one element with the symbols for the most deeply
|
||||
nested inlined location appearing first.
|
||||
"""
|
||||
if not lib:
|
||||
return None
|
||||
@@ -166,8 +195,9 @@ def CallAddr2LineForSet(lib, unique_addrs):
|
||||
if not os.path.exists(symbols):
|
||||
return None
|
||||
|
||||
(label, target) = TOOLCHAIN_INFO
|
||||
cmd = [ToolPath("addr2line"), "--functions", "--demangle", "--exe=" + symbols]
|
||||
(label, platform, target) = FindToolchain()
|
||||
cmd = [ToolPath("addr2line"), "--functions", "--inlines",
|
||||
"--demangle", "--exe=" + symbols]
|
||||
child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
|
||||
result = {}
|
||||
@@ -175,18 +205,45 @@ def CallAddr2LineForSet(lib, unique_addrs):
|
||||
for addr in addrs:
|
||||
child.stdin.write("0x%s\n" % addr)
|
||||
child.stdin.flush()
|
||||
records = []
|
||||
first = True
|
||||
while True:
|
||||
symbol = child.stdout.readline().strip()
|
||||
if symbol == "??":
|
||||
symbol = None
|
||||
location = child.stdout.readline().strip()
|
||||
if location == "??:0":
|
||||
location = None
|
||||
result[addr] = (symbol, location)
|
||||
if symbol is None and location is None:
|
||||
break
|
||||
records.append((symbol, location))
|
||||
if first:
|
||||
# Write a blank line as a sentinel so we know when to stop
|
||||
# reading inlines from the output.
|
||||
# The blank line will cause addr2line to emit "??\n??:0\n".
|
||||
child.stdin.write("\n")
|
||||
first = False
|
||||
result[addr] = records
|
||||
child.stdin.close()
|
||||
child.stdout.close()
|
||||
return result
|
||||
|
||||
|
||||
def StripPC(addr):
|
||||
"""Strips the Thumb bit a program counter address when appropriate.
|
||||
|
||||
Args:
|
||||
addr: the program counter address
|
||||
|
||||
Returns:
|
||||
The stripped program counter address.
|
||||
"""
|
||||
global ARCH
|
||||
|
||||
if ARCH == "arm":
|
||||
return addr & ~1
|
||||
return addr
|
||||
|
||||
def CallObjdumpForSet(lib, unique_addrs):
|
||||
"""Use objdump to find out the names of the containing functions.
|
||||
|
||||
@@ -209,13 +266,13 @@ def CallObjdumpForSet(lib, unique_addrs):
|
||||
return None
|
||||
|
||||
addrs = sorted(unique_addrs)
|
||||
start_addr_hex = addrs[0]
|
||||
stop_addr_dec = str(int(addrs[-1], 16) + 8)
|
||||
start_addr_dec = str(StripPC(int(addrs[0], 16)))
|
||||
stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
|
||||
cmd = [ToolPath("objdump"),
|
||||
"--section=.text",
|
||||
"--demangle",
|
||||
"--disassemble",
|
||||
"--start-address=0x" + start_addr_hex,
|
||||
"--start-address=" + start_addr_dec,
|
||||
"--stop-address=" + stop_addr_dec,
|
||||
symbols]
|
||||
|
||||
@@ -260,7 +317,7 @@ def CallObjdumpForSet(lib, unique_addrs):
|
||||
addr = components.group(1)
|
||||
target_addr = addrs[addr_index]
|
||||
i_addr = int(addr, 16)
|
||||
i_target = int(target_addr, 16)
|
||||
i_target = StripPC(int(target_addr, 16))
|
||||
if i_addr == i_target:
|
||||
result[target_addr] = (current_symbol, i_target - current_symbol_addr)
|
||||
addr_index += 1
|
||||
|
||||
Reference in New Issue
Block a user