blob: d5531ded0cf4d2a26bba9e2c4c6cc019e6b2b9fd [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 Clayton01f7c962012-01-20 03:15:45 +000031import optparse
32import os
33import plistlib
Greg Claytona3698c62012-01-21 00:37:19 +000034#import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Clayton01f7c962012-01-20 03:15:45 +000035import re
Greg Clayton223e8082012-01-21 04:26:24 +000036import shlex
Greg Clayton01f7c962012-01-20 03:15:45 +000037import sys
38import time
Greg Claytoncd793122012-01-20 06:12:47 +000039import uuid
40
Greg Clayton01f7c962012-01-20 03:15:45 +000041
42PARSE_MODE_NORMAL = 0
43PARSE_MODE_THREAD = 1
44PARSE_MODE_IMAGES = 2
45PARSE_MODE_THREGS = 3
46PARSE_MODE_SYSTEM = 4
47
48class CrashLog:
49 """Class that does parses darwin crash logs"""
50 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
51 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Greg Clayton8077a532012-01-20 03:32:35 +000052 frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t(0x[0-9a-fA-F]+) +(.*)')
53 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
54 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Clayton01f7c962012-01-20 03:15:45 +000055 empty_line_regex = re.compile('^$')
56
57 class Thread:
58 """Class that represents a thread in a darwin crash log"""
59 def __init__(self, index):
60 self.index = index
61 self.frames = list()
62 self.registers = dict()
63 self.reason = None
64 self.queue = None
65
66 def dump(self, prefix):
67 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
68 if self.frames:
69 print "%s Frames:" % (prefix)
70 for frame in self.frames:
71 frame.dump(prefix + ' ')
72 if self.registers:
73 print "%s Registers:" % (prefix)
74 for reg in self.registers.keys():
75 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
76
77 def did_crash(self):
78 return self.reason != None
79
80 def __str__(self):
81 s = "Thread[%u]" % self.index
82 if self.reason:
83 s += ' %s' % self.reason
84 return s
85
86
87 class Frame:
88 """Class that represents a stack frame in a thread in a darwin crash log"""
89 def __init__(self, index, pc, details):
90 self.index = index
91 self.pc = pc
92 self.sym_ctx = None
93 self.details = details
94
95 def __str__(self):
96 return "[%2u] %#16.16x %s" % (self.index, self.pc, self.details)
97
98 def dump(self, prefix):
99 print "%s%s" % (prefix, self)
100
101
102 class Image:
103 """Class that represents a binary images in a darwin crash log"""
Greg Clayton8077a532012-01-20 03:32:35 +0000104 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Claytoncd793122012-01-20 06:12:47 +0000105 if not os.path.exists(dsymForUUIDBinary):
106 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
107
108 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Clayton8077a532012-01-20 03:32:35 +0000109
Greg Clayton01f7c962012-01-20 03:15:45 +0000110 def __init__(self, text_addr_lo, text_addr_hi, ident, version, uuid, path):
111 self.text_addr_lo = text_addr_lo
112 self.text_addr_hi = text_addr_hi
113 self.ident = ident
114 self.version = version
Greg Claytoncd793122012-01-20 06:12:47 +0000115 self.arch = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000116 self.uuid = uuid
117 self.path = path
Greg Claytoncd793122012-01-20 06:12:47 +0000118 self.resolved_path = None
119 self.dsym = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000120 self.module = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000121
122 def dump(self, prefix):
123 print "%s%s" % (prefix, self)
124
125 def __str__(self):
Greg Claytoncd793122012-01-20 06:12:47 +0000126 return "%#16.16x %s %s" % (self.text_addr_lo, self.uuid, self.get_resolved_path())
Greg Clayton01f7c962012-01-20 03:15:45 +0000127
Greg Claytoncd793122012-01-20 06:12:47 +0000128 def get_resolved_path(self):
129 if self.resolved_path:
130 return self.resolved_path
131 elif self.path:
132 return self.path
133 return None
134
135 def get_resolved_path_basename(self):
136 path = self.get_resolved_path()
137 if path:
138 return os.path.basename(path)
139 return None
140
141 def dsym_basename(self):
142 if self.dsym:
143 return os.path.basename(self.dsym)
Greg Clayton01f7c962012-01-20 03:15:45 +0000144 return None
145
146 def fetch_symboled_executable_and_dsym(self):
Greg Claytoncd793122012-01-20 06:12:47 +0000147 if self.resolved_path:
148 # Don't load a module twice...
149 return 0
150 print 'Locating %s %s...' % (self.uuid, self.path),
Greg Clayton8077a532012-01-20 03:32:35 +0000151 if os.path.exists(self.dsymForUUIDBinary):
152 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, self.uuid)
Greg Clayton8077a532012-01-20 03:32:35 +0000153 s = commands.getoutput(dsym_for_uuid_command)
154 if s:
155 plist_root = plistlib.readPlistFromString (s)
156 if plist_root:
Greg Claytoncd793122012-01-20 06:12:47 +0000157 plist = plist_root[self.uuid]
158 if plist:
159 if 'DBGArchitecture' in plist:
160 self.arch = plist['DBGArchitecture']
161 if 'DBGDSYMPath' in plist:
162 self.dsym = os.path.realpath(plist['DBGDSYMPath'])
163 if 'DBGSymbolRichExecutable' in plist:
164 self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
165 if not self.resolved_path and os.path.exists(self.path):
166 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
167 self_uuid = uuid.UUID(self.uuid)
168 for line in dwarfdump_cmd_output.splitlines():
169 match = self.dwarfdump_uuid_regex.search (line)
170 if match:
171 dwarf_uuid_str = match.group(1)
172 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
173 if self_uuid == dwarf_uuid:
174 self.resolved_path = self.path
175 self.arch = match.group(2)
176 break;
177 if not self.resolved_path:
178 print "error: file %s '%s' doesn't match the UUID in the installed file" % (self.uuid, self.path)
179 return 0
180 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
181 print 'ok'
182 if self.path != self.resolved_path:
183 print ' exe = "%s"' % self.resolved_path
184 if self.dsym:
185 print ' dsym = "%s"' % self.dsym
186 return 1
Greg Clayton01f7c962012-01-20 03:15:45 +0000187 else:
Greg Claytoncd793122012-01-20 06:12:47 +0000188 return 0
Greg Clayton01f7c962012-01-20 03:15:45 +0000189
190 def load_module(self):
Greg Claytone9ee5502012-01-20 19:25:32 +0000191 if not lldb.target:
192 return 'error: no target'
Greg Clayton01f7c962012-01-20 03:15:45 +0000193 if self.module:
194 text_section = self.module.FindSection ("__TEXT")
195 if text_section:
Greg Claytone9ee5502012-01-20 19:25:32 +0000196 error = lldb.target.SetSectionLoadAddress (text_section, self.text_addr_lo)
Greg Clayton01f7c962012-01-20 03:15:45 +0000197 if error.Success():
Greg Clayton002945c2012-03-20 01:30:27 +0000198 #print 'Success: loaded %s.__TEXT = 0x%x' % (self.get_resolved_path_basename(), self.text_addr_lo)
Greg Clayton01f7c962012-01-20 03:15:45 +0000199 return None
200 else:
201 return 'error: %s' % error.GetCString()
202 else:
Greg Claytoncd793122012-01-20 06:12:47 +0000203 return 'error: unable to find "__TEXT" section in "%s"' % self.get_resolved_path()
Greg Clayton01f7c962012-01-20 03:15:45 +0000204 else:
205 return 'error: invalid module'
206
Greg Claytone9ee5502012-01-20 19:25:32 +0000207 def create_target(self):
Greg Clayton01f7c962012-01-20 03:15:45 +0000208 if self.fetch_symboled_executable_and_dsym ():
Greg Claytoncd793122012-01-20 06:12:47 +0000209 resolved_path = self.get_resolved_path();
210 path_spec = lldb.SBFileSpec (resolved_path)
Greg Clayton01f7c962012-01-20 03:15:45 +0000211 #result.PutCString ('plist[%s] = %s' % (uuid, self.plist))
212 error = lldb.SBError()
Greg Claytone9ee5502012-01-20 19:25:32 +0000213 lldb.target = lldb.debugger.CreateTarget (resolved_path, self.arch, None, False, error);
214 if lldb.target:
215 self.module = lldb.target.FindModule (path_spec)
Greg Clayton01f7c962012-01-20 03:15:45 +0000216 if self.module:
217 err = self.load_module()
218 if err:
219 print err
220 else:
221 return None
222 else:
Greg Claytoncd793122012-01-20 06:12:47 +0000223 return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
Greg Clayton01f7c962012-01-20 03:15:45 +0000224 else:
Greg Claytoncd793122012-01-20 06:12:47 +0000225 return 'error: unable to create target for (%s) "%s"' % (self.arch, resolved_path)
Greg Claytone9ee5502012-01-20 19:25:32 +0000226 else:
227 return 'error: unable to locate main executable (%s) "%s"' % (self.arch, self.path)
Greg Clayton01f7c962012-01-20 03:15:45 +0000228
Greg Claytone9ee5502012-01-20 19:25:32 +0000229 def add_target_module(self):
230 if lldb.target:
Greg Clayton002945c2012-03-20 01:30:27 +0000231 # Check for the module by UUID first in case it has been already loaded in LLDB
232 self.module = lldb.target.AddModule (None, None, str(self.uuid))
233 if not self.module:
234 if self.fetch_symboled_executable_and_dsym ():
235 resolved_path = self.get_resolved_path();
236 path_spec = lldb.SBFileSpec (resolved_path)
237 #print 'target.AddModule (path="%s", arch="%s", uuid=%s)' % (resolved_path, self.arch, self.uuid)
238 self.module = lldb.target.AddModule (resolved_path, self.arch, self.uuid)
239 if self.module:
240 err = self.load_module()
241 if err:
242 print err;
Greg Clayton01f7c962012-01-20 03:15:45 +0000243 else:
Greg Clayton002945c2012-03-20 01:30:27 +0000244 return None
245 else:
246 return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
Greg Clayton01f7c962012-01-20 03:15:45 +0000247 else:
248 return 'error: invalid target'
249
250 def __init__(self, path):
251 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton223e8082012-01-21 04:26:24 +0000252 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000253 self.info_lines = list()
254 self.system_profile = list()
255 self.threads = list()
256 self.images = list()
257 self.idents = list() # A list of the required identifiers for doing all stack backtraces
258 self.crashed_thread_idx = -1
259 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000260 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000261 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000262 try:
263 f = open(self.path)
264 except IOError:
265 self.error = 'error: cannot open "%s"' % self.path
266 return
267
Greg Clayton01f7c962012-01-20 03:15:45 +0000268 self.file_lines = f.read().splitlines()
269 parse_mode = PARSE_MODE_NORMAL
270 thread = None
271 for line in self.file_lines:
272 # print line
273 line_len = len(line)
274 if line_len == 0:
275 if thread:
276 if parse_mode == PARSE_MODE_THREAD:
277 if thread.index == self.crashed_thread_idx:
278 thread.reason = ''
279 if self.thread_exception:
280 thread.reason += self.thread_exception
281 if self.thread_exception_data:
282 thread.reason += " (%s)" % self.thread_exception_data
283 self.threads.append(thread)
284 thread = None
285 else:
286 # only append an extra empty line if the previous line
287 # in the info_lines wasn't empty
288 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
289 self.info_lines.append(line)
290 parse_mode = PARSE_MODE_NORMAL
291 # print 'PARSE_MODE_NORMAL'
292 elif parse_mode == PARSE_MODE_NORMAL:
293 if line.startswith ('Process:'):
294 (self.process_name, pid_with_brackets) = line[8:].strip().split()
295 self.process_id = pid_with_brackets.strip('[]')
296 elif line.startswith ('Path:'):
297 self.process_path = line[5:].strip()
298 elif line.startswith ('Identifier:'):
299 self.process_identifier = line[11:].strip()
300 elif line.startswith ('Version:'):
301 (self.process_version, compatability_version) = line[8:].strip().split()
302 self.process_compatability_version = compatability_version.strip('()')
303 elif line.startswith ('Parent Process:'):
304 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
305 self.parent_process_id = pid_with_brackets.strip('[]')
306 elif line.startswith ('Exception Type:'):
307 self.thread_exception = line[15:].strip()
308 continue
309 elif line.startswith ('Exception Codes:'):
310 self.thread_exception_data = line[16:].strip()
311 continue
312 elif line.startswith ('Crashed Thread:'):
313 self.crashed_thread_idx = int(line[15:].strip().split()[0])
314 continue
315 elif line.startswith ('Report Version:'):
316 self.version = int(line[15:].strip())
317 continue
318 elif line.startswith ('System Profile:'):
319 parse_mode = PARSE_MODE_SYSTEM
320 continue
321 elif (line.startswith ('Interval Since Last Report:') or
322 line.startswith ('Crashes Since Last Report:') or
323 line.startswith ('Per-App Interval Since Last Report:') or
324 line.startswith ('Per-App Crashes Since Last Report:') or
325 line.startswith ('Sleep/Wake UUID:') or
326 line.startswith ('Anonymous UUID:')):
327 # ignore these
328 continue
329 elif line.startswith ('Thread'):
330 thread_state_match = self.thread_state_regex.search (line)
331 if thread_state_match:
332 thread_state_match = self.thread_regex.search (line)
333 thread_idx = int(thread_state_match.group(1))
334 parse_mode = PARSE_MODE_THREGS
335 thread = self.threads[thread_idx]
336 else:
337 thread_match = self.thread_regex.search (line)
338 if thread_match:
339 # print 'PARSE_MODE_THREAD'
340 parse_mode = PARSE_MODE_THREAD
341 thread_idx = int(thread_match.group(1))
342 thread = CrashLog.Thread(thread_idx)
343 continue
344 elif line.startswith ('Binary Images:'):
345 parse_mode = PARSE_MODE_IMAGES
346 continue
347 self.info_lines.append(line.strip())
348 elif parse_mode == PARSE_MODE_THREAD:
349 frame_match = self.frame_regex.search(line)
350 if frame_match:
351 ident = frame_match.group(2)
352 if not ident in self.idents:
353 self.idents.append(ident)
354 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
355 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000356 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000357 elif parse_mode == PARSE_MODE_IMAGES:
358 image_match = self.image_regex_uuid.search (line)
359 if image_match:
360 image = CrashLog.Image (int(image_match.group(1),0),
361 int(image_match.group(2),0),
362 image_match.group(3).strip(),
363 image_match.group(4).strip(),
364 image_match.group(5),
365 image_match.group(6))
366 self.images.append (image)
367 else:
368 image_match = self.image_regex_no_uuid.search (line)
369 if image_match:
370 image = CrashLog.Image (int(image_match.group(1),0),
371 int(image_match.group(2),0),
372 image_match.group(3).strip(),
373 image_match.group(4).strip(),
374 None,
375 image_match.group(5))
376 self.images.append (image)
377 else:
378 print "error: image regex failed for: %s" % line
379
380 elif parse_mode == PARSE_MODE_THREGS:
381 stripped_line = line.strip()
382 reg_values = stripped_line.split(' ')
383 for reg_value in reg_values:
384 (reg, value) = reg_value.split(': ')
385 thread.registers[reg.strip()] = int(value, 0)
386 elif parse_mode == PARSE_MODE_SYSTEM:
387 self.system_profile.append(line)
388 f.close()
389
390 def dump(self):
391 print "Crash Log File: %s" % (self.path)
392 print "\nThreads:"
393 for thread in self.threads:
394 thread.dump(' ')
395 print "\nImages:"
396 for image in self.images:
397 image.dump(' ')
398
399 def find_image_with_identifier(self, ident):
400 for image in self.images:
401 if image.ident == ident:
402 return image
403 return None
404
Greg Claytone9ee5502012-01-20 19:25:32 +0000405 def create_target(self):
406 if not self.images:
407 return 'error: no images in crash log'
408 exe_path = self.images[0].get_resolved_path()
409 err = self.images[0].create_target ()
410 if not err:
411 return None # success
412 # We weren't able to open the main executable as, but we can still symbolicate
413 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000414 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000415 image = self.find_image_with_identifier (ident)
416 if image:
417 err = image.create_target ()
418 if not err:
419 return None # success
420 for image in self.images:
421 err = image.create_target ()
422 if not err:
423 return None # success
424 return 'error: unable to locate any executables from the crash log'
425
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000426def disassemble_instructions (instructions, pc, options, non_zeroeth_frame):
Greg Clayton01f7c962012-01-20 03:15:45 +0000427 lines = list()
428 pc_index = -1
429 comment_column = 50
430 for inst_idx, inst in enumerate(instructions):
Greg Claytone9ee5502012-01-20 19:25:32 +0000431 inst_pc = inst.GetAddress().GetLoadAddress(lldb.target);
Greg Clayton01f7c962012-01-20 03:15:45 +0000432 if pc == inst_pc:
433 pc_index = inst_idx
Greg Claytone9ee5502012-01-20 19:25:32 +0000434 mnemonic = inst.GetMnemonic (lldb.target)
435 operands = inst.GetOperands (lldb.target)
436 comment = inst.GetComment (lldb.target)
437 #data = inst.GetData (lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000438 lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands))
439 if comment:
440 line_len = len(lines[-1])
441 if line_len < comment_column:
442 lines[-1] += ' ' * (comment_column - line_len)
443 lines[-1] += "; %s" % comment
444
445 if pc_index >= 0:
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000446 # If we are disassembling the non-zeroeth frame, we need to backup the PC by 1
447 if non_zeroeth_frame and pc_index > 0:
448 pc_index = pc_index - 1
449 if options.disassemble_before == -1:
Greg Clayton01f7c962012-01-20 03:15:45 +0000450 start_idx = 0
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000451 else:
452 start_idx = pc_index - options.disassemble_before
453 if start_idx < 0:
454 start_idx = 0
455 if options.disassemble_before == -1:
456 end_idx = inst_idx
457 else:
458 end_idx = pc_index + options.disassemble_after
Greg Clayton01f7c962012-01-20 03:15:45 +0000459 if end_idx > inst_idx:
460 end_idx = inst_idx
461 for i in range(start_idx, end_idx+1):
462 if i == pc_index:
463 print ' -> ', lines[i]
464 else:
465 print ' ', lines[i]
466
467def print_module_section_data (section):
468 print section
469 section_data = section.GetSectionData()
470 if section_data:
471 ostream = lldb.SBStream()
472 section_data.GetDescription (ostream, section.GetFileAddress())
473 print ostream.GetData()
474
475def print_module_section (section, depth):
476 print section
477
478 if depth > 0:
479 num_sub_sections = section.GetNumSubSections()
480 for sect_idx in range(num_sub_sections):
481 print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1)
482
483def print_module_sections (module, depth):
484 for sect in module.section_iter():
485 print_module_section (sect, depth)
486
487def print_module_symbols (module):
488 for sym in module:
489 print sym
490
491def usage():
492 print "Usage: lldb-symbolicate.py [-n name] executable-image"
493 sys.exit(0)
494
495def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000496 try:
497 SymbolicateCrashLog (shlex.split(command))
498 except:
499 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
500
Greg Clayton01f7c962012-01-20 03:15:45 +0000501def SymbolicateCrashLog(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000502 usage = "usage: %prog [options] <FILE> [FILE ...]"
503 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
504inlined stack frames back to the concrete functions, and disassemble the location of the crash
505for the first frame of the crashed thread.
506If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
507for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
508created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
509you to explore the program as if it were stopped at the locations described in the crash log and functions can
510be disassembled and lookups can be performed using the addresses found in the crash log.'''
511 parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage)
Greg Clayton01f7c962012-01-20 03:15:45 +0000512 parser.add_option('--platform', type='string', metavar='platform', dest='platform', help='specify one platform by name')
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000513 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
Greg Clayton01f7c962012-01-20 03:15:45 +0000514 parser.add_option('--no-images', action='store_false', dest='show_images', help='don\'t show images in stack frames', default=True)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000515 parser.add_option('-a', '--load-all', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False)
Greg Clayton01f7c962012-01-20 03:15:45 +0000516 parser.add_option('--image-list', action='store_true', dest='dump_image_list', help='show image list', default=False)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000517 parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
518 parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
519 parser.add_option('-d', '--disasm-depth', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1)
520 parser.add_option('-D', '--disasm-all', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False)
521 parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
522 parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
Greg Clayton01f7c962012-01-20 03:15:45 +0000523 loaded_addresses = False
Greg Clayton223e8082012-01-21 04:26:24 +0000524 try:
525 (options, args) = parser.parse_args(command_args)
526 except:
527 return
528
Greg Clayton01f7c962012-01-20 03:15:45 +0000529 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000530 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000531 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000532 print 'args', args
533
Greg Clayton01f7c962012-01-20 03:15:45 +0000534 if options.debug_delay > 0:
535 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
536 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000537 error = lldb.SBError()
Greg Claytone9ee5502012-01-20 19:25:32 +0000538 if args:
539 for crash_log_file in args:
Greg Clayton01f7c962012-01-20 03:15:45 +0000540 crash_log = CrashLog(crash_log_file)
Greg Clayton223e8082012-01-21 04:26:24 +0000541 if crash_log.error:
542 print crash_log.error
543 return
Greg Clayton8077a532012-01-20 03:32:35 +0000544 if options.verbose:
545 crash_log.dump()
Greg Claytona3698c62012-01-21 00:37:19 +0000546 if not crash_log.images:
547 print 'error: no images in crash log'
548 return
549
550 err = crash_log.create_target ()
551 if err:
552 print err
553 return
554
555 exe_module = lldb.target.GetModuleAtIndex(0)
556 images_to_load = list()
557 loaded_image_paths = list()
558 if options.load_all_images:
559 # --load-all option was specified, load everything up
560 for image in crash_log.images:
561 images_to_load.append(image)
562 else:
563 # Only load the images found in stack frames for the crashed threads
564 for ident in crash_log.idents:
565 image = crash_log.find_image_with_identifier (ident)
566 if image:
567 images_to_load.append(image)
568 else:
569 print 'error: can\'t find image for identifier "%s"' % ident
570
571 for image in images_to_load:
572 if image.path in loaded_image_paths:
573 print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
Greg Clayton01f7c962012-01-20 03:15:45 +0000574 else:
Greg Claytona3698c62012-01-21 00:37:19 +0000575 err = image.add_target_module ()
576 if err:
577 print err
578 else:
579 loaded_image_paths.append(image.path)
580
581 for line in crash_log.info_lines:
582 print line
583
584 # Reconstruct inlined frames for all threads for anything that has debug info
585 for thread in crash_log.threads:
586 if options.crashed_only and thread.did_crash() == False:
587 continue
588 # start a new frame list that we will fixup for each thread
589 new_thread_frames = list()
590 # Iterate through all concrete frames for a thread and resolve
591 # any parent frames of inlined functions
592 for frame_idx, frame in enumerate(thread.frames):
593 # Resolve the frame's pc into a section + offset address 'pc_addr'
594 pc_addr = lldb.target.ResolveLoadAddress (frame.pc)
595 # Check to see if we were able to resolve the address
596 if pc_addr:
597 # We were able to resolve the frame's PC into a section offset
598 # address.
599
600 # Resolve the frame's PC value into a symbol context. A symbol
601 # context can resolve a module, compile unit, function, block,
602 # line table entry and/or symbol. If the frame has a block, then
603 # we can look for inlined frames, which are represented by blocks
604 # that have inlined information in them
605 frame.sym_ctx = lldb.target.ResolveSymbolContextForAddress (pc_addr, lldb.eSymbolContextEverything);
606
607 # dump if the verbose option was specified
608 if options.verbose:
609 print "frame.pc = %#16.16x (file_addr = %#16.16x)" % (frame.pc, pc_addr.GetFileAddress())
610 print "frame.pc_addr = ", pc_addr
611 print "frame.sym_ctx = "
612 print frame.sym_ctx
613 print
614
615 # Append the frame we already had from the crash log to the new
616 # frames list
617 new_thread_frames.append(frame)
618
619 new_frame = CrashLog.Frame (frame.index, -1, None)
620
621 # Try and use the current frame's symbol context to calculate a
622 # parent frame for an inlined function. If the curent frame is
623 # inlined, it will return a valid symbol context for the parent
624 # frame of the current inlined function
625 parent_pc_addr = lldb.SBAddress()
626 new_frame.sym_ctx = frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
627
628 # See if we were able to reconstruct anything?
629 while new_frame.sym_ctx:
630 # We have a parent frame of an inlined frame, create a new frame
631 # Convert the section + offset 'parent_pc_addr' to a load address
632 new_frame.pc = parent_pc_addr.GetLoadAddress(lldb.target)
633 # push the new frame onto the new frame stack
634 new_thread_frames.append (new_frame)
635 # dump if the verbose option was specified
636 if options.verbose:
637 print "new_frame.pc = %#16.16x (%s)" % (new_frame.pc, parent_pc_addr)
638 print "new_frame.sym_ctx = "
639 print new_frame.sym_ctx
640 print
641 # Create another new frame in case we have multiple inlined frames
642 prev_new_frame = new_frame
643 new_frame = CrashLog.Frame (frame.index, -1, None)
644 # Swap the addresses so we can try another inlined lookup
645 pc_addr = parent_pc_addr;
646 new_frame.sym_ctx = prev_new_frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
647 # Replace our thread frames with our new list that includes parent
648 # frames for inlined functions
649 thread.frames = new_thread_frames
650 # Now iterate through all threads and display our richer stack backtraces
651 for thread in crash_log.threads:
652 this_thread_crashed = thread.did_crash()
653 if options.crashed_only and this_thread_crashed == False:
654 continue
655 print "%s" % thread
656 prev_frame_index = -1
657 for frame_idx, frame in enumerate(thread.frames):
658 details = ' %s' % frame.details
659 module = frame.sym_ctx.GetModule()
660 instructions = None
661 if module:
662 module_basename = module.GetFileSpec().GetFilename();
663 function_start_load_addr = -1
664 function_name = None
665 function = frame.sym_ctx.GetFunction()
666 block = frame.sym_ctx.GetBlock()
667 line_entry = frame.sym_ctx.GetLineEntry()
668 symbol = frame.sym_ctx.GetSymbol()
669 inlined_block = block.GetContainingInlinedBlock();
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000670 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
Greg Claytona3698c62012-01-21 00:37:19 +0000671 if inlined_block:
672 function_name = inlined_block.GetInlinedName();
673 block_range_idx = inlined_block.GetRangeIndexForBlockAddress (lldb.target.ResolveLoadAddress (frame.pc))
674 if block_range_idx < lldb.UINT32_MAX:
675 block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx)
676 function_start_load_addr = block_range_start_addr.GetLoadAddress (lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000677 else:
Greg Claytona3698c62012-01-21 00:37:19 +0000678 function_start_load_addr = frame.pc
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000679 if disassemble:
Greg Claytona3698c62012-01-21 00:37:19 +0000680 instructions = function.GetInstructions(lldb.target)
681 elif function:
682 function_name = function.GetName()
683 function_start_load_addr = function.GetStartAddress().GetLoadAddress (lldb.target)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000684 if disassemble:
Greg Claytona3698c62012-01-21 00:37:19 +0000685 instructions = function.GetInstructions(lldb.target)
686 elif symbol:
687 function_name = symbol.GetName()
688 function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (lldb.target)
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000689 if disassemble:
Greg Claytona3698c62012-01-21 00:37:19 +0000690 instructions = symbol.GetInstructions(lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000691
Greg Claytona3698c62012-01-21 00:37:19 +0000692 if function_name:
693 # Print the function or symbol name and annotate if it was inlined
694 inline_suffix = ''
695 if inlined_block:
696 inline_suffix = '[inlined] '
697 else:
698 inline_suffix = ' '
699 if options.show_images:
700 details = "%s%s`%s" % (inline_suffix, module_basename, function_name)
701 else:
702 details = "%s" % (function_name)
703 # Dump the offset from the current function or symbol if it is non zero
704 function_offset = frame.pc - function_start_load_addr
705 if function_offset > 0:
706 details += " + %u" % (function_offset)
707 elif function_offset < 0:
708 defaults += " %i (invalid negative offset, file a bug) " % function_offset
709 # Print out any line information if any is available
710 if line_entry.GetFileSpec():
711 details += ' at %s' % line_entry.GetFileSpec().GetFilename()
712 details += ':%u' % line_entry.GetLine ()
713 column = line_entry.GetColumn()
714 if column > 0:
715 details += ':%u' % column
Greg Clayton01f7c962012-01-20 03:15:45 +0000716
717
Greg Claytona3698c62012-01-21 00:37:19 +0000718 # Only print out the concrete frame index if it changes.
719 # if prev_frame_index != frame.index:
720 # print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
721 # else:
722 # print " %#16.16x %s" % (frame.pc, details)
723 print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
724 prev_frame_index = frame.index
725 if instructions:
726 print
Greg Clayton2c1fdd02012-01-21 05:10:20 +0000727 disassemble_instructions (instructions, frame.pc, options, frame.index > 0)
Greg Claytona3698c62012-01-21 00:37:19 +0000728 print
Greg Clayton01f7c962012-01-20 03:15:45 +0000729
Greg Claytona3698c62012-01-21 00:37:19 +0000730 print
Greg Clayton01f7c962012-01-20 03:15:45 +0000731
Greg Claytona3698c62012-01-21 00:37:19 +0000732 if options.dump_image_list:
733 print "Binary Images:"
734 for image in crash_log.images:
735 print image
736
Greg Clayton01f7c962012-01-20 03:15:45 +0000737
738if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000739 # Create a new debugger instance
740 lldb.debugger = lldb.SBDebugger.Create()
741 SymbolicateCrashLog (sys.argv)
742elif lldb.debugger:
743 lldb.debugger.HandleCommand('command script add -f crashlog.Symbolicate crashlog')
744 print '"crashlog" command installed, type "crashlog --help" for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000745