blob: 05b7ce30578269f61a3c40ad63995e48191b389b [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
Greg Claytona838b422012-07-16 20:40:20 +0000408 if args:
409 # We have arguments, they must valid be crash log file indexes
410 for idx_str in args:
411 idx = int(idx_str)
412 if idx < len(self.crash_logs):
413 SymbolicateCrashLog (self.crash_logs[idx], options)
414 else:
415 print 'error: crash log index %u is out of range' % (idx)
416 else:
417 # No arguments, symbolicate all crash logs using the options provided
418 for idx in range(len(self.crash_logs)):
419 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytona3368dd2012-05-31 21:21:08 +0000420
Greg Clayton9d010422012-05-04 20:44:14 +0000421 def do_list(self, line=None):
422 '''Dump a list of all crash logs that are currently loaded.
423
424 USAGE: list'''
425 print '%u crash logs are loaded:' % len(self.crash_logs)
426 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
427 print '[%u] = %s' % (crash_log_idx, crash_log.path)
428
429 def do_image(self, line):
Greg Claytona838b422012-07-16 20:40:20 +0000430 '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
Greg Clayton9d010422012-05-04 20:44:14 +0000431 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Claytona838b422012-07-16 20:40:20 +0000432 description='''Dump information about one or more images in all crash logs. The <PATH> can be a full path, image basename, or partial path. Searches are done in this order.'''
Greg Clayton9d010422012-05-04 20:44:14 +0000433 command_args = shlex.split(line)
434 if not self.image_option_parser:
435 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
436 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
437 try:
438 (options, args) = self.image_option_parser.parse_args(command_args)
439 except:
440 return
441
Greg Claytond8056e22012-05-11 00:30:14 +0000442 if args:
443 for image_path in args:
444 fullpath_search = image_path[0] == '/'
Greg Claytona838b422012-07-16 20:40:20 +0000445 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Claytond8056e22012-05-11 00:30:14 +0000446 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000447 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000448 if fullpath_search:
449 if image.get_resolved_path() == image_path:
450 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000451 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000452 else:
453 image_basename = image.get_resolved_path_basename()
454 if image_basename == image_path:
455 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000456 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000457 if matches_found == 0:
458 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000459 resolved_image_path = image.get_resolved_path()
460 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytona838b422012-07-16 20:40:20 +0000461 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000462 else:
463 for crash_log in self.crash_logs:
464 for (image_idx, image) in enumerate(crash_log.images):
465 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000466 return False
467
468
469def interactive_crashlogs(options, args):
470 crash_log_files = list()
471 for arg in args:
472 for resolved_path in glob.glob(arg):
473 crash_log_files.append(resolved_path)
474
475 crash_logs = list();
476 for crash_log_file in crash_log_files:
477 #print 'crash_log_file = "%s"' % crash_log_file
478 crash_log = CrashLog(crash_log_file)
479 if crash_log.error:
480 print crash_log.error
481 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000482 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000483 crash_log.dump()
484 if not crash_log.images:
485 print 'error: no images in crash log "%s"' % (crash_log)
486 continue
487 else:
488 crash_logs.append(crash_log)
489
490 interpreter = Interactive(crash_logs)
491 # List all crash logs that were imported
492 interpreter.do_list()
493 interpreter.cmdloop()
494
Greg Claytonca1500f2012-06-27 20:02:04 +0000495
496def save_crashlog(debugger, command, result, dict):
497 usage = "usage: %prog [options] <output-path>"
498 description='''Export the state of current target into a crashlog file'''
499 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
500 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
501 try:
502 (options, args) = parser.parse_args(shlex.split(command))
503 except:
504 result.PutCString ("error: invalid options");
505 return
506 if len(args) != 1:
507 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
508 return
509 out_file = open(args[0], 'w')
510 if not out_file:
511 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
512 return
513 if lldb.target:
514 identifier = lldb.target.executable.basename
515 if lldb.process:
516 pid = lldb.process.id
517 if pid != lldb.LLDB_INVALID_PROCESS_ID:
518 out_file.write('Process: %s [%u]\n' % (identifier, pid))
519 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
520 out_file.write('Identifier: %s\n' % (identifier))
521 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
522 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
523 out_file.write('Report Version: 9\n')
524 for thread_idx in range(lldb.process.num_threads):
525 thread = lldb.process.thread[thread_idx]
526 out_file.write('\nThread %u:\n' % (thread_idx))
527 for (frame_idx, frame) in enumerate(thread.frames):
528 frame_pc = frame.pc
529 frame_offset = 0
530 if frame.function:
531 block = frame.GetFrameBlock()
532 block_range = block.range[frame.addr]
533 if block_range:
534 block_start_addr = block_range[0]
535 frame_offset = frame_pc - block_start_addr.load_addr
536 else:
537 frame_offset = frame_pc - frame.function.addr.load_addr
538 elif frame.symbol:
539 frame_offset = frame_pc - frame.symbol.addr.load_addr
540 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
541 if frame_offset > 0:
542 out_file.write(' + %u' % (frame_offset))
543 line_entry = frame.line_entry
544 if line_entry:
545 if options.verbose:
546 # This will output the fullpath + line + column
547 out_file.write(' %s' % (line_entry))
548 else:
549 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
550 column = line_entry.column
551 if column:
552 out_file.write(':%u' % (column))
553 out_file.write('\n')
554
555 out_file.write('\nBinary Images:\n')
556 for module in lldb.target.modules:
557 text_segment = module.section['__TEXT']
558 if text_segment:
559 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
560 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
561 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
562 identifier = module.file.basename
563 module_version = '???'
564 module_version_array = module.GetVersion()
565 if module_version_array:
566 module_version = '.'.join(map(str,module_version_array))
567 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))
568 out_file.close()
569 else:
570 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000571
Greg Claytonca1500f2012-06-27 20:02:04 +0000572
Greg Clayton01f7c962012-01-20 03:15:45 +0000573def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000574 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000575 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000576 except:
577 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000578
579def SymbolicateCrashLog(crash_log, options):
580 if crash_log.error:
581 print crash_log.error
582 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000583 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000584 crash_log.dump()
585 if not crash_log.images:
586 print 'error: no images in crash log'
587 return
588
589 target = crash_log.create_target ()
590 if not target:
591 return
592 exe_module = target.GetModuleAtIndex(0)
593 images_to_load = list()
594 loaded_images = list()
595 if options.load_all_images:
596 # --load-all option was specified, load everything up
597 for image in crash_log.images:
598 images_to_load.append(image)
599 else:
600 # Only load the images found in stack frames for the crashed threads
Greg Claytonaa7df342012-07-13 03:19:35 +0000601 if options.crashed_only:
602 for thread in crash_log.threads:
603 if thread.did_crash():
604 for ident in thread.idents:
605 images = crash_log.find_images_with_identifier (ident)
606 if images:
607 for image in images:
608 images_to_load.append(image)
609 else:
610 print 'error: can\'t find image for identifier "%s"' % ident
611 else:
612 for ident in crash_log.idents:
613 images = crash_log.find_images_with_identifier (ident)
614 if images:
615 for image in images:
616 images_to_load.append(image)
617 else:
618 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3368dd2012-05-31 21:21:08 +0000619
620 for image in images_to_load:
621 if image in loaded_images:
622 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
623 else:
624 err = image.add_module (target)
625 if err:
626 print err
627 else:
628 #print 'loaded %s' % image
629 loaded_images.append(image)
630
631 for thread in crash_log.threads:
632 this_thread_crashed = thread.did_crash()
633 if options.crashed_only and this_thread_crashed == False:
634 continue
635 print "%s" % thread
636 #prev_frame_index = -1
Greg Clayton007f73a2012-07-13 17:58:52 +0000637 display_frame_idx = -1
Greg Claytona3368dd2012-05-31 21:21:08 +0000638 for frame_idx, frame in enumerate(thread.frames):
639 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
640 if frame_idx == 0:
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000641 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000642 else:
643 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000644 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000645
646 if symbolicated_frame_addresses:
647 symbolicated_frame_address_idx = 0
648 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Clayton007f73a2012-07-13 17:58:52 +0000649 display_frame_idx += 1
Greg Claytona3368dd2012-05-31 21:21:08 +0000650 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton007f73a2012-07-13 17:58:52 +0000651 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
652 source_context = options.source_context
Greg Claytonaa7df342012-07-13 03:19:35 +0000653 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
654 if line_entry.IsValid():
655 strm = lldb.SBStream()
656 if line_entry:
Greg Clayton007f73a2012-07-13 17:58:52 +0000657 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Claytonaa7df342012-07-13 03:19:35 +0000658 source_text = strm.GetData()
659 if source_text:
660 # Indent the source a bit
661 indent_str = ' '
662 join_str = '\n' + indent_str
663 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytona3368dd2012-05-31 21:21:08 +0000664 if symbolicated_frame_address_idx == 0:
665 if disassemble:
666 instructions = symbolicated_frame_address.get_instructions()
667 if instructions:
668 print
669 symbolication.disassemble_instructions (target,
670 instructions,
671 frame.pc,
672 options.disassemble_before,
673 options.disassemble_after, frame.index > 0)
674 print
675 symbolicated_frame_address_idx += 1
676 else:
677 print frame
678 print
679
680 if options.dump_image_list:
681 print "Binary Images:"
682 for image in crash_log.images:
683 print image
684
685def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000686 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000687 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton007f73a2012-07-13 17:58:52 +0000688 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
689 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
690 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)
691 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
692 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
693 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
694 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)
695 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)
696 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)
697 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)
698 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)
699 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
700 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 +0000701 if add_interactive_options:
702 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
703 return option_parser
704
705def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000706 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
707inlined stack frames back to the concrete functions, and disassemble the location of the crash
708for the first frame of the crashed thread.
709If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
710for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
711created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
712you to explore the program as if it were stopped at the locations described in the crash log and functions can
713be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000714 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000715 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000716 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000717 except:
718 return
719
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000720 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000721 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000722 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000723 print 'args', args
724
Greg Clayton01f7c962012-01-20 03:15:45 +0000725 if options.debug_delay > 0:
726 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
727 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000728 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000729
Greg Claytone9ee5502012-01-20 19:25:32 +0000730 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000731 if options.interactive:
732 interactive_crashlogs(options, args)
733 else:
734 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000735 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000736 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000737if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000738 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000739 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000740 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000741 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000742elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000743 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000744 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
745 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000746