blob: c676b646a5736d66ffff6ad7084600906188c032 [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"""
Sean Callanan4d258c62013-01-12 02:11:49 +000086 parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]');
Greg Clayton01f7c962012-01-20 03:15:45 +000087 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
88 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Jason Molenda8b4bc2b2012-08-31 23:27:24 +000089 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t?(0x[0-9a-fA-F]+) +(.*)')
Greg Clayton8077a532012-01-20 03:32:35 +000090 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
91 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Clayton01f7c962012-01-20 03:15:45 +000092 empty_line_regex = re.compile('^$')
93
94 class Thread:
95 """Class that represents a thread in a darwin crash log"""
96 def __init__(self, index):
97 self.index = index
98 self.frames = list()
Greg Claytonaa7df342012-07-13 03:19:35 +000099 self.idents = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000100 self.registers = dict()
101 self.reason = None
102 self.queue = None
103
104 def dump(self, prefix):
105 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
106 if self.frames:
107 print "%s Frames:" % (prefix)
108 for frame in self.frames:
109 frame.dump(prefix + ' ')
110 if self.registers:
111 print "%s Registers:" % (prefix)
112 for reg in self.registers.keys():
113 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
114
Greg Claytonaa7df342012-07-13 03:19:35 +0000115 def add_ident(self, ident):
116 if not ident in self.idents:
117 self.idents.append(ident)
118
Greg Clayton01f7c962012-01-20 03:15:45 +0000119 def did_crash(self):
120 return self.reason != None
121
122 def __str__(self):
123 s = "Thread[%u]" % self.index
124 if self.reason:
125 s += ' %s' % self.reason
126 return s
127
128
129 class Frame:
130 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3d39f832012-04-03 21:35:43 +0000131 def __init__(self, index, pc, description):
Greg Clayton01f7c962012-01-20 03:15:45 +0000132 self.pc = pc
Greg Clayton3d39f832012-04-03 21:35:43 +0000133 self.description = description
134 self.index = index
Greg Clayton01f7c962012-01-20 03:15:45 +0000135
136 def __str__(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000137 if self.description:
138 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
139 else:
Johnny Chen4e468672012-05-03 18:46:28 +0000140 return "[%3u] 0x%16.16x" % (self.index, self.pc)
141
142 def dump(self, prefix):
143 print "%s%s" % (prefix, str(self))
Greg Clayton01f7c962012-01-20 03:15:45 +0000144
Greg Clayton9d010422012-05-04 20:44:14 +0000145 class DarwinImage(symbolication.Image):
Greg Clayton01f7c962012-01-20 03:15:45 +0000146 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000147 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000148 if not os.path.exists(dsymForUUIDBinary):
149 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
150
151 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000152
Greg Clayton3d39f832012-04-03 21:35:43 +0000153 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Clayton9d010422012-05-04 20:44:14 +0000154 symbolication.Image.__init__(self, path, uuid);
155 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3d39f832012-04-03 21:35:43 +0000156 self.identifier = identifier
Greg Clayton01f7c962012-01-20 03:15:45 +0000157 self.version = version
Greg Clayton01f7c962012-01-20 03:15:45 +0000158
Greg Clayton3d39f832012-04-03 21:35:43 +0000159 def locate_module_and_debug_symbols(self):
Greg Clayton1b62f592012-06-04 23:22:17 +0000160 # Don't load a module twice...
161 if self.resolved:
Greg Clayton4c983c82012-04-20 23:31:27 +0000162 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000163 # Mark this as resolved so we don't keep trying
164 self.resolved = True
Greg Claytond8056e22012-05-11 00:30:14 +0000165 uuid_str = self.get_normalized_uuid_string()
166 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000167 if os.path.exists(self.dsymForUUIDBinary):
Greg Claytond8056e22012-05-11 00:30:14 +0000168 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Clayton8077a532012-01-20 03:32:35 +0000169 s = commands.getoutput(dsym_for_uuid_command)
170 if s:
171 plist_root = plistlib.readPlistFromString (s)
172 if plist_root:
Greg Claytond8056e22012-05-11 00:30:14 +0000173 plist = plist_root[uuid_str]
Greg Claytoncd793122012-01-20 06:12:47 +0000174 if plist:
175 if 'DBGArchitecture' in plist:
176 self.arch = plist['DBGArchitecture']
177 if 'DBGDSYMPath' in plist:
Greg Clayton3d39f832012-04-03 21:35:43 +0000178 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Claytoncd793122012-01-20 06:12:47 +0000179 if 'DBGSymbolRichExecutable' in plist:
180 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
181 if not self.resolved_path and os.path.exists(self.path):
182 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Claytond8056e22012-05-11 00:30:14 +0000183 self_uuid = self.get_uuid()
Greg Claytoncd793122012-01-20 06:12:47 +0000184 for line in dwarfdump_cmd_output.splitlines():
185 match = self.dwarfdump_uuid_regex.search (line)
186 if match:
187 dwarf_uuid_str = match.group(1)
188 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
189 if self_uuid == dwarf_uuid:
190 self.resolved_path = self.path
191 self.arch = match.group(2)
192 break;
193 if not self.resolved_path:
Greg Clayton1b62f592012-06-04 23:22:17 +0000194 self.unavailable = True
195 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Clayton4c983c82012-04-20 23:31:27 +0000196 return False
Greg Claytoncd793122012-01-20 06:12:47 +0000197 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
198 print 'ok'
Greg Clayton1dae6f32012-04-25 18:40:20 +0000199 # if self.resolved_path:
200 # print ' exe = "%s"' % self.resolved_path
201 # if self.symfile:
202 # print ' dsym = "%s"' % self.symfile
Greg Clayton4c983c82012-04-20 23:31:27 +0000203 return True
Greg Clayton1b62f592012-06-04 23:22:17 +0000204 else:
205 self.unavailable = True
Greg Clayton4c983c82012-04-20 23:31:27 +0000206 return False
Greg Clayton01f7c962012-01-20 03:15:45 +0000207
Greg Clayton3d39f832012-04-03 21:35:43 +0000208
Greg Clayton01f7c962012-01-20 03:15:45 +0000209
210 def __init__(self, path):
211 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton9d010422012-05-04 20:44:14 +0000212 symbolication.Symbolicator.__init__(self);
Greg Clayton223e8082012-01-21 04:26:24 +0000213 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000214 self.info_lines = list()
215 self.system_profile = list()
216 self.threads = list()
Greg Clayton01f7c962012-01-20 03:15:45 +0000217 self.idents = list() # A list of the required identifiers for doing all stack backtraces
218 self.crashed_thread_idx = -1
219 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000220 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000221 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000222 try:
223 f = open(self.path)
224 except IOError:
225 self.error = 'error: cannot open "%s"' % self.path
226 return
227
Greg Clayton01f7c962012-01-20 03:15:45 +0000228 self.file_lines = f.read().splitlines()
229 parse_mode = PARSE_MODE_NORMAL
230 thread = None
231 for line in self.file_lines:
232 # print line
233 line_len = len(line)
234 if line_len == 0:
235 if thread:
236 if parse_mode == PARSE_MODE_THREAD:
237 if thread.index == self.crashed_thread_idx:
238 thread.reason = ''
239 if self.thread_exception:
240 thread.reason += self.thread_exception
241 if self.thread_exception_data:
242 thread.reason += " (%s)" % self.thread_exception_data
243 self.threads.append(thread)
244 thread = None
245 else:
246 # only append an extra empty line if the previous line
247 # in the info_lines wasn't empty
248 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
249 self.info_lines.append(line)
250 parse_mode = PARSE_MODE_NORMAL
251 # print 'PARSE_MODE_NORMAL'
252 elif parse_mode == PARSE_MODE_NORMAL:
253 if line.startswith ('Process:'):
Greg Clayton40819bf2012-08-13 18:48:03 +0000254 (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
Greg Clayton01f7c962012-01-20 03:15:45 +0000255 self.process_id = pid_with_brackets.strip('[]')
256 elif line.startswith ('Path:'):
257 self.process_path = line[5:].strip()
258 elif line.startswith ('Identifier:'):
259 self.process_identifier = line[11:].strip()
260 elif line.startswith ('Version:'):
Johnny Chen2bb4de32012-05-10 22:45:54 +0000261 version_string = line[8:].strip()
262 matched_pair = re.search("(.+)\((.+)\)", version_string)
263 if matched_pair:
264 self.process_version = matched_pair.group(1)
265 self.process_compatability_version = matched_pair.group(2)
266 else:
267 self.process = version_string
268 self.process_compatability_version = version_string
Sean Callanan4d258c62013-01-12 02:11:49 +0000269 elif self.parent_process_regex.search(line):
270 parent_process_match = self.parent_process_regex.search(line)
271 self.parent_process_name = parent_process_match.group(1)
272 self.parent_process_id = parent_process_match.group(2)
Greg Clayton01f7c962012-01-20 03:15:45 +0000273 elif line.startswith ('Exception Type:'):
274 self.thread_exception = line[15:].strip()
275 continue
276 elif line.startswith ('Exception Codes:'):
277 self.thread_exception_data = line[16:].strip()
278 continue
279 elif line.startswith ('Crashed Thread:'):
280 self.crashed_thread_idx = int(line[15:].strip().split()[0])
281 continue
282 elif line.startswith ('Report Version:'):
283 self.version = int(line[15:].strip())
284 continue
285 elif line.startswith ('System Profile:'):
286 parse_mode = PARSE_MODE_SYSTEM
287 continue
288 elif (line.startswith ('Interval Since Last Report:') or
289 line.startswith ('Crashes Since Last Report:') or
290 line.startswith ('Per-App Interval Since Last Report:') or
291 line.startswith ('Per-App Crashes Since Last Report:') or
292 line.startswith ('Sleep/Wake UUID:') or
293 line.startswith ('Anonymous UUID:')):
294 # ignore these
295 continue
296 elif line.startswith ('Thread'):
297 thread_state_match = self.thread_state_regex.search (line)
298 if thread_state_match:
299 thread_state_match = self.thread_regex.search (line)
300 thread_idx = int(thread_state_match.group(1))
301 parse_mode = PARSE_MODE_THREGS
302 thread = self.threads[thread_idx]
303 else:
304 thread_match = self.thread_regex.search (line)
305 if thread_match:
306 # print 'PARSE_MODE_THREAD'
307 parse_mode = PARSE_MODE_THREAD
308 thread_idx = int(thread_match.group(1))
309 thread = CrashLog.Thread(thread_idx)
310 continue
311 elif line.startswith ('Binary Images:'):
312 parse_mode = PARSE_MODE_IMAGES
313 continue
314 self.info_lines.append(line.strip())
315 elif parse_mode == PARSE_MODE_THREAD:
Greg Claytond8056e22012-05-11 00:30:14 +0000316 if line.startswith ('Thread'):
317 continue
Greg Clayton01f7c962012-01-20 03:15:45 +0000318 frame_match = self.frame_regex.search(line)
319 if frame_match:
320 ident = frame_match.group(2)
Greg Claytonaa7df342012-07-13 03:19:35 +0000321 thread.add_ident(ident)
Greg Clayton01f7c962012-01-20 03:15:45 +0000322 if not ident in self.idents:
323 self.idents.append(ident)
324 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
325 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000326 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000327 elif parse_mode == PARSE_MODE_IMAGES:
328 image_match = self.image_regex_uuid.search (line)
329 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000330 image = CrashLog.DarwinImage (int(image_match.group(1),0),
331 int(image_match.group(2),0),
332 image_match.group(3).strip(),
333 image_match.group(4).strip(),
Greg Claytond8056e22012-05-11 00:30:14 +0000334 uuid.UUID(image_match.group(5)),
Greg Clayton3d39f832012-04-03 21:35:43 +0000335 image_match.group(6))
Greg Clayton01f7c962012-01-20 03:15:45 +0000336 self.images.append (image)
337 else:
338 image_match = self.image_regex_no_uuid.search (line)
339 if image_match:
Greg Clayton3d39f832012-04-03 21:35:43 +0000340 image = CrashLog.DarwinImage (int(image_match.group(1),0),
341 int(image_match.group(2),0),
342 image_match.group(3).strip(),
343 image_match.group(4).strip(),
344 None,
345 image_match.group(5))
Greg Clayton01f7c962012-01-20 03:15:45 +0000346 self.images.append (image)
347 else:
348 print "error: image regex failed for: %s" % line
349
350 elif parse_mode == PARSE_MODE_THREGS:
351 stripped_line = line.strip()
Jason Molenda17d4a7f2012-08-28 23:46:12 +0000352 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
353 reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
Greg Clayton01f7c962012-01-20 03:15:45 +0000354 for reg_value in reg_values:
Greg Claytond8056e22012-05-11 00:30:14 +0000355 #print 'reg_value = "%s"' % reg_value
Greg Clayton01f7c962012-01-20 03:15:45 +0000356 (reg, value) = reg_value.split(': ')
Greg Claytond8056e22012-05-11 00:30:14 +0000357 #print 'reg = "%s"' % reg
358 #print 'value = "%s"' % value
Greg Clayton01f7c962012-01-20 03:15:45 +0000359 thread.registers[reg.strip()] = int(value, 0)
360 elif parse_mode == PARSE_MODE_SYSTEM:
361 self.system_profile.append(line)
362 f.close()
Greg Clayton1b62f592012-06-04 23:22:17 +0000363
Greg Clayton01f7c962012-01-20 03:15:45 +0000364 def dump(self):
365 print "Crash Log File: %s" % (self.path)
366 print "\nThreads:"
367 for thread in self.threads:
368 thread.dump(' ')
369 print "\nImages:"
370 for image in self.images:
371 image.dump(' ')
372
Greg Clayton3d39f832012-04-03 21:35:43 +0000373 def find_image_with_identifier(self, identifier):
Greg Clayton01f7c962012-01-20 03:15:45 +0000374 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000375 if image.identifier == identifier:
Greg Clayton01f7c962012-01-20 03:15:45 +0000376 return image
377 return None
378
Greg Claytone9ee5502012-01-20 19:25:32 +0000379 def create_target(self):
Greg Clayton3d39f832012-04-03 21:35:43 +0000380 #print 'crashlog.create_target()...'
Greg Clayton9d010422012-05-04 20:44:14 +0000381 target = symbolication.Symbolicator.create_target(self)
Greg Clayton3d39f832012-04-03 21:35:43 +0000382 if target:
383 return target
Greg Claytone9ee5502012-01-20 19:25:32 +0000384 # We weren't able to open the main executable as, but we can still symbolicate
Greg Clayton3d39f832012-04-03 21:35:43 +0000385 print 'crashlog.create_target()...2'
Greg Claytone9ee5502012-01-20 19:25:32 +0000386 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000387 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000388 image = self.find_image_with_identifier (ident)
389 if image:
Greg Clayton3d39f832012-04-03 21:35:43 +0000390 target = image.create_target ()
391 if target:
392 return target # success
393 print 'crashlog.create_target()...3'
Greg Claytone9ee5502012-01-20 19:25:32 +0000394 for image in self.images:
Greg Clayton3d39f832012-04-03 21:35:43 +0000395 target = image.create_target ()
396 if target:
397 return target # success
398 print 'crashlog.create_target()...4'
399 print 'error: unable to locate any executables from the crash log'
400 return None
Greg Clayton1b62f592012-06-04 23:22:17 +0000401
Greg Clayton01f7c962012-01-20 03:15:45 +0000402
403def usage():
404 print "Usage: lldb-symbolicate.py [-n name] executable-image"
405 sys.exit(0)
406
Greg Clayton9d010422012-05-04 20:44:14 +0000407class Interactive(cmd.Cmd):
408 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
409 image_option_parser = None
410
411 def __init__(self, crash_logs):
412 cmd.Cmd.__init__(self)
Greg Claytondcf56142012-07-03 21:40:18 +0000413 self.use_rawinput = False
Greg Clayton9d010422012-05-04 20:44:14 +0000414 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
415 self.crash_logs = crash_logs
416 self.prompt = '% '
417
418 def default(self, line):
419 '''Catch all for unknown command, which will exit the interpreter.'''
420 print "uknown command: %s" % line
421 return True
422
423 def do_q(self, line):
424 '''Quit command'''
425 return True
426
427 def do_quit(self, line):
428 '''Quit command'''
429 return True
430
Greg Claytona3368dd2012-05-31 21:21:08 +0000431 def do_symbolicate(self, line):
432 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
433 inlined stack frames back to the concrete functions, and disassemble the location of the crash
434 for the first frame of the crashed thread.'''
435 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
436 command_args = shlex.split(line)
437 try:
438 (options, args) = option_parser.parse_args(command_args)
439 except:
440 return
441
Greg Claytona838b422012-07-16 20:40:20 +0000442 if args:
443 # We have arguments, they must valid be crash log file indexes
444 for idx_str in args:
445 idx = int(idx_str)
446 if idx < len(self.crash_logs):
447 SymbolicateCrashLog (self.crash_logs[idx], options)
448 else:
449 print 'error: crash log index %u is out of range' % (idx)
450 else:
451 # No arguments, symbolicate all crash logs using the options provided
452 for idx in range(len(self.crash_logs)):
453 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytona3368dd2012-05-31 21:21:08 +0000454
Greg Clayton9d010422012-05-04 20:44:14 +0000455 def do_list(self, line=None):
456 '''Dump a list of all crash logs that are currently loaded.
457
458 USAGE: list'''
459 print '%u crash logs are loaded:' % len(self.crash_logs)
460 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
461 print '[%u] = %s' % (crash_log_idx, crash_log.path)
462
463 def do_image(self, line):
Greg Claytona838b422012-07-16 20:40:20 +0000464 '''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 +0000465 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Claytona838b422012-07-16 20:40:20 +0000466 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 +0000467 command_args = shlex.split(line)
468 if not self.image_option_parser:
469 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
470 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
471 try:
472 (options, args) = self.image_option_parser.parse_args(command_args)
473 except:
474 return
475
Greg Claytond8056e22012-05-11 00:30:14 +0000476 if args:
477 for image_path in args:
478 fullpath_search = image_path[0] == '/'
Greg Claytona838b422012-07-16 20:40:20 +0000479 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Claytond8056e22012-05-11 00:30:14 +0000480 matches_found = 0
Greg Clayton9d010422012-05-04 20:44:14 +0000481 for (image_idx, image) in enumerate(crash_log.images):
Greg Claytond8056e22012-05-11 00:30:14 +0000482 if fullpath_search:
483 if image.get_resolved_path() == image_path:
484 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000485 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000486 else:
487 image_basename = image.get_resolved_path_basename()
488 if image_basename == image_path:
489 matches_found += 1
Greg Claytona838b422012-07-16 20:40:20 +0000490 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000491 if matches_found == 0:
492 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton35f62f82012-05-16 20:49:19 +0000493 resolved_image_path = image.get_resolved_path()
494 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Claytona838b422012-07-16 20:40:20 +0000495 print '[%u] ' % (crash_log_idx), image
Greg Claytond8056e22012-05-11 00:30:14 +0000496 else:
497 for crash_log in self.crash_logs:
498 for (image_idx, image) in enumerate(crash_log.images):
499 print '[%u] %s' % (image_idx, image)
Greg Clayton9d010422012-05-04 20:44:14 +0000500 return False
501
502
503def interactive_crashlogs(options, args):
504 crash_log_files = list()
505 for arg in args:
506 for resolved_path in glob.glob(arg):
507 crash_log_files.append(resolved_path)
508
509 crash_logs = list();
510 for crash_log_file in crash_log_files:
511 #print 'crash_log_file = "%s"' % crash_log_file
512 crash_log = CrashLog(crash_log_file)
513 if crash_log.error:
514 print crash_log.error
515 continue
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000516 if options.debug:
Greg Clayton9d010422012-05-04 20:44:14 +0000517 crash_log.dump()
518 if not crash_log.images:
519 print 'error: no images in crash log "%s"' % (crash_log)
520 continue
521 else:
522 crash_logs.append(crash_log)
523
524 interpreter = Interactive(crash_logs)
525 # List all crash logs that were imported
526 interpreter.do_list()
527 interpreter.cmdloop()
528
Greg Claytonca1500f2012-06-27 20:02:04 +0000529
530def save_crashlog(debugger, command, result, dict):
531 usage = "usage: %prog [options] <output-path>"
532 description='''Export the state of current target into a crashlog file'''
533 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
534 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
535 try:
536 (options, args) = parser.parse_args(shlex.split(command))
537 except:
538 result.PutCString ("error: invalid options");
539 return
540 if len(args) != 1:
541 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
542 return
543 out_file = open(args[0], 'w')
544 if not out_file:
545 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
546 return
547 if lldb.target:
548 identifier = lldb.target.executable.basename
549 if lldb.process:
550 pid = lldb.process.id
551 if pid != lldb.LLDB_INVALID_PROCESS_ID:
552 out_file.write('Process: %s [%u]\n' % (identifier, pid))
553 out_file.write('Path: %s\n' % (lldb.target.executable.fullpath))
554 out_file.write('Identifier: %s\n' % (identifier))
555 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
556 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
557 out_file.write('Report Version: 9\n')
558 for thread_idx in range(lldb.process.num_threads):
559 thread = lldb.process.thread[thread_idx]
560 out_file.write('\nThread %u:\n' % (thread_idx))
561 for (frame_idx, frame) in enumerate(thread.frames):
562 frame_pc = frame.pc
563 frame_offset = 0
564 if frame.function:
565 block = frame.GetFrameBlock()
566 block_range = block.range[frame.addr]
567 if block_range:
568 block_start_addr = block_range[0]
569 frame_offset = frame_pc - block_start_addr.load_addr
570 else:
571 frame_offset = frame_pc - frame.function.addr.load_addr
572 elif frame.symbol:
573 frame_offset = frame_pc - frame.symbol.addr.load_addr
574 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
575 if frame_offset > 0:
576 out_file.write(' + %u' % (frame_offset))
577 line_entry = frame.line_entry
578 if line_entry:
579 if options.verbose:
580 # This will output the fullpath + line + column
581 out_file.write(' %s' % (line_entry))
582 else:
583 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
584 column = line_entry.column
585 if column:
586 out_file.write(':%u' % (column))
587 out_file.write('\n')
588
589 out_file.write('\nBinary Images:\n')
590 for module in lldb.target.modules:
591 text_segment = module.section['__TEXT']
592 if text_segment:
593 text_segment_load_addr = text_segment.GetLoadAddress(lldb.target)
594 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
595 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
596 identifier = module.file.basename
597 module_version = '???'
598 module_version_array = module.GetVersion()
599 if module_version_array:
600 module_version = '.'.join(map(str,module_version_array))
601 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))
602 out_file.close()
603 else:
604 result.PutCString ("error: invalid target");
Greg Clayton9d010422012-05-04 20:44:14 +0000605
Greg Claytonca1500f2012-06-27 20:02:04 +0000606
Greg Clayton01f7c962012-01-20 03:15:45 +0000607def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000608 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000609 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton223e8082012-01-21 04:26:24 +0000610 except:
611 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytona3368dd2012-05-31 21:21:08 +0000612
613def SymbolicateCrashLog(crash_log, options):
614 if crash_log.error:
615 print crash_log.error
616 return
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000617 if options.debug:
Greg Claytona3368dd2012-05-31 21:21:08 +0000618 crash_log.dump()
619 if not crash_log.images:
620 print 'error: no images in crash log'
621 return
622
Greg Clayton49ce8962012-08-29 21:13:06 +0000623 if options.dump_image_list:
624 print "Binary Images:"
625 for image in crash_log.images:
626 if options.verbose:
627 print image.debug_dump()
628 else:
629 print image
630
Greg Claytona3368dd2012-05-31 21:21:08 +0000631 target = crash_log.create_target ()
632 if not target:
633 return
634 exe_module = target.GetModuleAtIndex(0)
635 images_to_load = list()
636 loaded_images = list()
637 if options.load_all_images:
638 # --load-all option was specified, load everything up
639 for image in crash_log.images:
640 images_to_load.append(image)
641 else:
642 # Only load the images found in stack frames for the crashed threads
Greg Claytonaa7df342012-07-13 03:19:35 +0000643 if options.crashed_only:
644 for thread in crash_log.threads:
645 if thread.did_crash():
646 for ident in thread.idents:
647 images = crash_log.find_images_with_identifier (ident)
648 if images:
649 for image in images:
650 images_to_load.append(image)
651 else:
652 print 'error: can\'t find image for identifier "%s"' % ident
653 else:
654 for ident in crash_log.idents:
655 images = crash_log.find_images_with_identifier (ident)
656 if images:
657 for image in images:
658 images_to_load.append(image)
659 else:
660 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytona3368dd2012-05-31 21:21:08 +0000661
662 for image in images_to_load:
663 if image in loaded_images:
664 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
665 else:
666 err = image.add_module (target)
667 if err:
668 print err
669 else:
670 #print 'loaded %s' % image
671 loaded_images.append(image)
672
673 for thread in crash_log.threads:
674 this_thread_crashed = thread.did_crash()
675 if options.crashed_only and this_thread_crashed == False:
676 continue
677 print "%s" % thread
678 #prev_frame_index = -1
Greg Clayton007f73a2012-07-13 17:58:52 +0000679 display_frame_idx = -1
Greg Claytona3368dd2012-05-31 21:21:08 +0000680 for frame_idx, frame in enumerate(thread.frames):
681 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
682 if frame_idx == 0:
Greg Clayton4bb82ac2013-04-04 23:36:51 +0000683 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc & crash_log.addr_mask, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000684 else:
685 # Any frame above frame zero and we have to subtract one to get the previous line entry
Greg Clayton4bb82ac2013-04-04 23:36:51 +0000686 symbolicated_frame_addresses = crash_log.symbolicate ((frame.pc & crash_log.addr_mask) - 1, options.verbose)
Greg Claytona3368dd2012-05-31 21:21:08 +0000687
688 if symbolicated_frame_addresses:
689 symbolicated_frame_address_idx = 0
690 for symbolicated_frame_address in symbolicated_frame_addresses:
Greg Clayton007f73a2012-07-13 17:58:52 +0000691 display_frame_idx += 1
Greg Claytona3368dd2012-05-31 21:21:08 +0000692 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
Greg Clayton007f73a2012-07-13 17:58:52 +0000693 if (options.source_all or thread.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
694 source_context = options.source_context
Greg Claytonaa7df342012-07-13 03:19:35 +0000695 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
696 if line_entry.IsValid():
697 strm = lldb.SBStream()
698 if line_entry:
Greg Clayton007f73a2012-07-13 17:58:52 +0000699 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
Greg Claytonaa7df342012-07-13 03:19:35 +0000700 source_text = strm.GetData()
701 if source_text:
702 # Indent the source a bit
703 indent_str = ' '
704 join_str = '\n' + indent_str
705 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
Greg Claytona3368dd2012-05-31 21:21:08 +0000706 if symbolicated_frame_address_idx == 0:
707 if disassemble:
708 instructions = symbolicated_frame_address.get_instructions()
709 if instructions:
710 print
711 symbolication.disassemble_instructions (target,
712 instructions,
713 frame.pc,
714 options.disassemble_before,
715 options.disassemble_after, frame.index > 0)
716 print
717 symbolicated_frame_address_idx += 1
718 else:
719 print frame
720 print
721
Greg Claytona3368dd2012-05-31 21:21:08 +0000722def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Claytona3698c62012-01-21 00:37:19 +0000723 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytona3368dd2012-05-31 21:21:08 +0000724 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Clayton007f73a2012-07-13 17:58:52 +0000725 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
726 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
727 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)
728 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
729 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
730 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
731 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)
732 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)
733 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)
734 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)
735 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)
736 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
737 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 +0000738 if add_interactive_options:
739 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
740 return option_parser
741
742def SymbolicateCrashLogs(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000743 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
744inlined stack frames back to the concrete functions, and disassemble the location of the crash
745for the first frame of the crashed thread.
746If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
747for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
748created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
749you to explore the program as if it were stopped at the locations described in the crash log and functions can
750be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytona3368dd2012-05-31 21:21:08 +0000751 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton223e8082012-01-21 04:26:24 +0000752 try:
Greg Claytona3368dd2012-05-31 21:21:08 +0000753 (options, args) = option_parser.parse_args(command_args)
Greg Clayton223e8082012-01-21 04:26:24 +0000754 except:
755 return
756
Greg Clayton2b69a2b2012-06-28 18:10:14 +0000757 if options.debug:
Greg Clayton223e8082012-01-21 04:26:24 +0000758 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000759 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000760 print 'args', args
761
Greg Clayton01f7c962012-01-20 03:15:45 +0000762 if options.debug_delay > 0:
763 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
764 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000765 error = lldb.SBError()
Greg Clayton9d010422012-05-04 20:44:14 +0000766
Greg Claytone9ee5502012-01-20 19:25:32 +0000767 if args:
Greg Clayton9d010422012-05-04 20:44:14 +0000768 if options.interactive:
769 interactive_crashlogs(options, args)
770 else:
771 for crash_log_file in args:
Greg Clayton1b62f592012-06-04 23:22:17 +0000772 crash_log = CrashLog(crash_log_file)
Greg Claytona3368dd2012-05-31 21:21:08 +0000773 SymbolicateCrashLog (crash_log, options)
Greg Clayton01f7c962012-01-20 03:15:45 +0000774if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000775 # Create a new debugger instance
776 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytona3368dd2012-05-31 21:21:08 +0000777 SymbolicateCrashLogs (sys.argv[1:])
Johnny Chena889aee2012-05-03 22:31:30 +0000778elif getattr(lldb, 'debugger', None):
Greg Clayton6f2f0ab2012-04-25 01:49:50 +0000779 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonca1500f2012-06-27 20:02:04 +0000780 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
781 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000782