blob: 01e284d7d6c71bdd03998825deae85bf3e6eadbb [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
Greg Claytona3698c62012-01-21 00:37:19 +000029import commands
Greg Clayton9d010422012-05-04 20:44:14 +000030import cmd
Greg Claytonca1500f2012-06-27 20:02:04 +000031import datetime
Greg Clayton9d010422012-05-04 20:44:14 +000032import glob
Greg Clayton01f7c962012-01-20 03:15:45 +000033import optparse
34import os
Greg Claytonca1500f2012-06-27 20:02:04 +000035import platform
Greg Clayton01f7c962012-01-20 03:15:45 +000036import plistlib
Greg Clayton3d39f832012-04-03 21:35:43 +000037import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Clayton01f7c962012-01-20 03:15:45 +000038import re
Greg Clayton223e8082012-01-21 04:26:24 +000039import shlex
Greg Clayton9d010422012-05-04 20:44:14 +000040import string
Greg Clayton01f7c962012-01-20 03:15:45 +000041import sys
42import time
Greg Claytoncd793122012-01-20 06:12:47 +000043import uuid
Greg Clayton5ec0d022012-09-19 01:59:34 +000044
45try:
46 # Just try for LLDB in case PYTHONPATH is already correctly setup
47 import lldb
48except ImportError:
49 lldb_python_dirs = list()
50 # lldb is not in the PYTHONPATH, try some defaults for the current platform
51 platform_system = platform.system()
52 if platform_system == 'Darwin':
53 # On Darwin, try the currently selected Xcode directory
54 xcode_dir = commands.getoutput("xcode-select --print-path")
55 if xcode_dir:
56 lldb_python_dirs.append(os.path.realpath(xcode_dir + '/../SharedFrameworks/LLDB.framework/Resources/Python'))
57 lldb_python_dirs.append(xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
58 lldb_python_dirs.append('/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
59 success = False
60 for lldb_python_dir in lldb_python_dirs:
61 if os.path.exists(lldb_python_dir):
62 if not (sys.path.__contains__(lldb_python_dir)):
63 sys.path.append(lldb_python_dir)
64 try:
65 import lldb
66 except ImportError:
67 pass
68 else:
69 print 'imported lldb from: "%s"' % (lldb_python_dir)
70 success = True
71 break
72 if not success:
73 print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
74 sys.exit(1)
75
Greg Clayton9d010422012-05-04 20:44:14 +000076from lldb.utils import symbolication
Greg Clayton01f7c962012-01-20 03:15:45 +000077
78PARSE_MODE_NORMAL = 0
79PARSE_MODE_THREAD = 1
80PARSE_MODE_IMAGES = 2
81PARSE_MODE_THREGS = 3
82PARSE_MODE_SYSTEM = 4
83
Greg Clayton9d010422012-05-04 20:44:14 +000084class CrashLog(symbolication.Symbolicator):
Greg Clayton01f7c962012-01-20 03:15:45 +000085 """Class that does parses darwin crash logs"""
86 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
87 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Jason Molenda8b4bc2b2012-08-31 23:27:24 +000088 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t?(0x[0-9a-fA-F]+) +(.*)')
Greg Clayton8077a532012-01-20 03:32:35 +000089 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
90 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Clayton01f7c962012-01-20 03:15:45 +000091 empty_line_regex = re.compile('^$')
92
93 class Thread:
94 """Class that represents a thread in a darwin crash log"""
95 def __init__(self, index):
96 self.index = index
97 self.frames = list()
Greg Claytonaa7df342012-07-13 03:19:35 +000098 self.idents = list()
Greg Clayton01f7c962012-01-20 03:15:45 +000099 self.registers = dict()
100 self.reason = None
101 self.queue = None
102
103 def dump(self, prefix):
104 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
105 if self.frames:
106 print "%s Frames:" % (prefix)
107 for frame in self.frames:
108 frame.dump(prefix + ' ')
109 if self.registers:
110 print "%s Registers:" % (prefix)
111 for reg in self.registers.keys():
112 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
113
Greg Claytonaa7df342012-07-13 03:19:35 +0000114 def add_ident(self, ident):
115 if not ident in self.idents:
116 self.idents.append(ident)
117
Greg Clayton01f7c962012-01-20 03:15:45 +0000118 def did_crash(self):
119 return self.reason != None
120
121 def __str__(self):
122 s = "Thread[%u]" % self.index
123 if self.reason:
124 s += ' %s' % self.reason
125 return s
126
127
128 class Frame:
129 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3d39f832012-04-03 21:35:43 +0000130 def __init__(self, index, pc, description):
Greg Clayton01f7c962012-01-20 03:15:45 +0000131 self.pc = pc
Greg Clayton3d39f832012-04-03 21:35:43 +0000132 self.description = description
133 self.index = index
Greg Clayton01f7c962012-01-20 03:15:45 +0000134
135 def __str__(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000136 if self.description:
137 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
138 else:
Johnny Chen4e468672012-05-03 18:46:28 +0000139 return "[%3u] 0x%16.16x" % (self.index, self.pc)
140
141 def dump(self, prefix):
142 print "%s%s" % (prefix, str(self))
Greg Clayton01f7c962012-01-20 03:15:45 +0000143
Greg Clayton9d010422012-05-04 20:44:14 +0000144 class DarwinImage(symbolication.Image):
Greg Clayton01f7c962012-01-20 03:15:45 +0000145 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000146 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000147 if not os.path.exists(dsymForUUIDBinary):
148 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
149
150 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000151
Greg Clayton3d39f832012-04-03 21:35:43 +0000152 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Clayton9d010422012-05-04 20:44:14 +0000153 symbolication.Image.__init__(self, path, uuid);
154 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3d39f832012-04-03 21:35:43 +0000155 self.identifier = identifier
Greg Clayton01f7c962012-01-20 03:15:45 +0000156 self.version = version
Greg Clayton01f7c962012-01-20 03:15:45 +0000157
Greg Clayton3d39f832012-04-03 21:35:43 +0000158 def locate_module_and_debug_symbols(self):
Greg Clayton1b62f592012-06-04 23:22:17 +0000159 # Don't load a module twice...
160 if self.resolved:
Greg Clayton4c983c82012-04-20 23:31:27 +0000161 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000162 # Mark this as resolved so we don't keep trying
163 self.resolved = True
Greg Claytond8056e22012-05-11 00:30:14 +0000164 uuid_str = self.get_normalized_uuid_string()
165 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000166 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000167 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000168 s = commands.getoutput(dsym_for_uuid_command)
169 if s:
170 plist_root = plistlib.readPlistFromString (s)
171 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000172 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000173 if plist:
174 if 'DBGArchitecture' in plist:
175 self.arch = plist['DBGArchitecture']
176 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000177 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000178 if 'DBGSymbolRichExecutable' in plist:
179 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
180 if not self.resolved_path and os.path.exists(self.path):
181 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000182 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000183 for line in dwarfdump_cmd_output.splitlines():
184 match = self.dwarfdump_uuid_regex.search (line)
185 if match:
186 dwarf_uuid_str = match.group(1)
187 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
188 if self_uuid == dwarf_uuid:
189 self.resolved_path = self.path
190 self.arch = match.group(2)
191 break;
192 if not self.resolved_path:
Greg Clayton1b62f592012-06-04 23:22:17 +0000193 self.unavailable = True
194 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Clayton4c983c82012-04-20 23:31:27 +0000195 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000196 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
197 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000198 # if self.resolved_path:
199 # print ' exe = "%s"' % self.resolved_path
200 # if self.symfile:
201 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000202 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000203 else:
204 self.unavailable = True
Greg Clayton4c983c82012-04-20 23:31:27 +0000205 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000206
Greg Clayton3d39f832012-04-03 21:35:43 +0000207
Greg Clayton01f7c962012-01-20 03:15:45 +0000208
209 def __init__(self, path):
210 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000211 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000212 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000213 self.info_lines = list()
214 self.system_profile = list()
215 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000216 self.idents = list() # A list of the required identifiers for doing all stack backtraces
217 self.crashed_thread_idx = -1
218 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000219 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000220 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000221 try:
222 f = open(self.path)
223 except IOError:
224 self.error = 'error: cannot open "%s"' % self.path
225 return
226
Greg Clayton01f7c962012-01-20 03:15:45 +0000227 self.file_lines = f.read().splitlines()
228 parse_mode = PARSE_MODE_NORMAL
229 thread = None
230 for line in self.file_lines:
231 # print line
232 line_len = len(line)
233 if line_len == 0:
234 if thread:
235 if parse_mode == PARSE_MODE_THREAD:
236 if thread.index == self.crashed_thread_idx:
237 thread.reason = ''
238 if self.thread_exception:
239 thread.reason += self.thread_exception
240 if self.thread_exception_data:
241 thread.reason += " (%s)" % self.thread_exception_data
242 self.threads.append(thread)
243 thread = None
244 else:
245 # only append an extra empty line if the previous line
246 # in the info_lines wasn't empty
247 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
248 self.info_lines.append(line)
249 parse_mode = PARSE_MODE_NORMAL
250 # print 'PARSE_MODE_NORMAL'
251 elif parse_mode == PARSE_MODE_NORMAL:
252 if line.startswith ('Process:'):
Greg Clayton40819bf2012-08-13 18:48:03 +0000253 (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
Greg Clayton01f7c962012-01-20 03:15:45 +0000254 self.process_id = pid_with_brackets.strip('[]')
255 elif line.startswith ('Path:'):
256 self.process_path = line[5:].strip()
257 elif line.startswith ('Identifier:'):
258 self.process_identifier = line[11:].strip()
259 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000260 version_string = line[8:].strip()
261 matched_pair = re.search("(.+)\((.+)\)", version_string)
262 if matched_pair:
263 self.process_version = matched_pair.group(1)
264 self.process_compatability_version = matched_pair.group(2)
265 else:
266 self.process = version_string
267 self.process_compatability_version = version_string
Greg Clayton01f7c962012-01-20 03:15:45 +0000268 elif line.startswith ('Parent Process:'):
269 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
270 self.parent_process_id = pid_with_brackets.strip('[]')
271 elif line.startswith ('Exception Type:'):
272 self.thread_exception = line[15:].strip()
273 continue
274 elif line.startswith ('Exception Codes:'):
275 self.thread_exception_data = line[16:].strip()
276 continue
277 elif line.startswith ('Crashed Thread:'):
278 self.crashed_thread_idx = int(line[15:].strip().split()[0])
279 continue
280 elif line.startswith ('Report Version:'):
281 self.version = int(line[15:].strip())
282 continue
283 elif line.startswith ('System Profile:'):
284 parse_mode = PARSE_MODE_SYSTEM
285 continue
286 elif (line.startswith ('Interval Since Last Report:') or
287 line.startswith ('Crashes Since Last Report:') or
288 line.startswith ('Per-App Interval Since Last Report:') or
289 line.startswith ('Per-App Crashes Since Last Report:') or
290 line.startswith ('Sleep/Wake UUID:') or
291 line.startswith ('Anonymous UUID:')):
292 # ignore these
293 continue
294 elif line.startswith ('Thread'):
295 thread_state_match = self.thread_state_regex.search (line)
296 if thread_state_match:
297 thread_state_match = self.thread_regex.search (line)
298 thread_idx = int(thread_state_match.group(1))
299 parse_mode = PARSE_MODE_THREGS
300 thread = self.threads[thread_idx]
301 else:
302 thread_match = self.thread_regex.search (line)
303 if thread_match:
304 # print 'PARSE_MODE_THREAD'
305 parse_mode = PARSE_MODE_THREAD
306 thread_idx = int(thread_match.group(1))
307 thread = CrashLog.Thread(thread_idx)
308 continue
309 elif line.startswith ('Binary Images:'):
310 parse_mode = PARSE_MODE_IMAGES
311 continue
312 self.info_lines.append(line.strip())
313 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000314 if line.startswith ('Thread'):
315 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000316 frame_match = self.frame_regex.search(line)
317 if frame_match:
318 ident = frame_match.group(2)
Greg Claytonaa7df342012-07-13 03:19:35 +0000319 thread.add_ident(ident)
Greg Clayton01f7c962012-01-20 03:15:45 +0000320 if not ident in self.idents:
321 self.idents.append(ident)
322 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
323 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000324 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000325 elif parse_mode == PARSE_MODE_IMAGES:
326 image_match = self.image_regex_uuid.search (line)
327 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000328 image = CrashLog.DarwinImage (int(image_match.group(1),0),
329 int(image_match.group(2),0),
330 image_match.group(3).strip(),
331 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000332 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000333 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000334 self.images.append (image)
335 else:
336 image_match = self.image_regex_no_uuid.search (line)
337 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000338 image = CrashLog.DarwinImage (int(image_match.group(1),0),
339 int(image_match.group(2),0),
340 image_match.group(3).strip(),
341 image_match.group(4).strip(),
342 None,
343 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000344 self.images.append (image)
345 else:
346 print "error: image regex failed for: %s" % line
347
348 elif parse_mode == PARSE_MODE_THREGS:
349 stripped_line = line.strip()
Jason Molenda17d4a7f2012-08-28 23:46:12 +0000350 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
351 reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000352 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000353 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000354 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000355 #print 'reg = "%s"' % reg
356 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000357 thread.registers[reg.strip()] = int(value, 0)
358 elif parse_mode == PARSE_MODE_SYSTEM:
359 self.system_profile.append(line)
360 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000361
Greg Clayton01f7c962012-01-20 03:15:45 +0000362 def dump(self):
363 print "Crash Log File: %s" % (self.path)
364 print "\nThreads:"
365 for thread in self.threads:
366 thread.dump(' ')
367 print "\nImages:"
368 for image in self.images:
369 image.dump(' ')
370
Greg Clayton3d39f832012-04-03 21:35:43 +0000371 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000372 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000373 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000374 return image
375 return None
376
Greg Claytone9ee5502012-01-20 19:25:32 +0000377 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000378 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000379 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000380 if target:
381 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000382 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000383 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000384 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000385 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000386 image = self.find_image_with_identifier (ident)
387 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000388 target = image.create_target ()
389 if target:
390 return target # success
391 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000392 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000393 target = image.create_target ()
394 if target:
395 return target # success
396 print 'crashlog.create_target()...4'
397 print 'error: unable to locate any executables from the crash log'
398 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000399
Greg Clayton01f7c962012-01-20 03:15:45 +0000400
401def usage():
402 print "Usage: lldb-symbolicate.py [-n name] executable-image"
403 sys.exit(0)
404
Greg Clayton9d010422012-05-04 20:44:14 +0000405class Interactive(cmd.Cmd):
406 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
407 image_option_parser = None
408
409 def __init__(self, crash_logs):
410 cmd.Cmd.__init__(self)
Greg Claytondcf56142012-07-03 21:40:18 +0000411 self.use_rawinput = False
Greg Clayton9d010422012-05-04 20:44:14 +0000412 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
413 self.crash_logs = crash_logs
414 self.prompt = '% '
415
416 def default(self, line):
417 '''Catch all for unknown command, which will exit the interpreter.'''
418 print "uknown command: %s" % line
419 return True
420
421 def do_q(self, line):
422 '''Quit command'''
423 return True
424
425 def do_quit(self, line):
426 '''Quit command'''
427 return True
428
Greg Claytona3368dd2012-05-31 21:21:08 +0000429 def do_symbolicate(self, line):
430 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
431 inlined stack frames back to the concrete functions, and disassemble the location of the crash
432 for the first frame of the crashed thread.'''
433 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
434 command_args = shlex.split(line)
435 try:
436 (options, args) = option_parser.parse_args(command_args)
437 except:
438 return
439
Greg Claytona838b422012-07-16 20:40:20 +0000440 if args:
441 # We have arguments, they must valid be crash log file indexes
442 for idx_str in args:
443 idx = int(idx_str)
444 if idx < len(self.crash_logs):
445 SymbolicateCrashLog (self.crash_logs[idx], options)
446 else:
447 print 'error: crash log index %u is out of range' % (idx)
448 else:
449 # No arguments, symbolicate all crash logs using the options provided
450 for idx in range(len(self.crash_logs)):
451 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytona3368dd2012-05-31 21:21:08 +0000452
Greg Clayton9d010422012-05-04 20:44:14 +0000453 def do_list(self, line=None):
454 '''Dump a list of all crash logs that are currently loaded.
455
456 USAGE: list'''
457 print '%u crash logs are loaded:' % len(self.crash_logs)
458 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
459 print '[%u] = %s' % (crash_log_idx, crash_log.path)
460
461 def do_image(self, line):
Greg Claytona838b422012-07-16 20:40:20 +0000462 '''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 +0000463 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Claytona838b422012-07-16 20:40:20 +0000464 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 +0000465 command_args = shlex.split(line)
466 if not self.image_option_parser:
467 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
468 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
469 try:
470 (options, args) = self.image_option_parser.parse_args(command_args)
471 except:
472 return
473
Greg Claytond8056e22012-05-11 00:30:14 +0000474 if args:
475 for image_path in args:
476 fullpath_search = image_path[0] == '/'
Greg Claytona838b422012-07-16 20:40:20 +0000477 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Claytond8056e22012-05-11 00:30:14 +0000478 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000479 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000480 if fullpath_search:
481 if image.get_resolved_path() == image_path:
482 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000483 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000484 else:
485 image_basename = image.get_resolved_path_basename()
486 if image_basename == image_path:
487 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000488 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000489 if matches_found == 0:
490 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000491 resolved_image_path = image.get_resolved_path()
492 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytona838b422012-07-16 20:40:20 +0000493 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000494 else:
495 for crash_log in self.crash_logs:
496 for (image_idx, image) in enumerate(crash_log.images):
497 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000498 return False
499
500
501def interactive_crashlogs(options, args):
502 crash_log_files = list()
503 for arg in args:
504 for resolved_path in glob.glob(arg):
505 crash_log_files.append(resolved_path)
506
507 crash_logs = list();
508 for crash_log_file in crash_log_files:
509 #print 'crash_log_file = "%s"' % crash_log_file
510 crash_log = CrashLog(crash_log_file)
511 if crash_log.error:
512 print crash_log.error
513 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000514 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000515 crash_log.dump()
516 if not crash_log.images:
517 print 'error: no images in crash log "%s"' % (crash_log)
518 continue
519 else:
520 crash_logs.append(crash_log)
521
522 interpreter = Interactive(crash_logs)
523 # List all crash logs that were imported
524 interpreter.do_list()
525 interpreter.cmdloop()
526
Greg Claytonca1500f2012-06-27 20:02:04 +0000527
528def save_crashlog(debugger, command, result, dict):
529 usage = "usage: %prog [options] <output-path>"
530 description='''Export the state of current target into a crashlog file'''
531 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
532 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
533 try:
534 (options, args) = parser.parse_args(shlex.split(command))
535 except:
536 result.PutCString ("error: invalid options");
537 return
538 if len(args) != 1:
539 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
540 return
541 out_file = open(args[0], 'w')
542 if not out_file:
543 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
544 return
545 if lldb.target:
546 identifier = lldb.target.executable.basename
547 if lldb.process:
548 pid = lldb.process.id
549 if pid != lldb.LLDB_INVALID_PROCESS_ID:
550 out_file.write('Process: %s [%u]\n' % (identifier, pid))
551 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
552 out_file.write('Identifier: %s\n' % (identifier))
553 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
554 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
555 out_file.write('Report Version: 9\n')
556 for thread_idx in range(lldb.process.num_threads):
557 thread = lldb.process.thread[thread_idx]
558 out_file.write('\nThread %u:\n' % (thread_idx))
559 for (frame_idx, frame) in enumerate(thread.frames):
560 frame_pc = frame.pc
561 frame_offset = 0
562 if frame.function:
563 block = frame.GetFrameBlock()
564 block_range = block.range[frame.addr]
565 if block_range:
566 block_start_addr = block_range[0]
567 frame_offset = frame_pc - block_start_addr.load_addr
568 else:
569 frame_offset = frame_pc - frame.function.addr.load_addr
570 elif frame.symbol:
571 frame_offset = frame_pc - frame.symbol.addr.load_addr
572 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
573 if frame_offset > 0:
574 out_file.write(' + %u' % (frame_offset))
575 line_entry = frame.line_entry
576 if line_entry:
577 if options.verbose:
578 # This will output the fullpath + line + column
579 out_file.write(' %s' % (line_entry))
580 else:
581 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
582 column = line_entry.column
583 if column:
584 out_file.write(':%u' % (column))
585 out_file.write('\n')
586
587 out_file.write('\nBinary Images:\n')
588 for module in lldb.target.modules:
589 text_segment = module.section['__TEXT']
590 if text_segment:
591 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
592 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
593 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
594 identifier = module.file.basename
595 module_version = '???'
596 module_version_array = module.GetVersion()
597 if module_version_array:
598 module_version = '.'.join(map(str,module_version_array))
599 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))
600 out_file.close()
601 else:
602 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000603
Greg Claytonca1500f2012-06-27 20:02:04 +0000604
Greg Clayton01f7c962012-01-20 03:15:45 +0000605def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000606 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000607 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000608 except:
609 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000610
611def SymbolicateCrashLog(crash_log, options):
612 if crash_log.error:
613 print crash_log.error
614 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000615 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000616 crash_log.dump()
617 if not crash_log.images:
618 print 'error: no images in crash log'
619 return
620
Greg Clayton49ce8962012-08-29 21:13:06 +0000621 if options.dump_image_list:
622 print "Binary Images:"
623 for image in crash_log.images:
624 if options.verbose:
625 print image.debug_dump()
626 else:
627 print image
628
Greg Claytona3368dd2012-05-31 21:21:08 +0000629 target = crash_log.create_target ()
630 if not target:
631 return
632 exe_module = target.GetModuleAtIndex(0)
633 images_to_load = list()
634 loaded_images = list()
635 if options.load_all_images:
636 # --load-all option was specified, load everything up
637 for image in crash_log.images:
638 images_to_load.append(image)
639 else:
640 # Only load the images found in stack frames for the crashed threads
Greg Claytonaa7df342012-07-13 03:19:35 +0000641 if options.crashed_only:
642 for thread in crash_log.threads:
643 if thread.did_crash():
644 for ident in thread.idents:
645 images = crash_log.find_images_with_identifier (ident)
646 if images:
647 for image in images:
648 images_to_load.append(image)
649 else:
650 print 'error: can\'t find image for identifier "%s"' % ident
651 else:
652 for ident in crash_log.idents:
653 images = crash_log.find_images_with_identifier (ident)
654 if images:
655 for image in images:
656 images_to_load.append(image)
657 else:
658 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3368dd2012-05-31 21:21:08 +0000659
660 for image in images_to_load:
661 if image in loaded_images:
662 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
663 else:
664 err = image.add_module (target)
665 if err:
666 print err
667 else:
668 #print 'loaded %s' % image
669 loaded_images.append(image)
670
671 for thread in crash_log.threads:
672 this_thread_crashed = thread.did_crash()
673 if options.crashed_only and this_thread_crashed == False:
674 continue
675 print "%s" % thread
676 #prev_frame_index = -1
Greg Clayton007f73a2012-07-13 17:58:52 +0000677 display_frame_idx = -1
Greg Claytona3368dd2012-05-31 21:21:08 +0000678 for frame_idx, frame in enumerate(thread.frames):
679 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
680 if frame_idx == 0:
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000681 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000682 else:
683 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000684 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000685
686 if symbolicated_frame_addresses:
687 symbolicated_frame_address_idx = 0
688 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Clayton007f73a2012-07-13 17:58:52 +0000689 display_frame_idx += 1
Greg Claytona3368dd2012-05-31 21:21:08 +0000690 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton007f73a2012-07-13 17:58:52 +0000691 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
692 source_context = options.source_context
Greg Claytonaa7df342012-07-13 03:19:35 +0000693 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
694 if line_entry.IsValid():
695 strm = lldb.SBStream()
696 if line_entry:
Greg Clayton007f73a2012-07-13 17:58:52 +0000697 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Claytonaa7df342012-07-13 03:19:35 +0000698 source_text = strm.GetData()
699 if source_text:
700 # Indent the source a bit
701 indent_str = ' '
702 join_str = '\n' + indent_str
703 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytona3368dd2012-05-31 21:21:08 +0000704 if symbolicated_frame_address_idx == 0:
705 if disassemble:
706 instructions = symbolicated_frame_address.get_instructions()
707 if instructions:
708 print
709 symbolication.disassemble_instructions (target,
710 instructions,
711 frame.pc,
712 options.disassemble_before,
713 options.disassemble_after, frame.index > 0)
714 print
715 symbolicated_frame_address_idx += 1
716 else:
717 print frame
718 print
719
Greg Claytona3368dd2012-05-31 21:21:08 +0000720def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000721 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000722 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton007f73a2012-07-13 17:58:52 +0000723 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
724 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
725 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)
726 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
727 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
728 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
729 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)
730 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)
731 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)
732 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)
733 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)
734 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
735 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 +0000736 if add_interactive_options:
737 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
738 return option_parser
739
740def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000741 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
742inlined stack frames back to the concrete functions, and disassemble the location of the crash
743for the first frame of the crashed thread.
744If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
745for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
746created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
747you to explore the program as if it were stopped at the locations described in the crash log and functions can
748be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000749 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000750 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000751 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000752 except:
753 return
754
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000755 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000756 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000757 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000758 print 'args', args
759
Greg Clayton01f7c962012-01-20 03:15:45 +0000760 if options.debug_delay > 0:
761 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
762 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000763 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000764
Greg Claytone9ee5502012-01-20 19:25:32 +0000765 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000766 if options.interactive:
767 interactive_crashlogs(options, args)
768 else:
769 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000770 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000771 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000772if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000773 # Create a new debugger instance
774 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000775 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000776elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000777 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000778 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
779 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000780