diff options
| author | Iliyan Malchev <malchev@google.com> | 2011-08-04 17:44:40 -0700 |
|---|---|---|
| committer | Iliyan Malchev <malchev@google.com> | 2011-08-04 17:44:40 -0700 |
| commit | 4929d6a56221fa2167113159827d00c4157c24c0 (patch) | |
| tree | b5d3cde2216b5a101442fdb7e357e19204961e7f /scripts | |
| parent | 621770b1774010e71e4796b11b9b6d095f8f6eb0 (diff) | |
| download | android_development-4929d6a56221fa2167113159827d00c4157c24c0.tar.gz android_development-4929d6a56221fa2167113159827d00c4157c24c0.tar.bz2 android_development-4929d6a56221fa2167113159827d00c4157c24c0.zip | |
development: copy stack tool over from vendor/google/tools
The stack tool is not really proprietary, and is needed by vendors and
third-party developers working on native code.
Change-Id: I37f34b0681a0063ecf71f5a078d2c4a1ba622973
Signed-off-by: Iliyan Malchev <malchev@google.com>
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/stack | 418 | ||||
| -rwxr-xr-x | scripts/symbol.py | 287 |
2 files changed, 705 insertions, 0 deletions
diff --git a/scripts/stack b/scripts/stack new file mode 100755 index 000000000..6750752af --- /dev/null +++ b/scripts/stack @@ -0,0 +1,418 @@ +#!/usr/bin/env python +# +# Copyright 2006 Google Inc. All Rights Reserved. + +"""stack symbolizes native crash dumps.""" + +import getopt +import getpass +import glob +import os +import re +import subprocess +import sys +import urllib + +import symbol + + +def PrintUsage(): + """Print usage and exit with error.""" + # pylint: disable-msg=C6310 + print + print " usage: " + sys.argv[0] + " [options] [FILE]" + print + print " --symbols-dir=path" + print " the path to a symbols dir, 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 + print " FILE should contain a stack trace in it somewhere" + print " the tool will find that and re-print it with" + print " source files and line numbers. If you don't" + print " pass FILE, or if file is -, it reads from" + print " stdin." + print + # pylint: enable-msg=C6310 + sys.exit(1) + + +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=", + "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 + + 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 + else: + print "Searching for native crashes in %s" % arguments[0] + f = open(arguments[0], "r") + + 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) + +if __name__ == "__main__": + main() + +# vi: ts=2 sw=2 diff --git a/scripts/symbol.py b/scripts/symbol.py new file mode 100755 index 000000000..e36304c4c --- /dev/null +++ b/scripts/symbol.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# +# Copyright 2006 Google Inc. All Rights Reserved. + +"""Module for looking up symbolic debugging information. + +The information can include symbol names, offsets, and source locations. +""" + +import os +import re +import subprocess + +ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"] +if not ANDROID_BUILD_TOP: + ANDROID_BUILD_TOP = "." + +def FindSymbolsDir(): + saveddir = os.getcwd() + os.chdir(ANDROID_BUILD_TOP) + try: + cmd = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core " + "SRC_TARGET_DIR=build/target make -f build/core/config.mk " + "dumpvar-abs-TARGET_OUT_UNSTRIPPED") + stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout + return os.path.join(ANDROID_BUILD_TOP, stream.read().strip()) + finally: + os.chdir(saveddir) + +SYMBOLS_DIR = FindSymbolsDir() + +def Uname(): + """'uname' for constructing prebuilt/<...> and out/host/<...> paths.""" + uname = os.uname()[0] + if uname == "Darwin": + proc = os.uname()[-1] + if proc == "i386" or proc == "x86_64": + return "darwin-x86" + return "darwin-ppc" + if uname == "Linux": + return "linux-x86" + return 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", + target + "-" + tool) + +def FindToolchain(): + """Look for the latest available toolchain + + Args: + None + + Returns: + A pair of strings containing toolchain label and target prefix. + """ + + ## 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") + ] + + # Look for addr2line to check for valid toolchain path. + for (label, target) in known_toolchains: + toolchain_info = (label, target); + if os.path.exists(ToolPath("addr2line", 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. + + Args: + lib: library (or executable) pathname containing symbols + 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. + + 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. + + 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. + """ + info = SymbolInformationForSet(lib, set([addr])) + return (info and info.get(addr)) or (None, None, None) + + +def SymbolInformationForSet(lib, unique_addrs): + """Look up symbol information for a set of addresses from the given library. + + Args: + lib: library (or executable) pathname containing symbols + 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. + + 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. + + 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. + """ + if not lib: + return None + + addr_to_line = CallAddr2LineForSet(lib, unique_addrs) + if not addr_to_line: + return None + + addr_to_objdump = CallObjdumpForSet(lib, unique_addrs) + if not addr_to_objdump: + return None + + result = {} + for addr in unique_addrs: + (source_symbol, source_location) = addr_to_line.get(addr, (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) + + return result + + +def CallAddr2LineForSet(lib, unique_addrs): + """Look up line and symbol information for a set of addresses. + + Args: + lib: library (or executable) pathname containing symbols + 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. + """ + if not lib: + return None + + + symbols = SYMBOLS_DIR + lib + if not os.path.exists(symbols): + return None + + (label, target) = TOOLCHAIN_INFO + cmd = [ToolPath("addr2line"), "--functions", "--demangle", "--exe=" + symbols] + child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + result = {} + addrs = sorted(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) + child.stdin.close() + child.stdout.close() + return result + + +def CallObjdumpForSet(lib, unique_addrs): + """Use objdump to find out the names of the containing functions. + + Args: + lib: library (or executable) pathname containing symbols + unique_addrs: set of string hexidecimal addresses to find the functions for. + + Returns: + A dictionary of the form {addr: (string symbol, offset)}. + """ + if not lib: + return None + + symbols = SYMBOLS_DIR + lib + if not os.path.exists(symbols): + return None + + symbols = SYMBOLS_DIR + lib + if not os.path.exists(symbols): + return None + + addrs = sorted(unique_addrs) + start_addr_hex = addrs[0] + stop_addr_dec = str(int(addrs[-1], 16) + 8) + cmd = [ToolPath("objdump"), + "--section=.text", + "--demangle", + "--disassemble", + "--start-address=0x" + start_addr_hex, + "--stop-address=" + stop_addr_dec, + symbols] + + # Function lines look like: + # 000177b0 <android::IBinder::~IBinder()+0x2c>: + # We pull out the address and function first. Then we check for an optional + # offset. This is tricky due to functions that look like "operator+(..)+0x2c" + func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$") + offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)") + + # A disassembly line looks like: + # 177b2: b510 push {r4, lr} + asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$") + + current_symbol = None # The current function symbol in the disassembly. + current_symbol_addr = 0 # The address of the current function. + addr_index = 0 # The address that we are currently looking for. + + stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout + result = {} + for line in stream: + # Is it a function line like: + # 000177b0 <android::IBinder::~IBinder()>: + components = func_regexp.match(line) + if components: + # This is a new function, so record the current function and its address. + current_symbol_addr = int(components.group(1), 16) + current_symbol = components.group(2) + + # Does it have an optional offset like: "foo(..)+0x2c"? + components = offset_regexp.match(current_symbol) + if components: + current_symbol = components.group(1) + offset = components.group(2) + if offset: + current_symbol_addr -= int(offset, 16) + + # Is it an disassembly line like: + # 177b2: b510 push {r4, lr} + components = asm_regexp.match(line) + if components: + addr = components.group(1) + target_addr = addrs[addr_index] + i_addr = int(addr, 16) + i_target = int(target_addr, 16) + if i_addr == i_target: + result[target_addr] = (current_symbol, i_target - current_symbol_addr) + addr_index += 1 + if addr_index >= len(addrs): + break + stream.close() + + return result + + +def CallCppFilt(mangled_symbol): + cmd = [ToolPath("c++filt")] + process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + process.stdin.write(mangled_symbol) + process.stdin.write("\n") + process.stdin.close() + demangled_symbol = process.stdout.readline().strip() + process.stdout.close() + return demangled_symbol + +def FormatSymbolWithOffset(symbol, offset): + if offset == 0: + return symbol + return "%s+%d" % (symbol, offset) |
