blob: 57659040dfe0b75157a646b8d09def68799295ac [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)
373 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
374 self.crash_logs = crash_logs
375 self.prompt = '% '
376
377 def default(self, line):
378 '''Catch all for unknown command, which will exit the interpreter.'''
379 print "uknown command: %s" % line
380 return True
381
382 def do_q(self, line):
383 '''Quit command'''
384 return True
385
386 def do_quit(self, line):
387 '''Quit command'''
388 return True
389
Greg Claytona3368dd2012-05-31 21:21:08 +0000390 def do_symbolicate(self, line):
391 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
392 inlined stack frames back to the concrete functions, and disassemble the location of the crash
393 for the first frame of the crashed thread.'''
394 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
395 command_args = shlex.split(line)
396 try:
397 (options, args) = option_parser.parse_args(command_args)
398 except:
399 return
400
401 for idx_str in args:
402 idx = int(idx_str)
403 if idx < len(self.crash_logs):
404 SymbolicateCrashLog (self.crash_logs[idx], options)
405 else:
406 print 'error: crash log index %u is out of range' % (idx)
407
Greg Clayton9d010422012-05-04 20:44:14 +0000408 def do_list(self, line=None):
409 '''Dump a list of all crash logs that are currently loaded.
410
411 USAGE: list'''
412 print '%u crash logs are loaded:' % len(self.crash_logs)
413 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
414 print '[%u] = %s' % (crash_log_idx, crash_log.path)
415
416 def do_image(self, line):
417 '''Dump information about an image in the crash log given an image basename.
418
419 USAGE: image <basename>'''
420 usage = "usage: %prog [options] <PATH> [PATH ...]"
421 description='''Dump information about one or more images in all crash logs. The <PATH>
422 can be a full path or a image basename.'''
423 command_args = shlex.split(line)
424 if not self.image_option_parser:
425 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
426 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
427 try:
428 (options, args) = self.image_option_parser.parse_args(command_args)
429 except:
430 return
431
Greg Claytond8056e22012-05-11 00:30:14 +0000432 if args:
433 for image_path in args:
434 fullpath_search = image_path[0] == '/'
435 for crash_log in self.crash_logs:
436 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000437 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000438 if fullpath_search:
439 if image.get_resolved_path() == image_path:
440 matches_found += 1
441 print image
442 else:
443 image_basename = image.get_resolved_path_basename()
444 if image_basename == image_path:
445 matches_found += 1
446 print image
447 if matches_found == 0:
448 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000449 resolved_image_path = image.get_resolved_path()
450 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytond8056e22012-05-11 00:30:14 +0000451 print image
452 else:
453 for crash_log in self.crash_logs:
454 for (image_idx, image) in enumerate(crash_log.images):
455 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000456 return False
457
458
459def interactive_crashlogs(options, args):
460 crash_log_files = list()
461 for arg in args:
462 for resolved_path in glob.glob(arg):
463 crash_log_files.append(resolved_path)
464
465 crash_logs = list();
466 for crash_log_file in crash_log_files:
467 #print 'crash_log_file = "%s"' % crash_log_file
468 crash_log = CrashLog(crash_log_file)
469 if crash_log.error:
470 print crash_log.error
471 continue
472 if options.verbose:
473 crash_log.dump()
474 if not crash_log.images:
475 print 'error: no images in crash log "%s"' % (crash_log)
476 continue
477 else:
478 crash_logs.append(crash_log)
479
480 interpreter = Interactive(crash_logs)
481 # List all crash logs that were imported
482 interpreter.do_list()
483 interpreter.cmdloop()
484
Greg Claytonca1500f2012-06-27 20:02:04 +0000485
486def save_crashlog(debugger, command, result, dict):
487 usage = "usage: %prog [options] <output-path>"
488 description='''Export the state of current target into a crashlog file'''
489 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
490 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
491 try:
492 (options, args) = parser.parse_args(shlex.split(command))
493 except:
494 result.PutCString ("error: invalid options");
495 return
496 if len(args) != 1:
497 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
498 return
499 out_file = open(args[0], 'w')
500 if not out_file:
501 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
502 return
503 if lldb.target:
504 identifier = lldb.target.executable.basename
505 if lldb.process:
506 pid = lldb.process.id
507 if pid != lldb.LLDB_INVALID_PROCESS_ID:
508 out_file.write('Process: %s [%u]\n' % (identifier, pid))
509 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
510 out_file.write('Identifier: %s\n' % (identifier))
511 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
512 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
513 out_file.write('Report Version: 9\n')
514 for thread_idx in range(lldb.process.num_threads):
515 thread = lldb.process.thread[thread_idx]
516 out_file.write('\nThread %u:\n' % (thread_idx))
517 for (frame_idx, frame) in enumerate(thread.frames):
518 frame_pc = frame.pc
519 frame_offset = 0
520 if frame.function:
521 block = frame.GetFrameBlock()
522 block_range = block.range[frame.addr]
523 if block_range:
524 block_start_addr = block_range[0]
525 frame_offset = frame_pc - block_start_addr.load_addr
526 else:
527 frame_offset = frame_pc - frame.function.addr.load_addr
528 elif frame.symbol:
529 frame_offset = frame_pc - frame.symbol.addr.load_addr
530 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
531 if frame_offset > 0:
532 out_file.write(' + %u' % (frame_offset))
533 line_entry = frame.line_entry
534 if line_entry:
535 if options.verbose:
536 # This will output the fullpath + line + column
537 out_file.write(' %s' % (line_entry))
538 else:
539 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
540 column = line_entry.column
541 if column:
542 out_file.write(':%u' % (column))
543 out_file.write('\n')
544
545 out_file.write('\nBinary Images:\n')
546 for module in lldb.target.modules:
547 text_segment = module.section['__TEXT']
548 if text_segment:
549 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
550 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
551 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
552 identifier = module.file.basename
553 module_version = '???'
554 module_version_array = module.GetVersion()
555 if module_version_array:
556 module_version = '.'.join(map(str,module_version_array))
557 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))
558 out_file.close()
559 else:
560 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000561
Greg Claytonca1500f2012-06-27 20:02:04 +0000562
Greg Clayton01f7c962012-01-20 03:15:45 +0000563def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000564 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000565 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000566 except:
567 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000568
569def SymbolicateCrashLog(crash_log, options):
570 if crash_log.error:
571 print crash_log.error
572 return
573 if options.verbose:
574 crash_log.dump()
575 if not crash_log.images:
576 print 'error: no images in crash log'
577 return
578
579 target = crash_log.create_target ()
580 if not target:
581 return
582 exe_module = target.GetModuleAtIndex(0)
583 images_to_load = list()
584 loaded_images = list()
585 if options.load_all_images:
586 # --load-all option was specified, load everything up
587 for image in crash_log.images:
588 images_to_load.append(image)
589 else:
590 # Only load the images found in stack frames for the crashed threads
591 for ident in crash_log.idents:
592 images = crash_log.find_images_with_identifier (ident)
593 if images:
594 for image in images:
595 images_to_load.append(image)
596 else:
597 print 'error: can\'t find image for identifier "%s"' % ident
598
599 for image in images_to_load:
600 if image in loaded_images:
601 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
602 else:
603 err = image.add_module (target)
604 if err:
605 print err
606 else:
607 #print 'loaded %s' % image
608 loaded_images.append(image)
609
610 for thread in crash_log.threads:
611 this_thread_crashed = thread.did_crash()
612 if options.crashed_only and this_thread_crashed == False:
613 continue
614 print "%s" % thread
615 #prev_frame_index = -1
616 for frame_idx, frame in enumerate(thread.frames):
617 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
618 if frame_idx == 0:
619 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc)
620 else:
621 # Any frame above frame zero and we have to subtract one to get the previous line entry
622 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1)
623
624 if symbolicated_frame_addresses:
625 symbolicated_frame_address_idx = 0
626 for symbolicated_frame_address in symbolicated_frame_addresses:
627 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton223e8082012-01-21 04:26:24 +0000628
Greg Claytona3368dd2012-05-31 21:21:08 +0000629 if symbolicated_frame_address_idx == 0:
630 if disassemble:
631 instructions = symbolicated_frame_address.get_instructions()
632 if instructions:
633 print
634 symbolication.disassemble_instructions (target,
635 instructions,
636 frame.pc,
637 options.disassemble_before,
638 options.disassemble_after, frame.index > 0)
639 print
640 symbolicated_frame_address_idx += 1
641 else:
642 print frame
643 print
644
645 if options.dump_image_list:
646 print "Binary Images:"
647 for image in crash_log.images:
648 print image
649
650def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000651 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000652 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
653 option_parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
654 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)
655 option_parser.add_option('--images', action='store_true', dest='dump_image_list', help='show image list', default=False)
656 option_parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
657 option_parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
658 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)
659 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)
660 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)
661 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)
662 if add_interactive_options:
663 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
664 return option_parser
665
666def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000667 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
668inlined stack frames back to the concrete functions, and disassemble the location of the crash
669for the first frame of the crashed thread.
670If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
671for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
672created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
673you to explore the program as if it were stopped at the locations described in the crash log and functions can
674be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000675 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000676 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000677 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000678 except:
679 return
680
Greg Clayton01f7c962012-01-20 03:15:45 +0000681 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000682 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000683 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000684 print 'args', args
685
Greg Clayton01f7c962012-01-20 03:15:45 +0000686 if options.debug_delay > 0:
687 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
688 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000689 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000690
Greg Claytone9ee5502012-01-20 19:25:32 +0000691 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000692 if options.interactive:
693 interactive_crashlogs(options, args)
694 else:
695 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000696 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000697 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000698if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000699 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000700 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000701 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000702 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000703elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000704 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000705 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
706 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000707