blob: 752700f9b2aa0c9beda4e9753513e8e4451f6771 [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:'):
Greg Clayton40819bf2012-08-13 18:48:03 +0000222 (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
Greg Clayton01f7c962012-01-20 03:15:45 +0000223 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()
Jason Molenda17d4a7f2012-08-28 23:46:12 +0000319 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
320 reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000321 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000322 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000323 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000324 #print 'reg = "%s"' % reg
325 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000326 thread.registers[reg.strip()] = int(value, 0)
327 elif parse_mode == PARSE_MODE_SYSTEM:
328 self.system_profile.append(line)
329 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000330
Greg Clayton01f7c962012-01-20 03:15:45 +0000331 def dump(self):
332 print "Crash Log File: %s" % (self.path)
333 print "\nThreads:"
334 for thread in self.threads:
335 thread.dump(' ')
336 print "\nImages:"
337 for image in self.images:
338 image.dump(' ')
339
Greg Clayton3d39f832012-04-03 21:35:43 +0000340 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000341 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000342 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000343 return image
344 return None
345
Greg Claytone9ee5502012-01-20 19:25:32 +0000346 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000347 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000348 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000349 if target:
350 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000351 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000352 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000353 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000354 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000355 image = self.find_image_with_identifier (ident)
356 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000357 target = image.create_target ()
358 if target:
359 return target # success
360 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000361 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000362 target = image.create_target ()
363 if target:
364 return target # success
365 print 'crashlog.create_target()...4'
366 print 'error: unable to locate any executables from the crash log'
367 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000368
Greg Clayton01f7c962012-01-20 03:15:45 +0000369
370def usage():
371 print "Usage: lldb-symbolicate.py [-n name] executable-image"
372 sys.exit(0)
373
Greg Clayton9d010422012-05-04 20:44:14 +0000374class Interactive(cmd.Cmd):
375 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
376 image_option_parser = None
377
378 def __init__(self, crash_logs):
379 cmd.Cmd.__init__(self)
Greg Claytondcf56142012-07-03 21:40:18 +0000380 self.use_rawinput = False
Greg Clayton9d010422012-05-04 20:44:14 +0000381 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
382 self.crash_logs = crash_logs
383 self.prompt = '% '
384
385 def default(self, line):
386 '''Catch all for unknown command, which will exit the interpreter.'''
387 print "uknown command: %s" % line
388 return True
389
390 def do_q(self, line):
391 '''Quit command'''
392 return True
393
394 def do_quit(self, line):
395 '''Quit command'''
396 return True
397
Greg Claytona3368dd2012-05-31 21:21:08 +0000398 def do_symbolicate(self, line):
399 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
400 inlined stack frames back to the concrete functions, and disassemble the location of the crash
401 for the first frame of the crashed thread.'''
402 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
403 command_args = shlex.split(line)
404 try:
405 (options, args) = option_parser.parse_args(command_args)
406 except:
407 return
408
Greg Claytona838b422012-07-16 20:40:20 +0000409 if args:
410 # We have arguments, they must valid be crash log file indexes
411 for idx_str in args:
412 idx = int(idx_str)
413 if idx < len(self.crash_logs):
414 SymbolicateCrashLog (self.crash_logs[idx], options)
415 else:
416 print 'error: crash log index %u is out of range' % (idx)
417 else:
418 # No arguments, symbolicate all crash logs using the options provided
419 for idx in range(len(self.crash_logs)):
420 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytona3368dd2012-05-31 21:21:08 +0000421
Greg Clayton9d010422012-05-04 20:44:14 +0000422 def do_list(self, line=None):
423 '''Dump a list of all crash logs that are currently loaded.
424
425 USAGE: list'''
426 print '%u crash logs are loaded:' % len(self.crash_logs)
427 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
428 print '[%u] = %s' % (crash_log_idx, crash_log.path)
429
430 def do_image(self, line):
Greg Claytona838b422012-07-16 20:40:20 +0000431 '''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 +0000432 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Claytona838b422012-07-16 20:40:20 +0000433 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 +0000434 command_args = shlex.split(line)
435 if not self.image_option_parser:
436 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
437 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
438 try:
439 (options, args) = self.image_option_parser.parse_args(command_args)
440 except:
441 return
442
Greg Claytond8056e22012-05-11 00:30:14 +0000443 if args:
444 for image_path in args:
445 fullpath_search = image_path[0] == '/'
Greg Claytona838b422012-07-16 20:40:20 +0000446 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Claytond8056e22012-05-11 00:30:14 +0000447 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000448 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000449 if fullpath_search:
450 if image.get_resolved_path() == image_path:
451 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000452 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000453 else:
454 image_basename = image.get_resolved_path_basename()
455 if image_basename == image_path:
456 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000457 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000458 if matches_found == 0:
459 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000460 resolved_image_path = image.get_resolved_path()
461 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytona838b422012-07-16 20:40:20 +0000462 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000463 else:
464 for crash_log in self.crash_logs:
465 for (image_idx, image) in enumerate(crash_log.images):
466 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000467 return False
468
469
470def interactive_crashlogs(options, args):
471 crash_log_files = list()
472 for arg in args:
473 for resolved_path in glob.glob(arg):
474 crash_log_files.append(resolved_path)
475
476 crash_logs = list();
477 for crash_log_file in crash_log_files:
478 #print 'crash_log_file = "%s"' % crash_log_file
479 crash_log = CrashLog(crash_log_file)
480 if crash_log.error:
481 print crash_log.error
482 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000483 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000484 crash_log.dump()
485 if not crash_log.images:
486 print 'error: no images in crash log "%s"' % (crash_log)
487 continue
488 else:
489 crash_logs.append(crash_log)
490
491 interpreter = Interactive(crash_logs)
492 # List all crash logs that were imported
493 interpreter.do_list()
494 interpreter.cmdloop()
495
Greg Claytonca1500f2012-06-27 20:02:04 +0000496
497def save_crashlog(debugger, command, result, dict):
498 usage = "usage: %prog [options] <output-path>"
499 description='''Export the state of current target into a crashlog file'''
500 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
501 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
502 try:
503 (options, args) = parser.parse_args(shlex.split(command))
504 except:
505 result.PutCString ("error: invalid options");
506 return
507 if len(args) != 1:
508 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
509 return
510 out_file = open(args[0], 'w')
511 if not out_file:
512 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
513 return
514 if lldb.target:
515 identifier = lldb.target.executable.basename
516 if lldb.process:
517 pid = lldb.process.id
518 if pid != lldb.LLDB_INVALID_PROCESS_ID:
519 out_file.write('Process: %s [%u]\n' % (identifier, pid))
520 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
521 out_file.write('Identifier: %s\n' % (identifier))
522 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
523 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
524 out_file.write('Report Version: 9\n')
525 for thread_idx in range(lldb.process.num_threads):
526 thread = lldb.process.thread[thread_idx]
527 out_file.write('\nThread %u:\n' % (thread_idx))
528 for (frame_idx, frame) in enumerate(thread.frames):
529 frame_pc = frame.pc
530 frame_offset = 0
531 if frame.function:
532 block = frame.GetFrameBlock()
533 block_range = block.range[frame.addr]
534 if block_range:
535 block_start_addr = block_range[0]
536 frame_offset = frame_pc - block_start_addr.load_addr
537 else:
538 frame_offset = frame_pc - frame.function.addr.load_addr
539 elif frame.symbol:
540 frame_offset = frame_pc - frame.symbol.addr.load_addr
541 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
542 if frame_offset > 0:
543 out_file.write(' + %u' % (frame_offset))
544 line_entry = frame.line_entry
545 if line_entry:
546 if options.verbose:
547 # This will output the fullpath + line + column
548 out_file.write(' %s' % (line_entry))
549 else:
550 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
551 column = line_entry.column
552 if column:
553 out_file.write(':%u' % (column))
554 out_file.write('\n')
555
556 out_file.write('\nBinary Images:\n')
557 for module in lldb.target.modules:
558 text_segment = module.section['__TEXT']
559 if text_segment:
560 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
561 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
562 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
563 identifier = module.file.basename
564 module_version = '???'
565 module_version_array = module.GetVersion()
566 if module_version_array:
567 module_version = '.'.join(map(str,module_version_array))
568 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))
569 out_file.close()
570 else:
571 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000572
Greg Claytonca1500f2012-06-27 20:02:04 +0000573
Greg Clayton01f7c962012-01-20 03:15:45 +0000574def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000575 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000576 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000577 except:
578 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000579
580def SymbolicateCrashLog(crash_log, options):
581 if crash_log.error:
582 print crash_log.error
583 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000584 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000585 crash_log.dump()
586 if not crash_log.images:
587 print 'error: no images in crash log'
588 return
589
Greg Clayton49ce8962012-08-29 21:13:06 +0000590 if options.dump_image_list:
591 print "Binary Images:"
592 for image in crash_log.images:
593 if options.verbose:
594 print image.debug_dump()
595 else:
596 print image
597
Greg Claytona3368dd2012-05-31 21:21:08 +0000598 target = crash_log.create_target ()
599 if not target:
600 return
601 exe_module = target.GetModuleAtIndex(0)
602 images_to_load = list()
603 loaded_images = list()
604 if options.load_all_images:
605 # --load-all option was specified, load everything up
606 for image in crash_log.images:
607 images_to_load.append(image)
608 else:
609 # Only load the images found in stack frames for the crashed threads
Greg Claytonaa7df342012-07-13 03:19:35 +0000610 if options.crashed_only:
611 for thread in crash_log.threads:
612 if thread.did_crash():
613 for ident in thread.idents:
614 images = crash_log.find_images_with_identifier (ident)
615 if images:
616 for image in images:
617 images_to_load.append(image)
618 else:
619 print 'error: can\'t find image for identifier "%s"' % ident
620 else:
621 for ident in crash_log.idents:
622 images = crash_log.find_images_with_identifier (ident)
623 if images:
624 for image in images:
625 images_to_load.append(image)
626 else:
627 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3368dd2012-05-31 21:21:08 +0000628
629 for image in images_to_load:
630 if image in loaded_images:
631 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
632 else:
633 err = image.add_module (target)
634 if err:
635 print err
636 else:
637 #print 'loaded %s' % image
638 loaded_images.append(image)
639
640 for thread in crash_log.threads:
641 this_thread_crashed = thread.did_crash()
642 if options.crashed_only and this_thread_crashed == False:
643 continue
644 print "%s" % thread
645 #prev_frame_index = -1
Greg Clayton007f73a2012-07-13 17:58:52 +0000646 display_frame_idx = -1
Greg Claytona3368dd2012-05-31 21:21:08 +0000647 for frame_idx, frame in enumerate(thread.frames):
648 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
649 if frame_idx == 0:
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000650 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000651 else:
652 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000653 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000654
655 if symbolicated_frame_addresses:
656 symbolicated_frame_address_idx = 0
657 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Clayton007f73a2012-07-13 17:58:52 +0000658 display_frame_idx += 1
Greg Claytona3368dd2012-05-31 21:21:08 +0000659 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton007f73a2012-07-13 17:58:52 +0000660 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
661 source_context = options.source_context
Greg Claytonaa7df342012-07-13 03:19:35 +0000662 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
663 if line_entry.IsValid():
664 strm = lldb.SBStream()
665 if line_entry:
Greg Clayton007f73a2012-07-13 17:58:52 +0000666 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Claytonaa7df342012-07-13 03:19:35 +0000667 source_text = strm.GetData()
668 if source_text:
669 # Indent the source a bit
670 indent_str = ' '
671 join_str = '\n' + indent_str
672 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytona3368dd2012-05-31 21:21:08 +0000673 if symbolicated_frame_address_idx == 0:
674 if disassemble:
675 instructions = symbolicated_frame_address.get_instructions()
676 if instructions:
677 print
678 symbolication.disassemble_instructions (target,
679 instructions,
680 frame.pc,
681 options.disassemble_before,
682 options.disassemble_after, frame.index > 0)
683 print
684 symbolicated_frame_address_idx += 1
685 else:
686 print frame
687 print
688
Greg Claytona3368dd2012-05-31 21:21:08 +0000689def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000690 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000691 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton007f73a2012-07-13 17:58:52 +0000692 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
693 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
694 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)
695 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
696 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
697 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
698 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)
699 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)
700 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)
701 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)
702 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)
703 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
704 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 +0000705 if add_interactive_options:
706 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
707 return option_parser
708
709def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000710 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
711inlined stack frames back to the concrete functions, and disassemble the location of the crash
712for the first frame of the crashed thread.
713If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
714for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
715created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
716you to explore the program as if it were stopped at the locations described in the crash log and functions can
717be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000718 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000719 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000720 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000721 except:
722 return
723
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000724 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000725 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000726 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000727 print 'args', args
728
Greg Clayton01f7c962012-01-20 03:15:45 +0000729 if options.debug_delay > 0:
730 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
731 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000732 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000733
Greg Claytone9ee5502012-01-20 19:25:32 +0000734 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000735 if options.interactive:
736 interactive_crashlogs(options, args)
737 else:
738 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000739 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000740 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000741if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000742 # Create a new debugger instance
Greg Claytona3368dd2012-05-31 21:21:08 +0000743 print 'main'
Greg Claytone9ee5502012-01-20 19:25:32 +0000744 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000745 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000746elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000747 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000748 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
749 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000750