blob: 11a823e2323821899ed3d64ec3e4891232f98ccc [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 Clayton1b62f592012-06-04 23:22:17 +0000121 # Don't load a module twice...
122 if self.resolved:
Greg Clayton4c983c82012-04-20 23:31:27 +0000123 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000124 # Mark this as resolved so we don't keep trying
125 self.resolved = True
Greg Claytond8056e22012-05-11 00:30:14 +0000126 uuid_str = self.get_normalized_uuid_string()
127 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000128 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000129 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000130 s = commands.getoutput(dsym_for_uuid_command)
131 if s:
132 plist_root = plistlib.readPlistFromString (s)
133 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000134 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000135 if plist:
136 if 'DBGArchitecture' in plist:
137 self.arch = plist['DBGArchitecture']
138 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000139 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000140 if 'DBGSymbolRichExecutable' in plist:
141 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
142 if not self.resolved_path and os.path.exists(self.path):
143 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000144 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000145 for line in dwarfdump_cmd_output.splitlines():
146 match = self.dwarfdump_uuid_regex.search (line)
147 if match:
148 dwarf_uuid_str = match.group(1)
149 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
150 if self_uuid == dwarf_uuid:
151 self.resolved_path = self.path
152 self.arch = match.group(2)
153 break;
154 if not self.resolved_path:
Greg Clayton1b62f592012-06-04 23:22:17 +0000155 self.unavailable = True
156 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Clayton4c983c82012-04-20 23:31:27 +0000157 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000158 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
159 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000160 # if self.resolved_path:
161 # print ' exe = "%s"' % self.resolved_path
162 # if self.symfile:
163 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000164 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000165 else:
166 self.unavailable = True
Greg Clayton4c983c82012-04-20 23:31:27 +0000167 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000168
Greg Clayton3d39f832012-04-03 21:35:43 +0000169
Greg Clayton01f7c962012-01-20 03:15:45 +0000170
171 def __init__(self, path):
172 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000173 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000174 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000175 self.info_lines = list()
176 self.system_profile = list()
177 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000178 self.idents = list() # A list of the required identifiers for doing all stack backtraces
179 self.crashed_thread_idx = -1
180 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000181 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000182 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000183 try:
184 f = open(self.path)
185 except IOError:
186 self.error = 'error: cannot open "%s"' % self.path
187 return
188
Greg Clayton01f7c962012-01-20 03:15:45 +0000189 self.file_lines = f.read().splitlines()
190 parse_mode = PARSE_MODE_NORMAL
191 thread = None
192 for line in self.file_lines:
193 # print line
194 line_len = len(line)
195 if line_len == 0:
196 if thread:
197 if parse_mode == PARSE_MODE_THREAD:
198 if thread.index == self.crashed_thread_idx:
199 thread.reason = ''
200 if self.thread_exception:
201 thread.reason += self.thread_exception
202 if self.thread_exception_data:
203 thread.reason += " (%s)" % self.thread_exception_data
204 self.threads.append(thread)
205 thread = None
206 else:
207 # only append an extra empty line if the previous line
208 # in the info_lines wasn't empty
209 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
210 self.info_lines.append(line)
211 parse_mode = PARSE_MODE_NORMAL
212 # print 'PARSE_MODE_NORMAL'
213 elif parse_mode == PARSE_MODE_NORMAL:
214 if line.startswith ('Process:'):
215 (self.process_name, pid_with_brackets) = line[8:].strip().split()
216 self.process_id = pid_with_brackets.strip('[]')
217 elif line.startswith ('Path:'):
218 self.process_path = line[5:].strip()
219 elif line.startswith ('Identifier:'):
220 self.process_identifier = line[11:].strip()
221 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000222 version_string = line[8:].strip()
223 matched_pair = re.search("(.+)\((.+)\)", version_string)
224 if matched_pair:
225 self.process_version = matched_pair.group(1)
226 self.process_compatability_version = matched_pair.group(2)
227 else:
228 self.process = version_string
229 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000230 elif line.startswith ('Parent Process:'):
231 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
232 self.parent_process_id = pid_with_brackets.strip('[]')
233 elif line.startswith ('Exception Type:'):
234 self.thread_exception = line[15:].strip()
235 continue
236 elif line.startswith ('Exception Codes:'):
237 self.thread_exception_data = line[16:].strip()
238 continue
239 elif line.startswith ('Crashed Thread:'):
240 self.crashed_thread_idx = int(line[15:].strip().split()[0])
241 continue
242 elif line.startswith ('Report Version:'):
243 self.version = int(line[15:].strip())
244 continue
245 elif line.startswith ('System Profile:'):
246 parse_mode = PARSE_MODE_SYSTEM
247 continue
248 elif (line.startswith ('Interval Since Last Report:') or
249 line.startswith ('Crashes Since Last Report:') or
250 line.startswith ('Per-App Interval Since Last Report:') or
251 line.startswith ('Per-App Crashes Since Last Report:') or
252 line.startswith ('Sleep/Wake UUID:') or
253 line.startswith ('Anonymous UUID:')):
254 # ignore these
255 continue
256 elif line.startswith ('Thread'):
257 thread_state_match = self.thread_state_regex.search (line)
258 if thread_state_match:
259 thread_state_match = self.thread_regex.search (line)
260 thread_idx = int(thread_state_match.group(1))
261 parse_mode = PARSE_MODE_THREGS
262 thread = self.threads[thread_idx]
263 else:
264 thread_match = self.thread_regex.search (line)
265 if thread_match:
266 # print 'PARSE_MODE_THREAD'
267 parse_mode = PARSE_MODE_THREAD
268 thread_idx = int(thread_match.group(1))
269 thread = CrashLog.Thread(thread_idx)
270 continue
271 elif line.startswith ('Binary Images:'):
272 parse_mode = PARSE_MODE_IMAGES
273 continue
274 self.info_lines.append(line.strip())
275 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000276 if line.startswith ('Thread'):
277 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000278 frame_match = self.frame_regex.search(line)
279 if frame_match:
280 ident = frame_match.group(2)
281 if not ident in self.idents:
282 self.idents.append(ident)
283 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
284 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000285 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000286 elif parse_mode == PARSE_MODE_IMAGES:
287 image_match = self.image_regex_uuid.search (line)
288 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000289 image = CrashLog.DarwinImage (int(image_match.group(1),0),
290 int(image_match.group(2),0),
291 image_match.group(3).strip(),
292 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000293 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000294 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000295 self.images.append (image)
296 else:
297 image_match = self.image_regex_no_uuid.search (line)
298 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000299 image = CrashLog.DarwinImage (int(image_match.group(1),0),
300 int(image_match.group(2),0),
301 image_match.group(3).strip(),
302 image_match.group(4).strip(),
303 None,
304 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000305 self.images.append (image)
306 else:
307 print "error: image regex failed for: %s" % line
308
309 elif parse_mode == PARSE_MODE_THREGS:
310 stripped_line = line.strip()
Greg Claytond8056e22012-05-11 00:30:14 +0000311 reg_values = re.split(' +', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000312 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000313 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000314 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000315 #print 'reg = "%s"' % reg
316 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000317 thread.registers[reg.strip()] = int(value, 0)
318 elif parse_mode == PARSE_MODE_SYSTEM:
319 self.system_profile.append(line)
320 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000321
Greg Clayton01f7c962012-01-20 03:15:45 +0000322 def dump(self):
323 print "Crash Log File: %s" % (self.path)
324 print "\nThreads:"
325 for thread in self.threads:
326 thread.dump(' ')
327 print "\nImages:"
328 for image in self.images:
329 image.dump(' ')
330
Greg Clayton3d39f832012-04-03 21:35:43 +0000331 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000332 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000333 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000334 return image
335 return None
336
Greg Claytone9ee5502012-01-20 19:25:32 +0000337 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000338 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000339 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000340 if target:
341 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000342 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000343 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000344 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000345 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000346 image = self.find_image_with_identifier (ident)
347 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000348 target = image.create_target ()
349 if target:
350 return target # success
351 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000352 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000353 target = image.create_target ()
354 if target:
355 return target # success
356 print 'crashlog.create_target()...4'
357 print 'error: unable to locate any executables from the crash log'
358 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000359
Greg Clayton01f7c962012-01-20 03:15:45 +0000360
361def usage():
362 print "Usage: lldb-symbolicate.py [-n name] executable-image"
363 sys.exit(0)
364
Greg Clayton9d010422012-05-04 20:44:14 +0000365class Interactive(cmd.Cmd):
366 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
367 image_option_parser = None
368
369 def __init__(self, crash_logs):
370 cmd.Cmd.__init__(self)
371 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
372 self.crash_logs = crash_logs
373 self.prompt = '% '
374
375 def default(self, line):
376 '''Catch all for unknown command, which will exit the interpreter.'''
377 print "uknown command: %s" % line
378 return True
379
380 def do_q(self, line):
381 '''Quit command'''
382 return True
383
384 def do_quit(self, line):
385 '''Quit command'''
386 return True
387
Greg Claytona3368dd2012-05-31 21:21:08 +0000388 def do_symbolicate(self, line):
389 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
390 inlined stack frames back to the concrete functions, and disassemble the location of the crash
391 for the first frame of the crashed thread.'''
392 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
393 command_args = shlex.split(line)
394 try:
395 (options, args) = option_parser.parse_args(command_args)
396 except:
397 return
398
399 for idx_str in args:
400 idx = int(idx_str)
401 if idx < len(self.crash_logs):
402 SymbolicateCrashLog (self.crash_logs[idx], options)
403 else:
404 print 'error: crash log index %u is out of range' % (idx)
405
Greg Clayton9d010422012-05-04 20:44:14 +0000406 def do_list(self, line=None):
407 '''Dump a list of all crash logs that are currently loaded.
408
409 USAGE: list'''
410 print '%u crash logs are loaded:' % len(self.crash_logs)
411 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
412 print '[%u] = %s' % (crash_log_idx, crash_log.path)
413
414 def do_image(self, line):
415 '''Dump information about an image in the crash log given an image basename.
416
417 USAGE: image <basename>'''
418 usage = "usage: %prog [options] <PATH> [PATH ...]"
419 description='''Dump information about one or more images in all crash logs. The <PATH>
420 can be a full path or a image basename.'''
421 command_args = shlex.split(line)
422 if not self.image_option_parser:
423 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
424 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
425 try:
426 (options, args) = self.image_option_parser.parse_args(command_args)
427 except:
428 return
429
Greg Claytond8056e22012-05-11 00:30:14 +0000430 if args:
431 for image_path in args:
432 fullpath_search = image_path[0] == '/'
433 for crash_log in self.crash_logs:
434 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000435 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000436 if fullpath_search:
437 if image.get_resolved_path() == image_path:
438 matches_found += 1
439 print image
440 else:
441 image_basename = image.get_resolved_path_basename()
442 if image_basename == image_path:
443 matches_found += 1
444 print image
445 if matches_found == 0:
446 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000447 resolved_image_path = image.get_resolved_path()
448 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytond8056e22012-05-11 00:30:14 +0000449 print image
450 else:
451 for crash_log in self.crash_logs:
452 for (image_idx, image) in enumerate(crash_log.images):
453 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000454 return False
455
456
457def interactive_crashlogs(options, args):
458 crash_log_files = list()
459 for arg in args:
460 for resolved_path in glob.glob(arg):
461 crash_log_files.append(resolved_path)
462
463 crash_logs = list();
464 for crash_log_file in crash_log_files:
465 #print 'crash_log_file = "%s"' % crash_log_file
466 crash_log = CrashLog(crash_log_file)
467 if crash_log.error:
468 print crash_log.error
469 continue
470 if options.verbose:
471 crash_log.dump()
472 if not crash_log.images:
473 print 'error: no images in crash log "%s"' % (crash_log)
474 continue
475 else:
476 crash_logs.append(crash_log)
477
478 interpreter = Interactive(crash_logs)
479 # List all crash logs that were imported
480 interpreter.do_list()
481 interpreter.cmdloop()
482
483
Greg Clayton01f7c962012-01-20 03:15:45 +0000484def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000485 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000486 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000487 except:
488 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000489
490def SymbolicateCrashLog(crash_log, options):
491 if crash_log.error:
492 print crash_log.error
493 return
494 if options.verbose:
495 crash_log.dump()
496 if not crash_log.images:
497 print 'error: no images in crash log'
498 return
499
500 target = crash_log.create_target ()
501 if not target:
502 return
503 exe_module = target.GetModuleAtIndex(0)
504 images_to_load = list()
505 loaded_images = list()
506 if options.load_all_images:
507 # --load-all option was specified, load everything up
508 for image in crash_log.images:
509 images_to_load.append(image)
510 else:
511 # Only load the images found in stack frames for the crashed threads
512 for ident in crash_log.idents:
513 images = crash_log.find_images_with_identifier (ident)
514 if images:
515 for image in images:
516 images_to_load.append(image)
517 else:
518 print 'error: can\'t find image for identifier "%s"' % ident
519
520 for image in images_to_load:
521 if image in loaded_images:
522 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
523 else:
524 err = image.add_module (target)
525 if err:
526 print err
527 else:
528 #print 'loaded %s' % image
529 loaded_images.append(image)
530
531 for thread in crash_log.threads:
532 this_thread_crashed = thread.did_crash()
533 if options.crashed_only and this_thread_crashed == False:
534 continue
535 print "%s" % thread
536 #prev_frame_index = -1
537 for frame_idx, frame in enumerate(thread.frames):
538 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
539 if frame_idx == 0:
540 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc)
541 else:
542 # Any frame above frame zero and we have to subtract one to get the previous line entry
543 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1)
544
545 if symbolicated_frame_addresses:
546 symbolicated_frame_address_idx = 0
547 for symbolicated_frame_address in symbolicated_frame_addresses:
548 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton223e8082012-01-21 04:26:24 +0000549
Greg Claytona3368dd2012-05-31 21:21:08 +0000550 if symbolicated_frame_address_idx == 0:
551 if disassemble:
552 instructions = symbolicated_frame_address.get_instructions()
553 if instructions:
554 print
555 symbolication.disassemble_instructions (target,
556 instructions,
557 frame.pc,
558 options.disassemble_before,
559 options.disassemble_after, frame.index > 0)
560 print
561 symbolicated_frame_address_idx += 1
562 else:
563 print frame
564 print
565
566 if options.dump_image_list:
567 print "Binary Images:"
568 for image in crash_log.images:
569 print image
570
571def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000572 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000573 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
574 option_parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
575 option_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)
576 option_parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False)
577 option_parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
578 option_parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
579 option_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)
580 option_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)
581 option_parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
582 option_parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
583 if add_interactive_options:
584 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
585 return option_parser
586
587def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000588 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
589inlined stack frames back to the concrete functions, and disassemble the location of the crash
590for the first frame of the crashed thread.
591If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
592for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
593created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
594you to explore the program as if it were stopped at the locations described in the crash log and functions can
595be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000596 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000597 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000598 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000599 except:
600 return
601
Greg Clayton01f7c962012-01-20 03:15:45 +0000602 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000603 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000604 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000605 print 'args', args
606
Greg Clayton01f7c962012-01-20 03:15:45 +0000607 if options.debug_delay > 0:
608 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
609 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000610 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000611
Greg Claytone9ee5502012-01-20 19:25:32 +0000612 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000613 if options.interactive:
614 interactive_crashlogs(options, args)
615 else:
616 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000617 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000618 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000619if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000620 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000621 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000622 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000623 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000624elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000625 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytone9ee5502012-01-20 19:25:32 +0000626 print '"crashlog" command installed, type "crashlog --help" for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000627