blob: 60a6a1f50f00532b93420defeddef7429dd97f57 [file] [log] [blame]
Greg Claytonaa9d02b2012-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 Clayton5c0f4832012-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 Claytonaa9d02b2012-01-20 03:15:45 +000022# On MacOSX csh, tcsh:
Greg Clayton5c0f4832012-01-21 00:37:19 +000023# ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
24#
Greg Claytonaa9d02b2012-01-20 03:15:45 +000025# On MacOSX sh, bash:
Greg Clayton5c0f4832012-01-21 00:37:19 +000026# PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
Greg Claytonaa9d02b2012-01-20 03:15:45 +000027#----------------------------------------------------------------------
28
Greg Clayton5c0f4832012-01-21 00:37:19 +000029import commands
Greg Claytonc8f73d72012-05-04 20:44:14 +000030import cmd
Greg Claytonf47b2c22012-06-27 20:02:04 +000031import datetime
Greg Claytonc8f73d72012-05-04 20:44:14 +000032import glob
Greg Claytonaa9d02b2012-01-20 03:15:45 +000033import optparse
34import os
Greg Claytonf47b2c22012-06-27 20:02:04 +000035import platform
Greg Claytonaa9d02b2012-01-20 03:15:45 +000036import plistlib
Greg Clayton3c2c4bb2012-04-03 21:35:43 +000037import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
Greg Claytonaa9d02b2012-01-20 03:15:45 +000038import re
Greg Clayton53b43b02012-01-21 04:26:24 +000039import shlex
Greg Claytonc8f73d72012-05-04 20:44:14 +000040import string
Greg Claytonaa9d02b2012-01-20 03:15:45 +000041import sys
42import time
Greg Clayton3d8d3db2012-01-20 06:12:47 +000043import uuid
Greg Claytonaa4d4532012-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 Claytonc8f73d72012-05-04 20:44:14 +000076from lldb.utils import symbolication
Greg Claytonaa9d02b2012-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 Claytonc8f73d72012-05-04 20:44:14 +000084class CrashLog(symbolication.Symbolicator):
Greg Claytonaa9d02b2012-01-20 03:15:45 +000085 """Class that does parses darwin crash logs"""
Sean Callananc51cd472013-01-12 02:11:49 +000086 parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]');
Greg Claytonaa9d02b2012-01-20 03:15:45 +000087 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
88 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
Greg Clayton48d157d2015-03-05 22:53:06 +000089 app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
90 frame_regex = re.compile('^([0-9]+)\s+([^ ]+)\s+(0x[0-9a-fA-F]+) +(.*)')
Greg Claytona32bfbe2012-01-20 03:32:35 +000091 image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
92 image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
Greg Claytonaa9d02b2012-01-20 03:15:45 +000093 empty_line_regex = re.compile('^$')
94
95 class Thread:
96 """Class that represents a thread in a darwin crash log"""
Greg Clayton48d157d2015-03-05 22:53:06 +000097 def __init__(self, index, app_specific_backtrace):
Greg Claytonaa9d02b2012-01-20 03:15:45 +000098 self.index = index
99 self.frames = list()
Greg Clayton563d0392012-07-13 03:19:35 +0000100 self.idents = list()
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000101 self.registers = dict()
102 self.reason = None
103 self.queue = None
Greg Clayton48d157d2015-03-05 22:53:06 +0000104 self.app_specific_backtrace = app_specific_backtrace
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000105
106 def dump(self, prefix):
Greg Clayton48d157d2015-03-05 22:53:06 +0000107 if self.app_specific_backtrace:
108 print "%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason)
109 else:
110 print "%sThread[%u] %s" % (prefix, self.index, self.reason)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000111 if self.frames:
112 print "%s Frames:" % (prefix)
113 for frame in self.frames:
114 frame.dump(prefix + ' ')
115 if self.registers:
116 print "%s Registers:" % (prefix)
117 for reg in self.registers.keys():
118 print "%s %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
119
Greg Clayton48d157d2015-03-05 22:53:06 +0000120 def dump_symbolicated (self, crash_log, options):
121 this_thread_crashed = self.app_specific_backtrace
122 if not this_thread_crashed:
123 this_thread_crashed = self.did_crash()
124 if options.crashed_only and this_thread_crashed == False:
125 return
126
127 print "%s" % self
128 #prev_frame_index = -1
129 display_frame_idx = -1
130 for frame_idx, frame in enumerate(self.frames):
131 disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
132 if frame_idx == 0:
133 symbolicated_frame_addresses = crash_log.symbolicate (frame.pc & crash_log.addr_mask, options.verbose)
134 else:
135 # Any frame above frame zero and we have to subtract one to get the previous line entry
136 symbolicated_frame_addresses = crash_log.symbolicate ((frame.pc & crash_log.addr_mask) - 1, options.verbose)
137
138 if symbolicated_frame_addresses:
139 symbolicated_frame_address_idx = 0
140 for symbolicated_frame_address in symbolicated_frame_addresses:
141 display_frame_idx += 1
142 print '[%3u] %s' % (frame_idx, symbolicated_frame_address)
143 if (options.source_all or self.did_crash()) and display_frame_idx < options.source_frames and options.source_context:
144 source_context = options.source_context
145 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
146 if line_entry.IsValid():
147 strm = lldb.SBStream()
148 if line_entry:
149 lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(line_entry.file, line_entry.line, source_context, source_context, "->", strm)
150 source_text = strm.GetData()
151 if source_text:
152 # Indent the source a bit
153 indent_str = ' '
154 join_str = '\n' + indent_str
155 print '%s%s' % (indent_str, join_str.join(source_text.split('\n')))
156 if symbolicated_frame_address_idx == 0:
157 if disassemble:
158 instructions = symbolicated_frame_address.get_instructions()
159 if instructions:
160 print
161 symbolication.disassemble_instructions (crash_log.get_target(),
162 instructions,
163 frame.pc,
164 options.disassemble_before,
165 options.disassemble_after, frame.index > 0)
166 print
167 symbolicated_frame_address_idx += 1
168 else:
169 print frame
170
Greg Clayton563d0392012-07-13 03:19:35 +0000171 def add_ident(self, ident):
172 if not ident in self.idents:
173 self.idents.append(ident)
174
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000175 def did_crash(self):
176 return self.reason != None
177
178 def __str__(self):
Greg Clayton48d157d2015-03-05 22:53:06 +0000179 if self.app_specific_backtrace:
180 s = "Application Specific Backtrace[%u]" % self.index
181 else:
182 s = "Thread[%u]" % self.index
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000183 if self.reason:
184 s += ' %s' % self.reason
185 return s
186
187
188 class Frame:
189 """Class that represents a stack frame in a thread in a darwin crash log"""
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000190 def __init__(self, index, pc, description):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000191 self.pc = pc
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000192 self.description = description
193 self.index = index
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000194
195 def __str__(self):
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000196 if self.description:
197 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
198 else:
Johnny Chen8e1fd432012-05-03 18:46:28 +0000199 return "[%3u] 0x%16.16x" % (self.index, self.pc)
200
201 def dump(self, prefix):
202 print "%s%s" % (prefix, str(self))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000203
Greg Claytonc8f73d72012-05-04 20:44:14 +0000204 class DarwinImage(symbolication.Image):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000205 """Class that represents a binary images in a darwin crash log"""
Greg Claytona32bfbe2012-01-20 03:32:35 +0000206 dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000207 if not os.path.exists(dsymForUUIDBinary):
208 dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
209
210 dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
Greg Claytona32bfbe2012-01-20 03:32:35 +0000211
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000212 def __init__(self, text_addr_lo, text_addr_hi, identifier, version, uuid, path):
Greg Claytonc8f73d72012-05-04 20:44:14 +0000213 symbolication.Image.__init__(self, path, uuid);
214 self.add_section (symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT"))
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000215 self.identifier = identifier
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000216 self.version = version
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000217
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000218 def locate_module_and_debug_symbols(self):
Greg Claytonf51a23f2012-06-04 23:22:17 +0000219 # Don't load a module twice...
220 if self.resolved:
Greg Claytonf99295c2012-04-20 23:31:27 +0000221 return True
Greg Claytonf51a23f2012-06-04 23:22:17 +0000222 # Mark this as resolved so we don't keep trying
223 self.resolved = True
Greg Clayton60bb58f2012-05-11 00:30:14 +0000224 uuid_str = self.get_normalized_uuid_string()
225 print 'Getting symbols for %s %s...' % (uuid_str, self.path),
Greg Claytona32bfbe2012-01-20 03:32:35 +0000226 if os.path.exists(self.dsymForUUIDBinary):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000227 dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, uuid_str)
Greg Claytona32bfbe2012-01-20 03:32:35 +0000228 s = commands.getoutput(dsym_for_uuid_command)
229 if s:
230 plist_root = plistlib.readPlistFromString (s)
231 if plist_root:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000232 plist = plist_root[uuid_str]
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000233 if plist:
234 if 'DBGArchitecture' in plist:
235 self.arch = plist['DBGArchitecture']
236 if 'DBGDSYMPath' in plist:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000237 self.symfile = os.path.realpath(plist['DBGDSYMPath'])
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000238 if 'DBGSymbolRichExecutable' in plist:
Greg Clayton86e70cb2014-04-07 23:50:17 +0000239 self.path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
240 self.resolved_path = self.path
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000241 if not self.resolved_path and os.path.exists(self.path):
242 dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
Greg Clayton60bb58f2012-05-11 00:30:14 +0000243 self_uuid = self.get_uuid()
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000244 for line in dwarfdump_cmd_output.splitlines():
245 match = self.dwarfdump_uuid_regex.search (line)
246 if match:
247 dwarf_uuid_str = match.group(1)
248 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
249 if self_uuid == dwarf_uuid:
250 self.resolved_path = self.path
251 self.arch = match.group(2)
252 break;
253 if not self.resolved_path:
Greg Claytonf51a23f2012-06-04 23:22:17 +0000254 self.unavailable = True
255 print "error\n error: unable to locate '%s' with UUID %s" % (self.path, uuid_str)
Greg Claytonf99295c2012-04-20 23:31:27 +0000256 return False
Greg Clayton3d8d3db2012-01-20 06:12:47 +0000257 if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
258 print 'ok'
Greg Claytond712ef02012-04-25 18:40:20 +0000259 # if self.resolved_path:
260 # print ' exe = "%s"' % self.resolved_path
261 # if self.symfile:
262 # print ' dsym = "%s"' % self.symfile
Greg Claytonf99295c2012-04-20 23:31:27 +0000263 return True
Greg Claytonf51a23f2012-06-04 23:22:17 +0000264 else:
265 self.unavailable = True
Greg Claytonf99295c2012-04-20 23:31:27 +0000266 return False
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000267
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000268
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000269
270 def __init__(self, path):
271 """CrashLog constructor that take a path to a darwin crash log file"""
Greg Claytonc8f73d72012-05-04 20:44:14 +0000272 symbolication.Symbolicator.__init__(self);
Greg Clayton53b43b02012-01-21 04:26:24 +0000273 self.path = os.path.expanduser(path);
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000274 self.info_lines = list()
275 self.system_profile = list()
276 self.threads = list()
Greg Clayton48d157d2015-03-05 22:53:06 +0000277 self.backtraces = list() # For application specific backtraces
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000278 self.idents = list() # A list of the required identifiers for doing all stack backtraces
279 self.crashed_thread_idx = -1
280 self.version = -1
Greg Clayton53b43b02012-01-21 04:26:24 +0000281 self.error = None
Greg Clayton48d157d2015-03-05 22:53:06 +0000282 self.target = None
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000283 # With possible initial component of ~ or ~user replaced by that user's home directory.
Greg Clayton53b43b02012-01-21 04:26:24 +0000284 try:
285 f = open(self.path)
286 except IOError:
287 self.error = 'error: cannot open "%s"' % self.path
288 return
289
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000290 self.file_lines = f.read().splitlines()
291 parse_mode = PARSE_MODE_NORMAL
292 thread = None
Greg Clayton48d157d2015-03-05 22:53:06 +0000293 app_specific_backtrace = False
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000294 for line in self.file_lines:
295 # print line
296 line_len = len(line)
297 if line_len == 0:
298 if thread:
299 if parse_mode == PARSE_MODE_THREAD:
300 if thread.index == self.crashed_thread_idx:
301 thread.reason = ''
302 if self.thread_exception:
303 thread.reason += self.thread_exception
304 if self.thread_exception_data:
Greg Clayton48d157d2015-03-05 22:53:06 +0000305 thread.reason += " (%s)" % self.thread_exception_data
306 if app_specific_backtrace:
307 self.backtraces.append(thread)
308 else:
309 self.threads.append(thread)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000310 thread = None
311 else:
312 # only append an extra empty line if the previous line
313 # in the info_lines wasn't empty
314 if len(self.info_lines) > 0 and len(self.info_lines[-1]):
315 self.info_lines.append(line)
316 parse_mode = PARSE_MODE_NORMAL
317 # print 'PARSE_MODE_NORMAL'
318 elif parse_mode == PARSE_MODE_NORMAL:
319 if line.startswith ('Process:'):
Greg Clayton45b673d2012-08-13 18:48:03 +0000320 (self.process_name, pid_with_brackets) = line[8:].strip().split(' [')
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000321 self.process_id = pid_with_brackets.strip('[]')
322 elif line.startswith ('Path:'):
323 self.process_path = line[5:].strip()
324 elif line.startswith ('Identifier:'):
325 self.process_identifier = line[11:].strip()
326 elif line.startswith ('Version:'):
Johnny Chenafc98a62012-05-10 22:45:54 +0000327 version_string = line[8:].strip()
328 matched_pair = re.search("(.+)\((.+)\)", version_string)
329 if matched_pair:
330 self.process_version = matched_pair.group(1)
331 self.process_compatability_version = matched_pair.group(2)
332 else:
333 self.process = version_string
334 self.process_compatability_version = version_string
Sean Callananc51cd472013-01-12 02:11:49 +0000335 elif self.parent_process_regex.search(line):
336 parent_process_match = self.parent_process_regex.search(line)
337 self.parent_process_name = parent_process_match.group(1)
338 self.parent_process_id = parent_process_match.group(2)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000339 elif line.startswith ('Exception Type:'):
340 self.thread_exception = line[15:].strip()
341 continue
342 elif line.startswith ('Exception Codes:'):
343 self.thread_exception_data = line[16:].strip()
344 continue
345 elif line.startswith ('Crashed Thread:'):
346 self.crashed_thread_idx = int(line[15:].strip().split()[0])
347 continue
348 elif line.startswith ('Report Version:'):
349 self.version = int(line[15:].strip())
350 continue
351 elif line.startswith ('System Profile:'):
352 parse_mode = PARSE_MODE_SYSTEM
353 continue
354 elif (line.startswith ('Interval Since Last Report:') or
355 line.startswith ('Crashes Since Last Report:') or
356 line.startswith ('Per-App Interval Since Last Report:') or
357 line.startswith ('Per-App Crashes Since Last Report:') or
358 line.startswith ('Sleep/Wake UUID:') or
359 line.startswith ('Anonymous UUID:')):
360 # ignore these
361 continue
362 elif line.startswith ('Thread'):
363 thread_state_match = self.thread_state_regex.search (line)
364 if thread_state_match:
Greg Clayton48d157d2015-03-05 22:53:06 +0000365 app_specific_backtrace = False
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000366 thread_state_match = self.thread_regex.search (line)
367 thread_idx = int(thread_state_match.group(1))
368 parse_mode = PARSE_MODE_THREGS
369 thread = self.threads[thread_idx]
370 else:
371 thread_match = self.thread_regex.search (line)
372 if thread_match:
Greg Clayton48d157d2015-03-05 22:53:06 +0000373 app_specific_backtrace = False
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000374 parse_mode = PARSE_MODE_THREAD
375 thread_idx = int(thread_match.group(1))
Greg Clayton48d157d2015-03-05 22:53:06 +0000376 thread = CrashLog.Thread(thread_idx, False)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000377 continue
378 elif line.startswith ('Binary Images:'):
379 parse_mode = PARSE_MODE_IMAGES
380 continue
Greg Clayton48d157d2015-03-05 22:53:06 +0000381 elif line.startswith ('Application Specific Backtrace'):
382 app_backtrace_match = self.app_backtrace_regex.search (line)
383 if app_backtrace_match:
384 parse_mode = PARSE_MODE_THREAD
385 app_specific_backtrace = True
386 idx = int(app_backtrace_match.group(1))
387 thread = CrashLog.Thread(idx, True)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000388 self.info_lines.append(line.strip())
389 elif parse_mode == PARSE_MODE_THREAD:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000390 if line.startswith ('Thread'):
391 continue
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000392 frame_match = self.frame_regex.search(line)
393 if frame_match:
394 ident = frame_match.group(2)
Greg Clayton563d0392012-07-13 03:19:35 +0000395 thread.add_ident(ident)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000396 if not ident in self.idents:
397 self.idents.append(ident)
398 thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
399 else:
Greg Claytona32bfbe2012-01-20 03:32:35 +0000400 print 'error: frame regex failed for line: "%s"' % line
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000401 elif parse_mode == PARSE_MODE_IMAGES:
402 image_match = self.image_regex_uuid.search (line)
403 if image_match:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000404 image = CrashLog.DarwinImage (int(image_match.group(1),0),
405 int(image_match.group(2),0),
406 image_match.group(3).strip(),
407 image_match.group(4).strip(),
Greg Clayton60bb58f2012-05-11 00:30:14 +0000408 uuid.UUID(image_match.group(5)),
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000409 image_match.group(6))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000410 self.images.append (image)
411 else:
412 image_match = self.image_regex_no_uuid.search (line)
413 if image_match:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000414 image = CrashLog.DarwinImage (int(image_match.group(1),0),
415 int(image_match.group(2),0),
416 image_match.group(3).strip(),
417 image_match.group(4).strip(),
418 None,
419 image_match.group(5))
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000420 self.images.append (image)
421 else:
422 print "error: image regex failed for: %s" % line
423
424 elif parse_mode == PARSE_MODE_THREGS:
425 stripped_line = line.strip()
Jason Molendae5ad3852012-08-28 23:46:12 +0000426 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
427 reg_values = re.findall ('([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line);
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000428 for reg_value in reg_values:
Greg Clayton60bb58f2012-05-11 00:30:14 +0000429 #print 'reg_value = "%s"' % reg_value
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000430 (reg, value) = reg_value.split(': ')
Greg Clayton60bb58f2012-05-11 00:30:14 +0000431 #print 'reg = "%s"' % reg
432 #print 'value = "%s"' % value
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000433 thread.registers[reg.strip()] = int(value, 0)
434 elif parse_mode == PARSE_MODE_SYSTEM:
435 self.system_profile.append(line)
436 f.close()
Greg Claytonf51a23f2012-06-04 23:22:17 +0000437
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000438 def dump(self):
439 print "Crash Log File: %s" % (self.path)
Greg Clayton48d157d2015-03-05 22:53:06 +0000440 if self.backtraces:
441 print "\nApplication Specific Backtraces:"
442 for thread in self.backtraces:
443 thread.dump(' ')
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000444 print "\nThreads:"
445 for thread in self.threads:
446 thread.dump(' ')
447 print "\nImages:"
448 for image in self.images:
449 image.dump(' ')
450
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000451 def find_image_with_identifier(self, identifier):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000452 for image in self.images:
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000453 if image.identifier == identifier:
Greg Clayton48d157d2015-03-05 22:53:06 +0000454 return image
455 regex_text = '^.*\.%s$' % (identifier)
456 regex = re.compile(regex_text)
457 for image in self.images:
458 if regex.match(image.identifier):
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000459 return image
460 return None
461
Greg Clayton42a6eb72012-01-20 19:25:32 +0000462 def create_target(self):
Greg Clayton3c2c4bb2012-04-03 21:35:43 +0000463 #print 'crashlog.create_target()...'
Greg Clayton48d157d2015-03-05 22:53:06 +0000464 if self.target is None:
465 self.target = symbolication.Symbolicator.create_target(self)
466 if self.target:
467 return self.target
468 # We weren't able to open the main executable as, but we can still symbolicate
469 print 'crashlog.create_target()...2'
470 if self.idents:
471 for ident in self.idents:
472 image = self.find_image_with_identifier (ident)
473 if image:
474 self.target = image.create_target ()
475 if self.target:
476 return self.target # success
477 print 'crashlog.create_target()...3'
478 for image in self.images:
479 self.target = image.create_target ()
480 if self.target:
481 return self.target # success
482 print 'crashlog.create_target()...4'
483 print 'error: unable to locate any executables from the crash log'
484 return self.target
Greg Claytonf51a23f2012-06-04 23:22:17 +0000485
Greg Clayton48d157d2015-03-05 22:53:06 +0000486 def get_target(self):
487 return self.target
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000488
489def usage():
490 print "Usage: lldb-symbolicate.py [-n name] executable-image"
491 sys.exit(0)
492
Greg Claytonc8f73d72012-05-04 20:44:14 +0000493class Interactive(cmd.Cmd):
494 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
495 image_option_parser = None
496
497 def __init__(self, crash_logs):
498 cmd.Cmd.__init__(self)
Greg Claytonf7ab0382012-07-03 21:40:18 +0000499 self.use_rawinput = False
Greg Claytonc8f73d72012-05-04 20:44:14 +0000500 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
501 self.crash_logs = crash_logs
502 self.prompt = '% '
503
504 def default(self, line):
505 '''Catch all for unknown command, which will exit the interpreter.'''
506 print "uknown command: %s" % line
507 return True
508
509 def do_q(self, line):
510 '''Quit command'''
511 return True
512
513 def do_quit(self, line):
514 '''Quit command'''
515 return True
516
Greg Claytoneb749092012-05-31 21:21:08 +0000517 def do_symbolicate(self, line):
518 description='''Symbolicate one or more darwin crash log files by index to provide source file and line information,
519 inlined stack frames back to the concrete functions, and disassemble the location of the crash
520 for the first frame of the crashed thread.'''
521 option_parser = CreateSymbolicateCrashLogOptions ('symbolicate', description, False)
522 command_args = shlex.split(line)
523 try:
524 (options, args) = option_parser.parse_args(command_args)
525 except:
526 return
527
Greg Clayton784933b2012-07-16 20:40:20 +0000528 if args:
529 # We have arguments, they must valid be crash log file indexes
530 for idx_str in args:
531 idx = int(idx_str)
532 if idx < len(self.crash_logs):
533 SymbolicateCrashLog (self.crash_logs[idx], options)
534 else:
535 print 'error: crash log index %u is out of range' % (idx)
536 else:
537 # No arguments, symbolicate all crash logs using the options provided
538 for idx in range(len(self.crash_logs)):
539 SymbolicateCrashLog (self.crash_logs[idx], options)
Greg Claytoneb749092012-05-31 21:21:08 +0000540
Greg Claytonc8f73d72012-05-04 20:44:14 +0000541 def do_list(self, line=None):
542 '''Dump a list of all crash logs that are currently loaded.
543
544 USAGE: list'''
545 print '%u crash logs are loaded:' % len(self.crash_logs)
546 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
547 print '[%u] = %s' % (crash_log_idx, crash_log.path)
548
549 def do_image(self, line):
Greg Clayton784933b2012-07-16 20:40:20 +0000550 '''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 Claytonc8f73d72012-05-04 20:44:14 +0000551 usage = "usage: %prog [options] <PATH> [PATH ...]"
Greg Clayton784933b2012-07-16 20:40:20 +0000552 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 Claytonc8f73d72012-05-04 20:44:14 +0000553 command_args = shlex.split(line)
554 if not self.image_option_parser:
555 self.image_option_parser = optparse.OptionParser(description=description, prog='image',usage=usage)
556 self.image_option_parser.add_option('-a', '--all', action='store_true', help='show all images', default=False)
557 try:
558 (options, args) = self.image_option_parser.parse_args(command_args)
559 except:
560 return
561
Greg Clayton60bb58f2012-05-11 00:30:14 +0000562 if args:
563 for image_path in args:
564 fullpath_search = image_path[0] == '/'
Greg Clayton784933b2012-07-16 20:40:20 +0000565 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000566 matches_found = 0
Greg Claytonc8f73d72012-05-04 20:44:14 +0000567 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton60bb58f2012-05-11 00:30:14 +0000568 if fullpath_search:
569 if image.get_resolved_path() == image_path:
570 matches_found += 1
Greg Clayton784933b2012-07-16 20:40:20 +0000571 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000572 else:
573 image_basename = image.get_resolved_path_basename()
574 if image_basename == image_path:
575 matches_found += 1
Greg Clayton784933b2012-07-16 20:40:20 +0000576 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000577 if matches_found == 0:
578 for (image_idx, image) in enumerate(crash_log.images):
Greg Clayton563566c2012-05-16 20:49:19 +0000579 resolved_image_path = image.get_resolved_path()
580 if resolved_image_path and string.find(image.get_resolved_path(), image_path) >= 0:
Greg Clayton784933b2012-07-16 20:40:20 +0000581 print '[%u] ' % (crash_log_idx), image
Greg Clayton60bb58f2012-05-11 00:30:14 +0000582 else:
583 for crash_log in self.crash_logs:
584 for (image_idx, image) in enumerate(crash_log.images):
585 print '[%u] %s' % (image_idx, image)
Greg Claytonc8f73d72012-05-04 20:44:14 +0000586 return False
587
588
589def interactive_crashlogs(options, args):
590 crash_log_files = list()
591 for arg in args:
592 for resolved_path in glob.glob(arg):
593 crash_log_files.append(resolved_path)
594
595 crash_logs = list();
596 for crash_log_file in crash_log_files:
597 #print 'crash_log_file = "%s"' % crash_log_file
598 crash_log = CrashLog(crash_log_file)
599 if crash_log.error:
600 print crash_log.error
601 continue
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000602 if options.debug:
Greg Claytonc8f73d72012-05-04 20:44:14 +0000603 crash_log.dump()
604 if not crash_log.images:
605 print 'error: no images in crash log "%s"' % (crash_log)
606 continue
607 else:
608 crash_logs.append(crash_log)
609
610 interpreter = Interactive(crash_logs)
611 # List all crash logs that were imported
612 interpreter.do_list()
613 interpreter.cmdloop()
614
Greg Claytonf47b2c22012-06-27 20:02:04 +0000615
616def save_crashlog(debugger, command, result, dict):
617 usage = "usage: %prog [options] <output-path>"
618 description='''Export the state of current target into a crashlog file'''
619 parser = optparse.OptionParser(description=description, prog='save_crashlog',usage=usage)
620 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
621 try:
622 (options, args) = parser.parse_args(shlex.split(command))
623 except:
624 result.PutCString ("error: invalid options");
625 return
626 if len(args) != 1:
627 result.PutCString ("error: invalid arguments, a single output file is the only valid argument")
628 return
629 out_file = open(args[0], 'w')
630 if not out_file:
631 result.PutCString ("error: failed to open file '%s' for writing...", args[0]);
632 return
Jim Inghamc917a382015-06-23 20:26:45 +0000633 target = debugger.GetSelectedTarget()
634 if target:
635 identifier = target.executable.basename
Greg Claytonf47b2c22012-06-27 20:02:04 +0000636 if lldb.process:
637 pid = lldb.process.id
638 if pid != lldb.LLDB_INVALID_PROCESS_ID:
639 out_file.write('Process: %s [%u]\n' % (identifier, pid))
Jim Inghamc917a382015-06-23 20:26:45 +0000640 out_file.write('Path: %s\n' % (target.executable.fullpath))
Greg Claytonf47b2c22012-06-27 20:02:04 +0000641 out_file.write('Identifier: %s\n' % (identifier))
642 out_file.write('\nDate/Time: %s\n' % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
643 out_file.write('OS Version: Mac OS X %s (%s)\n' % (platform.mac_ver()[0], commands.getoutput('sysctl -n kern.osversion')));
644 out_file.write('Report Version: 9\n')
645 for thread_idx in range(lldb.process.num_threads):
646 thread = lldb.process.thread[thread_idx]
647 out_file.write('\nThread %u:\n' % (thread_idx))
648 for (frame_idx, frame) in enumerate(thread.frames):
649 frame_pc = frame.pc
650 frame_offset = 0
651 if frame.function:
652 block = frame.GetFrameBlock()
653 block_range = block.range[frame.addr]
654 if block_range:
655 block_start_addr = block_range[0]
656 frame_offset = frame_pc - block_start_addr.load_addr
657 else:
658 frame_offset = frame_pc - frame.function.addr.load_addr
659 elif frame.symbol:
660 frame_offset = frame_pc - frame.symbol.addr.load_addr
661 out_file.write('%-3u %-32s 0x%16.16x %s' % (frame_idx, frame.module.file.basename, frame_pc, frame.name))
662 if frame_offset > 0:
663 out_file.write(' + %u' % (frame_offset))
664 line_entry = frame.line_entry
665 if line_entry:
666 if options.verbose:
667 # This will output the fullpath + line + column
668 out_file.write(' %s' % (line_entry))
669 else:
670 out_file.write(' %s:%u' % (line_entry.file.basename, line_entry.line))
671 column = line_entry.column
672 if column:
673 out_file.write(':%u' % (column))
674 out_file.write('\n')
675
676 out_file.write('\nBinary Images:\n')
Jim Inghamc917a382015-06-23 20:26:45 +0000677 for module in target.modules:
Greg Claytonf47b2c22012-06-27 20:02:04 +0000678 text_segment = module.section['__TEXT']
679 if text_segment:
Jim Inghamc917a382015-06-23 20:26:45 +0000680 text_segment_load_addr = text_segment.GetLoadAddress(target)
Greg Claytonf47b2c22012-06-27 20:02:04 +0000681 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
682 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
683 identifier = module.file.basename
684 module_version = '???'
685 module_version_array = module.GetVersion()
686 if module_version_array:
687 module_version = '.'.join(map(str,module_version_array))
688 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))
689 out_file.close()
690 else:
691 result.PutCString ("error: invalid target");
Greg Claytonc8f73d72012-05-04 20:44:14 +0000692
Greg Claytonf47b2c22012-06-27 20:02:04 +0000693
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000694def Symbolicate(debugger, command, result, dict):
Greg Clayton53b43b02012-01-21 04:26:24 +0000695 try:
Greg Claytoneb749092012-05-31 21:21:08 +0000696 SymbolicateCrashLogs (shlex.split(command))
Greg Clayton53b43b02012-01-21 04:26:24 +0000697 except:
698 result.PutCString ("error: python exception %s" % sys.exc_info()[0])
Greg Claytoneb749092012-05-31 21:21:08 +0000699
700def SymbolicateCrashLog(crash_log, options):
701 if crash_log.error:
702 print crash_log.error
703 return
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000704 if options.debug:
Greg Claytoneb749092012-05-31 21:21:08 +0000705 crash_log.dump()
706 if not crash_log.images:
707 print 'error: no images in crash log'
708 return
709
Greg Clayton1f746072012-08-29 21:13:06 +0000710 if options.dump_image_list:
711 print "Binary Images:"
712 for image in crash_log.images:
713 if options.verbose:
714 print image.debug_dump()
715 else:
716 print image
717
Greg Claytoneb749092012-05-31 21:21:08 +0000718 target = crash_log.create_target ()
719 if not target:
720 return
721 exe_module = target.GetModuleAtIndex(0)
722 images_to_load = list()
723 loaded_images = list()
724 if options.load_all_images:
725 # --load-all option was specified, load everything up
726 for image in crash_log.images:
727 images_to_load.append(image)
728 else:
729 # Only load the images found in stack frames for the crashed threads
Greg Clayton563d0392012-07-13 03:19:35 +0000730 if options.crashed_only:
731 for thread in crash_log.threads:
732 if thread.did_crash():
733 for ident in thread.idents:
734 images = crash_log.find_images_with_identifier (ident)
735 if images:
736 for image in images:
737 images_to_load.append(image)
738 else:
739 print 'error: can\'t find image for identifier "%s"' % ident
740 else:
741 for ident in crash_log.idents:
742 images = crash_log.find_images_with_identifier (ident)
743 if images:
744 for image in images:
745 images_to_load.append(image)
746 else:
747 print 'error: can\'t find image for identifier "%s"' % ident
Greg Claytoneb749092012-05-31 21:21:08 +0000748
749 for image in images_to_load:
Greg Clayton48d157d2015-03-05 22:53:06 +0000750 if not image in loaded_images:
Greg Claytoneb749092012-05-31 21:21:08 +0000751 err = image.add_module (target)
752 if err:
753 print err
754 else:
755 #print 'loaded %s' % image
756 loaded_images.append(image)
757
Greg Clayton48d157d2015-03-05 22:53:06 +0000758 if crash_log.backtraces:
759 for thread in crash_log.backtraces:
760 thread.dump_symbolicated (crash_log, options)
761 print
762
Greg Claytoneb749092012-05-31 21:21:08 +0000763 for thread in crash_log.threads:
Greg Clayton48d157d2015-03-05 22:53:06 +0000764 thread.dump_symbolicated (crash_log, options)
Greg Claytoneb749092012-05-31 21:21:08 +0000765 print
766
Greg Clayton48d157d2015-03-05 22:53:06 +0000767
Greg Claytoneb749092012-05-31 21:21:08 +0000768def CreateSymbolicateCrashLogOptions(command_name, description, add_interactive_options):
Greg Clayton5c0f4832012-01-21 00:37:19 +0000769 usage = "usage: %prog [options] <FILE> [FILE ...]"
Greg Claytoneb749092012-05-31 21:21:08 +0000770 option_parser = optparse.OptionParser(description=description, prog='crashlog',usage=usage)
Greg Claytonc51d70d2012-07-13 17:58:52 +0000771 option_parser.add_option('--verbose' , '-v', action='store_true', dest='verbose', help='display verbose debug info', default=False)
772 option_parser.add_option('--debug' , '-g', action='store_true', dest='debug', help='display verbose debug logging', default=False)
773 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)
774 option_parser.add_option('--images' , action='store_true', dest='dump_image_list', help='show image list', default=False)
775 option_parser.add_option('--debug-delay' , type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
776 option_parser.add_option('--crashed-only' , '-c', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
777 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)
778 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)
779 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)
780 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)
781 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)
782 option_parser.add_option('--source-frames' , type='int', metavar='NFRAMES', dest='source_frames', help='show source for NFRAMES (default = 4)', default=4)
783 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 Claytoneb749092012-05-31 21:21:08 +0000784 if add_interactive_options:
785 option_parser.add_option('-i', '--interactive', action='store_true', help='parse all crash logs and enter interactive mode', default=False)
786 return option_parser
787
788def SymbolicateCrashLogs(command_args):
Greg Clayton5c0f4832012-01-21 00:37:19 +0000789 description='''Symbolicate one or more darwin crash log files to provide source file and line information,
790inlined stack frames back to the concrete functions, and disassemble the location of the crash
791for the first frame of the crashed thread.
792If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
793for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
794created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
795you to explore the program as if it were stopped at the locations described in the crash log and functions can
796be disassembled and lookups can be performed using the addresses found in the crash log.'''
Greg Claytoneb749092012-05-31 21:21:08 +0000797 option_parser = CreateSymbolicateCrashLogOptions ('crashlog', description, True)
Greg Clayton53b43b02012-01-21 04:26:24 +0000798 try:
Greg Claytoneb749092012-05-31 21:21:08 +0000799 (options, args) = option_parser.parse_args(command_args)
Greg Clayton53b43b02012-01-21 04:26:24 +0000800 except:
801 return
802
Greg Claytona7fb1dc2012-06-28 18:10:14 +0000803 if options.debug:
Greg Clayton53b43b02012-01-21 04:26:24 +0000804 print 'command_args = %s' % command_args
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000805 print 'options', options
Greg Clayton53b43b02012-01-21 04:26:24 +0000806 print 'args', args
807
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000808 if options.debug_delay > 0:
809 print "Waiting %u seconds for debugger to attach..." % options.debug_delay
810 time.sleep(options.debug_delay)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000811 error = lldb.SBError()
Greg Claytonc8f73d72012-05-04 20:44:14 +0000812
Greg Clayton42a6eb72012-01-20 19:25:32 +0000813 if args:
Greg Claytonc8f73d72012-05-04 20:44:14 +0000814 if options.interactive:
815 interactive_crashlogs(options, args)
816 else:
817 for crash_log_file in args:
Greg Claytonf51a23f2012-06-04 23:22:17 +0000818 crash_log = CrashLog(crash_log_file)
Greg Claytoneb749092012-05-31 21:21:08 +0000819 SymbolicateCrashLog (crash_log, options)
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000820if __name__ == '__main__':
Greg Clayton42a6eb72012-01-20 19:25:32 +0000821 # Create a new debugger instance
822 lldb.debugger = lldb.SBDebugger.Create()
Greg Claytoneb749092012-05-31 21:21:08 +0000823 SymbolicateCrashLogs (sys.argv[1:])
Jason Molenda7a5014b2015-04-01 02:09:04 +0000824 lldb.SBDebugger.Destroy (lldb.debugger)
Johnny Chen356782f2012-05-03 22:31:30 +0000825elif getattr(lldb, 'debugger', None):
Greg Claytoned3eee62012-04-25 01:49:50 +0000826 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
Greg Claytonf47b2c22012-06-27 20:02:04 +0000827 lldb.debugger.HandleCommand('command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
828 print '"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help'
Greg Claytonaa9d02b2012-01-20 03:15:45 +0000829