diff options
author | Ben Cheng <bccheng@google.com> | 2013-04-25 15:14:04 -0700 |
---|---|---|
committer | Ben Cheng <bccheng@google.com> | 2013-04-25 15:34:14 -0700 |
commit | b42dad0dee1ab7f0c61e9086859ecf8a1f2e070e (patch) | |
tree | 2d03accc0e534662b08a3a40eb5d710d18842d05 | |
parent | b32539b1e4534443b0e2b1cfdde056506e66dbd3 (diff) | |
download | android_development-b42dad0dee1ab7f0c61e9086859ecf8a1f2e070e.tar.gz android_development-b42dad0dee1ab7f0c61e9086859ecf8a1f2e070e.tar.bz2 android_development-b42dad0dee1ab7f0c61e9086859ecf8a1f2e070e.zip |
Clean up the stack trace symbolization tool.
Change-Id: Id71cacde653a5c1c92a028ee80f5aa8264e1963a
-rwxr-xr-x | scripts/stack | 380 | ||||
-rwxr-xr-x | scripts/stack_core.py | 196 | ||||
-rwxr-xr-x | scripts/symbol.py | 160 |
3 files changed, 324 insertions, 412 deletions
diff --git a/scripts/stack b/scripts/stack index 6750752af..6bb8d0acb 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 @@ 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() diff --git a/scripts/stack_core.py b/scripts/stack_core.py new file mode 100755 index 000000000..42285d462 --- /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 b0e94d825..0f58df64c 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 @@ 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, "prebuilts", "gcc", Uname(), "arm", 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,27 +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. - known_toolchains = [ - ("arm-eabi-4.6", "arm-eabi"), - ("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. @@ -87,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): @@ -111,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 @@ -136,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 @@ -156,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 @@ -167,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 = {} @@ -176,18 +205,45 @@ def CallAddr2LineForSet(lib, unique_addrs): 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. @@ -210,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] @@ -261,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 |