Clean up the stack trace symbolization tool.
[cherry-picked from internal master]
Change-Id: Idba672b1c12d17b790c7901514f90b7f14f7256c
diff --git a/scripts/stack b/scripts/stack
index 6750752..6bb8d0a 100755
--- a/scripts/stack
+++ b/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 @@
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 @@
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 @@
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()
diff --git a/scripts/stack_core.py b/scripts/stack_core.py
new file mode 100755
index 0000000..42285d4
--- /dev/null
+++ b/scripts/stack_core.py
@@ -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
diff --git a/scripts/symbol.py b/scripts/symbol.py
index e36304c..0f58df6 100755
--- a/scripts/symbol.py
+++ b/scripts/symbol.py
@@ -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 @@
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 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 @@
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.
- 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")
- ]
+ if ARCH == "arm":
+ gcc_version = os.environ["TARGET_GCC_VERSION"]
+ known_toolchains = [
+ ("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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
for addr in addrs:
child.stdin.write("0x%s\n" % addr)
child.stdin.flush()
- symbol = child.stdout.readline().strip()
- if symbol == "??":
- symbol = None
- location = child.stdout.readline().strip()
- if location == "??:0":
- location = None
- result[addr] = (symbol, location)
+ records = []
+ first = True
+ while True:
+ symbol = child.stdout.readline().strip()
+ if symbol == "??":
+ symbol = None
+ location = child.stdout.readline().strip()
+ if location == "??:0":
+ location = None
+ 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 @@
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 @@
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