blob: 35c1b14dc221718e2779c419e9dd8e9cbaddacbf [file] [log] [blame]
Greg Clayton01f7c962012-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 Claytona3698c62012-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 Clayton01f7c962012-01-20 03:15:45 +000022# On MacOSX csh, tcsh:
Greg Claytona3698c62012-01-21 00:37:19 +000023# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
24#
Greg Clayton01f7c962012-01-20 03:15:45 +000025# On MacOSX sh, bash:
Greg Claytona3698c62012-01-21 00:37:19 +000026# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
Greg Clayton01f7c962012-01-20 03:15:45 +000027#----------------------------------------------------------------------
28
29import lldb
Greg Claytona3698c62012-01-21 00:37:19 +000030import commands
Greg Clayton9d010422012-05-04 20:44:14 +000031import cmd
32import glob
Greg Clayton01f7c962012-01-20 03:15:45 +000033import optparse
34import os
35import plistlib
Greg Clayton3d39f832012-04-03 21:35:43 +000036import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Clayton01f7c962012-01-20 03:15:45 +000037import re
Greg Clayton223e8082012-01-21 04:26:24 +000038import shlex
Greg Clayton9d010422012-05-04 20:44:14 +000039import string
Greg Clayton01f7c962012-01-20 03:15:45 +000040import sys
41import time
Greg Claytoncd793122012-01-20 06:12:47 +000042import uuid
Greg Clayton9d010422012-05-04 20:44:14 +000043from lldb.utils import symbolication
Greg Clayton01f7c962012-01-20 03:15:45 +000044
45PARSE_MODE_NORMAL = 0
46PARSE_MODE_THREAD = 1
47PARSE_MODE_IMAGES = 2
48PARSE_MODE_THREGS = 3
49PARSE_MODE_SYSTEM = 4
50
Greg Clayton9d010422012-05-04 20:44:14 +000051class CrashLog(symbolication.Symbolicator):
Greg Clayton01f7c962012-01-20 03:15:45 +000052 """Class that does parses darwin crash logs"""
53 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
54 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Greg Clayton8077a532012-01-20 03:32:35 +000055 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t(0x[0-9a-fA-F]+) +(.*)')
56 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
57 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Clayton01f7c962012-01-20 03:15:45 +000058 empty_line_regex = re.compile('^$')
59
60 class Thread:
61 """Class that represents a thread in a darwin crash log"""
62 def __init__(self, index):
63 self.index = index
64 self.frames = list()
65 self.registers = dict()
66 self.reason = None
67 self.queue = None
68
69 def dump(self, prefix):
70 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
71 if self.frames:
72 print "%s Frames:" % (prefix)
73 for frame in self.frames:
74 frame.dump(prefix + ' ')
75 if self.registers:
76 print "%s Registers:" % (prefix)
77 for reg in self.registers.keys():
78 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
79
80 def did_crash(self):
81 return self.reason != None
82
83 def __str__(self):
84 s = "Thread[%u]" % self.index
85 if self.reason:
86 s += ' %s' % self.reason
87 return s
88
89
90 class Frame:
91 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3d39f832012-04-03 21:35:43 +000092 def __init__(self, index, pc, description):
Greg Clayton01f7c962012-01-20 03:15:45 +000093 self.pc = pc
Greg Clayton3d39f832012-04-03 21:35:43 +000094 self.description = description
95 self.index = index
Greg Clayton01f7c962012-01-20 03:15:45 +000096
97 def __str__(self):
Greg Clayton3d39f832012-04-03 21:35:43 +000098 if self.description:
99 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
100 else:
Johnny Chen4e468672012-05-03 18:46:28 +0000101 return "[%3u] 0x%16.16x" % (self.index, self.pc)
102
103 def dump(self, prefix):
104 print "%s%s" % (prefix, str(self))
Greg Clayton01f7c962012-01-20 03:15:45 +0000105
Greg Clayton9d010422012-05-04 20:44:14 +0000106 class DarwinImage(symbolication.Image):
Greg Clayton01f7c962012-01-20 03:15:45 +0000107 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000108 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000109 if not os.path.exists(dsymForUUIDBinary):
110 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
111
112 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000113
Greg Clayton3d39f832012-04-03 21:35:43 +0000114 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Clayton9d010422012-05-04 20:44:14 +0000115 symbolication.Image.__init__(self, path, uuid);
116 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3d39f832012-04-03 21:35:43 +0000117 self.identifier = identifier
Greg Clayton01f7c962012-01-20 03:15:45 +0000118 self.version = version
Greg Clayton01f7c962012-01-20 03:15:45 +0000119
Greg Clayton3d39f832012-04-03 21:35:43 +0000120 def locate_module_and_debug_symbols(self):
Greg Claytoncd793122012-01-20 06:12:47 +0000121 if self.resolved_path:
122 # Don't load a module twice...
Greg Clayton4c983c82012-04-20 23:31:27 +0000123 return True
Greg Clayton1dae6f32012-04-25 18:40:20 +0000124 print 'Getting symbols for %s %s...' % (self.uuid, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000125 if os.path.exists(self.dsymForUUIDBinary):
126 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, self.uuid)
Greg Clayton8077a532012-01-20 03:32:35 +0000127 s = commands.getoutput(dsym_for_uuid_command)
128 if s:
129 plist_root = plistlib.readPlistFromString (s)
130 if plist_root:
Greg Claytoncd793122012-01-20 06:12:47 +0000131 plist = plist_root[self.uuid]
132 if plist:
133 if 'DBGArchitecture' in plist:
134 self.arch = plist['DBGArchitecture']
135 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000136 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000137 if 'DBGSymbolRichExecutable' in plist:
138 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
139 if not self.resolved_path and os.path.exists(self.path):
140 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
141 self_uuid = uuid.UUID(self.uuid)
142 for line in dwarfdump_cmd_output.splitlines():
143 match = self.dwarfdump_uuid_regex.search (line)
144 if match:
145 dwarf_uuid_str = match.group(1)
146 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
147 if self_uuid == dwarf_uuid:
148 self.resolved_path = self.path
149 self.arch = match.group(2)
150 break;
151 if not self.resolved_path:
152 print "error: file %s '%s' doesn't match the UUID in the installed file" % (self.uuid, self.path)
Greg Clayton4c983c82012-04-20 23:31:27 +0000153 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000154 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
155 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000156 # if self.resolved_path:
157 # print ' exe = "%s"' % self.resolved_path
158 # if self.symfile:
159 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000160 return True
161 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000162
Greg Clayton3d39f832012-04-03 21:35:43 +0000163
Greg Clayton01f7c962012-01-20 03:15:45 +0000164
165 def __init__(self, path):
166 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000167 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000168 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000169 self.info_lines = list()
170 self.system_profile = list()
171 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000172 self.idents = list() # A list of the required identifiers for doing all stack backtraces
173 self.crashed_thread_idx = -1
174 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000175 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000176 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000177 try:
178 f = open(self.path)
179 except IOError:
180 self.error = 'error: cannot open "%s"' % self.path
181 return
182
Greg Clayton01f7c962012-01-20 03:15:45 +0000183 self.file_lines = f.read().splitlines()
184 parse_mode = PARSE_MODE_NORMAL
185 thread = None
186 for line in self.file_lines:
187 # print line
188 line_len = len(line)
189 if line_len == 0:
190 if thread:
191 if parse_mode == PARSE_MODE_THREAD:
192 if thread.index == self.crashed_thread_idx:
193 thread.reason = ''
194 if self.thread_exception:
195 thread.reason += self.thread_exception
196 if self.thread_exception_data:
197 thread.reason += " (%s)" % self.thread_exception_data
198 self.threads.append(thread)
199 thread = None
200 else:
201 # only append an extra empty line if the previous line
202 # in the info_lines wasn't empty
203 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
204 self.info_lines.append(line)
205 parse_mode = PARSE_MODE_NORMAL
206 # print 'PARSE_MODE_NORMAL'
207 elif parse_mode == PARSE_MODE_NORMAL:
208 if line.startswith ('Process:'):
209 (self.process_name, pid_with_brackets) = line[8:].strip().split()
210 self.process_id = pid_with_brackets.strip('[]')
211 elif line.startswith ('Path:'):
212 self.process_path = line[5:].strip()
213 elif line.startswith ('Identifier:'):
214 self.process_identifier = line[11:].strip()
215 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000216 version_string = line[8:].strip()
217 matched_pair = re.search("(.+)\((.+)\)", version_string)
218 if matched_pair:
219 self.process_version = matched_pair.group(1)
220 self.process_compatability_version = matched_pair.group(2)
221 else:
222 self.process = version_string
223 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000224 elif line.startswith ('Parent Process:'):
225 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
226 self.parent_process_id = pid_with_brackets.strip('[]')
227 elif line.startswith ('Exception Type:'):
228 self.thread_exception = line[15:].strip()
229 continue
230 elif line.startswith ('Exception Codes:'):
231 self.thread_exception_data = line[16:].strip()
232 continue
233 elif line.startswith ('Crashed Thread:'):
234 self.crashed_thread_idx = int(line[15:].strip().split()[0])
235 continue
236 elif line.startswith ('Report Version:'):
237 self.version = int(line[15:].strip())
238 continue
239 elif line.startswith ('System Profile:'):
240 parse_mode = PARSE_MODE_SYSTEM
241 continue
242 elif (line.startswith ('Interval Since Last Report:') or
243 line.startswith ('Crashes Since Last Report:') or
244 line.startswith ('Per-App Interval Since Last Report:') or
245 line.startswith ('Per-App Crashes Since Last Report:') or
246 line.startswith ('Sleep/Wake UUID:') or
247 line.startswith ('Anonymous UUID:')):
248 # ignore these
249 continue
250 elif line.startswith ('Thread'):
251 thread_state_match = self.thread_state_regex.search (line)
252 if thread_state_match:
253 thread_state_match = self.thread_regex.search (line)
254 thread_idx = int(thread_state_match.group(1))
255 parse_mode = PARSE_MODE_THREGS
256 thread = self.threads[thread_idx]
257 else:
258 thread_match = self.thread_regex.search (line)
259 if thread_match:
260 # print 'PARSE_MODE_THREAD'
261 parse_mode = PARSE_MODE_THREAD
262 thread_idx = int(thread_match.group(1))
263 thread = CrashLog.Thread(thread_idx)
264 continue
265 elif line.startswith ('Binary Images:'):
266 parse_mode = PARSE_MODE_IMAGES
267 continue
268 self.info_lines.append(line.strip())
269 elif parse_mode == PARSE_MODE_THREAD:
270 frame_match = self.frame_regex.search(line)
271 if frame_match:
272 ident = frame_match.group(2)
273 if not ident in self.idents:
274 self.idents.append(ident)
275 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
276 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000277 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000278 elif parse_mode == PARSE_MODE_IMAGES:
279 image_match = self.image_regex_uuid.search (line)
280 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000281 image = CrashLog.DarwinImage (int(image_match.group(1),0),
282 int(image_match.group(2),0),
283 image_match.group(3).strip(),
284 image_match.group(4).strip(),
285 image_match.group(5),
286 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000287 self.images.append (image)
288 else:
289 image_match = self.image_regex_no_uuid.search (line)
290 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000291 image = CrashLog.DarwinImage (int(image_match.group(1),0),
292 int(image_match.group(2),0),
293 image_match.group(3).strip(),
294 image_match.group(4).strip(),
295 None,
296 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000297 self.images.append (image)
298 else:
299 print "error: image regex failed for: %s" % line
300
301 elif parse_mode == PARSE_MODE_THREGS:
302 stripped_line = line.strip()
303 reg_values = stripped_line.split(' ')
304 for reg_value in reg_values:
305 (reg, value) = reg_value.split(': ')
306 thread.registers[reg.strip()] = int(value, 0)
307 elif parse_mode == PARSE_MODE_SYSTEM:
308 self.system_profile.append(line)
309 f.close()
310
311 def dump(self):
312 print "Crash Log File: %s" % (self.path)
313 print "\nThreads:"
314 for thread in self.threads:
315 thread.dump(' ')
316 print "\nImages:"
317 for image in self.images:
318 image.dump(' ')
319
Greg Clayton3d39f832012-04-03 21:35:43 +0000320 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000321 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000322 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000323 return image
324 return None
325
Greg Claytone9ee5502012-01-20 19:25:32 +0000326 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000327 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000328 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000329 if target:
330 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000331 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000332 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000333 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000334 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000335 image = self.find_image_with_identifier (ident)
336 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000337 target = image.create_target ()
338 if target:
339 return target # success
340 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000341 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000342 target = image.create_target ()
343 if target:
344 return target # success
345 print 'crashlog.create_target()...4'
346 print 'error: unable to locate any executables from the crash log'
347 return None
Greg Claytone9ee5502012-01-20 19:25:32 +0000348
Greg Clayton01f7c962012-01-20 03:15:45 +0000349
350def usage():
351 print "Usage: lldb-symbolicate.py [-n name] executable-image"
352 sys.exit(0)
353
Greg Clayton9d010422012-05-04 20:44:14 +0000354class Interactive(cmd.Cmd):
355 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
356 image_option_parser = None
357
358 def __init__(self, crash_logs):
359 cmd.Cmd.__init__(self)
360 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
361 self.crash_logs = crash_logs
362 self.prompt = '% '
363
364 def default(self, line):
365 '''Catch all for unknown command, which will exit the interpreter.'''
366 print "uknown command: %s" % line
367 return True
368
369 def do_q(self, line):
370 '''Quit command'''
371 return True
372
373 def do_quit(self, line):
374 '''Quit command'''
375 return True
376
377 def do_list(self, line=None):
378 '''Dump a list of all crash logs that are currently loaded.
379
380 USAGE: list'''
381 print '%u crash logs are loaded:' % len(self.crash_logs)
382 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
383 print '[%u] = %s' % (crash_log_idx, crash_log.path)
384
385 def do_image(self, line):
386 '''Dump information about an image in the crash log given an image basename.
387
388 USAGE: image <basename>'''
389 usage = "usage: %prog [options] <PATH> [PATH ...]"
390 description='''Dump information about one or more images in all crash logs. The <PATH>
391 can be a full path or a image basename.'''
392 command_args = shlex.split(line)
393 if not self.image_option_parser:
394 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
395 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
396 try:
397 (options, args) = self.image_option_parser.parse_args(command_args)
398 except:
399 return
400
401 for image_path in args:
402 fullpath_search = image_path[0] == '/'
403 for crash_log in self.crash_logs:
404 matches_found = 0
405 for (image_idx, image) in enumerate(crash_log.images):
406 if fullpath_search:
407 if image.get_resolved_path() == image_path:
408 matches_found += 1
409 print image
410 else:
411 image_basename = image.get_resolved_path_basename()
412 if image_basename == image_path:
413 matches_found += 1
414 print image
415 if matches_found == 0:
416 for (image_idx, image) in enumerate(crash_log.images):
417 if string.find(image.get_resolved_path(), image_path) >= 0:
418 print image
419 return False
420
421
422def interactive_crashlogs(options, args):
423 crash_log_files = list()
424 for arg in args:
425 for resolved_path in glob.glob(arg):
426 crash_log_files.append(resolved_path)
427
428 crash_logs = list();
429 for crash_log_file in crash_log_files:
430 #print 'crash_log_file = "%s"' % crash_log_file
431 crash_log = CrashLog(crash_log_file)
432 if crash_log.error:
433 print crash_log.error
434 continue
435 if options.verbose:
436 crash_log.dump()
437 if not crash_log.images:
438 print 'error: no images in crash log "%s"' % (crash_log)
439 continue
440 else:
441 crash_logs.append(crash_log)
442
443 interpreter = Interactive(crash_logs)
444 # List all crash logs that were imported
445 interpreter.do_list()
446 interpreter.cmdloop()
447
448
Greg Clayton01f7c962012-01-20 03:15:45 +0000449def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000450 try:
451 SymbolicateCrashLog (shlex.split(command))
452 except:
453 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
454
Greg Clayton01f7c962012-01-20 03:15:45 +0000455def SymbolicateCrashLog(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000456 usage = "usage: %prog [options] <FILE> [FILE ...]"
457 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
458inlined stack frames back to the concrete functions, and disassemble the location of the crash
459for the first frame of the crashed thread.
460If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
461for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
462created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
463you to explore the program as if it were stopped at the locations described in the crash log and functions can
464be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Clayton9d010422012-05-04 20:44:14 +0000465 parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000466 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000467 parser.add_option('-a', '--load-all', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False)
Greg Clayton9d010422012-05-04 20:44:14 +0000468 parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000469 parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
470 parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
471 parser.add_option('-d', '--disasm-depth', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1)
472 parser.add_option('-D', '--disasm-all', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False)
473 parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
474 parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
Greg Clayton9d010422012-05-04 20:44:14 +0000475 parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
Greg Clayton223e8082012-01-21 04:26:24 +0000476 try:
477 (options, args) = parser.parse_args(command_args)
478 except:
479 return
480
Greg Clayton01f7c962012-01-20 03:15:45 +0000481 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000482 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000483 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000484 print 'args', args
485
Greg Clayton01f7c962012-01-20 03:15:45 +0000486 if options.debug_delay > 0:
487 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
488 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000489 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000490
Greg Claytone9ee5502012-01-20 19:25:32 +0000491 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000492 if options.interactive:
493 interactive_crashlogs(options, args)
494 else:
495 for crash_log_file in args:
496 crash_log = CrashLog(crash_log_file)
Greg Clayton3d39f832012-04-03 21:35:43 +0000497
Greg Clayton9d010422012-05-04 20:44:14 +0000498 #pp = pprint.PrettyPrinter(indent=4); pp.pprint(args)
499 if crash_log.error:
500 print crash_log.error
501 return
502 if options.verbose:
503 crash_log.dump()
504 if not crash_log.images:
505 print 'error: no images in crash log'
506 return
Greg Claytona3698c62012-01-21 00:37:19 +0000507
Greg Clayton9d010422012-05-04 20:44:14 +0000508 target = crash_log.create_target ()
509 if not target:
510 return
511 exe_module = target.GetModuleAtIndex(0)
512 images_to_load = list()
513 loaded_images = list()
514 if options.load_all_images:
515 # --load-all option was specified, load everything up
516 for image in crash_log.images:
517 images_to_load.append(image)
Greg Clayton01f7c962012-01-20 03:15:45 +0000518 else:
Greg Clayton9d010422012-05-04 20:44:14 +0000519 # Only load the images found in stack frames for the crashed threads
520 for ident in crash_log.idents:
521 images = crash_log.find_images_with_identifier (ident)
522 if images:
523 for image in images:
524 images_to_load.append(image)
525 else:
526 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3698c62012-01-21 00:37:19 +0000527
Greg Clayton9d010422012-05-04 20:44:14 +0000528 for image in images_to_load:
529 if image in loaded_images:
530 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
Greg Clayton3d39f832012-04-03 21:35:43 +0000531 else:
Greg Clayton9d010422012-05-04 20:44:14 +0000532 err = image.add_module (target)
533 if err:
534 print err
535 else:
536 #print 'loaded %s' % image
537 loaded_images.append(image)
538
539 for thread in crash_log.threads:
540 this_thread_crashed = thread.did_crash()
541 if options.crashed_only and this_thread_crashed == False:
542 continue
543 print "%s" % thread
544 #prev_frame_index = -1
545 for frame_idx, frame in enumerate(thread.frames):
546 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
547 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc)
548 if symbolicated_frame_addresses:
549 symbolicated_frame_address_idx = 0
550 for symbolicated_frame_address in symbolicated_frame_addresses:
551 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
552
553 if symbolicated_frame_address_idx == 0:
554 if disassemble:
555 instructions = symbolicated_frame_address.get_instructions()
556 if instructions:
557 print
558 symbolication.disassemble_instructions (target,
559 instructions,
560 frame.pc,
561 options.disassemble_before,
562 options.disassemble_after, frame.index > 0)
563 print
564 symbolicated_frame_address_idx += 1
565 else:
566 print frame
567 print
Greg Clayton01f7c962012-01-20 03:15:45 +0000568
Greg Clayton9d010422012-05-04 20:44:14 +0000569 if options.dump_image_list:
570 print "Binary Images:"
571 for image in crash_log.images:
572 print image
Greg Claytona3698c62012-01-21 00:37:19 +0000573
Greg Clayton01f7c962012-01-20 03:15:45 +0000574if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000575 # Create a new debugger instance
576 lldb.debugger = lldb.SBDebugger.Create()
Greg Clayton3d39f832012-04-03 21:35:43 +0000577 SymbolicateCrashLog (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000578elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000579 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytone9ee5502012-01-20 19:25:32 +0000580 print '"crashlog" command installed, type "crashlog --help" for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000581