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 
     21 import symbol
     22 
     23 def PrintTraceLines(trace_lines):
     24   """Print back trace."""
     25   maxlen = max(map(lambda tl: len(tl[1]), trace_lines))
     26   print
     27   print "Stack Trace:"
     28   print "  RELADDR   " + "FUNCTION".ljust(maxlen) + "  FILE:LINE"
     29   for tl in trace_lines:
     30     (addr, symbol_with_offset, location) = tl
     31     print "  %8s  %s  %s" % (addr, symbol_with_offset.ljust(maxlen), location)
     32   return
     33 
     34 
     35 def PrintValueLines(value_lines):
     36   """Print stack data values."""
     37   maxlen = max(map(lambda tl: len(tl[2]), value_lines))
     38   print
     39   print "Stack Data:"
     40   print "  ADDR      VALUE     " + "FUNCTION".ljust(maxlen) + "  FILE:LINE"
     41   for vl in value_lines:
     42     (addr, value, symbol_with_offset, location) = vl
     43     print "  %8s  %8s  %s  %s" % (addr, value, symbol_with_offset.ljust(maxlen), location)
     44   return
     45 
     46 UNKNOWN = "<unknown>"
     47 HEAP = "[heap]"
     48 STACK = "[stack]"
     49 
     50 
     51 def PrintOutput(trace_lines, value_lines, more_info):
     52   if trace_lines:
     53     PrintTraceLines(trace_lines)
     54   if value_lines:
     55     # TODO(cjhopman): it seems that symbol.SymbolInformation always fails to
     56     # find information for addresses in value_lines in chrome libraries, and so
     57     # value_lines have little value to us and merely clutter the output.
     58     # Since information is sometimes contained in these lines (from system
     59     # libraries), don't completely disable them.
     60     if more_info:
     61       PrintValueLines(value_lines)
     62 
     63 def PrintDivider():
     64   print
     65   print "-----------------------------------------------------\n"
     66 
     67 def ConvertTrace(lines, more_info):
     68   """Convert strings containing native crash to a stack."""
     69   process_info_line = re.compile("(pid: [0-9]+, tid: [0-9]+.*)")
     70   signal_line = re.compile("(signal [0-9]+ \(.*\).*)")
     71   register_line = re.compile("(([ ]*[0-9a-z]{2} [0-9a-f]{8}){4})")
     72   thread_line = re.compile("(.*)(\-\-\- ){15}\-\-\-")
     73   dalvik_jni_thread_line = re.compile("(\".*\" prio=[0-9]+ tid=[0-9]+ NATIVE.*)")
     74   dalvik_native_thread_line = re.compile("(\".*\" sysTid=[0-9]+ nice=[0-9]+.*)")
     75 
     76   width = "{8}"
     77   if symbol.ARCH == "arm64" or symbol.ARCH == "x86_64":
     78     width = "{16}"
     79 
     80   # Matches LOG(FATAL) lines, like the following example:
     81   #   [FATAL:source_file.cc(33)] Check failed: !instances_.empty()
     82   log_fatal_line = re.compile("(\[FATAL\:.*\].*)$")
     83 
     84   # Note that both trace and value line matching allow for variable amounts of
     85   # whitespace (e.g. \t). This is because the we want to allow for the stack
     86   # tool to operate on AndroidFeedback provided system logs. AndroidFeedback
     87   # strips out double spaces that are found in tombsone files and logcat output.
     88   #
     89   # Examples of matched trace lines include lines from tombstone files like:
     90   #   #00  pc 001cf42e  /data/data/com.my.project/lib/libmyproject.so
     91   #   #00  pc 001cf42e  /data/data/com.my.project/lib/libmyproject.so (symbol)
     92   # Or lines from AndroidFeedback crash report system logs like:
     93   #   03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so
     94   # Please note the spacing differences.
     95   trace_line = re.compile("(.*)\#(?P<frame>[0-9]+)[ \t]+(..)[ \t]+(0x)?(?P<address>[0-9a-f]{0,16})[ \t]+(?P<lib>[^\r\n \t]*)(?P<symbol_present> \((?P<symbol_name>.*)\))?")  # pylint: disable-msg=C6310
     96 
     97   # Matches lines emitted by src/base/debug/stack_trace_android.cc, like:
     98   #   #00 0x7324d92d /data/app-lib/org.chromium.native_test-1/libbase.cr.so+0x0006992d
     99   # This pattern includes the unused named capture groups <symbol_present> and
    100   # <symbol_name> so that it can interoperate with the |trace_line| regex.
    101   debug_trace_line = re.compile(
    102       '(.*)(?P<frame>\#[0-9]+ 0x[0-9a-f]' + width + ') '
    103       '(?P<lib>[^+]+)\+0x(?P<address>[0-9a-f]' + width + ')'
    104       '(?P<symbol_present>)(?P<symbol_name>)')
    105 
    106   # Examples of matched value lines include:
    107   #   bea4170c  8018e4e9  /data/data/com.my.project/lib/libmyproject.so
    108   #   bea4170c  8018e4e9  /data/data/com.my.project/lib/libmyproject.so (symbol)
    109   #   03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so
    110   # Again, note the spacing differences.
    111   value_line = re.compile("(.*)([0-9a-f]" + width + ")[ \t]+([0-9a-f]" + width + ")[ \t]+([^\r\n \t]*)( \((.*)\))?")
    112   # Lines from 'code around' sections of the output will be matched before
    113   # value lines because otheriwse the 'code around' sections will be confused as
    114   # value lines.
    115   #
    116   # Examples include:
    117   #   801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
    118   #   03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8
    119   code_line = re.compile("(.*)[ \t]*[a-f0-9]" + width +
    120                          "[ \t]*[a-f0-9]" + width +
    121                          "[ \t]*[a-f0-9]" + width +
    122                          "[ \t]*[a-f0-9]" + width +
    123                          "[ \t]*[a-f0-9]" + width +
    124                          "[ \t]*[ \r\n]")  # pylint: disable-msg=C6310
    125 
    126   trace_lines = []
    127   value_lines = []
    128   last_frame = -1
    129 
    130   # It is faster to get symbol information with a single call rather than with
    131   # separate calls for each line. Since symbol.SymbolInformation caches results,
    132   # we can extract all the addresses that we will want symbol information for
    133   # from the log and call symbol.SymbolInformation so that the results are
    134   # cached in the following lookups.
    135   code_addresses = {}
    136   for ln in lines:
    137     line = unicode(ln, errors='ignore')
    138     lib, address = None, None
    139 
    140     match = trace_line.match(line) or debug_trace_line.match(line)
    141     if match:
    142       address, lib = match.group('address', 'lib')
    143 
    144     match = value_line.match(line)
    145     if match and not code_line.match(line):
    146       (_0, _1, address, lib, _2, _3) = match.groups()
    147 
    148     if lib:
    149       code_addresses.setdefault(lib, set()).add(address)
    150 
    151   for lib in code_addresses:
    152     symbol.SymbolInformationForSet(
    153         symbol.TranslateLibPath(lib), code_addresses[lib], more_info)
    154 
    155   for ln in lines:
    156     # AndroidFeedback adds zero width spaces into its crash reports. These
    157     # should be removed or the regular expresssions will fail to match.
    158     line = unicode(ln, errors='ignore')
    159     process_header = process_info_line.search(line)
    160     signal_header = signal_line.search(line)
    161     register_header = register_line.search(line)
    162     thread_header = thread_line.search(line)
    163     dalvik_jni_thread_header = dalvik_jni_thread_line.search(line)
    164     dalvik_native_thread_header = dalvik_native_thread_line.search(line)
    165     log_fatal_header = log_fatal_line.search(line)
    166     if (process_header or signal_header or register_header or thread_header or
    167         dalvik_jni_thread_header or dalvik_native_thread_header or
    168         log_fatal_header) :
    169       if trace_lines or value_lines:
    170         PrintOutput(trace_lines, value_lines, more_info)
    171         PrintDivider()
    172         trace_lines = []
    173         value_lines = []
    174         last_frame = -1
    175       if process_header:
    176         print process_header.group(1)
    177       if signal_header:
    178         print signal_header.group(1)
    179       if register_header:
    180         print register_header.group(1)
    181       if thread_header:
    182         print thread_header.group(1)
    183       if dalvik_jni_thread_header:
    184         print dalvik_jni_thread_header.group(1)
    185       if dalvik_native_thread_header:
    186         print dalvik_native_thread_header.group(1)
    187       if log_fatal_header:
    188         print log_fatal_header.group(1)
    189       continue
    190 
    191     match = trace_line.match(line) or debug_trace_line.match(line)
    192     if match:
    193       frame, code_addr, area, symbol_present, symbol_name = match.group(
    194           'frame', 'address', 'lib', 'symbol_present', 'symbol_name')
    195 
    196       if frame <= last_frame and (trace_lines or value_lines):
    197         PrintOutput(trace_lines, value_lines, more_info)
    198         PrintDivider()
    199         trace_lines = []
    200         value_lines = []
    201       last_frame = frame
    202 
    203       if area == UNKNOWN or area == HEAP or area == STACK:
    204         trace_lines.append((code_addr, "", area))
    205       else:
    206         # If a calls b which further calls c and c is inlined to b, we want to
    207         # display "a -> b -> c" in the stack trace instead of just "a -> c"
    208         info = symbol.SymbolInformation(area, code_addr, more_info)
    209         nest_count = len(info) - 1
    210         for (source_symbol, source_location, object_symbol_with_offset) in info:
    211           if not source_symbol:
    212             if symbol_present:
    213               source_symbol = symbol.CallCppFilt(symbol_name)
    214             else:
    215               source_symbol = UNKNOWN
    216           if not source_location:
    217             source_location = area
    218           if nest_count > 0:
    219             nest_count = nest_count - 1
    220             trace_lines.append(("v------>", source_symbol, source_location))
    221           else:
    222             if not object_symbol_with_offset:
    223               object_symbol_with_offset = source_symbol
    224             trace_lines.append((code_addr,
    225                                 object_symbol_with_offset,
    226                                 source_location))
    227     if 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       continue;
    231     match = value_line.match(line)
    232     if match:
    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         value_lines.append((addr, value, "", area))
    236       else:
    237         info = symbol.SymbolInformation(area, value, more_info)
    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         value_lines.append((addr,
    249                             value,
    250                             object_symbol_with_offset,
    251                             source_location))
    252 
    253   PrintOutput(trace_lines, value_lines, more_info)
    254