blob: 7d09a3d501d40ddf4d0ed6e08bc632fce428be58 [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
Greg Claytonca1500f2012-06-27 20:02:04 +000032import datetime
Greg Clayton9d010422012-05-04 20:44:14 +000033import glob
Greg Clayton01f7c962012-01-20 03:15:45 +000034import optparse
35import os
Greg Claytonca1500f2012-06-27 20:02:04 +000036import platform
Greg Clayton01f7c962012-01-20 03:15:45 +000037import plistlib
Greg Clayton3d39f832012-04-03 21:35:43 +000038import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Clayton01f7c962012-01-20 03:15:45 +000039import re
Greg Clayton223e8082012-01-21 04:26:24 +000040import shlex
Greg Clayton9d010422012-05-04 20:44:14 +000041import string
Greg Clayton01f7c962012-01-20 03:15:45 +000042import sys
43import time
Greg Claytoncd793122012-01-20 06:12:47 +000044import uuid
Greg Clayton9d010422012-05-04 20:44:14 +000045from lldb.utils import symbolication
Greg Clayton01f7c962012-01-20 03:15:45 +000046
47PARSE_MODE_NORMAL = 0
48PARSE_MODE_THREAD = 1
49PARSE_MODE_IMAGES = 2
50PARSE_MODE_THREGS = 3
51PARSE_MODE_SYSTEM = 4
52
Greg Clayton9d010422012-05-04 20:44:14 +000053class CrashLog(symbolication.Symbolicator):
Greg Clayton01f7c962012-01-20 03:15:45 +000054 """Class that does parses darwin crash logs"""
55 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
56 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Greg Clayton8077a532012-01-20 03:32:35 +000057 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t(0x[0-9a-fA-F]+) +(.*)')
58 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
59 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Clayton01f7c962012-01-20 03:15:45 +000060 empty_line_regex = re.compile('^$')
61
62 class Thread:
63 """Class that represents a thread in a darwin crash log"""
64 def __init__(self, index):
65 self.index = index
66 self.frames = list()
Greg Claytonaa7df342012-07-13 03:19:35 +000067 self.idents = list()
Greg Clayton01f7c962012-01-20 03:15:45 +000068 self.registers = dict()
69 self.reason = None
70 self.queue = None
71
72 def dump(self, prefix):
73 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
74 if self.frames:
75 print "%s Frames:" % (prefix)
76 for frame in self.frames:
77 frame.dump(prefix + ' ')
78 if self.registers:
79 print "%s Registers:" % (prefix)
80 for reg in self.registers.keys():
81 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
82
Greg Claytonaa7df342012-07-13 03:19:35 +000083 def add_ident(self, ident):
84 if not ident in self.idents:
85 self.idents.append(ident)
86
Greg Clayton01f7c962012-01-20 03:15:45 +000087 def did_crash(self):
88 return self.reason != None
89
90 def __str__(self):
91 s = "Thread[%u]" % self.index
92 if self.reason:
93 s += ' %s' % self.reason
94 return s
95
96
97 class Frame:
98 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3d39f832012-04-03 21:35:43 +000099 def __init__(self, index, pc, description):
Greg Clayton01f7c962012-01-20 03:15:45 +0000100 self.pc = pc
Greg Clayton3d39f832012-04-03 21:35:43 +0000101 self.description = description
102 self.index = index
Greg Clayton01f7c962012-01-20 03:15:45 +0000103
104 def __str__(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000105 if self.description:
106 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
107 else:
Johnny Chen4e468672012-05-03 18:46:28 +0000108 return "[%3u] 0x%16.16x" % (self.index, self.pc)
109
110 def dump(self, prefix):
111 print "%s%s" % (prefix, str(self))
Greg Clayton01f7c962012-01-20 03:15:45 +0000112
Greg Clayton9d010422012-05-04 20:44:14 +0000113 class DarwinImage(symbolication.Image):
Greg Clayton01f7c962012-01-20 03:15:45 +0000114 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000115 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000116 if not os.path.exists(dsymForUUIDBinary):
117 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
118
119 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000120
Greg Clayton3d39f832012-04-03 21:35:43 +0000121 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Clayton9d010422012-05-04 20:44:14 +0000122 symbolication.Image.__init__(self, path, uuid);
123 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3d39f832012-04-03 21:35:43 +0000124 self.identifier = identifier
Greg Clayton01f7c962012-01-20 03:15:45 +0000125 self.version = version
Greg Clayton01f7c962012-01-20 03:15:45 +0000126
Greg Clayton3d39f832012-04-03 21:35:43 +0000127 def locate_module_and_debug_symbols(self):
Greg Clayton1b62f592012-06-04 23:22:17 +0000128 # Don't load a module twice...
129 if self.resolved:
Greg Clayton4c983c82012-04-20 23:31:27 +0000130 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000131 # Mark this as resolved so we don't keep trying
132 self.resolved = True
Greg Claytond8056e22012-05-11 00:30:14 +0000133 uuid_str = self.get_normalized_uuid_string()
134 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000135 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000136 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000137 s = commands.getoutput(dsym_for_uuid_command)
138 if s:
139 plist_root = plistlib.readPlistFromString (s)
140 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000141 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000142 if plist:
143 if 'DBGArchitecture' in plist:
144 self.arch = plist['DBGArchitecture']
145 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000146 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000147 if 'DBGSymbolRichExecutable' in plist:
148 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
149 if not self.resolved_path and os.path.exists(self.path):
150 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000151 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000152 for line in dwarfdump_cmd_output.splitlines():
153 match = self.dwarfdump_uuid_regex.search (line)
154 if match:
155 dwarf_uuid_str = match.group(1)
156 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
157 if self_uuid == dwarf_uuid:
158 self.resolved_path = self.path
159 self.arch = match.group(2)
160 break;
161 if not self.resolved_path:
Greg Clayton1b62f592012-06-04 23:22:17 +0000162 self.unavailable = True
163 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Clayton4c983c82012-04-20 23:31:27 +0000164 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000165 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
166 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000167 # if self.resolved_path:
168 # print ' exe = "%s"' % self.resolved_path
169 # if self.symfile:
170 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000171 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000172 else:
173 self.unavailable = True
Greg Clayton4c983c82012-04-20 23:31:27 +0000174 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000175
Greg Clayton3d39f832012-04-03 21:35:43 +0000176
Greg Clayton01f7c962012-01-20 03:15:45 +0000177
178 def __init__(self, path):
179 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000180 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000181 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000182 self.info_lines = list()
183 self.system_profile = list()
184 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000185 self.idents = list() # A list of the required identifiers for doing all stack backtraces
186 self.crashed_thread_idx = -1
187 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000188 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000189 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000190 try:
191 f = open(self.path)
192 except IOError:
193 self.error = 'error: cannot open "%s"' % self.path
194 return
195
Greg Clayton01f7c962012-01-20 03:15:45 +0000196 self.file_lines = f.read().splitlines()
197 parse_mode = PARSE_MODE_NORMAL
198 thread = None
199 for line in self.file_lines:
200 # print line
201 line_len = len(line)
202 if line_len == 0:
203 if thread:
204 if parse_mode == PARSE_MODE_THREAD:
205 if thread.index == self.crashed_thread_idx:
206 thread.reason = ''
207 if self.thread_exception:
208 thread.reason += self.thread_exception
209 if self.thread_exception_data:
210 thread.reason += " (%s)" % self.thread_exception_data
211 self.threads.append(thread)
212 thread = None
213 else:
214 # only append an extra empty line if the previous line
215 # in the info_lines wasn't empty
216 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
217 self.info_lines.append(line)
218 parse_mode = PARSE_MODE_NORMAL
219 # print 'PARSE_MODE_NORMAL'
220 elif parse_mode == PARSE_MODE_NORMAL:
221 if line.startswith ('Process:'):
222 (self.process_name, pid_with_brackets) = line[8:].strip().split()
223 self.process_id = pid_with_brackets.strip('[]')
224 elif line.startswith ('Path:'):
225 self.process_path = line[5:].strip()
226 elif line.startswith ('Identifier:'):
227 self.process_identifier = line[11:].strip()
228 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000229 version_string = line[8:].strip()
230 matched_pair = re.search("(.+)\((.+)\)", version_string)
231 if matched_pair:
232 self.process_version = matched_pair.group(1)
233 self.process_compatability_version = matched_pair.group(2)
234 else:
235 self.process = version_string
236 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000237 elif line.startswith ('Parent Process:'):
238 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
239 self.parent_process_id = pid_with_brackets.strip('[]')
240 elif line.startswith ('Exception Type:'):
241 self.thread_exception = line[15:].strip()
242 continue
243 elif line.startswith ('Exception Codes:'):
244 self.thread_exception_data = line[16:].strip()
245 continue
246 elif line.startswith ('Crashed Thread:'):
247 self.crashed_thread_idx = int(line[15:].strip().split()[0])
248 continue
249 elif line.startswith ('Report Version:'):
250 self.version = int(line[15:].strip())
251 continue
252 elif line.startswith ('System Profile:'):
253 parse_mode = PARSE_MODE_SYSTEM
254 continue
255 elif (line.startswith ('Interval Since Last Report:') or
256 line.startswith ('Crashes Since Last Report:') or
257 line.startswith ('Per-App Interval Since Last Report:') or
258 line.startswith ('Per-App Crashes Since Last Report:') or
259 line.startswith ('Sleep/Wake UUID:') or
260 line.startswith ('Anonymous UUID:')):
261 # ignore these
262 continue
263 elif line.startswith ('Thread'):
264 thread_state_match = self.thread_state_regex.search (line)
265 if thread_state_match:
266 thread_state_match = self.thread_regex.search (line)
267 thread_idx = int(thread_state_match.group(1))
268 parse_mode = PARSE_MODE_THREGS
269 thread = self.threads[thread_idx]
270 else:
271 thread_match = self.thread_regex.search (line)
272 if thread_match:
273 # print 'PARSE_MODE_THREAD'
274 parse_mode = PARSE_MODE_THREAD
275 thread_idx = int(thread_match.group(1))
276 thread = CrashLog.Thread(thread_idx)
277 continue
278 elif line.startswith ('Binary Images:'):
279 parse_mode = PARSE_MODE_IMAGES
280 continue
281 self.info_lines.append(line.strip())
282 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000283 if line.startswith ('Thread'):
284 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000285 frame_match = self.frame_regex.search(line)
286 if frame_match:
287 ident = frame_match.group(2)
Greg Claytonaa7df342012-07-13 03:19:35 +0000288 thread.add_ident(ident)
Greg Clayton01f7c962012-01-20 03:15:45 +0000289 if not ident in self.idents:
290 self.idents.append(ident)
291 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
292 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000293 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000294 elif parse_mode == PARSE_MODE_IMAGES:
295 image_match = self.image_regex_uuid.search (line)
296 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000297 image = CrashLog.DarwinImage (int(image_match.group(1),0),
298 int(image_match.group(2),0),
299 image_match.group(3).strip(),
300 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000301 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000302 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000303 self.images.append (image)
304 else:
305 image_match = self.image_regex_no_uuid.search (line)
306 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000307 image = CrashLog.DarwinImage (int(image_match.group(1),0),
308 int(image_match.group(2),0),
309 image_match.group(3).strip(),
310 image_match.group(4).strip(),
311 None,
312 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000313 self.images.append (image)
314 else:
315 print "error: image regex failed for: %s" % line
316
317 elif parse_mode == PARSE_MODE_THREGS:
318 stripped_line = line.strip()
Greg Claytond8056e22012-05-11 00:30:14 +0000319 reg_values = re.split(' +', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000320 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000321 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000322 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000323 #print 'reg = "%s"' % reg
324 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000325 thread.registers[reg.strip()] = int(value, 0)
326 elif parse_mode == PARSE_MODE_SYSTEM:
327 self.system_profile.append(line)
328 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000329
Greg Clayton01f7c962012-01-20 03:15:45 +0000330 def dump(self):
331 print "Crash Log File: %s" % (self.path)
332 print "\nThreads:"
333 for thread in self.threads:
334 thread.dump(' ')
335 print "\nImages:"
336 for image in self.images:
337 image.dump(' ')
338
Greg Clayton3d39f832012-04-03 21:35:43 +0000339 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000340 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000341 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000342 return image
343 return None
344
Greg Claytone9ee5502012-01-20 19:25:32 +0000345 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000346 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000347 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000348 if target:
349 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000350 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000351 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000352 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000353 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000354 image = self.find_image_with_identifier (ident)
355 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000356 target = image.create_target ()
357 if target:
358 return target # success
359 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000360 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000361 target = image.create_target ()
362 if target:
363 return target # success
364 print 'crashlog.create_target()...4'
365 print 'error: unable to locate any executables from the crash log'
366 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000367
Greg Clayton01f7c962012-01-20 03:15:45 +0000368
369def usage():
370 print "Usage: lldb-symbolicate.py [-n name] executable-image"
371 sys.exit(0)
372
Greg Clayton9d010422012-05-04 20:44:14 +0000373class Interactive(cmd.Cmd):
374 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
375 image_option_parser = None
376
377 def __init__(self, crash_logs):
378 cmd.Cmd.__init__(self)
Greg Claytondcf56142012-07-03 21:40:18 +0000379 self.use_rawinput = False
Greg Clayton9d010422012-05-04 20:44:14 +0000380 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
381 self.crash_logs = crash_logs
382 self.prompt = '% '
383
384 def default(self, line):
385 '''Catch all for unknown command, which will exit the interpreter.'''
386 print "uknown command: %s" % line
387 return True
388
389 def do_q(self, line):
390 '''Quit command'''
391 return True
392
393 def do_quit(self, line):
394 '''Quit command'''
395 return True
396
Greg Claytona3368dd2012-05-31 21:21:08 +0000397 def do_symbolicate(self, line):
398 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
399 inlined stack frames back to the concrete functions, and disassemble the location of the crash
400 for the first frame of the crashed thread.'''
401 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
402 command_args = shlex.split(line)
403 try:
404 (options, args) = option_parser.parse_args(command_args)
405 except:
406 return
407
408 for idx_str in args:
409 idx = int(idx_str)
410 if idx < len(self.crash_logs):
411 SymbolicateCrashLog (self.crash_logs[idx], options)
412 else:
413 print 'error: crash log index %u is out of range' % (idx)
414
Greg Clayton9d010422012-05-04 20:44:14 +0000415 def do_list(self, line=None):
416 '''Dump a list of all crash logs that are currently loaded.
417
418 USAGE: list'''
419 print '%u crash logs are loaded:' % len(self.crash_logs)
420 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
421 print '[%u] = %s' % (crash_log_idx, crash_log.path)
422
423 def do_image(self, line):
424 '''Dump information about an image in the crash log given an image basename.
425
426 USAGE: image <basename>'''
427 usage = "usage: %prog [options] <PATH> [PATH ...]"
428 description='''Dump information about one or more images in all crash logs. The <PATH>
429 can be a full path or a image basename.'''
430 command_args = shlex.split(line)
431 if not self.image_option_parser:
432 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
433 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
434 try:
435 (options, args) = self.image_option_parser.parse_args(command_args)
436 except:
437 return
438
Greg Claytond8056e22012-05-11 00:30:14 +0000439 if args:
440 for image_path in args:
441 fullpath_search = image_path[0] == '/'
442 for crash_log in self.crash_logs:
443 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000444 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000445 if fullpath_search:
446 if image.get_resolved_path() == image_path:
447 matches_found += 1
448 print image
449 else:
450 image_basename = image.get_resolved_path_basename()
451 if image_basename == image_path:
452 matches_found += 1
453 print image
454 if matches_found == 0:
455 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000456 resolved_image_path = image.get_resolved_path()
457 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytond8056e22012-05-11 00:30:14 +0000458 print image
459 else:
460 for crash_log in self.crash_logs:
461 for (image_idx, image) in enumerate(crash_log.images):
462 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000463 return False
464
465
466def interactive_crashlogs(options, args):
467 crash_log_files = list()
468 for arg in args:
469 for resolved_path in glob.glob(arg):
470 crash_log_files.append(resolved_path)
471
472 crash_logs = list();
473 for crash_log_file in crash_log_files:
474 #print 'crash_log_file = "%s"' % crash_log_file
475 crash_log = CrashLog(crash_log_file)
476 if crash_log.error:
477 print crash_log.error
478 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000479 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000480 crash_log.dump()
481 if not crash_log.images:
482 print 'error: no images in crash log "%s"' % (crash_log)
483 continue
484 else:
485 crash_logs.append(crash_log)
486
487 interpreter = Interactive(crash_logs)
488 # List all crash logs that were imported
489 interpreter.do_list()
490 interpreter.cmdloop()
491
Greg Claytonca1500f2012-06-27 20:02:04 +0000492
493def save_crashlog(debugger, command, result, dict):
494 usage = "usage: %prog [options] <output-path>"
495 description='''Export the state of current target into a crashlog file'''
496 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
497 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
498 try:
499 (options, args) = parser.parse_args(shlex.split(command))
500 except:
501 result.PutCString ("error: invalid options");
502 return
503 if len(args) != 1:
504 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
505 return
506 out_file = open(args[0], 'w')
507 if not out_file:
508 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
509 return
510 if lldb.target:
511 identifier = lldb.target.executable.basename
512 if lldb.process:
513 pid = lldb.process.id
514 if pid != lldb.LLDB_INVALID_PROCESS_ID:
515 out_file.write('Process: %s [%u]\n' % (identifier, pid))
516 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
517 out_file.write('Identifier: %s\n' % (identifier))
518 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
519 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
520 out_file.write('Report Version: 9\n')
521 for thread_idx in range(lldb.process.num_threads):
522 thread = lldb.process.thread[thread_idx]
523 out_file.write('\nThread %u:\n' % (thread_idx))
524 for (frame_idx, frame) in enumerate(thread.frames):
525 frame_pc = frame.pc
526 frame_offset = 0
527 if frame.function:
528 block = frame.GetFrameBlock()
529 block_range = block.range[frame.addr]
530 if block_range:
531 block_start_addr = block_range[0]
532 frame_offset = frame_pc - block_start_addr.load_addr
533 else:
534 frame_offset = frame_pc - frame.function.addr.load_addr
535 elif frame.symbol:
536 frame_offset = frame_pc - frame.symbol.addr.load_addr
537 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
538 if frame_offset > 0:
539 out_file.write(' + %u' % (frame_offset))
540 line_entry = frame.line_entry
541 if line_entry:
542 if options.verbose:
543 # This will output the fullpath + line + column
544 out_file.write(' %s' % (line_entry))
545 else:
546 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
547 column = line_entry.column
548 if column:
549 out_file.write(':%u' % (column))
550 out_file.write('\n')
551
552 out_file.write('\nBinary Images:\n')
553 for module in lldb.target.modules:
554 text_segment = module.section['__TEXT']
555 if text_segment:
556 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
557 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
558 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
559 identifier = module.file.basename
560 module_version = '???'
561 module_version_array = module.GetVersion()
562 if module_version_array:
563 module_version = '.'.join(map(str,module_version_array))
564 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))
565 out_file.close()
566 else:
567 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000568
Greg Claytonca1500f2012-06-27 20:02:04 +0000569
Greg Clayton01f7c962012-01-20 03:15:45 +0000570def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000571 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000572 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000573 except:
574 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000575
576def SymbolicateCrashLog(crash_log, options):
577 if crash_log.error:
578 print crash_log.error
579 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000580 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000581 crash_log.dump()
582 if not crash_log.images:
583 print 'error: no images in crash log'
584 return
585
586 target = crash_log.create_target ()
587 if not target:
588 return
589 exe_module = target.GetModuleAtIndex(0)
590 images_to_load = list()
591 loaded_images = list()
592 if options.load_all_images:
593 # --load-all option was specified, load everything up
594 for image in crash_log.images:
595 images_to_load.append(image)
596 else:
597 # Only load the images found in stack frames for the crashed threads
Greg Claytonaa7df342012-07-13 03:19:35 +0000598 if options.crashed_only:
599 for thread in crash_log.threads:
600 if thread.did_crash():
601 for ident in thread.idents:
602 images = crash_log.find_images_with_identifier (ident)
603 if images:
604 for image in images:
605 images_to_load.append(image)
606 else:
607 print 'error: can\'t find image for identifier "%s"' % ident
608 else:
609 for ident in crash_log.idents:
610 images = crash_log.find_images_with_identifier (ident)
611 if images:
612 for image in images:
613 images_to_load.append(image)
614 else:
615 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3368dd2012-05-31 21:21:08 +0000616
617 for image in images_to_load:
618 if image in loaded_images:
619 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
620 else:
621 err = image.add_module (target)
622 if err:
623 print err
624 else:
625 #print 'loaded %s' % image
626 loaded_images.append(image)
627
628 for thread in crash_log.threads:
629 this_thread_crashed = thread.did_crash()
630 if options.crashed_only and this_thread_crashed == False:
631 continue
632 print "%s" % thread
633 #prev_frame_index = -1
Greg Clayton007f73a2012-07-13 17:58:52 +0000634 display_frame_idx = -1
Greg Claytona3368dd2012-05-31 21:21:08 +0000635 for frame_idx, frame in enumerate(thread.frames):
636 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
637 if frame_idx == 0:
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000638 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000639 else:
640 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000641 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000642
643 if symbolicated_frame_addresses:
644 symbolicated_frame_address_idx = 0
645 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Clayton007f73a2012-07-13 17:58:52 +0000646 display_frame_idx += 1
Greg Claytona3368dd2012-05-31 21:21:08 +0000647 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton007f73a2012-07-13 17:58:52 +0000648 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
649 source_context = options.source_context
Greg Claytonaa7df342012-07-13 03:19:35 +0000650 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
651 if line_entry.IsValid():
652 strm = lldb.SBStream()
653 if line_entry:
Greg Clayton007f73a2012-07-13 17:58:52 +0000654 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Claytonaa7df342012-07-13 03:19:35 +0000655 source_text = strm.GetData()
656 if source_text:
657 # Indent the source a bit
658 indent_str = ' '
659 join_str = '\n' + indent_str
660 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytona3368dd2012-05-31 21:21:08 +0000661 if symbolicated_frame_address_idx == 0:
662 if disassemble:
663 instructions = symbolicated_frame_address.get_instructions()
664 if instructions:
665 print
666 symbolication.disassemble_instructions (target,
667 instructions,
668 frame.pc,
669 options.disassemble_before,
670 options.disassemble_after, frame.index > 0)
671 print
672 symbolicated_frame_address_idx += 1
673 else:
674 print frame
675 print
676
677 if options.dump_image_list:
678 print "Binary Images:"
679 for image in crash_log.images:
680 print image
681
682def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000683 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000684 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton007f73a2012-07-13 17:58:52 +0000685 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
686 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
687 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)
688 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
689 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
690 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
691 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)
692 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)
693 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)
694 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)
695 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)
696 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
697 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 Claytona3368dd2012-05-31 21:21:08 +0000698 if add_interactive_options:
699 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
700 return option_parser
701
702def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000703 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
704inlined stack frames back to the concrete functions, and disassemble the location of the crash
705for the first frame of the crashed thread.
706If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
707for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
708created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
709you to explore the program as if it were stopped at the locations described in the crash log and functions can
710be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000711 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000712 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000713 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000714 except:
715 return
716
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000717 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000718 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000719 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000720 print 'args', args
721
Greg Clayton01f7c962012-01-20 03:15:45 +0000722 if options.debug_delay > 0:
723 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
724 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000725 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000726
Greg Claytone9ee5502012-01-20 19:25:32 +0000727 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000728 if options.interactive:
729 interactive_crashlogs(options, args)
730 else:
731 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000732 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000733 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000734if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000735 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000736 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000737 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000738 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000739elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000740 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000741 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
742 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000743