1#!/usr/bin/env python 2# 3# Copyright (C) 2013 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""stack symbolizes native crash dumps.""" 18 19import re 20import symbol 21import unittest 22 23import example_crashes 24 25def ConvertTrace(lines): 26 tracer = TraceConverter() 27 print "Reading symbols from", symbol.SYMBOLS_DIR 28 tracer.ConvertTrace(lines) 29 30class TraceConverter: 31 process_info_line = re.compile("(pid: [0-9]+, tid: [0-9]+.*)") 32 abi_line = re.compile("(ABI: \'(.*)\')") 33 revision_line = re.compile("(Revision: \'(.*)\')") 34 signal_line = re.compile("(signal [0-9]+ \(.*\).*)") 35 abort_message_line = re.compile("(Abort message: '.*')") 36 thread_line = re.compile("(.*)(\-\-\- ){15}\-\-\-") 37 dalvik_jni_thread_line = re.compile("(\".*\" prio=[0-9]+ tid=[0-9]+ NATIVE.*)") 38 dalvik_native_thread_line = re.compile("(\".*\" sysTid=[0-9]+ nice=[0-9]+.*)") 39 register_line = re.compile("$a") 40 trace_line = re.compile("$a") 41 value_line = re.compile("$a") 42 code_line = re.compile("$a") 43 trace_lines = [] 44 value_lines = [] 45 last_frame = -1 46 width = "{8}" 47 spacing = "" 48 49 def __init__(self): 50 self.UpdateAbiRegexes() 51 52 register_names = { 53 "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr", 54 "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|pstate", 55 "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", 56 "mips64": "zr|at|v0|v1|a0|a1|a2|a3|a4|a5|a6|a7|t0|t1|t2|t3|s0|s1|s2|s3|s4|s5|s6|s7|t8|t9|k0|k1|gp|sp|s8|ra|hi|lo|bva|epc", 57 "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags", 58 "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags", 59 } 60 61 def UpdateAbiRegexes(self): 62 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 63 self.width = "{16}" 64 self.spacing = " " 65 else: 66 self.width = "{8}" 67 self.spacing = "" 68 69 self.register_line = re.compile("(([ ]*\\b(" + self.register_names[symbol.ARCH] + ")\\b +[0-9a-f]" + self.width + "){2,5})") 70 71 # Note that both trace and value line matching allow for variable amounts of 72 # whitespace (e.g. \t). This is because the we want to allow for the stack 73 # tool to operate on AndroidFeedback provided system logs. AndroidFeedback 74 # strips out double spaces that are found in tombsone files and logcat output. 75 # 76 # Examples of matched trace lines include lines from tombstone files like: 77 # #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 78 # 79 # Or lines from AndroidFeedback crash report system logs like: 80 # 03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 81 # Please note the spacing differences. 82 self.trace_line = re.compile("(.*)\#([0-9]+)[ \t]+(..)[ \t]+([0-9a-f]" + self.width + ")[ \t]+([^\r\n \t]*)( \((.*)\))?") # pylint: disable-msg=C6310 83 # Examples of matched value lines include: 84 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 85 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so (symbol) 86 # 03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 87 # Again, note the spacing differences. 88 self.value_line = re.compile("(.*)([0-9a-f]" + self.width + ")[ \t]+([0-9a-f]" + self.width + ")[ \t]+([^\r\n \t]*)( \((.*)\))?") 89 # Lines from 'code around' sections of the output will be matched before 90 # value lines because otheriwse the 'code around' sections will be confused as 91 # value lines. 92 # 93 # Examples include: 94 # 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 95 # 03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 96 self.code_line = re.compile("(.*)[ \t]*[a-f0-9]" + self.width + 97 "[ \t]*[a-f0-9]" + self.width + 98 "[ \t]*[a-f0-9]" + self.width + 99 "[ \t]*[a-f0-9]" + self.width + 100 "[ \t]*[a-f0-9]" + self.width + 101 "[ \t]*[ \r\n]") # pylint: disable-msg=C6310 102 103 def CleanLine(self, ln): 104 # AndroidFeedback adds zero width spaces into its crash reports. These 105 # should be removed or the regular expresssions will fail to match. 106 return unicode(ln, errors='ignore') 107 108 def PrintTraceLines(self, trace_lines): 109 """Print back trace.""" 110 maxlen = max(map(lambda tl: len(tl[1]), trace_lines)) 111 print 112 print "Stack Trace:" 113 print " RELADDR " + self.spacing + "FUNCTION".ljust(maxlen) + " FILE:LINE" 114 for tl in self.trace_lines: 115 (addr, symbol_with_offset, location) = tl 116 print " %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location) 117 return 118 119 def PrintValueLines(self, value_lines): 120 """Print stack data values.""" 121 maxlen = max(map(lambda tl: len(tl[2]), self.value_lines)) 122 print 123 print "Stack Data:" 124 print " ADDR " + self.spacing + "VALUE " + "FUNCTION".ljust(maxlen) + " FILE:LINE" 125 for vl in self.value_lines: 126 (addr, value, symbol_with_offset, location) = vl 127 print " %8s %8s %s %s" % (addr, value, symbol_with_offset.ljust(maxlen), location) 128 return 129 130 def PrintOutput(self, trace_lines, value_lines): 131 if self.trace_lines: 132 self.PrintTraceLines(self.trace_lines) 133 if self.value_lines: 134 self.PrintValueLines(self.value_lines) 135 136 def PrintDivider(self): 137 print 138 print "-----------------------------------------------------\n" 139 140 def ConvertTrace(self, lines): 141 lines = map(self.CleanLine, lines) 142 for line in lines: 143 self.ProcessLine(line) 144 self.PrintOutput(self.trace_lines, self.value_lines) 145 146 def ProcessLine(self, line): 147 ret = False 148 process_header = self.process_info_line.search(line) 149 signal_header = self.signal_line.search(line) 150 abort_message_header = self.abort_message_line.search(line) 151 thread_header = self.thread_line.search(line) 152 register_header = self.register_line.search(line) 153 abi_header = self.abi_line.search(line) 154 revision_header = self.revision_line.search(line) 155 dalvik_jni_thread_header = self.dalvik_jni_thread_line.search(line) 156 dalvik_native_thread_header = self.dalvik_native_thread_line.search(line) 157 if process_header or signal_header or abort_message_header or thread_header or abi_header or \ 158 register_header or dalvik_jni_thread_header or dalvik_native_thread_header or revision_header: 159 ret = True 160 if self.trace_lines or self.value_lines: 161 self.PrintOutput(self.trace_lines, self.value_lines) 162 self.PrintDivider() 163 self.trace_lines = [] 164 self.value_lines = [] 165 self.last_frame = -1 166 if process_header: 167 print process_header.group(1) 168 if signal_header: 169 print signal_header.group(1) 170 if abort_message_header: 171 print abort_message_header.group(1) 172 if register_header: 173 print register_header.group(1) 174 if thread_header: 175 print thread_header.group(1) 176 if dalvik_jni_thread_header: 177 print dalvik_jni_thread_header.group(1) 178 if dalvik_native_thread_header: 179 print dalvik_native_thread_header.group(1) 180 if revision_header: 181 print revision_header.group(1) 182 if abi_header: 183 print abi_header.group(1) 184 symbol.ARCH = abi_header.group(2) 185 self.UpdateAbiRegexes() 186 return ret 187 if self.trace_line.match(line): 188 ret = True 189 match = self.trace_line.match(line) 190 (unused_0, frame, unused_1, 191 code_addr, area, symbol_present, symbol_name) = match.groups() 192 193 if frame <= self.last_frame and (self.trace_lines or self.value_lines): 194 self.PrintOutput(self.trace_lines, self.value_lines) 195 self.PrintDivider() 196 self.trace_lines = [] 197 self.value_lines = [] 198 self.last_frame = frame 199 200 if area == "<unknown>" or area == "[heap]" or area == "[stack]": 201 self.trace_lines.append((code_addr, "", area)) 202 else: 203 # If a calls b which further calls c and c is inlined to b, we want to 204 # display "a -> b -> c" in the stack trace instead of just "a -> c" 205 info = symbol.SymbolInformation(area, code_addr) 206 nest_count = len(info) - 1 207 for (source_symbol, source_location, object_symbol_with_offset) in info: 208 if not source_symbol: 209 if symbol_present: 210 source_symbol = symbol.CallCppFilt(symbol_name) 211 else: 212 source_symbol = "<unknown>" 213 if not source_location: 214 source_location = area 215 if nest_count > 0: 216 nest_count = nest_count - 1 217 arrow = "v------>" 218 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 219 arrow = "v-------------->" 220 self.trace_lines.append((arrow, source_symbol, source_location)) 221 else: 222 if not object_symbol_with_offset: 223 object_symbol_with_offset = source_symbol 224 self.trace_lines.append((code_addr, 225 object_symbol_with_offset, 226 source_location)) 227 if self.code_line.match(line): 228 # Code lines should be ignored. If this were exluded the 'code around' 229 # sections would trigger value_line matches. 230 return ret 231 if self.value_line.match(line): 232 ret = True 233 match = self.value_line.match(line) 234 (unused_, addr, value, area, symbol_present, symbol_name) = match.groups() 235 if area == "<unknown>" or area == "[heap]" or area == "[stack]" or not area: 236 self.value_lines.append((addr, value, "", area)) 237 else: 238 info = symbol.SymbolInformation(area, value) 239 (source_symbol, source_location, object_symbol_with_offset) = info.pop() 240 if not source_symbol: 241 if symbol_present: 242 source_symbol = symbol.CallCppFilt(symbol_name) 243 else: 244 source_symbol = "<unknown>" 245 if not source_location: 246 source_location = area 247 if not object_symbol_with_offset: 248 object_symbol_with_offset = source_symbol 249 self.value_lines.append((addr, 250 value, 251 object_symbol_with_offset, 252 source_location)) 253 254 return ret 255 256 257class RegisterPatternTests(unittest.TestCase): 258 def assert_register_matches(self, abi, example_crash, stupid_pattern): 259 tc = TraceConverter() 260 for line in example_crash.split('\n'): 261 tc.ProcessLine(line) 262 is_register = (re.search(stupid_pattern, line) is not None) 263 matched = (tc.register_line.search(line) is not None) 264 self.assertEquals(matched, is_register, line) 265 tc.PrintOutput(tc.trace_lines, tc.value_lines) 266 267 def test_arm_registers(self): 268 self.assert_register_matches("arm", example_crashes.arm, '\\b(r0|r4|r8|ip)\\b') 269 270 def test_arm64_registers(self): 271 self.assert_register_matches("arm64", example_crashes.arm64, '\\b(x0|x4|x8|x12|x16|x20|x24|x28|sp)\\b') 272 273 def test_mips_registers(self): 274 self.assert_register_matches("mips", example_crashes.mips, '\\b(zr|a0|t0|t4|s0|s4|t8|gp|hi)\\b') 275 276 def test_x86_registers(self): 277 self.assert_register_matches("x86", example_crashes.x86, '\\b(eax|esi|xcs|eip)\\b') 278 279 def test_x86_64_registers(self): 280 self.assert_register_matches("x86_64", example_crashes.x86_64, '\\b(rax|rsi|r8|r12|cs|rip)\\b') 281 282 283if __name__ == '__main__': 284 unittest.main() 285