blob: 329b1c3a34efe8f32c561600f051972805a5decf [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 Clayton8077a532012-01-20 03:32:35 +0000198 #print 'Success: loaded %s.__TEXT = 0x%x' % (self.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 Clayton01f7c962012-01-20 03:15:45 +0000231 if self.fetch_symboled_executable_and_dsym ():
Greg Claytoncd793122012-01-20 06:12:47 +0000232 resolved_path = self.get_resolved_path();
233 path_spec = lldb.SBFileSpec (resolved_path)
234 #print 'target.AddModule (path="%s", arch="%s", uuid=%s)' % (resolved_path, self.arch, self.uuid)
Greg Claytone9ee5502012-01-20 19:25:32 +0000235 self.module = lldb.target.AddModule (resolved_path, self.arch, self.uuid)
Greg Clayton01f7c962012-01-20 03:15:45 +0000236 if self.module:
237 err = self.load_module()
238 if err:
239 print err;
240 else:
241 return None
242 else:
Greg Claytoncd793122012-01-20 06:12:47 +0000243 return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
Greg Clayton01f7c962012-01-20 03:15:45 +0000244 else:
245 return 'error: invalid target'
246
247 def __init__(self, path):
248 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Clayton223e8082012-01-21 04:26:24 +0000249 self.path = os.path.expanduser(path);
Greg Clayton01f7c962012-01-20 03:15:45 +0000250 self.info_lines = list()
251 self.system_profile = list()
252 self.threads = list()
253 self.images = list()
254 self.idents = list() # A list of the required identifiers for doing all stack backtraces
255 self.crashed_thread_idx = -1
256 self.version = -1
Greg Clayton223e8082012-01-21 04:26:24 +0000257 self.error = None
Greg Clayton01f7c962012-01-20 03:15:45 +0000258 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton223e8082012-01-21 04:26:24 +0000259 try:
260 f = open(self.path)
261 except IOError:
262 self.error = 'error: cannot open "%s"' % self.path
263 return
264
Greg Clayton01f7c962012-01-20 03:15:45 +0000265 self.file_lines = f.read().splitlines()
266 parse_mode = PARSE_MODE_NORMAL
267 thread = None
268 for line in self.file_lines:
269 # print line
270 line_len = len(line)
271 if line_len == 0:
272 if thread:
273 if parse_mode == PARSE_MODE_THREAD:
274 if thread.index == self.crashed_thread_idx:
275 thread.reason = ''
276 if self.thread_exception:
277 thread.reason += self.thread_exception
278 if self.thread_exception_data:
279 thread.reason += " (%s)" % self.thread_exception_data
280 self.threads.append(thread)
281 thread = None
282 else:
283 # only append an extra empty line if the previous line
284 # in the info_lines wasn't empty
285 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
286 self.info_lines.append(line)
287 parse_mode = PARSE_MODE_NORMAL
288 # print 'PARSE_MODE_NORMAL'
289 elif parse_mode == PARSE_MODE_NORMAL:
290 if line.startswith ('Process:'):
291 (self.process_name, pid_with_brackets) = line[8:].strip().split()
292 self.process_id = pid_with_brackets.strip('[]')
293 elif line.startswith ('Path:'):
294 self.process_path = line[5:].strip()
295 elif line.startswith ('Identifier:'):
296 self.process_identifier = line[11:].strip()
297 elif line.startswith ('Version:'):
298 (self.process_version, compatability_version) = line[8:].strip().split()
299 self.process_compatability_version = compatability_version.strip('()')
300 elif line.startswith ('Parent Process:'):
301 (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
302 self.parent_process_id = pid_with_brackets.strip('[]')
303 elif line.startswith ('Exception Type:'):
304 self.thread_exception = line[15:].strip()
305 continue
306 elif line.startswith ('Exception Codes:'):
307 self.thread_exception_data = line[16:].strip()
308 continue
309 elif line.startswith ('Crashed Thread:'):
310 self.crashed_thread_idx = int(line[15:].strip().split()[0])
311 continue
312 elif line.startswith ('Report Version:'):
313 self.version = int(line[15:].strip())
314 continue
315 elif line.startswith ('System Profile:'):
316 parse_mode = PARSE_MODE_SYSTEM
317 continue
318 elif (line.startswith ('Interval Since Last Report:') or
319 line.startswith ('Crashes Since Last Report:') or
320 line.startswith ('Per-App Interval Since Last Report:') or
321 line.startswith ('Per-App Crashes Since Last Report:') or
322 line.startswith ('Sleep/Wake UUID:') or
323 line.startswith ('Anonymous UUID:')):
324 # ignore these
325 continue
326 elif line.startswith ('Thread'):
327 thread_state_match = self.thread_state_regex.search (line)
328 if thread_state_match:
329 thread_state_match = self.thread_regex.search (line)
330 thread_idx = int(thread_state_match.group(1))
331 parse_mode = PARSE_MODE_THREGS
332 thread = self.threads[thread_idx]
333 else:
334 thread_match = self.thread_regex.search (line)
335 if thread_match:
336 # print 'PARSE_MODE_THREAD'
337 parse_mode = PARSE_MODE_THREAD
338 thread_idx = int(thread_match.group(1))
339 thread = CrashLog.Thread(thread_idx)
340 continue
341 elif line.startswith ('Binary Images:'):
342 parse_mode = PARSE_MODE_IMAGES
343 continue
344 self.info_lines.append(line.strip())
345 elif parse_mode == PARSE_MODE_THREAD:
346 frame_match = self.frame_regex.search(line)
347 if frame_match:
348 ident = frame_match.group(2)
349 if not ident in self.idents:
350 self.idents.append(ident)
351 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
352 else:
Greg Clayton8077a532012-01-20 03:32:35 +0000353 print 'error: frame regex failed for line: "%s"' % line
Greg Clayton01f7c962012-01-20 03:15:45 +0000354 elif parse_mode == PARSE_MODE_IMAGES:
355 image_match = self.image_regex_uuid.search (line)
356 if image_match:
357 image = CrashLog.Image (int(image_match.group(1),0),
358 int(image_match.group(2),0),
359 image_match.group(3).strip(),
360 image_match.group(4).strip(),
361 image_match.group(5),
362 image_match.group(6))
363 self.images.append (image)
364 else:
365 image_match = self.image_regex_no_uuid.search (line)
366 if image_match:
367 image = CrashLog.Image (int(image_match.group(1),0),
368 int(image_match.group(2),0),
369 image_match.group(3).strip(),
370 image_match.group(4).strip(),
371 None,
372 image_match.group(5))
373 self.images.append (image)
374 else:
375 print "error: image regex failed for: %s" % line
376
377 elif parse_mode == PARSE_MODE_THREGS:
378 stripped_line = line.strip()
379 reg_values = stripped_line.split(' ')
380 for reg_value in reg_values:
381 (reg, value) = reg_value.split(': ')
382 thread.registers[reg.strip()] = int(value, 0)
383 elif parse_mode == PARSE_MODE_SYSTEM:
384 self.system_profile.append(line)
385 f.close()
386
387 def dump(self):
388 print "Crash Log File: %s" % (self.path)
389 print "\nThreads:"
390 for thread in self.threads:
391 thread.dump(' ')
392 print "\nImages:"
393 for image in self.images:
394 image.dump(' ')
395
396 def find_image_with_identifier(self, ident):
397 for image in self.images:
398 if image.ident == ident:
399 return image
400 return None
401
Greg Claytone9ee5502012-01-20 19:25:32 +0000402 def create_target(self):
403 if not self.images:
404 return 'error: no images in crash log'
405 exe_path = self.images[0].get_resolved_path()
406 err = self.images[0].create_target ()
407 if not err:
408 return None # success
409 # We weren't able to open the main executable as, but we can still symbolicate
410 if self.idents:
Sean Callananf7fb7332012-01-20 19:27:48 +0000411 for ident in self.idents:
Greg Claytone9ee5502012-01-20 19:25:32 +0000412 image = self.find_image_with_identifier (ident)
413 if image:
414 err = image.create_target ()
415 if not err:
416 return None # success
417 for image in self.images:
418 err = image.create_target ()
419 if not err:
420 return None # success
421 return 'error: unable to locate any executables from the crash log'
422
423def disassemble_instructions (instructions, pc, insts_before_pc, insts_after_pc):
Greg Clayton01f7c962012-01-20 03:15:45 +0000424 lines = list()
425 pc_index = -1
426 comment_column = 50
427 for inst_idx, inst in enumerate(instructions):
Greg Claytone9ee5502012-01-20 19:25:32 +0000428 inst_pc = inst.GetAddress().GetLoadAddress(lldb.target);
Greg Clayton01f7c962012-01-20 03:15:45 +0000429 if pc == inst_pc:
430 pc_index = inst_idx
Greg Claytone9ee5502012-01-20 19:25:32 +0000431 mnemonic = inst.GetMnemonic (lldb.target)
432 operands = inst.GetOperands (lldb.target)
433 comment = inst.GetComment (lldb.target)
434 #data = inst.GetData (lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000435 lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands))
436 if comment:
437 line_len = len(lines[-1])
438 if line_len < comment_column:
439 lines[-1] += ' ' * (comment_column - line_len)
440 lines[-1] += "; %s" % comment
441
442 if pc_index >= 0:
443 if pc_index >= insts_before_pc:
444 start_idx = pc_index - insts_before_pc
445 else:
446 start_idx = 0
447 end_idx = pc_index + insts_after_pc
448 if end_idx > inst_idx:
449 end_idx = inst_idx
450 for i in range(start_idx, end_idx+1):
451 if i == pc_index:
452 print ' -> ', lines[i]
453 else:
454 print ' ', lines[i]
455
456def print_module_section_data (section):
457 print section
458 section_data = section.GetSectionData()
459 if section_data:
460 ostream = lldb.SBStream()
461 section_data.GetDescription (ostream, section.GetFileAddress())
462 print ostream.GetData()
463
464def print_module_section (section, depth):
465 print section
466
467 if depth > 0:
468 num_sub_sections = section.GetNumSubSections()
469 for sect_idx in range(num_sub_sections):
470 print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1)
471
472def print_module_sections (module, depth):
473 for sect in module.section_iter():
474 print_module_section (sect, depth)
475
476def print_module_symbols (module):
477 for sym in module:
478 print sym
479
480def usage():
481 print "Usage: lldb-symbolicate.py [-n name] executable-image"
482 sys.exit(0)
483
484def Symbolicate(debugger, command, result, dict):
Greg Clayton223e8082012-01-21 04:26:24 +0000485 try:
486 SymbolicateCrashLog (shlex.split(command))
487 except:
488 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
489
Greg Clayton01f7c962012-01-20 03:15:45 +0000490def SymbolicateCrashLog(command_args):
Greg Claytona3698c62012-01-21 00:37:19 +0000491 usage = "usage: %prog [options] <FILE> [FILE ...]"
492 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
493inlined stack frames back to the concrete functions, and disassemble the location of the crash
494for the first frame of the crashed thread.
495If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
496for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
497created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
498you to explore the program as if it were stopped at the locations described in the crash log and functions can
499be disassembled and lookups can be performed using the addresses found in the crash log.'''
500 parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage)
Greg Clayton01f7c962012-01-20 03:15:45 +0000501 parser.add_option('--platform', type='string', metavar='platform', dest='platform', help='specify one platform by name')
502 parser.add_option('--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
Greg Clayton01f7c962012-01-20 03:15:45 +0000503 parser.add_option('--no-images', action='store_false', dest='show_images', help='don\'t show images in stack frames', default=True)
Greg Claytona3698c62012-01-21 00:37:19 +0000504 parser.add_option('--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 +0000505 parser.add_option('--image-list', action='store_true', dest='dump_image_list', help='show image list', default=False)
506 parser.add_option('--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
Greg Claytone9ee5502012-01-20 19:25:32 +0000507 parser.add_option('--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
Greg Clayton01f7c962012-01-20 03:15:45 +0000508 loaded_addresses = False
Greg Clayton223e8082012-01-21 04:26:24 +0000509 try:
510 (options, args) = parser.parse_args(command_args)
511 except:
512 return
513
Greg Clayton01f7c962012-01-20 03:15:45 +0000514 if options.verbose:
Greg Clayton223e8082012-01-21 04:26:24 +0000515 print 'command_args = %s' % command_args
Greg Clayton01f7c962012-01-20 03:15:45 +0000516 print 'options', options
Greg Clayton223e8082012-01-21 04:26:24 +0000517 print 'args', args
518
Greg Clayton01f7c962012-01-20 03:15:45 +0000519 if options.debug_delay > 0:
520 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
521 time.sleep(options.debug_delay)
Greg Clayton01f7c962012-01-20 03:15:45 +0000522 error = lldb.SBError()
Greg Claytone9ee5502012-01-20 19:25:32 +0000523 if args:
524 for crash_log_file in args:
Greg Clayton01f7c962012-01-20 03:15:45 +0000525 crash_log = CrashLog(crash_log_file)
Greg Clayton223e8082012-01-21 04:26:24 +0000526 if crash_log.error:
527 print crash_log.error
528 return
Greg Clayton8077a532012-01-20 03:32:35 +0000529 if options.verbose:
530 crash_log.dump()
Greg Claytona3698c62012-01-21 00:37:19 +0000531 if not crash_log.images:
532 print 'error: no images in crash log'
533 return
534
535 err = crash_log.create_target ()
536 if err:
537 print err
538 return
539
540 exe_module = lldb.target.GetModuleAtIndex(0)
541 images_to_load = list()
542 loaded_image_paths = list()
543 if options.load_all_images:
544 # --load-all option was specified, load everything up
545 for image in crash_log.images:
546 images_to_load.append(image)
547 else:
548 # Only load the images found in stack frames for the crashed threads
549 for ident in crash_log.idents:
550 image = crash_log.find_image_with_identifier (ident)
551 if image:
552 images_to_load.append(image)
553 else:
554 print 'error: can\'t find image for identifier "%s"' % ident
555
556 for image in images_to_load:
557 if image.path in loaded_image_paths:
558 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 +0000559 else:
Greg Claytona3698c62012-01-21 00:37:19 +0000560 err = image.add_target_module ()
561 if err:
562 print err
563 else:
564 loaded_image_paths.append(image.path)
565
566 for line in crash_log.info_lines:
567 print line
568
569 # Reconstruct inlined frames for all threads for anything that has debug info
570 for thread in crash_log.threads:
571 if options.crashed_only and thread.did_crash() == False:
572 continue
573 # start a new frame list that we will fixup for each thread
574 new_thread_frames = list()
575 # Iterate through all concrete frames for a thread and resolve
576 # any parent frames of inlined functions
577 for frame_idx, frame in enumerate(thread.frames):
578 # Resolve the frame's pc into a section + offset address 'pc_addr'
579 pc_addr = lldb.target.ResolveLoadAddress (frame.pc)
580 # Check to see if we were able to resolve the address
581 if pc_addr:
582 # We were able to resolve the frame's PC into a section offset
583 # address.
584
585 # Resolve the frame's PC value into a symbol context. A symbol
586 # context can resolve a module, compile unit, function, block,
587 # line table entry and/or symbol. If the frame has a block, then
588 # we can look for inlined frames, which are represented by blocks
589 # that have inlined information in them
590 frame.sym_ctx = lldb.target.ResolveSymbolContextForAddress (pc_addr, lldb.eSymbolContextEverything);
591
592 # dump if the verbose option was specified
593 if options.verbose:
594 print "frame.pc = %#16.16x (file_addr = %#16.16x)" % (frame.pc, pc_addr.GetFileAddress())
595 print "frame.pc_addr = ", pc_addr
596 print "frame.sym_ctx = "
597 print frame.sym_ctx
598 print
599
600 # Append the frame we already had from the crash log to the new
601 # frames list
602 new_thread_frames.append(frame)
603
604 new_frame = CrashLog.Frame (frame.index, -1, None)
605
606 # Try and use the current frame's symbol context to calculate a
607 # parent frame for an inlined function. If the curent frame is
608 # inlined, it will return a valid symbol context for the parent
609 # frame of the current inlined function
610 parent_pc_addr = lldb.SBAddress()
611 new_frame.sym_ctx = frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
612
613 # See if we were able to reconstruct anything?
614 while new_frame.sym_ctx:
615 # We have a parent frame of an inlined frame, create a new frame
616 # Convert the section + offset 'parent_pc_addr' to a load address
617 new_frame.pc = parent_pc_addr.GetLoadAddress(lldb.target)
618 # push the new frame onto the new frame stack
619 new_thread_frames.append (new_frame)
620 # dump if the verbose option was specified
621 if options.verbose:
622 print "new_frame.pc = %#16.16x (%s)" % (new_frame.pc, parent_pc_addr)
623 print "new_frame.sym_ctx = "
624 print new_frame.sym_ctx
625 print
626 # Create another new frame in case we have multiple inlined frames
627 prev_new_frame = new_frame
628 new_frame = CrashLog.Frame (frame.index, -1, None)
629 # Swap the addresses so we can try another inlined lookup
630 pc_addr = parent_pc_addr;
631 new_frame.sym_ctx = prev_new_frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
632 # Replace our thread frames with our new list that includes parent
633 # frames for inlined functions
634 thread.frames = new_thread_frames
635 # Now iterate through all threads and display our richer stack backtraces
636 for thread in crash_log.threads:
637 this_thread_crashed = thread.did_crash()
638 if options.crashed_only and this_thread_crashed == False:
639 continue
640 print "%s" % thread
641 prev_frame_index = -1
642 for frame_idx, frame in enumerate(thread.frames):
643 details = ' %s' % frame.details
644 module = frame.sym_ctx.GetModule()
645 instructions = None
646 if module:
647 module_basename = module.GetFileSpec().GetFilename();
648 function_start_load_addr = -1
649 function_name = None
650 function = frame.sym_ctx.GetFunction()
651 block = frame.sym_ctx.GetBlock()
652 line_entry = frame.sym_ctx.GetLineEntry()
653 symbol = frame.sym_ctx.GetSymbol()
654 inlined_block = block.GetContainingInlinedBlock();
655 if inlined_block:
656 function_name = inlined_block.GetInlinedName();
657 block_range_idx = inlined_block.GetRangeIndexForBlockAddress (lldb.target.ResolveLoadAddress (frame.pc))
658 if block_range_idx < lldb.UINT32_MAX:
659 block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx)
660 function_start_load_addr = block_range_start_addr.GetLoadAddress (lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000661 else:
Greg Claytona3698c62012-01-21 00:37:19 +0000662 function_start_load_addr = frame.pc
663 if this_thread_crashed and frame_idx == 0:
664 instructions = function.GetInstructions(lldb.target)
665 elif function:
666 function_name = function.GetName()
667 function_start_load_addr = function.GetStartAddress().GetLoadAddress (lldb.target)
668 if this_thread_crashed and frame_idx == 0:
669 instructions = function.GetInstructions(lldb.target)
670 elif symbol:
671 function_name = symbol.GetName()
672 function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (lldb.target)
673 if this_thread_crashed and frame_idx == 0:
674 instructions = symbol.GetInstructions(lldb.target)
Greg Clayton01f7c962012-01-20 03:15:45 +0000675
Greg Claytona3698c62012-01-21 00:37:19 +0000676 if function_name:
677 # Print the function or symbol name and annotate if it was inlined
678 inline_suffix = ''
679 if inlined_block:
680 inline_suffix = '[inlined] '
681 else:
682 inline_suffix = ' '
683 if options.show_images:
684 details = "%s%s`%s" % (inline_suffix, module_basename, function_name)
685 else:
686 details = "%s" % (function_name)
687 # Dump the offset from the current function or symbol if it is non zero
688 function_offset = frame.pc - function_start_load_addr
689 if function_offset > 0:
690 details += " + %u" % (function_offset)
691 elif function_offset < 0:
692 defaults += " %i (invalid negative offset, file a bug) " % function_offset
693 # Print out any line information if any is available
694 if line_entry.GetFileSpec():
695 details += ' at %s' % line_entry.GetFileSpec().GetFilename()
696 details += ':%u' % line_entry.GetLine ()
697 column = line_entry.GetColumn()
698 if column > 0:
699 details += ':%u' % column
Greg Clayton01f7c962012-01-20 03:15:45 +0000700
701
Greg Claytona3698c62012-01-21 00:37:19 +0000702 # Only print out the concrete frame index if it changes.
703 # if prev_frame_index != frame.index:
704 # print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
705 # else:
706 # print " %#16.16x %s" % (frame.pc, details)
707 print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
708 prev_frame_index = frame.index
709 if instructions:
710 print
711 disassemble_instructions (instructions, frame.pc, 4, 4)
712 print
Greg Clayton01f7c962012-01-20 03:15:45 +0000713
Greg Claytona3698c62012-01-21 00:37:19 +0000714 print
Greg Clayton01f7c962012-01-20 03:15:45 +0000715
Greg Claytona3698c62012-01-21 00:37:19 +0000716 if options.dump_image_list:
717 print "Binary Images:"
718 for image in crash_log.images:
719 print image
720
Greg Clayton01f7c962012-01-20 03:15:45 +0000721
722if __name__ == '__main__':
Greg Claytone9ee5502012-01-20 19:25:32 +0000723 # Create a new debugger instance
724 lldb.debugger = lldb.SBDebugger.Create()
725 SymbolicateCrashLog (sys.argv)
726elif lldb.debugger:
727 lldb.debugger.HandleCommand('command script add -f crashlog.Symbolicate crashlog')
728 print '"crashlog" command installed, type "crashlog --help" for detailed help'
Greg Clayton01f7c962012-01-20 03:15:45 +0000729