blob: 2f53ad4339fa925031d930c7db76f998f6a3df4e [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()
67 self.registers = dict()
68 self.reason = None
69 self.queue = None
70
71 def dump(self, prefix):
72 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
73 if self.frames:
74 print "%s Frames:" % (prefix)
75 for frame in self.frames:
76 frame.dump(prefix + ' ')
77 if self.registers:
78 print "%s Registers:" % (prefix)
79 for reg in self.registers.keys():
80 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
81
82 def did_crash(self):
83 return self.reason != None
84
85 def __str__(self):
86 s = "Thread[%u]" % self.index
87 if self.reason:
88 s += ' %s' % self.reason
89 return s
90
91
92 class Frame:
93 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3d39f832012-04-03 21:35:43 +000094 def __init__(self, index, pc, description):
Greg Clayton01f7c962012-01-20 03:15:45 +000095 self.pc = pc
Greg Clayton3d39f832012-04-03 21:35:43 +000096 self.description = description
97 self.index = index
Greg Clayton01f7c962012-01-20 03:15:45 +000098
99 def __str__(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000100 if self.description:
101 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
102 else:
Johnny Chen4e468672012-05-03 18:46:28 +0000103 return "[%3u] 0x%16.16x" % (self.index, self.pc)
104
105 def dump(self, prefix):
106 print "%s%s" % (prefix, str(self))
Greg Clayton01f7c962012-01-20 03:15:45 +0000107
Greg Clayton9d010422012-05-04 20:44:14 +0000108 class DarwinImage(symbolication.Image):
Greg Clayton01f7c962012-01-20 03:15:45 +0000109 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000110 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000111 if not os.path.exists(dsymForUUIDBinary):
112 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
113
114 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000115
Greg Clayton3d39f832012-04-03 21:35:43 +0000116 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Clayton9d010422012-05-04 20:44:14 +0000117 symbolication.Image.__init__(self, path, uuid);
118 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3d39f832012-04-03 21:35:43 +0000119 self.identifier = identifier
Greg Clayton01f7c962012-01-20 03:15:45 +0000120 self.version = version
Greg Clayton01f7c962012-01-20 03:15:45 +0000121
Greg Clayton3d39f832012-04-03 21:35:43 +0000122 def locate_module_and_debug_symbols(self):
Greg Clayton1b62f592012-06-04 23:22:17 +0000123 # Don't load a module twice...
124 if self.resolved:
Greg Clayton4c983c82012-04-20 23:31:27 +0000125 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000126 # Mark this as resolved so we don't keep trying
127 self.resolved = True
Greg Claytond8056e22012-05-11 00:30:14 +0000128 uuid_str = self.get_normalized_uuid_string()
129 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000130 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000131 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000132 s = commands.getoutput(dsym_for_uuid_command)
133 if s:
134 plist_root = plistlib.readPlistFromString (s)
135 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000136 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000137 if plist:
138 if 'DBGArchitecture' in plist:
139 self.arch = plist['DBGArchitecture']
140 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000141 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000142 if 'DBGSymbolRichExecutable' in plist:
143 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
144 if not self.resolved_path and os.path.exists(self.path):
145 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000146 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000147 for line in dwarfdump_cmd_output.splitlines():
148 match = self.dwarfdump_uuid_regex.search (line)
149 if match:
150 dwarf_uuid_str = match.group(1)
151 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
152 if self_uuid == dwarf_uuid:
153 self.resolved_path = self.path
154 self.arch = match.group(2)
155 break;
156 if not self.resolved_path:
Greg Clayton1b62f592012-06-04 23:22:17 +0000157 self.unavailable = True
158 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Clayton4c983c82012-04-20 23:31:27 +0000159 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000160 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
161 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000162 # if self.resolved_path:
163 # print ' exe = "%s"' % self.resolved_path
164 # if self.symfile:
165 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000166 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000167 else:
168 self.unavailable = True
Greg Clayton4c983c82012-04-20 23:31:27 +0000169 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000170
Greg Clayton3d39f832012-04-03 21:35:43 +0000171
Greg Clayton01f7c962012-01-20 03:15:45 +0000172
173 def __init__(self, path):
174 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000175 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000176 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000177 self.info_lines = list()
178 self.system_profile = list()
179 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000180 self.idents = list() # A list of the required identifiers for doing all stack backtraces
181 self.crashed_thread_idx = -1
182 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000183 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000184 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000185 try:
186 f = open(self.path)
187 except IOError:
188 self.error = 'error: cannot open "%s"' % self.path
189 return
190
Greg Clayton01f7c962012-01-20 03:15:45 +0000191 self.file_lines = f.read().splitlines()
192 parse_mode = PARSE_MODE_NORMAL
193 thread = None
194 for line in self.file_lines:
195 # print line
196 line_len = len(line)
197 if line_len == 0:
198 if thread:
199 if parse_mode == PARSE_MODE_THREAD:
200 if thread.index == self.crashed_thread_idx:
201 thread.reason = ''
202 if self.thread_exception:
203 thread.reason += self.thread_exception
204 if self.thread_exception_data:
205 thread.reason += " (%s)" % self.thread_exception_data
206 self.threads.append(thread)
207 thread = None
208 else:
209 # only append an extra empty line if the previous line
210 # in the info_lines wasn't empty
211 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
212 self.info_lines.append(line)
213 parse_mode = PARSE_MODE_NORMAL
214 # print 'PARSE_MODE_NORMAL'
215 elif parse_mode == PARSE_MODE_NORMAL:
216 if line.startswith ('Process:'):
217 (self.process_name, pid_with_brackets) = line[8:].strip().split()
218 self.process_id = pid_with_brackets.strip('[]')
219 elif line.startswith ('Path:'):
220 self.process_path = line[5:].strip()
221 elif line.startswith ('Identifier:'):
222 self.process_identifier = line[11:].strip()
223 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000224 version_string = line[8:].strip()
225 matched_pair = re.search("(.+)\((.+)\)", version_string)
226 if matched_pair:
227 self.process_version = matched_pair.group(1)
228 self.process_compatability_version = matched_pair.group(2)
229 else:
230 self.process = version_string
231 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000232 elif line.startswith ('Parent Process:'):
233 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
234 self.parent_process_id = pid_with_brackets.strip('[]')
235 elif line.startswith ('Exception Type:'):
236 self.thread_exception = line[15:].strip()
237 continue
238 elif line.startswith ('Exception Codes:'):
239 self.thread_exception_data = line[16:].strip()
240 continue
241 elif line.startswith ('Crashed Thread:'):
242 self.crashed_thread_idx = int(line[15:].strip().split()[0])
243 continue
244 elif line.startswith ('Report Version:'):
245 self.version = int(line[15:].strip())
246 continue
247 elif line.startswith ('System Profile:'):
248 parse_mode = PARSE_MODE_SYSTEM
249 continue
250 elif (line.startswith ('Interval Since Last Report:') or
251 line.startswith ('Crashes Since Last Report:') or
252 line.startswith ('Per-App Interval Since Last Report:') or
253 line.startswith ('Per-App Crashes Since Last Report:') or
254 line.startswith ('Sleep/Wake UUID:') or
255 line.startswith ('Anonymous UUID:')):
256 # ignore these
257 continue
258 elif line.startswith ('Thread'):
259 thread_state_match = self.thread_state_regex.search (line)
260 if thread_state_match:
261 thread_state_match = self.thread_regex.search (line)
262 thread_idx = int(thread_state_match.group(1))
263 parse_mode = PARSE_MODE_THREGS
264 thread = self.threads[thread_idx]
265 else:
266 thread_match = self.thread_regex.search (line)
267 if thread_match:
268 # print 'PARSE_MODE_THREAD'
269 parse_mode = PARSE_MODE_THREAD
270 thread_idx = int(thread_match.group(1))
271 thread = CrashLog.Thread(thread_idx)
272 continue
273 elif line.startswith ('Binary Images:'):
274 parse_mode = PARSE_MODE_IMAGES
275 continue
276 self.info_lines.append(line.strip())
277 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000278 if line.startswith ('Thread'):
279 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000280 frame_match = self.frame_regex.search(line)
281 if frame_match:
282 ident = frame_match.group(2)
283 if not ident in self.idents:
284 self.idents.append(ident)
285 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
286 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000287 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000288 elif parse_mode == PARSE_MODE_IMAGES:
289 image_match = self.image_regex_uuid.search (line)
290 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000291 image = CrashLog.DarwinImage (int(image_match.group(1),0),
292 int(image_match.group(2),0),
293 image_match.group(3).strip(),
294 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000295 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000296 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000297 self.images.append (image)
298 else:
299 image_match = self.image_regex_no_uuid.search (line)
300 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000301 image = CrashLog.DarwinImage (int(image_match.group(1),0),
302 int(image_match.group(2),0),
303 image_match.group(3).strip(),
304 image_match.group(4).strip(),
305 None,
306 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000307 self.images.append (image)
308 else:
309 print "error: image regex failed for: %s" % line
310
311 elif parse_mode == PARSE_MODE_THREGS:
312 stripped_line = line.strip()
Greg Claytond8056e22012-05-11 00:30:14 +0000313 reg_values = re.split(' +', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000314 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000315 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000316 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000317 #print 'reg = "%s"' % reg
318 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000319 thread.registers[reg.strip()] = int(value, 0)
320 elif parse_mode == PARSE_MODE_SYSTEM:
321 self.system_profile.append(line)
322 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000323
Greg Clayton01f7c962012-01-20 03:15:45 +0000324 def dump(self):
325 print "Crash Log File: %s" % (self.path)
326 print "\nThreads:"
327 for thread in self.threads:
328 thread.dump(' ')
329 print "\nImages:"
330 for image in self.images:
331 image.dump(' ')
332
Greg Clayton3d39f832012-04-03 21:35:43 +0000333 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000334 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000335 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000336 return image
337 return None
338
Greg Claytone9ee5502012-01-20 19:25:32 +0000339 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000340 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000341 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000342 if target:
343 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000344 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000345 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000346 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000347 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000348 image = self.find_image_with_identifier (ident)
349 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000350 target = image.create_target ()
351 if target:
352 return target # success
353 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000354 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000355 target = image.create_target ()
356 if target:
357 return target # success
358 print 'crashlog.create_target()...4'
359 print 'error: unable to locate any executables from the crash log'
360 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000361
Greg Clayton01f7c962012-01-20 03:15:45 +0000362
363def usage():
364 print "Usage: lldb-symbolicate.py [-n name] executable-image"
365 sys.exit(0)
366
Greg Clayton9d010422012-05-04 20:44:14 +0000367class Interactive(cmd.Cmd):
368 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
369 image_option_parser = None
370
371 def __init__(self, crash_logs):
372 cmd.Cmd.__init__(self)
Greg Claytondcf56142012-07-03 21:40:18 +0000373 self.use_rawinput = False
Greg Clayton9d010422012-05-04 20:44:14 +0000374 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
375 self.crash_logs = crash_logs
376 self.prompt = '% '
377
378 def default(self, line):
379 '''Catch all for unknown command, which will exit the interpreter.'''
380 print "uknown command: %s" % line
381 return True
382
383 def do_q(self, line):
384 '''Quit command'''
385 return True
386
387 def do_quit(self, line):
388 '''Quit command'''
389 return True
390
Greg Claytona3368dd2012-05-31 21:21:08 +0000391 def do_symbolicate(self, line):
392 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
393 inlined stack frames back to the concrete functions, and disassemble the location of the crash
394 for the first frame of the crashed thread.'''
395 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
396 command_args = shlex.split(line)
397 try:
398 (options, args) = option_parser.parse_args(command_args)
399 except:
400 return
401
402 for idx_str in args:
403 idx = int(idx_str)
404 if idx < len(self.crash_logs):
405 SymbolicateCrashLog (self.crash_logs[idx], options)
406 else:
407 print 'error: crash log index %u is out of range' % (idx)
408
Greg Clayton9d010422012-05-04 20:44:14 +0000409 def do_list(self, line=None):
410 '''Dump a list of all crash logs that are currently loaded.
411
412 USAGE: list'''
413 print '%u crash logs are loaded:' % len(self.crash_logs)
414 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
415 print '[%u] = %s' % (crash_log_idx, crash_log.path)
416
417 def do_image(self, line):
418 '''Dump information about an image in the crash log given an image basename.
419
420 USAGE: image <basename>'''
421 usage = "usage: %prog [options] <PATH> [PATH ...]"
422 description='''Dump information about one or more images in all crash logs. The <PATH>
423 can be a full path or a image basename.'''
424 command_args = shlex.split(line)
425 if not self.image_option_parser:
426 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
427 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
428 try:
429 (options, args) = self.image_option_parser.parse_args(command_args)
430 except:
431 return
432
Greg Claytond8056e22012-05-11 00:30:14 +0000433 if args:
434 for image_path in args:
435 fullpath_search = image_path[0] == '/'
436 for crash_log in self.crash_logs:
437 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000438 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000439 if fullpath_search:
440 if image.get_resolved_path() == image_path:
441 matches_found += 1
442 print image
443 else:
444 image_basename = image.get_resolved_path_basename()
445 if image_basename == image_path:
446 matches_found += 1
447 print image
448 if matches_found == 0:
449 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000450 resolved_image_path = image.get_resolved_path()
451 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytond8056e22012-05-11 00:30:14 +0000452 print image
453 else:
454 for crash_log in self.crash_logs:
455 for (image_idx, image) in enumerate(crash_log.images):
456 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000457 return False
458
459
460def interactive_crashlogs(options, args):
461 crash_log_files = list()
462 for arg in args:
463 for resolved_path in glob.glob(arg):
464 crash_log_files.append(resolved_path)
465
466 crash_logs = list();
467 for crash_log_file in crash_log_files:
468 #print 'crash_log_file = "%s"' % crash_log_file
469 crash_log = CrashLog(crash_log_file)
470 if crash_log.error:
471 print crash_log.error
472 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000473 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000474 crash_log.dump()
475 if not crash_log.images:
476 print 'error: no images in crash log "%s"' % (crash_log)
477 continue
478 else:
479 crash_logs.append(crash_log)
480
481 interpreter = Interactive(crash_logs)
482 # List all crash logs that were imported
483 interpreter.do_list()
484 interpreter.cmdloop()
485
Greg Claytonca1500f2012-06-27 20:02:04 +0000486
487def save_crashlog(debugger, command, result, dict):
488 usage = "usage: %prog [options] <output-path>"
489 description='''Export the state of current target into a crashlog file'''
490 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
491 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
492 try:
493 (options, args) = parser.parse_args(shlex.split(command))
494 except:
495 result.PutCString ("error: invalid options");
496 return
497 if len(args) != 1:
498 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
499 return
500 out_file = open(args[0], 'w')
501 if not out_file:
502 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
503 return
504 if lldb.target:
505 identifier = lldb.target.executable.basename
506 if lldb.process:
507 pid = lldb.process.id
508 if pid != lldb.LLDB_INVALID_PROCESS_ID:
509 out_file.write('Process: %s [%u]\n' % (identifier, pid))
510 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
511 out_file.write('Identifier: %s\n' % (identifier))
512 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
513 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
514 out_file.write('Report Version: 9\n')
515 for thread_idx in range(lldb.process.num_threads):
516 thread = lldb.process.thread[thread_idx]
517 out_file.write('\nThread %u:\n' % (thread_idx))
518 for (frame_idx, frame) in enumerate(thread.frames):
519 frame_pc = frame.pc
520 frame_offset = 0
521 if frame.function:
522 block = frame.GetFrameBlock()
523 block_range = block.range[frame.addr]
524 if block_range:
525 block_start_addr = block_range[0]
526 frame_offset = frame_pc - block_start_addr.load_addr
527 else:
528 frame_offset = frame_pc - frame.function.addr.load_addr
529 elif frame.symbol:
530 frame_offset = frame_pc - frame.symbol.addr.load_addr
531 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
532 if frame_offset > 0:
533 out_file.write(' + %u' % (frame_offset))
534 line_entry = frame.line_entry
535 if line_entry:
536 if options.verbose:
537 # This will output the fullpath + line + column
538 out_file.write(' %s' % (line_entry))
539 else:
540 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
541 column = line_entry.column
542 if column:
543 out_file.write(':%u' % (column))
544 out_file.write('\n')
545
546 out_file.write('\nBinary Images:\n')
547 for module in lldb.target.modules:
548 text_segment = module.section['__TEXT']
549 if text_segment:
550 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
551 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
552 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
553 identifier = module.file.basename
554 module_version = '???'
555 module_version_array = module.GetVersion()
556 if module_version_array:
557 module_version = '.'.join(map(str,module_version_array))
558 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))
559 out_file.close()
560 else:
561 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000562
Greg Claytonca1500f2012-06-27 20:02:04 +0000563
Greg Clayton01f7c962012-01-20 03:15:45 +0000564def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000565 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000566 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000567 except:
568 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000569
570def SymbolicateCrashLog(crash_log, options):
571 if crash_log.error:
572 print crash_log.error
573 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000574 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000575 crash_log.dump()
576 if not crash_log.images:
577 print 'error: no images in crash log'
578 return
579
580 target = crash_log.create_target ()
581 if not target:
582 return
583 exe_module = target.GetModuleAtIndex(0)
584 images_to_load = list()
585 loaded_images = list()
586 if options.load_all_images:
587 # --load-all option was specified, load everything up
588 for image in crash_log.images:
589 images_to_load.append(image)
590 else:
591 # Only load the images found in stack frames for the crashed threads
592 for ident in crash_log.idents:
593 images = crash_log.find_images_with_identifier (ident)
594 if images:
595 for image in images:
596 images_to_load.append(image)
597 else:
598 print 'error: can\'t find image for identifier "%s"' % ident
599
600 for image in images_to_load:
601 if image in loaded_images:
602 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
603 else:
604 err = image.add_module (target)
605 if err:
606 print err
607 else:
608 #print 'loaded %s' % image
609 loaded_images.append(image)
610
611 for thread in crash_log.threads:
612 this_thread_crashed = thread.did_crash()
613 if options.crashed_only and this_thread_crashed == False:
614 continue
615 print "%s" % thread
616 #prev_frame_index = -1
617 for frame_idx, frame in enumerate(thread.frames):
618 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
619 if frame_idx == 0:
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000620 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000621 else:
622 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000623 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000624
625 if symbolicated_frame_addresses:
626 symbolicated_frame_address_idx = 0
627 for symbolicated_frame_address in symbolicated_frame_addresses:
628 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton223e8082012-01-21 04:26:24 +0000629
Greg Claytona3368dd2012-05-31 21:21:08 +0000630 if symbolicated_frame_address_idx == 0:
631 if disassemble:
632 instructions = symbolicated_frame_address.get_instructions()
633 if instructions:
634 print
635 symbolication.disassemble_instructions (target,
636 instructions,
637 frame.pc,
638 options.disassemble_before,
639 options.disassemble_after, frame.index > 0)
640 print
641 symbolicated_frame_address_idx += 1
642 else:
643 print frame
644 print
645
646 if options.dump_image_list:
647 print "Binary Images:"
648 for image in crash_log.images:
649 print image
650
651def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000652 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000653 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
654 option_parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000655 option_parser.add_option('-g', '--debug', action='store_true', dest='debug', help='display verbose debug logging', default=False)
Greg Claytona3368dd2012-05-31 21:21:08 +0000656 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)
657 option_parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False)
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000658 option_parser.add_option('--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
Greg Claytona3368dd2012-05-31 21:21:08 +0000659 option_parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
660 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)
661 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)
662 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)
663 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)
664 if add_interactive_options:
665 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
666 return option_parser
667
668def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000669 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
670inlined stack frames back to the concrete functions, and disassemble the location of the crash
671for the first frame of the crashed thread.
672If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
673for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
674created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
675you to explore the program as if it were stopped at the locations described in the crash log and functions can
676be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000677 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000678 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000679 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000680 except:
681 return
682
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000683 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000684 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000685 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000686 print 'args', args
687
Greg Clayton01f7c962012-01-20 03:15:45 +0000688 if options.debug_delay > 0:
689 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
690 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000691 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000692
Greg Claytone9ee5502012-01-20 19:25:32 +0000693 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000694 if options.interactive:
695 interactive_crashlogs(options, args)
696 else:
697 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000698 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000699 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000700if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000701 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000702 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000703 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000704 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000705elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000706 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000707 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
708 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000709