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 48 def __init__(self): 49 self.UpdateAbiRegexes() 50 51 register_names = { 52 "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr", 53 "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", 54 "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", 55 "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags", 56 "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags", 57 } 58 59 def UpdateAbiRegexes(self): 60 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 61 self.width = "{16}" 62 else: 63 self.width = "{8}" 64 65 self.register_line = re.compile("(([ ]*\\b(" + self.register_names[symbol.ARCH] + ")\\b +[0-9a-f]" + self.width + "){2,5})") 66 67 # Note that both trace and value line matching allow for variable amounts of 68 # whitespace (e.g. \t). This is because the we want to allow for the stack 69 # tool to operate on AndroidFeedback provided system logs. AndroidFeedback 70 # strips out double spaces that are found in tombsone files and logcat output. 71 # 72 # Examples of matched trace lines include lines from tombstone files like: 73 # #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 74 # 75 # Or lines from AndroidFeedback crash report system logs like: 76 # 03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 77 # Please note the spacing differences. 78 self.trace_line = re.compile("(.*)\#([0-9]+)[ \t]+(..)[ \t]+([0-9a-f]" + self.width + ")[ \t]+([^\r\n \t]*)( \((.*)\))?") # pylint: disable-msg=C6310 79 # Examples of matched value lines include: 80 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 81 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so (symbol) 82 # 03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 83 # Again, note the spacing differences. 84 self.value_line = re.compile("(.*)([0-9a-f]" + self.width + ")[ \t]+([0-9a-f]" + self.width + ")[ \t]+([^\r\n \t]*)( \((.*)\))?") 85 # Lines from 'code around' sections of the output will be matched before 86 # value lines because otheriwse the 'code around' sections will be confused as 87 # value lines. 88 # 89 # Examples include: 90 # 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 91 # 03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 92 self.code_line = re.compile("(.*)[ \t]*[a-f0-9]" + self.width + 93 "[ \t]*[a-f0-9]" + self.width + 94 "[ \t]*[a-f0-9]" + self.width + 95 "[ \t]*[a-f0-9]" + self.width + 96 "[ \t]*[a-f0-9]" + self.width + 97 "[ \t]*[ \r\n]") # pylint: disable-msg=C6310 98 99 def CleanLine(self, ln): 100 # AndroidFeedback adds zero width spaces into its crash reports. These 101 # should be removed or the regular expresssions will fail to match. 102 return unicode(ln, errors='ignore') 103 104 def PrintTraceLines(self, trace_lines): 105 """Print back trace.""" 106 maxlen = max(map(lambda tl: len(tl[1]), trace_lines)) 107 spacing = "" 108 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 109 spacing = " " 110 print 111 print "Stack Trace:" 112 print " RELADDR " + spacing + "FUNCTION".ljust(maxlen) + " FILE:LINE" 113 for tl in self.trace_lines: 114 (addr, symbol_with_offset, location) = tl 115 print " %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location) 116 return 117 118 def PrintValueLines(self, value_lines): 119 """Print stack data values.""" 120 maxlen = max(map(lambda tl: len(tl[2]), self.value_lines)) 121 print 122 print "Stack Data:" 123 print " ADDR VALUE " + "FUNCTION".ljust(maxlen) + " FILE:LINE" 124 for vl in self.value_lines: 125 (addr, value, symbol_with_offset, location) = vl 126 print " %8s %8s %s %s" % (addr, value, symbol_with_offset.ljust(maxlen), location) 127 return 128 129 def PrintOutput(self, trace_lines, value_lines): 130 if self.trace_lines: 131 self.PrintTraceLines(self.trace_lines) 132 if self.value_lines: 133 self.PrintValueLines(self.value_lines) 134 135 def PrintDivider(self): 136 print 137 print "-----------------------------------------------------\n" 138 139 def ConvertTrace(self, lines): 140 lines = map(self.CleanLine, lines) 141 for line in lines: 142 self.ProcessLine(line) 143 self.PrintOutput(self.trace_lines, self.value_lines) 144 145 def ProcessLine(self, line): 146 ret = False 147 process_header = self.process_info_line.search(line) 148 signal_header = self.signal_line.search(line) 149 abort_message_header = self.abort_message_line.search(line) 150 thread_header = self.thread_line.search(line) 151 register_header = self.register_line.search(line) 152 abi_header = self.abi_line.search(line) 153 revision_header = self.revision_line.search(line) 154 dalvik_jni_thread_header = self.dalvik_jni_thread_line.search(line) 155 dalvik_native_thread_header = self.dalvik_native_thread_line.search(line) 156 if process_header or signal_header or abort_message_header or thread_header or abi_header or \ 157 register_header or dalvik_jni_thread_header or dalvik_native_thread_header or revision_header: 158 ret = True 159 if self.trace_lines or self.value_lines: 160 self.PrintOutput(self.trace_lines, self.value_lines) 161 self.PrintDivider() 162 self.trace_lines = [] 163 self.value_lines = [] 164 self.last_frame = -1 165 if process_header: 166 print process_header.group(1) 167 if signal_header: 168 print signal_header.group(1) 169 if abort_message_header: 170 print abort_message_header.group(1) 171 if register_header: 172 print register_header.group(1) 173 if thread_header: 174 print thread_header.group(1) 175 if dalvik_jni_thread_header: 176 print dalvik_jni_thread_header.group(1) 177 if dalvik_native_thread_header: 178 print dalvik_native_thread_header.group(1) 179 if revision_header: 180 print revision_header.group(1) 181 if abi_header: 182 print abi_header.group(1) 183 symbol.ARCH = abi_header.group(2) 184 self.UpdateAbiRegexes() 185 return ret 186 if self.trace_line.match(line): 187 ret = True 188 match = self.trace_line.match(line) 189 (unused_0, frame, unused_1, 190 code_addr, area, symbol_present, symbol_name) = match.groups() 191 192 if frame <= self.last_frame and (self.trace_lines or self.value_lines): 193 self.PrintOutput(self.trace_lines, self.value_lines) 194 self.PrintDivider() 195 self.trace_lines = [] 196 self.value_lines = [] 197 self.last_frame = frame 198 199 if area == "<unknown>" or area == "[heap]" or area == "[stack]": 200 self.trace_lines.append((code_addr, "", area)) 201 else: 202 # If a calls b which further calls c and c is inlined to b, we want to 203 # display "a -> b -> c" in the stack trace instead of just "a -> c" 204 info = symbol.SymbolInformation(area, code_addr) 205 nest_count = len(info) - 1 206 for (source_symbol, source_location, object_symbol_with_offset) in info: 207 if not source_symbol: 208 if symbol_present: 209 source_symbol = symbol.CallCppFilt(symbol_name) 210 else: 211 source_symbol = "<unknown>" 212 if not source_location: 213 source_location = area 214 if nest_count > 0: 215 nest_count = nest_count - 1 216 arrow = "v------>" 217 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 218 arrow = "v-------------->" 219 self.trace_lines.append((arrow, source_symbol, source_location)) 220 else: 221 if not object_symbol_with_offset: 222 object_symbol_with_offset = source_symbol 223 self.trace_lines.append((code_addr, 224 object_symbol_with_offset, 225 source_location)) 226 if self.code_line.match(line): 227 # Code lines should be ignored. If this were exluded the 'code around' 228 # sections would trigger value_line matches. 229 return ret 230 if self.value_line.match(line): 231 ret = True 232 match = self.value_line.match(line) 233 (unused_, addr, value, area, symbol_present, symbol_name) = match.groups() 234 if area == "<unknown>" or area == "[heap]" or area == "[stack]" or not area: 235 self.value_lines.append((addr, value, "", area)) 236 else: 237 info = symbol.SymbolInformation(area, value) 238 (source_symbol, source_location, object_symbol_with_offset) = info.pop() 239 if not source_symbol: 240 if symbol_present: 241 source_symbol = symbol.CallCppFilt(symbol_name) 242 else: 243 source_symbol = "<unknown>" 244 if not source_location: 245 source_location = area 246 if not object_symbol_with_offset: 247 object_symbol_with_offset = source_symbol 248 self.value_lines.append((addr, 249 value, 250 object_symbol_with_offset, 251 source_location)) 252 253 return ret 254 255 256class RegisterPatternTests(unittest.TestCase): 257 def assert_register_matches(self, abi, example_crash, stupid_pattern): 258 tc = TraceConverter() 259 symbol.ARCH = abi 260 tc.UpdateAbiRegexes() 261 for line in example_crash.split('\n'): 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 266 def test_arm_registers(self): 267 self.assert_register_matches("arm", example_crashes.arm, '\\b(r0|r4|r8|ip)\\b') 268 269 def test_arm64_registers(self): 270 self.assert_register_matches("arm64", example_crashes.arm64, '\\b(x0|x4|x8|x12|x16|x20|x24|x28|sp)\\b') 271 272 def test_mips_registers(self): 273 self.assert_register_matches("mips", example_crashes.mips, '\\b(zr|a0|t0|t4|s0|s4|t8|gp|hi)\\b') 274 275 def test_x86_registers(self): 276 self.assert_register_matches("x86", example_crashes.x86, '\\b(eax|esi|xcs|eip)\\b') 277 278 def test_x86_64_registers(self): 279 self.assert_register_matches("x86_64", example_crashes.x86_64, '\\b(rax|rsi|r8|r12|cs|rip)\\b') 280 281 282if __name__ == '__main__': 283 unittest.main() 284