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
20
21import symbol
22
23def 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
35def 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
46UNKNOWN = "<unknown>"
47HEAP = "[heap]"
48STACK = "[stack]"
49
50
51def 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
63def PrintDivider():
64  print
65  print "-----------------------------------------------------\n"
66
67def 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