diff options
Diffstat (limited to 'scripts/stack_core.py')
| -rwxr-xr-x | scripts/stack_core.py | 298 |
1 files changed, 191 insertions, 107 deletions
diff --git a/scripts/stack_core.py b/scripts/stack_core.py index 42285d462..62d2aa51c 100755 --- a/scripts/stack_core.py +++ b/scripts/stack_core.py @@ -17,108 +17,154 @@ """stack symbolizes native crash dumps.""" import re - import symbol +import unittest -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" +import example_crashes def ConvertTrace(lines): - """Convert strings containing native crash to a stack.""" + tracer = TraceConverter() + print "Reading symbols from", symbol.SYMBOLS_DIR + tracer.ConvertTrace(lines) + +class TraceConverter: process_info_line = re.compile("(pid: [0-9]+, tid: [0-9]+.*)") + abi_line = re.compile("(ABI: \'(.*)\')") + revision_line = re.compile("(Revision: \'(.*)\')") signal_line = re.compile("(signal [0-9]+ \(.*\).*)") - register_line = re.compile("(([ ]*[0-9a-z]{2} [0-9a-f]{8}){4})") + abort_message_line = re.compile("(Abort message: '.*')") 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 - + register_line = re.compile("$a") + trace_line = re.compile("$a") + value_line = re.compile("$a") + code_line = re.compile("$a") trace_lines = [] value_lines = [] last_frame = -1 + width = "{8}" + + def __init__(self): pass + + register_names = { + "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr", + "arm64": "x0|x1|x2|x3|x4|x5|x6|x7|x8|x9|x10|x11|x12|x13|x14|x15|x16|x17|x18|x19|x20|x21|x22|x23|x24|x25|x26|x27|x28|x29|x30|sp|pc", + "mips": "zr|at|v0|v1|a0|a1|a2|a3|t0|t1|t2|t3|t4|t5|t6|t7|s0|s1|s2|s3|s4|s5|s6|s7|t8|t9|k0|k1|gp|sp|s8|ra|hi|lo|bva|epc", + "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags", + "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags", + } + + def UpdateAbiRegexes(self): + if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": + self.width = "{16}" - for ln in lines: + self.register_line = re.compile("(([ ]*\\b(" + self.register_names[symbol.ARCH] + ")\\b +[0-9a-f]" + self.width + "){2,4})") + + # 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 + # + # 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. + self.trace_line = re.compile("(.*)\#([0-9]+)[ \t]+(..)[ \t]+([0-9a-f]" + self.width + ")[ \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. + self.value_line = re.compile("(.*)([0-9a-f]" + self.width + ")[ \t]+([0-9a-f]" + self.width + ")[ \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 + self.code_line = re.compile("(.*)[ \t]*[a-f0-9]" + self.width + + "[ \t]*[a-f0-9]" + self.width + + "[ \t]*[a-f0-9]" + self.width + + "[ \t]*[a-f0-9]" + self.width + + "[ \t]*[a-f0-9]" + self.width + + "[ \t]*[ \r\n]") # pylint: disable-msg=C6310 + + def CleanLine(self, ln): # 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 + return unicode(ln, errors='ignore') + + def PrintTraceLines(self, trace_lines): + """Print back trace.""" + maxlen = max(map(lambda tl: len(tl[1]), trace_lines)) + spacing = "" + if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": + spacing = " " + print + print "Stack Trace:" + print " RELADDR " + spacing + "FUNCTION".ljust(maxlen) + " FILE:LINE" + for tl in self.trace_lines: + (addr, symbol_with_offset, location) = tl + print " %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location) + return + + def PrintValueLines(self, value_lines): + """Print stack data values.""" + maxlen = max(map(lambda tl: len(tl[2]), self.value_lines)) + print + print "Stack Data:" + print " ADDR VALUE " + "FUNCTION".ljust(maxlen) + " FILE:LINE" + for vl in self.value_lines: + (addr, value, symbol_with_offset, location) = vl + print " %8s %8s %s %s" % (addr, value, symbol_with_offset.ljust(maxlen), location) + return + + def PrintOutput(self, trace_lines, value_lines): + if self.trace_lines: + self.PrintTraceLines(self.trace_lines) + if self.value_lines: + self.PrintValueLines(self.value_lines) + + def PrintDivider(self): + print + print "-----------------------------------------------------\n" + + def ConvertTrace(self, lines): + lines = map(self.CleanLine, lines) + for line in lines: + self.ProcessLine(line) + self.PrintOutput(self.trace_lines, self.value_lines) + + def ProcessLine(self, line): + ret = False + process_header = self.process_info_line.search(line) + signal_header = self.signal_line.search(line) + abort_message_header = self.abort_message_line.search(line) + thread_header = self.thread_line.search(line) + register_header = self.register_line.search(line) + abi_header = self.abi_line.search(line) + revision_header = self.revision_line.search(line) + dalvik_jni_thread_header = self.dalvik_jni_thread_line.search(line) + dalvik_native_thread_header = self.dalvik_native_thread_line.search(line) + if process_header or signal_header or abort_message_header or thread_header or abi_header or \ + register_header or dalvik_jni_thread_header or dalvik_native_thread_header or revision_header: + ret = True + if self.trace_lines or self.value_lines: + self.PrintOutput(self.trace_lines, self.value_lines) + self.PrintDivider() + self.trace_lines = [] + self.value_lines = [] + self.last_frame = -1 if process_header: print process_header.group(1) if signal_header: print signal_header.group(1) + if abort_message_header: + print abort_message_header.group(1) if register_header: print register_header.group(1) if thread_header: @@ -127,21 +173,28 @@ def ConvertTrace(lines): 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) + if revision_header: + print revision_header.group(1) + if abi_header: + print abi_header.group(1) + symbol.ARCH = abi_header.group(2) + self.UpdateAbiRegexes() + return ret + if self.trace_line.match(line): + ret = True + match = self.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 frame <= self.last_frame and (self.trace_lines or self.value_lines): + self.PrintOutput(self.trace_lines, self.value_lines) + self.PrintDivider() + self.trace_lines = [] + self.value_lines = [] + self.last_frame = frame - if area == UNKNOWN or area == HEAP or area == STACK: - trace_lines.append((code_addr, "", area)) + if area == "<unknown>" or area == "[heap]" or area == "[stack]": + self.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" @@ -152,27 +205,31 @@ def ConvertTrace(lines): if symbol_present: source_symbol = symbol.CallCppFilt(symbol_name) else: - source_symbol = UNKNOWN + 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)) + arrow = "v------>" + if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": + arrow = "v-------------->" + self.trace_lines.append((arrow, source_symbol, source_location)) else: if not object_symbol_with_offset: object_symbol_with_offset = source_symbol - trace_lines.append((code_addr, + self.trace_lines.append((code_addr, object_symbol_with_offset, source_location)) - if code_line.match(line): + if self.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) + return ret + if self.value_line.match(line): + ret = True + match = self.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)) + if area == "<unknown>" or area == "[heap]" or area == "[stack]" or not area: + self.value_lines.append((addr, value, "", area)) else: info = symbol.SymbolInformation(area, value) (source_symbol, source_location, object_symbol_with_offset) = info.pop() @@ -180,17 +237,44 @@ def ConvertTrace(lines): if symbol_present: source_symbol = symbol.CallCppFilt(symbol_name) else: - source_symbol = UNKNOWN + 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, + self.value_lines.append((addr, value, object_symbol_with_offset, source_location)) - PrintOutput(trace_lines, value_lines) + return ret + + +class RegisterPatternTests(unittest.TestCase): + def assert_register_matches(self, abi, example_crash, stupid_pattern): + tc = TraceConverter() + symbol.ARCH = abi + tc.UpdateAbiRegexes() + for line in example_crash.split('\n'): + is_register = (re.search(stupid_pattern, line) is not None) + matched = (tc.register_line.search(line) is not None) + self.assertEquals(matched, is_register, line) + + def test_arm_registers(self): + self.assert_register_matches("arm", example_crashes.arm, '\\b(r0|r4|r8|ip)\\b') + + def test_arm64_registers(self): + self.assert_register_matches("arm64", example_crashes.arm64, '\\b(x0|x4|x8|x12|x16|x20|x24|x28|sp)\\b') + + def test_mips_registers(self): + self.assert_register_matches("mips", example_crashes.mips, '\\b(zr|a0|t0|t4|s0|s4|t8|gp|hi)\\b') + + def test_x86_registers(self): + self.assert_register_matches("x86", example_crashes.x86, '\\b(eax|esi|xcs|eip)\\b') + + def test_x86_64_registers(self): + self.assert_register_matches("x86_64", example_crashes.x86_64, '\\b(rax|rsi|r8|r12|cs|rip)\\b') -# vi: ts=2 sw=2 +if __name__ == '__main__': + unittest.main() |
