blob: 1884a4ac389f21fdc96cf8a67816e400d48c98e8 [file] [log] [blame]
Greg Claytonaa9d02b2012-01-20 03:15:45 +00001#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# Be sure to add the python path that points to the LLDB shared library.
Greg Clayton5c0f4832012-01-21 00:37:19 +00005#
6# To use this in the embedded python interpreter using "lldb":
7#
8# cd /path/containing/crashlog.py
9# lldb
10# (lldb) script import crashlog
11# "crashlog" command installed, type "crashlog --help" for detailed help
12# (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
13#
14# The benefit of running the crashlog command inside lldb in the
15# embedded python interpreter is when the command completes, there
16# will be a target with all of the files loaded at the locations
17# described in the crash log. Only the files that have stack frames
18# in the backtrace will be loaded unless the "--load-all" option
19# has been specified. This allows users to explore the program in the
20# state it was in right at crash time.
21#
Greg Claytonaa9d02b2012-01-20 03:15:45 +000022# On MacOSX csh, tcsh:
Greg Clayton5c0f4832012-01-21 00:37:19 +000023# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
24#
Greg Claytonaa9d02b2012-01-20 03:15:45 +000025# On MacOSX sh, bash:
Greg Clayton5c0f4832012-01-21 00:37:19 +000026# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
Greg Claytonaa9d02b2012-01-20 03:15:45 +000027#----------------------------------------------------------------------
28
Greg Clayton5c0f4832012-01-21 00:37:19 +000029import commands
Greg Claytonc8f73d72012-05-04 20:44:14 +000030import cmd
Greg Claytonf47b2c22012-06-27 20:02:04 +000031import datetime
Greg Claytonc8f73d72012-05-04 20:44:14 +000032import glob
Greg Claytonaa9d02b2012-01-20 03:15:45 +000033import optparse
34import os
Greg Claytonf47b2c22012-06-27 20:02:04 +000035import platform
Greg Claytonaa9d02b2012-01-20 03:15:45 +000036import plistlib
Greg Clayton3c2c4bb2012-04-03 21:35:43 +000037import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Claytonaa9d02b2012-01-20 03:15:45 +000038import re
Greg Clayton53b43b02012-01-21 04:26:24 +000039import shlex
Greg Claytonc8f73d72012-05-04 20:44:14 +000040import string
Greg Claytonaa9d02b2012-01-20 03:15:45 +000041import sys
42import time
Greg Clayton3d8d3db2012-01-20 06:12:47 +000043import uuid
Greg Claytonaa4d4532012-09-19 01:59:34 +000044
45try:
46 # Just try for LLDB in case PYTHONPATH is already correctly setup
47 import lldb
48except ImportError:
49 lldb_python_dirs = list()
50 # lldb is not in the PYTHONPATH, try some defaults for the current platform
51 platform_system = platform.system()
52 if platform_system == 'Darwin':
53 # On Darwin, try the currently selected Xcode directory
54 xcode_dir = commands.getoutput("xcode-select --print-path")
55 if xcode_dir:
56 lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python'))
57 lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
58 lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
59 success = False
60 for lldb_python_dir in lldb_python_dirs:
61 if os.path.exists(lldb_python_dir):
62 if not (sys.path.__contains__(lldb_python_dir)):
63 sys.path.append(lldb_python_dir)
64 try:
65 import lldb
66 except ImportError:
67 pass
68 else:
69 print 'imported lldb from: "%s"' % (lldb_python_dir)
70 success = True
71 break
72 if not success:
73 print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
74 sys.exit(1)
75
Greg Claytonc8f73d72012-05-04 20:44:14 +000076from lldb.utils import symbolication
Greg Claytonaa9d02b2012-01-20 03:15:45 +000077
78PARSE_MODE_NORMAL = 0
79PARSE_MODE_THREAD = 1
80PARSE_MODE_IMAGES = 2
81PARSE_MODE_THREGS = 3
82PARSE_MODE_SYSTEM = 4
83
Greg Claytonc8f73d72012-05-04 20:44:14 +000084class CrashLog(symbolication.Symbolicator):
Greg Claytonaa9d02b2012-01-20 03:15:45 +000085 """Class that does parses darwin crash logs"""
Sean Callananc51cd472013-01-12 02:11:49 +000086 parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]');
Greg Claytonaa9d02b2012-01-20 03:15:45 +000087 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
88 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Jason Molendad204afa2012-08-31 23:27:24 +000089 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t?(0x[0-9a-fA-F]+) +(.*)')
Greg Claytona32bfbe2012-01-20 03:32:35 +000090 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
91 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Claytonaa9d02b2012-01-20 03:15:45 +000092 empty_line_regex = re.compile('^$')
93
94 class Thread:
95 """Class that represents a thread in a darwin crash log"""
96 def __init__(self, index):
97 self.index = index
98 self.frames = list()
Greg Clayton563d0392012-07-13 03:19:35 +000099 self.idents = list()
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000100 self.registers = dict()
101 self.reason = None
102 self.queue = None
103
104 def dump(self, prefix):
105 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
106 if self.frames:
107 print "%s Frames:" % (prefix)
108 for frame in self.frames:
109 frame.dump(prefix + ' ')
110 if self.registers:
111 print "%s Registers:" % (prefix)
112 for reg in self.registers.keys():
113 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
114
Greg Clayton563d0392012-07-13 03:19:35 +0000115 def add_ident(self, ident):
116 if not ident in self.idents:
117 self.idents.append(ident)
118
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000119 def did_crash(self):
120 return self.reason != None
121
122 def __str__(self):
123 s = "Thread[%u]" % self.index
124 if self.reason:
125 s += ' %s' % self.reason
126 return s
127
128
129 class Frame:
130 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000131 def __init__(self, index, pc, description):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000132 self.pc = pc
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000133 self.description = description
134 self.index = index
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000135
136 def __str__(self):
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000137 if self.description:
138 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
139 else:
Johnny Chen8e1fd432012-05-03 18:46:28 +0000140 return "[%3u] 0x%16.16x" % (self.index, self.pc)
141
142 def dump(self, prefix):
143 print "%s%s" % (prefix, str(self))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000144
Greg Claytonc8f73d72012-05-04 20:44:14 +0000145 class DarwinImage(symbolication.Image):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000146 """Class that represents a binary images in a darwin crash log"""
Greg Claytona32bfbe2012-01-20 03:32:35 +0000147 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000148 if not os.path.exists(dsymForUUIDBinary):
149 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
150
151 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Claytona32bfbe2012-01-20 03:32:35 +0000152
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000153 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Claytonc8f73d72012-05-04 20:44:14 +0000154 symbolication.Image.__init__(self, path, uuid);
155 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000156 self.identifier = identifier
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000157 self.version = version
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000158
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000159 def locate_module_and_debug_symbols(self):
Greg Claytonf51a23f2012-06-04 23:22:17 +0000160 # Don't load a module twice...
161 if self.resolved:
Greg Claytonf99295c2012-04-20 23:31:27 +0000162 return True
Greg Claytonf51a23f2012-06-04 23:22:17 +0000163 # Mark this as resolved so we don't keep trying
164 self.resolved = True
Greg Clayton60bb58f2012-05-11 00:30:14 +0000165 uuid_str = self.get_normalized_uuid_string()
166 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Claytona32bfbe2012-01-20 03:32:35 +0000167 if os.path.exists(self.dsymForUUIDBinary):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000168 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Claytona32bfbe2012-01-20 03:32:35 +0000169 s = commands.getoutput(dsym_for_uuid_command)
170 if s:
171 plist_root = plistlib.readPlistFromString (s)
172 if plist_root:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000173 plist = plist_root[uuid_str]
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000174 if plist:
175 if 'DBGArchitecture' in plist:
176 self.arch = plist['DBGArchitecture']
177 if 'DBGDSYMPath' in plist:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000178 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000179 if 'DBGSymbolRichExecutable' in plist:
Greg Clayton86e70cb2014-04-07 23:50:17 +0000180 self.path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
181 self.resolved_path = self.path
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000182 if not self.resolved_path and os.path.exists(self.path):
183 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Clayton60bb58f2012-05-11 00:30:14 +0000184 self_uuid = self.get_uuid()
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000185 for line in dwarfdump_cmd_output.splitlines():
186 match = self.dwarfdump_uuid_regex.search (line)
187 if match:
188 dwarf_uuid_str = match.group(1)
189 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
190 if self_uuid == dwarf_uuid:
191 self.resolved_path = self.path
192 self.arch = match.group(2)
193 break;
194 if not self.resolved_path:
Greg Claytonf51a23f2012-06-04 23:22:17 +0000195 self.unavailable = True
196 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Claytonf99295c2012-04-20 23:31:27 +0000197 return False
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000198 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
199 print 'ok'
Greg Claytond712ef02012-04-25 18:40:20 +0000200 # if self.resolved_path:
201 # print ' exe = "%s"' % self.resolved_path
202 # if self.symfile:
203 # print ' dsym = "%s"' % self.symfile
Greg Claytonf99295c2012-04-20 23:31:27 +0000204 return True
Greg Claytonf51a23f2012-06-04 23:22:17 +0000205 else:
206 self.unavailable = True
Greg Claytonf99295c2012-04-20 23:31:27 +0000207 return False
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000208
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000209
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000210
211 def __init__(self, path):
212 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Claytonc8f73d72012-05-04 20:44:14 +0000213 symbolication.Symbolicator.__init__(self);
Greg Clayton53b43b02012-01-21 04:26:24 +0000214 self.path = os.path.expanduser(path);
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000215 self.info_lines = list()
216 self.system_profile = list()
217 self.threads = list()
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000218 self.idents = list() # A list of the required identifiers for doing all stack backtraces
219 self.crashed_thread_idx = -1
220 self.version = -1
Greg Clayton53b43b02012-01-21 04:26:24 +0000221 self.error = None
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000222 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton53b43b02012-01-21 04:26:24 +0000223 try:
224 f = open(self.path)
225 except IOError:
226 self.error = 'error: cannot open "%s"' % self.path
227 return
228
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000229 self.file_lines = f.read().splitlines()
230 parse_mode = PARSE_MODE_NORMAL
231 thread = None
232 for line in self.file_lines:
233 # print line
234 line_len = len(line)
235 if line_len == 0:
236 if thread:
237 if parse_mode == PARSE_MODE_THREAD:
238 if thread.index == self.crashed_thread_idx:
239 thread.reason = ''
240 if self.thread_exception:
241 thread.reason += self.thread_exception
242 if self.thread_exception_data:
243 thread.reason += " (%s)" % self.thread_exception_data
244 self.threads.append(thread)
245 thread = None
246 else:
247 # only append an extra empty line if the previous line
248 # in the info_lines wasn't empty
249 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
250 self.info_lines.append(line)
251 parse_mode = PARSE_MODE_NORMAL
252 # print 'PARSE_MODE_NORMAL'
253 elif parse_mode == PARSE_MODE_NORMAL:
254 if line.startswith ('Process:'):
Greg Clayton45b673d2012-08-13 18:48:03 +0000255 (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000256 self.process_id = pid_with_brackets.strip('[]')
257 elif line.startswith ('Path:'):
258 self.process_path = line[5:].strip()
259 elif line.startswith ('Identifier:'):
260 self.process_identifier = line[11:].strip()
261 elif line.startswith ('Version:'):
Johnny Chenafc98a62012-05-10 22:45:54 +0000262 version_string = line[8:].strip()
263 matched_pair = re.search("(.+)\((.+)\)", version_string)
264 if matched_pair:
265 self.process_version = matched_pair.group(1)
266 self.process_compatability_version = matched_pair.group(2)
267 else:
268 self.process = version_string
269 self.process_compatability_version = version_string
Sean Callananc51cd472013-01-12 02:11:49 +0000270 elif self.parent_process_regex.search(line):
271 parent_process_match = self.parent_process_regex.search(line)
272 self.parent_process_name = parent_process_match.group(1)
273 self.parent_process_id = parent_process_match.group(2)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000274 elif line.startswith ('Exception Type:'):
275 self.thread_exception = line[15:].strip()
276 continue
277 elif line.startswith ('Exception Codes:'):
278 self.thread_exception_data = line[16:].strip()
279 continue
280 elif line.startswith ('Crashed Thread:'):
281 self.crashed_thread_idx = int(line[15:].strip().split()[0])
282 continue
283 elif line.startswith ('Report Version:'):
284 self.version = int(line[15:].strip())
285 continue
286 elif line.startswith ('System Profile:'):
287 parse_mode = PARSE_MODE_SYSTEM
288 continue
289 elif (line.startswith ('Interval Since Last Report:') or
290 line.startswith ('Crashes Since Last Report:') or
291 line.startswith ('Per-App Interval Since Last Report:') or
292 line.startswith ('Per-App Crashes Since Last Report:') or
293 line.startswith ('Sleep/Wake UUID:') or
294 line.startswith ('Anonymous UUID:')):
295 # ignore these
296 continue
297 elif line.startswith ('Thread'):
298 thread_state_match = self.thread_state_regex.search (line)
299 if thread_state_match:
300 thread_state_match = self.thread_regex.search (line)
301 thread_idx = int(thread_state_match.group(1))
302 parse_mode = PARSE_MODE_THREGS
303 thread = self.threads[thread_idx]
304 else:
305 thread_match = self.thread_regex.search (line)
306 if thread_match:
307 # print 'PARSE_MODE_THREAD'
308 parse_mode = PARSE_MODE_THREAD
309 thread_idx = int(thread_match.group(1))
310 thread = CrashLog.Thread(thread_idx)
311 continue
312 elif line.startswith ('Binary Images:'):
313 parse_mode = PARSE_MODE_IMAGES
314 continue
315 self.info_lines.append(line.strip())
316 elif parse_mode == PARSE_MODE_THREAD:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000317 if line.startswith ('Thread'):
318 continue
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000319 frame_match = self.frame_regex.search(line)
320 if frame_match:
321 ident = frame_match.group(2)
Greg Clayton563d0392012-07-13 03:19:35 +0000322 thread.add_ident(ident)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000323 if not ident in self.idents:
324 self.idents.append(ident)
325 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
326 else:
Greg Claytona32bfbe2012-01-20 03:32:35 +0000327 print 'error: frame regex failed for line: "%s"' % line
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000328 elif parse_mode == PARSE_MODE_IMAGES:
329 image_match = self.image_regex_uuid.search (line)
330 if image_match:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000331 image = CrashLog.DarwinImage (int(image_match.group(1),0),
332 int(image_match.group(2),0),
333 image_match.group(3).strip(),
334 image_match.group(4).strip(),
Greg Clayton60bb58f2012-05-11 00:30:14 +0000335 uuid.UUID(image_match.group(5)),
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000336 image_match.group(6))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000337 self.images.append (image)
338 else:
339 image_match = self.image_regex_no_uuid.search (line)
340 if image_match:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000341 image = CrashLog.DarwinImage (int(image_match.group(1),0),
342 int(image_match.group(2),0),
343 image_match.group(3).strip(),
344 image_match.group(4).strip(),
345 None,
346 image_match.group(5))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000347 self.images.append (image)
348 else:
349 print "error: image regex failed for: %s" % line
350
351 elif parse_mode == PARSE_MODE_THREGS:
352 stripped_line = line.strip()
Jason Molendae5ad3852012-08-28 23:46:12 +0000353 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
354 reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000355 for reg_value in reg_values:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000356 #print 'reg_value = "%s"' % reg_value
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000357 (reg, value) = reg_value.split(': ')
Greg Clayton60bb58f2012-05-11 00:30:14 +0000358 #print 'reg = "%s"' % reg
359 #print 'value = "%s"' % value
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000360 thread.registers[reg.strip()] = int(value, 0)
361 elif parse_mode == PARSE_MODE_SYSTEM:
362 self.system_profile.append(line)
363 f.close()
Greg Claytonf51a23f2012-06-04 23:22:17 +0000364
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000365 def dump(self):
366 print "Crash Log File: %s" % (self.path)
367 print "\nThreads:"
368 for thread in self.threads:
369 thread.dump(' ')
370 print "\nImages:"
371 for image in self.images:
372 image.dump(' ')
373
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000374 def find_image_with_identifier(self, identifier):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000375 for image in self.images:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000376 if image.identifier == identifier:
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000377 return image
378 return None
379
Greg Clayton42a6eb72012-01-20 19:25:32 +0000380 def create_target(self):
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000381 #print 'crashlog.create_target()...'
Greg Claytonc8f73d72012-05-04 20:44:14 +0000382 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000383 if target:
384 return target
Greg Clayton42a6eb72012-01-20 19:25:32 +0000385 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000386 print 'crashlog.create_target()...2'
Greg Clayton42a6eb72012-01-20 19:25:32 +0000387 if self.idents:
Sean Callanan88685f22012-01-20 19:27:48 +0000388 for ident in self.idents:
Greg Clayton42a6eb72012-01-20 19:25:32 +0000389 image = self.find_image_with_identifier (ident)
390 if image:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000391 target = image.create_target ()
392 if target:
393 return target # success
394 print 'crashlog.create_target()...3'
Greg Clayton42a6eb72012-01-20 19:25:32 +0000395 for image in self.images:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000396 target = image.create_target ()
397 if target:
398 return target # success
399 print 'crashlog.create_target()...4'
400 print 'error: unable to locate any executables from the crash log'
401 return None
Greg Claytonf51a23f2012-06-04 23:22:17 +0000402
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000403
404def usage():
405 print "Usage: lldb-symbolicate.py [-n name] executable-image"
406 sys.exit(0)
407
Greg Claytonc8f73d72012-05-04 20:44:14 +0000408class Interactive(cmd.Cmd):
409 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
410 image_option_parser = None
411
412 def __init__(self, crash_logs):
413 cmd.Cmd.__init__(self)
Greg Claytonf7ab0382012-07-03 21:40:18 +0000414 self.use_rawinput = False
Greg Claytonc8f73d72012-05-04 20:44:14 +0000415 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
416 self.crash_logs = crash_logs
417 self.prompt = '% '
418
419 def default(self, line):
420 '''Catch all for unknown command, which will exit the interpreter.'''
421 print "uknown command: %s" % line
422 return True
423
424 def do_q(self, line):
425 '''Quit command'''
426 return True
427
428 def do_quit(self, line):
429 '''Quit command'''
430 return True
431
Greg Claytoneb749092012-05-31 21:21:08 +0000432 def do_symbolicate(self, line):
433 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
434 inlined stack frames back to the concrete functions, and disassemble the location of the crash
435 for the first frame of the crashed thread.'''
436 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
437 command_args = shlex.split(line)
438 try:
439 (options, args) = option_parser.parse_args(command_args)
440 except:
441 return
442
Greg Clayton784933b2012-07-16 20:40:20 +0000443 if args:
444 # We have arguments, they must valid be crash log file indexes
445 for idx_str in args:
446 idx = int(idx_str)
447 if idx < len(self.crash_logs):
448 SymbolicateCrashLog (self.crash_logs[idx], options)
449 else:
450 print 'error: crash log index %u is out of range' % (idx)
451 else:
452 # No arguments, symbolicate all crash logs using the options provided
453 for idx in range(len(self.crash_logs)):
454 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytoneb749092012-05-31 21:21:08 +0000455
Greg Claytonc8f73d72012-05-04 20:44:14 +0000456 def do_list(self, line=None):
457 '''Dump a list of all crash logs that are currently loaded.
458
459 USAGE: list'''
460 print '%u crash logs are loaded:' % len(self.crash_logs)
461 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
462 print '[%u] = %s' % (crash_log_idx, crash_log.path)
463
464 def do_image(self, line):
Greg Clayton784933b2012-07-16 20:40:20 +0000465 '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
Greg Claytonc8f73d72012-05-04 20:44:14 +0000466 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Clayton784933b2012-07-16 20:40:20 +0000467 description='''Dump information about one or more images in all crash logs. The <PATH> can be a full path, image basename, or partial path. Searches are done in this order.'''
Greg Claytonc8f73d72012-05-04 20:44:14 +0000468 command_args = shlex.split(line)
469 if not self.image_option_parser:
470 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
471 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
472 try:
473 (options, args) = self.image_option_parser.parse_args(command_args)
474 except:
475 return
476
Greg Clayton60bb58f2012-05-11 00:30:14 +0000477 if args:
478 for image_path in args:
479 fullpath_search = image_path[0] == '/'
Greg Clayton784933b2012-07-16 20:40:20 +0000480 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000481 matches_found = 0
Greg Claytonc8f73d72012-05-04 20:44:14 +0000482 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000483 if fullpath_search:
484 if image.get_resolved_path() == image_path:
485 matches_found += 1
Greg Clayton784933b2012-07-16 20:40:20 +0000486 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000487 else:
488 image_basename = image.get_resolved_path_basename()
489 if image_basename == image_path:
490 matches_found += 1
Greg Clayton784933b2012-07-16 20:40:20 +0000491 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000492 if matches_found == 0:
493 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton563566c2012-05-16 20:49:19 +0000494 resolved_image_path = image.get_resolved_path()
495 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Clayton784933b2012-07-16 20:40:20 +0000496 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000497 else:
498 for crash_log in self.crash_logs:
499 for (image_idx, image) in enumerate(crash_log.images):
500 print '[%u] %s' % (image_idx, image)
Greg Claytonc8f73d72012-05-04 20:44:14 +0000501 return False
502
503
504def interactive_crashlogs(options, args):
505 crash_log_files = list()
506 for arg in args:
507 for resolved_path in glob.glob(arg):
508 crash_log_files.append(resolved_path)
509
510 crash_logs = list();
511 for crash_log_file in crash_log_files:
512 #print 'crash_log_file = "%s"' % crash_log_file
513 crash_log = CrashLog(crash_log_file)
514 if crash_log.error:
515 print crash_log.error
516 continue
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000517 if options.debug:
Greg Claytonc8f73d72012-05-04 20:44:14 +0000518 crash_log.dump()
519 if not crash_log.images:
520 print 'error: no images in crash log "%s"' % (crash_log)
521 continue
522 else:
523 crash_logs.append(crash_log)
524
525 interpreter = Interactive(crash_logs)
526 # List all crash logs that were imported
527 interpreter.do_list()
528 interpreter.cmdloop()
529
Greg Claytonf47b2c22012-06-27 20:02:04 +0000530
531def save_crashlog(debugger, command, result, dict):
532 usage = "usage: %prog [options] <output-path>"
533 description='''Export the state of current target into a crashlog file'''
534 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
535 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
536 try:
537 (options, args) = parser.parse_args(shlex.split(command))
538 except:
539 result.PutCString ("error: invalid options");
540 return
541 if len(args) != 1:
542 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
543 return
544 out_file = open(args[0], 'w')
545 if not out_file:
546 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
547 return
548 if lldb.target:
549 identifier = lldb.target.executable.basename
550 if lldb.process:
551 pid = lldb.process.id
552 if pid != lldb.LLDB_INVALID_PROCESS_ID:
553 out_file.write('Process: %s [%u]\n' % (identifier, pid))
554 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
555 out_file.write('Identifier: %s\n' % (identifier))
556 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
557 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
558 out_file.write('Report Version: 9\n')
559 for thread_idx in range(lldb.process.num_threads):
560 thread = lldb.process.thread[thread_idx]
561 out_file.write('\nThread %u:\n' % (thread_idx))
562 for (frame_idx, frame) in enumerate(thread.frames):
563 frame_pc = frame.pc
564 frame_offset = 0
565 if frame.function:
566 block = frame.GetFrameBlock()
567 block_range = block.range[frame.addr]
568 if block_range:
569 block_start_addr = block_range[0]
570 frame_offset = frame_pc - block_start_addr.load_addr
571 else:
572 frame_offset = frame_pc - frame.function.addr.load_addr
573 elif frame.symbol:
574 frame_offset = frame_pc - frame.symbol.addr.load_addr
575 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
576 if frame_offset > 0:
577 out_file.write(' + %u' % (frame_offset))
578 line_entry = frame.line_entry
579 if line_entry:
580 if options.verbose:
581 # This will output the fullpath + line + column
582 out_file.write(' %s' % (line_entry))
583 else:
584 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
585 column = line_entry.column
586 if column:
587 out_file.write(':%u' % (column))
588 out_file.write('\n')
589
590 out_file.write('\nBinary Images:\n')
591 for module in lldb.target.modules:
592 text_segment = module.section['__TEXT']
593 if text_segment:
594 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
595 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
596 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
597 identifier = module.file.basename
598 module_version = '???'
599 module_version_array = module.GetVersion()
600 if module_version_array:
601 module_version = '.'.join(map(str,module_version_array))
602 out_file.write (' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' % (text_segment_load_addr, text_segment_end_load_addr, identifier, module_version, module.GetUUIDString(), module.file.fullpath))
603 out_file.close()
604 else:
605 result.PutCString ("error: invalid target");
Greg Claytonc8f73d72012-05-04 20:44:14 +0000606
Greg Claytonf47b2c22012-06-27 20:02:04 +0000607
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000608def Symbolicate(debugger, command, result, dict):
Greg Clayton53b43b02012-01-21 04:26:24 +0000609 try:
Greg Claytoneb749092012-05-31 21:21:08 +0000610 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton53b43b02012-01-21 04:26:24 +0000611 except:
612 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytoneb749092012-05-31 21:21:08 +0000613
614def SymbolicateCrashLog(crash_log, options):
615 if crash_log.error:
616 print crash_log.error
617 return
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000618 if options.debug:
Greg Claytoneb749092012-05-31 21:21:08 +0000619 crash_log.dump()
620 if not crash_log.images:
621 print 'error: no images in crash log'
622 return
623
Greg Clayton1f746072012-08-29 21:13:06 +0000624 if options.dump_image_list:
625 print "Binary Images:"
626 for image in crash_log.images:
627 if options.verbose:
628 print image.debug_dump()
629 else:
630 print image
631
Greg Claytoneb749092012-05-31 21:21:08 +0000632 target = crash_log.create_target ()
633 if not target:
634 return
635 exe_module = target.GetModuleAtIndex(0)
636 images_to_load = list()
637 loaded_images = list()
638 if options.load_all_images:
639 # --load-all option was specified, load everything up
640 for image in crash_log.images:
641 images_to_load.append(image)
642 else:
643 # Only load the images found in stack frames for the crashed threads
Greg Clayton563d0392012-07-13 03:19:35 +0000644 if options.crashed_only:
645 for thread in crash_log.threads:
646 if thread.did_crash():
647 for ident in thread.idents:
648 images = crash_log.find_images_with_identifier (ident)
649 if images:
650 for image in images:
651 images_to_load.append(image)
652 else:
653 print 'error: can\'t find image for identifier "%s"' % ident
654 else:
655 for ident in crash_log.idents:
656 images = crash_log.find_images_with_identifier (ident)
657 if images:
658 for image in images:
659 images_to_load.append(image)
660 else:
661 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytoneb749092012-05-31 21:21:08 +0000662
663 for image in images_to_load:
664 if image in loaded_images:
665 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
666 else:
667 err = image.add_module (target)
668 if err:
669 print err
670 else:
671 #print 'loaded %s' % image
672 loaded_images.append(image)
673
674 for thread in crash_log.threads:
675 this_thread_crashed = thread.did_crash()
676 if options.crashed_only and this_thread_crashed == False:
677 continue
678 print "%s" % thread
679 #prev_frame_index = -1
Greg Claytonc51d70d2012-07-13 17:58:52 +0000680 display_frame_idx = -1
Greg Claytoneb749092012-05-31 21:21:08 +0000681 for frame_idx, frame in enumerate(thread.frames):
682 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
683 if frame_idx == 0:
Greg Claytona16cb162013-04-04 23:36:51 +0000684 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc & crash_log.addr_mask, options.verbose)
Greg Claytoneb749092012-05-31 21:21:08 +0000685 else:
686 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Claytona16cb162013-04-04 23:36:51 +0000687 symbolicated_frame_addresses = crash_log.symbolicate ((frame.pc & crash_log.addr_mask) - 1, options.verbose)
Greg Claytoneb749092012-05-31 21:21:08 +0000688
689 if symbolicated_frame_addresses:
690 symbolicated_frame_address_idx = 0
691 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Claytonc51d70d2012-07-13 17:58:52 +0000692 display_frame_idx += 1
Greg Claytoneb749092012-05-31 21:21:08 +0000693 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Claytonc51d70d2012-07-13 17:58:52 +0000694 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
695 source_context = options.source_context
Greg Clayton563d0392012-07-13 03:19:35 +0000696 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
697 if line_entry.IsValid():
698 strm = lldb.SBStream()
699 if line_entry:
Greg Claytonc51d70d2012-07-13 17:58:52 +0000700 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Clayton563d0392012-07-13 03:19:35 +0000701 source_text = strm.GetData()
702 if source_text:
703 # Indent the source a bit
704 indent_str = ' '
705 join_str = '\n' + indent_str
706 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytoneb749092012-05-31 21:21:08 +0000707 if symbolicated_frame_address_idx == 0:
708 if disassemble:
709 instructions = symbolicated_frame_address.get_instructions()
710 if instructions:
711 print
712 symbolication.disassemble_instructions (target,
713 instructions,
714 frame.pc,
715 options.disassemble_before,
716 options.disassemble_after, frame.index > 0)
717 print
718 symbolicated_frame_address_idx += 1
719 else:
720 print frame
721 print
722
Greg Claytoneb749092012-05-31 21:21:08 +0000723def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Clayton5c0f4832012-01-21 00:37:19 +0000724 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytoneb749092012-05-31 21:21:08 +0000725 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Claytonc51d70d2012-07-13 17:58:52 +0000726 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
727 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
728 option_parser.add_option('--load-all' , '-a', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False)
729 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
730 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
731 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
732 option_parser.add_option('--disasm-depth' , '-d', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1)
733 option_parser.add_option('--disasm-all' , '-D', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False)
734 option_parser.add_option('--disasm-before' , '-B', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
735 option_parser.add_option('--disasm-after' , '-A', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
736 option_parser.add_option('--source-context', '-C', type='int', metavar='NLINES', dest='source_context', help='show NLINES source lines of source context (default = 4)', default=4)
737 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
738 option_parser.add_option('--source-all' , action='store_true', dest='source_all', help='show source for all threads, not just the crashed thread', default=False)
Greg Claytoneb749092012-05-31 21:21:08 +0000739 if add_interactive_options:
740 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
741 return option_parser
742
743def SymbolicateCrashLogs(command_args):
Greg Clayton5c0f4832012-01-21 00:37:19 +0000744 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
745inlined stack frames back to the concrete functions, and disassemble the location of the crash
746for the first frame of the crashed thread.
747If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
748for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
749created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
750you to explore the program as if it were stopped at the locations described in the crash log and functions can
751be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytoneb749092012-05-31 21:21:08 +0000752 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton53b43b02012-01-21 04:26:24 +0000753 try:
Greg Claytoneb749092012-05-31 21:21:08 +0000754 (options, args) = option_parser.parse_args(command_args)
Greg Clayton53b43b02012-01-21 04:26:24 +0000755 except:
756 return
757
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000758 if options.debug:
Greg Clayton53b43b02012-01-21 04:26:24 +0000759 print 'command_args = %s' % command_args
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000760 print 'options', options
Greg Clayton53b43b02012-01-21 04:26:24 +0000761 print 'args', args
762
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000763 if options.debug_delay > 0:
764 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
765 time.sleep(options.debug_delay)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000766 error = lldb.SBError()
Greg Claytonc8f73d72012-05-04 20:44:14 +0000767
Greg Clayton42a6eb72012-01-20 19:25:32 +0000768 if args:
Greg Claytonc8f73d72012-05-04 20:44:14 +0000769 if options.interactive:
770 interactive_crashlogs(options, args)
771 else:
772 for crash_log_file in args:
Greg Claytonf51a23f2012-06-04 23:22:17 +0000773 crash_log = CrashLog(crash_log_file)
Greg Claytoneb749092012-05-31 21:21:08 +0000774 SymbolicateCrashLog (crash_log, options)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000775if __name__ == '__main__':
Greg Clayton42a6eb72012-01-20 19:25:32 +0000776 # Create a new debugger instance
777 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytoneb749092012-05-31 21:21:08 +0000778 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chen356782f2012-05-03 22:31:30 +0000779elif getattr(lldb, 'debugger', None):
Greg Claytoned3eee62012-04-25 01:49:50 +0000780 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonf47b2c22012-06-27 20:02:04 +0000781 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
782 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000783