blob: 2928eecf4672612e5dfb44ebeef2bb5f39c4b0af [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 Claytond8056e22012-05-11 00:30:14 +0000124 uuid_str = self.get_normalized_uuid_string()
125 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000126 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000127 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000128 s = commands.getoutput(dsym_for_uuid_command)
129 if s:
130 plist_root = plistlib.readPlistFromString (s)
131 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000132 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000133 if plist:
134 if 'DBGArchitecture' in plist:
135 self.arch = plist['DBGArchitecture']
136 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000137 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000138 if 'DBGSymbolRichExecutable' in plist:
139 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
140 if not self.resolved_path and os.path.exists(self.path):
141 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000142 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000143 for line in dwarfdump_cmd_output.splitlines():
144 match = self.dwarfdump_uuid_regex.search (line)
145 if match:
146 dwarf_uuid_str = match.group(1)
147 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
148 if self_uuid == dwarf_uuid:
149 self.resolved_path = self.path
150 self.arch = match.group(2)
151 break;
152 if not self.resolved_path:
Greg Claytond8056e22012-05-11 00:30:14 +0000153 print "error: file %s '%s' doesn't match the UUID in the installed file" % (uuid_str, self.path)
Greg Clayton4c983c82012-04-20 23:31:27 +0000154 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000155 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
156 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000157 # if self.resolved_path:
158 # print ' exe = "%s"' % self.resolved_path
159 # if self.symfile:
160 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000161 return True
162 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000163
Greg Clayton3d39f832012-04-03 21:35:43 +0000164
Greg Clayton01f7c962012-01-20 03:15:45 +0000165
166 def __init__(self, path):
167 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000168 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000169 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000170 self.info_lines = list()
171 self.system_profile = list()
172 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000173 self.idents = list() # A list of the required identifiers for doing all stack backtraces
174 self.crashed_thread_idx = -1
175 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000176 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000177 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000178 try:
179 f = open(self.path)
180 except IOError:
181 self.error = 'error: cannot open "%s"' % self.path
182 return
183
Greg Clayton01f7c962012-01-20 03:15:45 +0000184 self.file_lines = f.read().splitlines()
185 parse_mode = PARSE_MODE_NORMAL
186 thread = None
187 for line in self.file_lines:
188 # print line
189 line_len = len(line)
190 if line_len == 0:
191 if thread:
192 if parse_mode == PARSE_MODE_THREAD:
193 if thread.index == self.crashed_thread_idx:
194 thread.reason = ''
195 if self.thread_exception:
196 thread.reason += self.thread_exception
197 if self.thread_exception_data:
198 thread.reason += " (%s)" % self.thread_exception_data
199 self.threads.append(thread)
200 thread = None
201 else:
202 # only append an extra empty line if the previous line
203 # in the info_lines wasn't empty
204 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
205 self.info_lines.append(line)
206 parse_mode = PARSE_MODE_NORMAL
207 # print 'PARSE_MODE_NORMAL'
208 elif parse_mode == PARSE_MODE_NORMAL:
209 if line.startswith ('Process:'):
210 (self.process_name, pid_with_brackets) = line[8:].strip().split()
211 self.process_id = pid_with_brackets.strip('[]')
212 elif line.startswith ('Path:'):
213 self.process_path = line[5:].strip()
214 elif line.startswith ('Identifier:'):
215 self.process_identifier = line[11:].strip()
216 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000217 version_string = line[8:].strip()
218 matched_pair = re.search("(.+)\((.+)\)", version_string)
219 if matched_pair:
220 self.process_version = matched_pair.group(1)
221 self.process_compatability_version = matched_pair.group(2)
222 else:
223 self.process = version_string
224 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000225 elif line.startswith ('Parent Process:'):
226 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
227 self.parent_process_id = pid_with_brackets.strip('[]')
228 elif line.startswith ('Exception Type:'):
229 self.thread_exception = line[15:].strip()
230 continue
231 elif line.startswith ('Exception Codes:'):
232 self.thread_exception_data = line[16:].strip()
233 continue
234 elif line.startswith ('Crashed Thread:'):
235 self.crashed_thread_idx = int(line[15:].strip().split()[0])
236 continue
237 elif line.startswith ('Report Version:'):
238 self.version = int(line[15:].strip())
239 continue
240 elif line.startswith ('System Profile:'):
241 parse_mode = PARSE_MODE_SYSTEM
242 continue
243 elif (line.startswith ('Interval Since Last Report:') or
244 line.startswith ('Crashes Since Last Report:') or
245 line.startswith ('Per-App Interval Since Last Report:') or
246 line.startswith ('Per-App Crashes Since Last Report:') or
247 line.startswith ('Sleep/Wake UUID:') or
248 line.startswith ('Anonymous UUID:')):
249 # ignore these
250 continue
251 elif line.startswith ('Thread'):
252 thread_state_match = self.thread_state_regex.search (line)
253 if thread_state_match:
254 thread_state_match = self.thread_regex.search (line)
255 thread_idx = int(thread_state_match.group(1))
256 parse_mode = PARSE_MODE_THREGS
257 thread = self.threads[thread_idx]
258 else:
259 thread_match = self.thread_regex.search (line)
260 if thread_match:
261 # print 'PARSE_MODE_THREAD'
262 parse_mode = PARSE_MODE_THREAD
263 thread_idx = int(thread_match.group(1))
264 thread = CrashLog.Thread(thread_idx)
265 continue
266 elif line.startswith ('Binary Images:'):
267 parse_mode = PARSE_MODE_IMAGES
268 continue
269 self.info_lines.append(line.strip())
270 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000271 if line.startswith ('Thread'):
272 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000273 frame_match = self.frame_regex.search(line)
274 if frame_match:
275 ident = frame_match.group(2)
276 if not ident in self.idents:
277 self.idents.append(ident)
278 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
279 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000280 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000281 elif parse_mode == PARSE_MODE_IMAGES:
282 image_match = self.image_regex_uuid.search (line)
283 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000284 image = CrashLog.DarwinImage (int(image_match.group(1),0),
285 int(image_match.group(2),0),
286 image_match.group(3).strip(),
287 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000288 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000289 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000290 self.images.append (image)
291 else:
292 image_match = self.image_regex_no_uuid.search (line)
293 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000294 image = CrashLog.DarwinImage (int(image_match.group(1),0),
295 int(image_match.group(2),0),
296 image_match.group(3).strip(),
297 image_match.group(4).strip(),
298 None,
299 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000300 self.images.append (image)
301 else:
302 print "error: image regex failed for: %s" % line
303
304 elif parse_mode == PARSE_MODE_THREGS:
305 stripped_line = line.strip()
Greg Claytond8056e22012-05-11 00:30:14 +0000306 reg_values = re.split(' +', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000307 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000308 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000309 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000310 #print 'reg = "%s"' % reg
311 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000312 thread.registers[reg.strip()] = int(value, 0)
313 elif parse_mode == PARSE_MODE_SYSTEM:
314 self.system_profile.append(line)
315 f.close()
316
317 def dump(self):
318 print "Crash Log File: %s" % (self.path)
319 print "\nThreads:"
320 for thread in self.threads:
321 thread.dump(' ')
322 print "\nImages:"
323 for image in self.images:
324 image.dump(' ')
325
Greg Clayton3d39f832012-04-03 21:35:43 +0000326 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000327 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000328 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000329 return image
330 return None
331
Greg Claytone9ee5502012-01-20 19:25:32 +0000332 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000333 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000334 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000335 if target:
336 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000337 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000338 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000339 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000340 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000341 image = self.find_image_with_identifier (ident)
342 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000343 target = image.create_target ()
344 if target:
345 return target # success
346 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000347 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000348 target = image.create_target ()
349 if target:
350 return target # success
351 print 'crashlog.create_target()...4'
352 print 'error: unable to locate any executables from the crash log'
353 return None
Greg Claytone9ee5502012-01-20 19:25:32 +0000354
Greg Clayton01f7c962012-01-20 03:15:45 +0000355
356def usage():
357 print "Usage: lldb-symbolicate.py [-n name] executable-image"
358 sys.exit(0)
359
Greg Clayton9d010422012-05-04 20:44:14 +0000360class Interactive(cmd.Cmd):
361 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
362 image_option_parser = None
363
364 def __init__(self, crash_logs):
365 cmd.Cmd.__init__(self)
366 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
367 self.crash_logs = crash_logs
368 self.prompt = '% '
369
370 def default(self, line):
371 '''Catch all for unknown command, which will exit the interpreter.'''
372 print "uknown command: %s" % line
373 return True
374
375 def do_q(self, line):
376 '''Quit command'''
377 return True
378
379 def do_quit(self, line):
380 '''Quit command'''
381 return True
382
Greg Claytona3368dd2012-05-31 21:21:08 +0000383 def do_symbolicate(self, line):
384 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
385 inlined stack frames back to the concrete functions, and disassemble the location of the crash
386 for the first frame of the crashed thread.'''
387 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
388 command_args = shlex.split(line)
389 try:
390 (options, args) = option_parser.parse_args(command_args)
391 except:
392 return
393
394 for idx_str in args:
395 idx = int(idx_str)
396 if idx < len(self.crash_logs):
397 SymbolicateCrashLog (self.crash_logs[idx], options)
398 else:
399 print 'error: crash log index %u is out of range' % (idx)
400
Greg Clayton9d010422012-05-04 20:44:14 +0000401 def do_list(self, line=None):
402 '''Dump a list of all crash logs that are currently loaded.
403
404 USAGE: list'''
405 print '%u crash logs are loaded:' % len(self.crash_logs)
406 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
407 print '[%u] = %s' % (crash_log_idx, crash_log.path)
408
409 def do_image(self, line):
410 '''Dump information about an image in the crash log given an image basename.
411
412 USAGE: image <basename>'''
413 usage = "usage: %prog [options] <PATH> [PATH ...]"
414 description='''Dump information about one or more images in all crash logs. The <PATH>
415 can be a full path or a image basename.'''
416 command_args = shlex.split(line)
417 if not self.image_option_parser:
418 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
419 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
420 try:
421 (options, args) = self.image_option_parser.parse_args(command_args)
422 except:
423 return
424
Greg Claytond8056e22012-05-11 00:30:14 +0000425 if args:
426 for image_path in args:
427 fullpath_search = image_path[0] == '/'
428 for crash_log in self.crash_logs:
429 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000430 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000431 if fullpath_search:
432 if image.get_resolved_path() == image_path:
433 matches_found += 1
434 print image
435 else:
436 image_basename = image.get_resolved_path_basename()
437 if image_basename == image_path:
438 matches_found += 1
439 print image
440 if matches_found == 0:
441 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000442 resolved_image_path = image.get_resolved_path()
443 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytond8056e22012-05-11 00:30:14 +0000444 print image
445 else:
446 for crash_log in self.crash_logs:
447 for (image_idx, image) in enumerate(crash_log.images):
448 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000449 return False
450
451
452def interactive_crashlogs(options, args):
453 crash_log_files = list()
454 for arg in args:
455 for resolved_path in glob.glob(arg):
456 crash_log_files.append(resolved_path)
457
458 crash_logs = list();
459 for crash_log_file in crash_log_files:
460 #print 'crash_log_file = "%s"' % crash_log_file
461 crash_log = CrashLog(crash_log_file)
462 if crash_log.error:
463 print crash_log.error
464 continue
465 if options.verbose:
466 crash_log.dump()
467 if not crash_log.images:
468 print 'error: no images in crash log "%s"' % (crash_log)
469 continue
470 else:
471 crash_logs.append(crash_log)
472
473 interpreter = Interactive(crash_logs)
474 # List all crash logs that were imported
475 interpreter.do_list()
476 interpreter.cmdloop()
477
478
Greg Clayton01f7c962012-01-20 03:15:45 +0000479def Symbolicate(debugger, command, result, dict):
Greg Claytona3368dd2012-05-31 21:21:08 +0000480 print 'def Symbolicate(debugger, command, result, dict): called with "%s"' % (command)
Greg Clayton223e8082012-01-21 04:26:24 +0000481 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000482 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000483 except:
484 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000485
486def SymbolicateCrashLog(crash_log, options):
487 if crash_log.error:
488 print crash_log.error
489 return
490 if options.verbose:
491 crash_log.dump()
492 if not crash_log.images:
493 print 'error: no images in crash log'
494 return
495
496 target = crash_log.create_target ()
497 if not target:
498 return
499 exe_module = target.GetModuleAtIndex(0)
500 images_to_load = list()
501 loaded_images = list()
502 if options.load_all_images:
503 # --load-all option was specified, load everything up
504 for image in crash_log.images:
505 images_to_load.append(image)
506 else:
507 # Only load the images found in stack frames for the crashed threads
508 for ident in crash_log.idents:
509 images = crash_log.find_images_with_identifier (ident)
510 if images:
511 for image in images:
512 images_to_load.append(image)
513 else:
514 print 'error: can\'t find image for identifier "%s"' % ident
515
516 for image in images_to_load:
517 if image in loaded_images:
518 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
519 else:
520 err = image.add_module (target)
521 if err:
522 print err
523 else:
524 #print 'loaded %s' % image
525 loaded_images.append(image)
526
527 for thread in crash_log.threads:
528 this_thread_crashed = thread.did_crash()
529 if options.crashed_only and this_thread_crashed == False:
530 continue
531 print "%s" % thread
532 #prev_frame_index = -1
533 for frame_idx, frame in enumerate(thread.frames):
534 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
535 if frame_idx == 0:
536 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc)
537 else:
538 # Any frame above frame zero and we have to subtract one to get the previous line entry
539 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1)
540
541 if symbolicated_frame_addresses:
542 symbolicated_frame_address_idx = 0
543 for symbolicated_frame_address in symbolicated_frame_addresses:
544 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton223e8082012-01-21 04:26:24 +0000545
Greg Claytona3368dd2012-05-31 21:21:08 +0000546 if symbolicated_frame_address_idx == 0:
547 if disassemble:
548 instructions = symbolicated_frame_address.get_instructions()
549 if instructions:
550 print
551 symbolication.disassemble_instructions (target,
552 instructions,
553 frame.pc,
554 options.disassemble_before,
555 options.disassemble_after, frame.index > 0)
556 print
557 symbolicated_frame_address_idx += 1
558 else:
559 print frame
560 print
561
562 if options.dump_image_list:
563 print "Binary Images:"
564 for image in crash_log.images:
565 print image
566
567def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000568 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000569 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
570 option_parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
571 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)
572 option_parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False)
573 option_parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
574 option_parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
575 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)
576 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)
577 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)
578 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)
579 if add_interactive_options:
580 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
581 return option_parser
582
583def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000584 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
585inlined stack frames back to the concrete functions, and disassemble the location of the crash
586for the first frame of the crashed thread.
587If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
588for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
589created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
590you to explore the program as if it were stopped at the locations described in the crash log and functions can
591be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000592 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000593 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000594 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000595 except:
596 return
597
Greg Clayton01f7c962012-01-20 03:15:45 +0000598 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000599 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000600 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000601 print 'args', args
602
Greg Clayton01f7c962012-01-20 03:15:45 +0000603 if options.debug_delay > 0:
604 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
605 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000606 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000607
Greg Claytone9ee5502012-01-20 19:25:32 +0000608 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000609 if options.interactive:
610 interactive_crashlogs(options, args)
611 else:
612 for crash_log_file in args:
Greg Claytona3368dd2012-05-31 21:21:08 +0000613 crash_log = CrashLog(crash_log_file)
614 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000615if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000616 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000617 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000618 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000619 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000620elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000621 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytone9ee5502012-01-20 19:25:32 +0000622 print '"crashlog" command installed, type "crashlog --help" for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000623