Home | History | Annotate | Download | only in scripts
      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 
     19 import re
     20 import symbol
     21 import unittest
     22 
     23 import example_crashes
     24 
     25 def ConvertTrace(lines):
     26   tracer = TraceConverter()
     27   print "Reading symbols from", symbol.SYMBOLS_DIR
     28   tracer.ConvertTrace(lines)
     29 
     30 class 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 
    256 class 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 
    282 if __name__ == '__main__':
    283     unittest.main()
    284