Revert "[clang-repl] Implement partial translation units and error recovery."
[llvm-project.git] / lldb / examples / python / crashlog.py
bloba7b2d3b52b56231dc1ecea9e89c1f8fb9c380be3
1 #!/usr/bin/env python3
3 #----------------------------------------------------------------------
4 # Be sure to add the python path that points to the LLDB shared library.
6 # To use this in the embedded python interpreter using "lldb":
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
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.
22 # On MacOSX csh, tcsh:
23 # ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
25 # On MacOSX sh, bash:
26 # PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
27 #----------------------------------------------------------------------
29 from __future__ import print_function
30 import cmd
31 import datetime
32 import glob
33 import optparse
34 import os
35 import platform
36 import plistlib
37 import re
38 import shlex
39 import string
40 import subprocess
41 import sys
42 import time
43 import uuid
44 import json
46 try:
47 # First try for LLDB in case PYTHONPATH is already correctly setup.
48 import lldb
49 except ImportError:
50 # Ask the command line driver for the path to the lldb module. Copy over
51 # the environment so that SDKROOT is propagated to xcrun.
52 env = os.environ.copy()
53 env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
54 command = ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P']
55 # Extend the PYTHONPATH if the path exists and isn't already there.
56 lldb_python_path = subprocess.check_output(command, env=env).decode("utf-8").strip()
57 if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path):
58 sys.path.append(lldb_python_path)
59 # Try importing LLDB again.
60 try:
61 import lldb
62 except ImportError:
63 print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
64 sys.exit(1)
66 from lldb.utils import symbolication
69 def read_plist(s):
70 if sys.version_info.major == 3:
71 return plistlib.loads(s)
72 else:
73 return plistlib.readPlistFromString(s)
75 class CrashLog(symbolication.Symbolicator):
76 class Thread:
77 """Class that represents a thread in a darwin crash log"""
79 def __init__(self, index, app_specific_backtrace):
80 self.index = index
81 self.frames = list()
82 self.idents = list()
83 self.registers = dict()
84 self.reason = None
85 self.queue = None
86 self.app_specific_backtrace = app_specific_backtrace
88 def dump(self, prefix):
89 if self.app_specific_backtrace:
90 print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason))
91 else:
92 print("%sThread[%u] %s" % (prefix, self.index, self.reason))
93 if self.frames:
94 print("%s Frames:" % (prefix))
95 for frame in self.frames:
96 frame.dump(prefix + ' ')
97 if self.registers:
98 print("%s Registers:" % (prefix))
99 for reg in self.registers.keys():
100 print("%s %-8s = %#16.16x" % (prefix, reg, self.registers[reg]))
102 def dump_symbolicated(self, crash_log, options):
103 this_thread_crashed = self.app_specific_backtrace
104 if not this_thread_crashed:
105 this_thread_crashed = self.did_crash()
106 if options.crashed_only and this_thread_crashed == False:
107 return
109 print("%s" % self)
110 display_frame_idx = -1
111 for frame_idx, frame in enumerate(self.frames):
112 disassemble = (
113 this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth
114 if frame_idx == 0:
115 symbolicated_frame_addresses = crash_log.symbolicate(
116 frame.pc & crash_log.addr_mask, options.verbose)
117 else:
118 # Any frame above frame zero and we have to subtract one to
119 # get the previous line entry
120 symbolicated_frame_addresses = crash_log.symbolicate(
121 (frame.pc & crash_log.addr_mask) - 1, options.verbose)
123 if symbolicated_frame_addresses:
124 symbolicated_frame_address_idx = 0
125 for symbolicated_frame_address in symbolicated_frame_addresses:
126 display_frame_idx += 1
127 print('[%3u] %s' % (frame_idx, symbolicated_frame_address))
128 if (options.source_all or self.did_crash(
129 )) and display_frame_idx < options.source_frames and options.source_context:
130 source_context = options.source_context
131 line_entry = symbolicated_frame_address.get_symbol_context().line_entry
132 if line_entry.IsValid():
133 strm = lldb.SBStream()
134 if line_entry:
135 crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
136 line_entry.file, line_entry.line, source_context, source_context, "->", strm)
137 source_text = strm.GetData()
138 if source_text:
139 # Indent the source a bit
140 indent_str = ' '
141 join_str = '\n' + indent_str
142 print('%s%s' % (indent_str, join_str.join(source_text.split('\n'))))
143 if symbolicated_frame_address_idx == 0:
144 if disassemble:
145 instructions = symbolicated_frame_address.get_instructions()
146 if instructions:
147 print()
148 symbolication.disassemble_instructions(
149 crash_log.get_target(),
150 instructions,
151 frame.pc,
152 options.disassemble_before,
153 options.disassemble_after,
154 frame.index > 0)
155 print()
156 symbolicated_frame_address_idx += 1
157 else:
158 print(frame)
159 if self.registers:
160 print()
161 for reg in self.registers.keys():
162 print(" %-8s = %#16.16x" % (reg, self.registers[reg]))
164 def add_ident(self, ident):
165 if ident not in self.idents:
166 self.idents.append(ident)
168 def did_crash(self):
169 return self.reason is not None
171 def __str__(self):
172 if self.app_specific_backtrace:
173 s = "Application Specific Backtrace[%u]" % self.index
174 else:
175 s = "Thread[%u]" % self.index
176 if self.reason:
177 s += ' %s' % self.reason
178 return s
180 class Frame:
181 """Class that represents a stack frame in a thread in a darwin crash log"""
183 def __init__(self, index, pc, description):
184 self.pc = pc
185 self.description = description
186 self.index = index
188 def __str__(self):
189 if self.description:
190 return "[%3u] 0x%16.16x %s" % (
191 self.index, self.pc, self.description)
192 else:
193 return "[%3u] 0x%16.16x" % (self.index, self.pc)
195 def dump(self, prefix):
196 print("%s%s" % (prefix, str(self)))
198 class DarwinImage(symbolication.Image):
199 """Class that represents a binary images in a darwin crash log"""
200 dsymForUUIDBinary = '/usr/local/bin/dsymForUUID'
201 if not os.path.exists(dsymForUUIDBinary):
202 try:
203 dsymForUUIDBinary = subprocess.check_output('which dsymForUUID',
204 shell=True).decode("utf-8").rstrip('\n')
205 except:
206 dsymForUUIDBinary = ""
208 dwarfdump_uuid_regex = re.compile(
209 'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
211 def __init__(
212 self,
213 text_addr_lo,
214 text_addr_hi,
215 identifier,
216 version,
217 uuid,
218 path,
219 verbose):
220 symbolication.Image.__init__(self, path, uuid)
221 self.add_section(
222 symbolication.Section(
223 text_addr_lo,
224 text_addr_hi,
225 "__TEXT"))
226 self.identifier = identifier
227 self.version = version
228 self.verbose = verbose
230 def show_symbol_progress(self):
232 Hide progress output and errors from system frameworks as they are plentiful.
234 if self.verbose:
235 return True
236 return not (self.path.startswith("/System/Library/") or
237 self.path.startswith("/usr/lib/"))
240 def find_matching_slice(self):
241 dwarfdump_cmd_output = subprocess.check_output(
242 'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8")
243 self_uuid = self.get_uuid()
244 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 return True
253 if not self.resolved_path:
254 self.unavailable = True
255 if self.show_symbol_progress():
256 print(("error\n error: unable to locate '%s' with UUID %s"
257 % (self.path, self.get_normalized_uuid_string())))
258 return False
260 def locate_module_and_debug_symbols(self):
261 # Don't load a module twice...
262 if self.resolved:
263 return True
264 # Mark this as resolved so we don't keep trying
265 self.resolved = True
266 uuid_str = self.get_normalized_uuid_string()
267 if self.show_symbol_progress():
268 print('Getting symbols for %s %s...' % (uuid_str, self.path), end=' ')
269 if os.path.exists(self.dsymForUUIDBinary):
270 dsym_for_uuid_command = '%s %s' % (
271 self.dsymForUUIDBinary, uuid_str)
272 s = subprocess.check_output(dsym_for_uuid_command, shell=True)
273 if s:
274 try:
275 plist_root = read_plist(s)
276 except:
277 print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s))
278 raise
279 if plist_root:
280 plist = plist_root[uuid_str]
281 if plist:
282 if 'DBGArchitecture' in plist:
283 self.arch = plist['DBGArchitecture']
284 if 'DBGDSYMPath' in plist:
285 self.symfile = os.path.realpath(
286 plist['DBGDSYMPath'])
287 if 'DBGSymbolRichExecutable' in plist:
288 self.path = os.path.expanduser(
289 plist['DBGSymbolRichExecutable'])
290 self.resolved_path = self.path
291 if not self.resolved_path and os.path.exists(self.path):
292 if not self.find_matching_slice():
293 return False
294 if not self.resolved_path and not os.path.exists(self.path):
295 try:
296 dsym = subprocess.check_output(
297 ["/usr/bin/mdfind",
298 "com_apple_xcode_dsym_uuids == %s"%uuid_str]).decode("utf-8")[:-1]
299 if dsym and os.path.exists(dsym):
300 print(('falling back to binary inside "%s"'%dsym))
301 self.symfile = dsym
302 dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
303 for filename in os.listdir(dwarf_dir):
304 self.path = os.path.join(dwarf_dir, filename)
305 if not self.find_matching_slice():
306 return False
307 break
308 except:
309 pass
310 if (self.resolved_path and os.path.exists(self.resolved_path)) or (
311 self.path and os.path.exists(self.path)):
312 print('ok')
313 return True
314 else:
315 self.unavailable = True
316 return False
318 def __init__(self, debugger, path, verbose):
319 """CrashLog constructor that take a path to a darwin crash log file"""
320 symbolication.Symbolicator.__init__(self, debugger)
321 self.path = os.path.expanduser(path)
322 self.info_lines = list()
323 self.system_profile = list()
324 self.threads = list()
325 self.backtraces = list() # For application specific backtraces
326 self.idents = list() # A list of the required identifiers for doing all stack backtraces
327 self.crashed_thread_idx = -1
328 self.version = -1
329 self.target = None
330 self.verbose = verbose
332 def dump(self):
333 print("Crash Log File: %s" % (self.path))
334 if self.backtraces:
335 print("\nApplication Specific Backtraces:")
336 for thread in self.backtraces:
337 thread.dump(' ')
338 print("\nThreads:")
339 for thread in self.threads:
340 thread.dump(' ')
341 print("\nImages:")
342 for image in self.images:
343 image.dump(' ')
345 def find_image_with_identifier(self, identifier):
346 for image in self.images:
347 if image.identifier == identifier:
348 return image
349 regex_text = '^.*\.%s$' % (re.escape(identifier))
350 regex = re.compile(regex_text)
351 for image in self.images:
352 if regex.match(image.identifier):
353 return image
354 return None
356 def create_target(self):
357 if self.target is None:
358 self.target = symbolication.Symbolicator.create_target(self)
359 if self.target:
360 return self.target
361 # We weren't able to open the main executable as, but we can still
362 # symbolicate
363 print('crashlog.create_target()...2')
364 if self.idents:
365 for ident in self.idents:
366 image = self.find_image_with_identifier(ident)
367 if image:
368 self.target = image.create_target(self.debugger)
369 if self.target:
370 return self.target # success
371 print('crashlog.create_target()...3')
372 for image in self.images:
373 self.target = image.create_target(self.debugger)
374 if self.target:
375 return self.target # success
376 print('crashlog.create_target()...4')
377 print('error: Unable to locate any executables from the crash log.')
378 print(' Try loading the executable into lldb before running crashlog')
379 print(' and/or make sure the .dSYM bundles can be found by Spotlight.')
380 return self.target
382 def get_target(self):
383 return self.target
386 class CrashLogFormatException(Exception):
387 pass
390 class CrashLogParseException(Exception):
391 pass
394 class CrashLogParser:
395 def parse(self, debugger, path, verbose):
396 try:
397 return JSONCrashLogParser(debugger, path, verbose).parse()
398 except CrashLogFormatException:
399 return TextCrashLogParser(debugger, path, verbose).parse()
402 class JSONCrashLogParser:
403 def __init__(self, debugger, path, verbose):
404 self.path = os.path.expanduser(path)
405 self.verbose = verbose
406 self.crashlog = CrashLog(debugger, self.path, self.verbose)
408 def parse(self):
409 with open(self.path, 'r') as f:
410 buffer = f.read()
412 # First line is meta-data.
413 buffer = buffer[buffer.index('\n') + 1:]
415 try:
416 self.data = json.loads(buffer)
417 except ValueError:
418 raise CrashLogFormatException()
420 try:
421 self.parse_process_info(self.data)
422 self.parse_images(self.data['usedImages'])
423 self.parse_threads(self.data['threads'])
424 thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
425 reason = self.parse_crash_reason(self.data['exception'])
426 if thread.reason:
427 thread.reason = '{} {}'.format(thread.reason, reason)
428 else:
429 thread.reason = reason
430 except (KeyError, ValueError, TypeError) as e:
431 raise CrashLogParseException(
432 'Failed to parse JSON crashlog: {}: {}'.format(
433 type(e).__name__, e))
435 return self.crashlog
437 def get_used_image(self, idx):
438 return self.data['usedImages'][idx]
440 def parse_process_info(self, json_data):
441 self.crashlog.process_id = json_data['pid']
442 self.crashlog.process_identifier = json_data['procName']
443 self.crashlog.process_path = json_data['procPath']
445 def parse_crash_reason(self, json_exception):
446 exception_type = json_exception['type']
447 exception_signal = json_exception['signal']
448 if 'codes' in json_exception:
449 exception_extra = " ({})".format(json_exception['codes'])
450 elif 'subtype' in json_exception:
451 exception_extra = " ({})".format(json_exception['subtype'])
452 else:
453 exception_extra = ""
454 return "{} ({}){}".format(exception_type, exception_signal,
455 exception_extra)
457 def parse_images(self, json_images):
458 idx = 0
459 for json_image in json_images:
460 img_uuid = uuid.UUID(json_image['uuid'])
461 low = int(json_image['base'])
462 high = int(0)
463 name = json_image['name'] if 'name' in json_image else ''
464 path = json_image['path'] if 'path' in json_image else ''
465 version = ''
466 darwin_image = self.crashlog.DarwinImage(low, high, name, version,
467 img_uuid, path,
468 self.verbose)
469 self.crashlog.images.append(darwin_image)
470 idx += 1
472 def parse_frames(self, thread, json_frames):
473 idx = 0
474 for json_frame in json_frames:
475 image_id = int(json_frame['imageIndex'])
476 ident = self.get_used_image(image_id)['name']
477 thread.add_ident(ident)
478 if ident not in self.crashlog.idents:
479 self.crashlog.idents.append(ident)
481 frame_offset = int(json_frame['imageOffset'])
482 image_addr = self.get_used_image(image_id)['base']
483 pc = image_addr + frame_offset
484 thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
485 idx += 1
487 def parse_threads(self, json_threads):
488 idx = 0
489 for json_thread in json_threads:
490 thread = self.crashlog.Thread(idx, False)
491 if 'name' in json_thread:
492 thread.reason = json_thread['name']
493 if json_thread.get('triggered', False):
494 self.crashlog.crashed_thread_idx = idx
495 thread.registers = self.parse_thread_registers(
496 json_thread['threadState'])
497 thread.queue = json_thread.get('queue')
498 self.parse_frames(thread, json_thread.get('frames', []))
499 self.crashlog.threads.append(thread)
500 idx += 1
502 def parse_thread_registers(self, json_thread_state):
503 registers = dict()
504 for key, state in json_thread_state.items():
505 try:
506 value = int(state['value'])
507 registers[key] = value
508 except (TypeError, ValueError):
509 pass
510 return registers
513 class CrashLogParseMode:
514 NORMAL = 0
515 THREAD = 1
516 IMAGES = 2
517 THREGS = 3
518 SYSTEM = 4
519 INSTRS = 5
522 class TextCrashLogParser:
523 parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]')
524 thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
525 thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream')
526 thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
527 app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
528 version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+'
529 frame_regex = re.compile(r'^([0-9]+)' r'\s' # id
530 r'+(.+?)' r'\s+' # img_name
531 r'(' +version+ r')?' # img_version
532 r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr
533 r' +(.*)' # offs
535 null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)')
536 image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)' # img_lo
537 r'\s+' '-' r'\s+' # -
538 r'(0x[0-9a-fA-F]+)' r'\s+' # img_hi
539 r'[+]?(.+?)' r'\s+' # img_name
540 r'(' +version+ ')?' # img_version
541 r'(<([-0-9a-fA-F]+)>\s+)?' # img_uuid
542 r'(/.*)' # img_path
546 def __init__(self, debugger, path, verbose):
547 self.path = os.path.expanduser(path)
548 self.verbose = verbose
549 self.thread = None
550 self.app_specific_backtrace = False
551 self.crashlog = CrashLog(debugger, self.path, self.verbose)
552 self.parse_mode = CrashLogParseMode.NORMAL
553 self.parsers = {
554 CrashLogParseMode.NORMAL : self.parse_normal,
555 CrashLogParseMode.THREAD : self.parse_thread,
556 CrashLogParseMode.IMAGES : self.parse_images,
557 CrashLogParseMode.THREGS : self.parse_thread_registers,
558 CrashLogParseMode.SYSTEM : self.parse_system,
559 CrashLogParseMode.INSTRS : self.parse_instructions,
562 def parse(self):
563 with open(self.path,'r') as f:
564 lines = f.read().splitlines()
566 for line in lines:
567 line_len = len(line)
568 if line_len == 0:
569 if self.thread:
570 if self.parse_mode == CrashLogParseMode.THREAD:
571 if self.thread.index == self.crashlog.crashed_thread_idx:
572 self.thread.reason = ''
573 if self.crashlog.thread_exception:
574 self.thread.reason += self.crashlog.thread_exception
575 if self.crashlog.thread_exception_data:
576 self.thread.reason += " (%s)" % self.crashlog.thread_exception_data
577 if self.app_specific_backtrace:
578 self.crashlog.backtraces.append(self.thread)
579 else:
580 self.crashlog.threads.append(self.thread)
581 self.thread = None
582 else:
583 # only append an extra empty line if the previous line
584 # in the info_lines wasn't empty
585 if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]):
586 self.crashlog.info_lines.append(line)
587 self.parse_mode = CrashLogParseMode.NORMAL
588 else:
589 self.parsers[self.parse_mode](line)
591 return self.crashlog
594 def parse_normal(self, line):
595 if line.startswith('Process:'):
596 (self.crashlog.process_name, pid_with_brackets) = line[
597 8:].strip().split(' [')
598 self.crashlog.process_id = pid_with_brackets.strip('[]')
599 elif line.startswith('Path:'):
600 self.crashlog.process_path = line[5:].strip()
601 elif line.startswith('Identifier:'):
602 self.crashlog.process_identifier = line[11:].strip()
603 elif line.startswith('Version:'):
604 version_string = line[8:].strip()
605 matched_pair = re.search("(.+)\((.+)\)", version_string)
606 if matched_pair:
607 self.crashlog.process_version = matched_pair.group(1)
608 self.crashlog.process_compatability_version = matched_pair.group(
610 else:
611 self.crashlog.process = version_string
612 self.crashlog.process_compatability_version = version_string
613 elif self.parent_process_regex.search(line):
614 parent_process_match = self.parent_process_regex.search(
615 line)
616 self.crashlog.parent_process_name = parent_process_match.group(1)
617 self.crashlog.parent_process_id = parent_process_match.group(2)
618 elif line.startswith('Exception Type:'):
619 self.crashlog.thread_exception = line[15:].strip()
620 return
621 elif line.startswith('Exception Codes:'):
622 self.crashlog.thread_exception_data = line[16:].strip()
623 return
624 elif line.startswith('Exception Subtype:'): # iOS
625 self.crashlog.thread_exception_data = line[18:].strip()
626 return
627 elif line.startswith('Crashed Thread:'):
628 self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0])
629 return
630 elif line.startswith('Triggered by Thread:'): # iOS
631 self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0])
632 return
633 elif line.startswith('Report Version:'):
634 self.crashlog.version = int(line[15:].strip())
635 return
636 elif line.startswith('System Profile:'):
637 self.parse_mode = CrashLogParseMode.SYSTEM
638 return
639 elif (line.startswith('Interval Since Last Report:') or
640 line.startswith('Crashes Since Last Report:') or
641 line.startswith('Per-App Interval Since Last Report:') or
642 line.startswith('Per-App Crashes Since Last Report:') or
643 line.startswith('Sleep/Wake UUID:') or
644 line.startswith('Anonymous UUID:')):
645 # ignore these
646 return
647 elif line.startswith('Thread'):
648 thread_state_match = self.thread_state_regex.search(line)
649 if thread_state_match:
650 self.app_specific_backtrace = False
651 thread_state_match = self.thread_regex.search(line)
652 thread_idx = int(thread_state_match.group(1))
653 self.parse_mode = CrashLogParseMode.THREGS
654 self.thread = self.crashlog.threads[thread_idx]
655 return
656 thread_insts_match = self.thread_instrs_regex.search(line)
657 if thread_insts_match:
658 self.parse_mode = CrashLogParseMode.INSTRS
659 return
660 thread_match = self.thread_regex.search(line)
661 if thread_match:
662 self.app_specific_backtrace = False
663 self.parse_mode = CrashLogParseMode.THREAD
664 thread_idx = int(thread_match.group(1))
665 self.thread = self.crashlog.Thread(thread_idx, False)
666 return
667 return
668 elif line.startswith('Binary Images:'):
669 self.parse_mode = CrashLogParseMode.IMAGES
670 return
671 elif line.startswith('Application Specific Backtrace'):
672 app_backtrace_match = self.app_backtrace_regex.search(line)
673 if app_backtrace_match:
674 self.parse_mode = CrashLogParseMode.THREAD
675 self.app_specific_backtrace = True
676 idx = int(app_backtrace_match.group(1))
677 self.thread = self.crashlog.Thread(idx, True)
678 elif line.startswith('Last Exception Backtrace:'): # iOS
679 self.parse_mode = CrashLogParseMode.THREAD
680 self.app_specific_backtrace = True
681 idx = 1
682 self.thread = self.crashlog.Thread(idx, True)
683 self.crashlog.info_lines.append(line.strip())
685 def parse_thread(self, line):
686 if line.startswith('Thread'):
687 return
688 if self.null_frame_regex.search(line):
689 print('warning: thread parser ignored null-frame: "%s"' % line)
690 return
691 frame_match = self.frame_regex.search(line)
692 if frame_match:
693 (frame_id, frame_img_name, _, frame_img_version, _,
694 frame_addr, frame_ofs) = frame_match.groups()
695 ident = frame_img_name
696 self.thread.add_ident(ident)
697 if ident not in self.crashlog.idents:
698 self.crashlog.idents.append(ident)
699 self.thread.frames.append(self.crashlog.Frame(int(frame_id), int(
700 frame_addr, 0), frame_ofs))
701 else:
702 print('error: frame regex failed for line: "%s"' % line)
704 def parse_images(self, line):
705 image_match = self.image_regex_uuid.search(line)
706 if image_match:
707 (img_lo, img_hi, img_name, _, img_version, _,
708 _, img_uuid, img_path) = image_match.groups()
709 image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
710 img_name.strip(),
711 img_version.strip()
712 if img_version else "",
713 uuid.UUID(img_uuid), img_path,
714 self.verbose)
715 self.crashlog.images.append(image)
716 else:
717 print("error: image regex failed for: %s" % line)
720 def parse_thread_registers(self, line):
721 stripped_line = line.strip()
722 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
723 reg_values = re.findall(
724 '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line)
725 for reg_value in reg_values:
726 (reg, value) = reg_value.split(': ')
727 self.thread.registers[reg.strip()] = int(value, 0)
729 def parse_system(self, line):
730 self.crashlog.system_profile.append(line)
732 def parse_instructions(self, line):
733 pass
736 def usage():
737 print("Usage: lldb-symbolicate.py [-n name] executable-image")
738 sys.exit(0)
741 class Interactive(cmd.Cmd):
742 '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
743 image_option_parser = None
745 def __init__(self, crash_logs):
746 cmd.Cmd.__init__(self)
747 self.use_rawinput = False
748 self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
749 self.crash_logs = crash_logs
750 self.prompt = '% '
752 def default(self, line):
753 '''Catch all for unknown command, which will exit the interpreter.'''
754 print("uknown command: %s" % line)
755 return True
757 def do_q(self, line):
758 '''Quit command'''
759 return True
761 def do_quit(self, line):
762 '''Quit command'''
763 return True
765 def do_symbolicate(self, line):
766 description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information,
767 inlined stack frames back to the concrete functions, and disassemble the location of the crash
768 for the first frame of the crashed thread.'''
769 option_parser = CreateSymbolicateCrashLogOptions(
770 'symbolicate', description, False)
771 command_args = shlex.split(line)
772 try:
773 (options, args) = option_parser.parse_args(command_args)
774 except:
775 return
777 if args:
778 # We have arguments, they must valid be crash log file indexes
779 for idx_str in args:
780 idx = int(idx_str)
781 if idx < len(self.crash_logs):
782 SymbolicateCrashLog(self.crash_logs[idx], options)
783 else:
784 print('error: crash log index %u is out of range' % (idx))
785 else:
786 # No arguments, symbolicate all crash logs using the options
787 # provided
788 for idx in range(len(self.crash_logs)):
789 SymbolicateCrashLog(self.crash_logs[idx], options)
791 def do_list(self, line=None):
792 '''Dump a list of all crash logs that are currently loaded.
794 USAGE: list'''
795 print('%u crash logs are loaded:' % len(self.crash_logs))
796 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
797 print('[%u] = %s' % (crash_log_idx, crash_log.path))
799 def do_image(self, line):
800 '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
801 usage = "usage: %prog [options] <PATH> [PATH ...]"
802 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.'''
803 command_args = shlex.split(line)
804 if not self.image_option_parser:
805 self.image_option_parser = optparse.OptionParser(
806 description=description, prog='image', usage=usage)
807 self.image_option_parser.add_option(
808 '-a',
809 '--all',
810 action='store_true',
811 help='show all images',
812 default=False)
813 try:
814 (options, args) = self.image_option_parser.parse_args(command_args)
815 except:
816 return
818 if args:
819 for image_path in args:
820 fullpath_search = image_path[0] == '/'
821 for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
822 matches_found = 0
823 for (image_idx, image) in enumerate(crash_log.images):
824 if fullpath_search:
825 if image.get_resolved_path() == image_path:
826 matches_found += 1
827 print('[%u] ' % (crash_log_idx), image)
828 else:
829 image_basename = image.get_resolved_path_basename()
830 if image_basename == image_path:
831 matches_found += 1
832 print('[%u] ' % (crash_log_idx), image)
833 if matches_found == 0:
834 for (image_idx, image) in enumerate(crash_log.images):
835 resolved_image_path = image.get_resolved_path()
836 if resolved_image_path and string.find(
837 image.get_resolved_path(), image_path) >= 0:
838 print('[%u] ' % (crash_log_idx), image)
839 else:
840 for crash_log in self.crash_logs:
841 for (image_idx, image) in enumerate(crash_log.images):
842 print('[%u] %s' % (image_idx, image))
843 return False
846 def interactive_crashlogs(debugger, options, args):
847 crash_log_files = list()
848 for arg in args:
849 for resolved_path in glob.glob(arg):
850 crash_log_files.append(resolved_path)
852 crash_logs = list()
853 for crash_log_file in crash_log_files:
854 try:
855 crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose)
856 except Exception as e:
857 print(e)
858 continue
859 if options.debug:
860 crash_log.dump()
861 if not crash_log.images:
862 print('error: no images in crash log "%s"' % (crash_log))
863 continue
864 else:
865 crash_logs.append(crash_log)
867 interpreter = Interactive(crash_logs)
868 # List all crash logs that were imported
869 interpreter.do_list()
870 interpreter.cmdloop()
873 def save_crashlog(debugger, command, exe_ctx, result, dict):
874 usage = "usage: %prog [options] <output-path>"
875 description = '''Export the state of current target into a crashlog file'''
876 parser = optparse.OptionParser(
877 description=description,
878 prog='save_crashlog',
879 usage=usage)
880 parser.add_option(
881 '-v',
882 '--verbose',
883 action='store_true',
884 dest='verbose',
885 help='display verbose debug info',
886 default=False)
887 try:
888 (options, args) = parser.parse_args(shlex.split(command))
889 except:
890 result.PutCString("error: invalid options")
891 return
892 if len(args) != 1:
893 result.PutCString(
894 "error: invalid arguments, a single output file is the only valid argument")
895 return
896 out_file = open(args[0], 'w')
897 if not out_file:
898 result.PutCString(
899 "error: failed to open file '%s' for writing...",
900 args[0])
901 return
902 target = exe_ctx.target
903 if target:
904 identifier = target.executable.basename
905 process = exe_ctx.process
906 if process:
907 pid = process.id
908 if pid != lldb.LLDB_INVALID_PROCESS_ID:
909 out_file.write(
910 'Process: %s [%u]\n' %
911 (identifier, pid))
912 out_file.write('Path: %s\n' % (target.executable.fullpath))
913 out_file.write('Identifier: %s\n' % (identifier))
914 out_file.write('\nDate/Time: %s\n' %
915 (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
916 out_file.write(
917 'OS Version: Mac OS X %s (%s)\n' %
918 (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8")))
919 out_file.write('Report Version: 9\n')
920 for thread_idx in range(process.num_threads):
921 thread = process.thread[thread_idx]
922 out_file.write('\nThread %u:\n' % (thread_idx))
923 for (frame_idx, frame) in enumerate(thread.frames):
924 frame_pc = frame.pc
925 frame_offset = 0
926 if frame.function:
927 block = frame.GetFrameBlock()
928 block_range = block.range[frame.addr]
929 if block_range:
930 block_start_addr = block_range[0]
931 frame_offset = frame_pc - block_start_addr.GetLoadAddress(target)
932 else:
933 frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target)
934 elif frame.symbol:
935 frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
936 out_file.write(
937 '%-3u %-32s 0x%16.16x %s' %
938 (frame_idx, frame.module.file.basename, frame_pc, frame.name))
939 if frame_offset > 0:
940 out_file.write(' + %u' % (frame_offset))
941 line_entry = frame.line_entry
942 if line_entry:
943 if options.verbose:
944 # This will output the fullpath + line + column
945 out_file.write(' %s' % (line_entry))
946 else:
947 out_file.write(
948 ' %s:%u' %
949 (line_entry.file.basename, line_entry.line))
950 column = line_entry.column
951 if column:
952 out_file.write(':%u' % (column))
953 out_file.write('\n')
955 out_file.write('\nBinary Images:\n')
956 for module in target.modules:
957 text_segment = module.section['__TEXT']
958 if text_segment:
959 text_segment_load_addr = text_segment.GetLoadAddress(target)
960 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
961 text_segment_end_load_addr = text_segment_load_addr + text_segment.size
962 identifier = module.file.basename
963 module_version = '???'
964 module_version_array = module.GetVersion()
965 if module_version_array:
966 module_version = '.'.join(
967 map(str, module_version_array))
968 out_file.write(
969 ' 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n' %
970 (text_segment_load_addr,
971 text_segment_end_load_addr,
972 identifier,
973 module_version,
974 module.GetUUIDString(),
975 module.file.fullpath))
976 out_file.close()
977 else:
978 result.PutCString("error: invalid target")
981 def Symbolicate(debugger, command, result, dict):
982 try:
983 SymbolicateCrashLogs(debugger, shlex.split(command))
984 except Exception as e:
985 result.PutCString("error: python exception: %s" % e)
988 def SymbolicateCrashLog(crash_log, options):
989 if options.debug:
990 crash_log.dump()
991 if not crash_log.images:
992 print('error: no images in crash log')
993 return
995 if options.dump_image_list:
996 print("Binary Images:")
997 for image in crash_log.images:
998 if options.verbose:
999 print(image.debug_dump())
1000 else:
1001 print(image)
1003 target = crash_log.create_target()
1004 if not target:
1005 return
1006 exe_module = target.GetModuleAtIndex(0)
1007 images_to_load = list()
1008 loaded_images = list()
1009 if options.load_all_images:
1010 # --load-all option was specified, load everything up
1011 for image in crash_log.images:
1012 images_to_load.append(image)
1013 else:
1014 # Only load the images found in stack frames for the crashed threads
1015 if options.crashed_only:
1016 for thread in crash_log.threads:
1017 if thread.did_crash():
1018 for ident in thread.idents:
1019 images = crash_log.find_images_with_identifier(ident)
1020 if images:
1021 for image in images:
1022 images_to_load.append(image)
1023 else:
1024 print('error: can\'t find image for identifier "%s"' % ident)
1025 else:
1026 for ident in crash_log.idents:
1027 images = crash_log.find_images_with_identifier(ident)
1028 if images:
1029 for image in images:
1030 images_to_load.append(image)
1031 else:
1032 print('error: can\'t find image for identifier "%s"' % ident)
1034 for image in images_to_load:
1035 if image not in loaded_images:
1036 err = image.add_module(target)
1037 if err:
1038 print(err)
1039 else:
1040 loaded_images.append(image)
1042 if crash_log.backtraces:
1043 for thread in crash_log.backtraces:
1044 thread.dump_symbolicated(crash_log, options)
1045 print()
1047 for thread in crash_log.threads:
1048 thread.dump_symbolicated(crash_log, options)
1049 print()
1052 def CreateSymbolicateCrashLogOptions(
1053 command_name,
1054 description,
1055 add_interactive_options):
1056 usage = "usage: %prog [options] <FILE> [FILE ...]"
1057 option_parser = optparse.OptionParser(
1058 description=description, prog='crashlog', usage=usage)
1059 option_parser.add_option(
1060 '--verbose',
1061 '-v',
1062 action='store_true',
1063 dest='verbose',
1064 help='display verbose debug info',
1065 default=False)
1066 option_parser.add_option(
1067 '--debug',
1068 '-g',
1069 action='store_true',
1070 dest='debug',
1071 help='display verbose debug logging',
1072 default=False)
1073 option_parser.add_option(
1074 '--load-all',
1075 '-a',
1076 action='store_true',
1077 dest='load_all_images',
1078 help='load all executable images, not just the images found in the crashed stack frames',
1079 default=False)
1080 option_parser.add_option(
1081 '--images',
1082 action='store_true',
1083 dest='dump_image_list',
1084 help='show image list',
1085 default=False)
1086 option_parser.add_option(
1087 '--debug-delay',
1088 type='int',
1089 dest='debug_delay',
1090 metavar='NSEC',
1091 help='pause for NSEC seconds for debugger',
1092 default=0)
1093 option_parser.add_option(
1094 '--crashed-only',
1095 '-c',
1096 action='store_true',
1097 dest='crashed_only',
1098 help='only symbolicate the crashed thread',
1099 default=False)
1100 option_parser.add_option(
1101 '--disasm-depth',
1102 '-d',
1103 type='int',
1104 dest='disassemble_depth',
1105 help='set the depth in stack frames that should be disassembled (default is 1)',
1106 default=1)
1107 option_parser.add_option(
1108 '--disasm-all',
1109 '-D',
1110 action='store_true',
1111 dest='disassemble_all_threads',
1112 help='enabled disassembly of frames on all threads (not just the crashed thread)',
1113 default=False)
1114 option_parser.add_option(
1115 '--disasm-before',
1116 '-B',
1117 type='int',
1118 dest='disassemble_before',
1119 help='the number of instructions to disassemble before the frame PC',
1120 default=4)
1121 option_parser.add_option(
1122 '--disasm-after',
1123 '-A',
1124 type='int',
1125 dest='disassemble_after',
1126 help='the number of instructions to disassemble after the frame PC',
1127 default=4)
1128 option_parser.add_option(
1129 '--source-context',
1130 '-C',
1131 type='int',
1132 metavar='NLINES',
1133 dest='source_context',
1134 help='show NLINES source lines of source context (default = 4)',
1135 default=4)
1136 option_parser.add_option(
1137 '--source-frames',
1138 type='int',
1139 metavar='NFRAMES',
1140 dest='source_frames',
1141 help='show source for NFRAMES (default = 4)',
1142 default=4)
1143 option_parser.add_option(
1144 '--source-all',
1145 action='store_true',
1146 dest='source_all',
1147 help='show source for all threads, not just the crashed thread',
1148 default=False)
1149 if add_interactive_options:
1150 option_parser.add_option(
1151 '-i',
1152 '--interactive',
1153 action='store_true',
1154 help='parse all crash logs and enter interactive mode',
1155 default=False)
1156 return option_parser
1159 def SymbolicateCrashLogs(debugger, command_args):
1160 description = '''Symbolicate one or more darwin crash log files to provide source file and line information,
1161 inlined stack frames back to the concrete functions, and disassemble the location of the crash
1162 for the first frame of the crashed thread.
1163 If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1164 for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1165 created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1166 you to explore the program as if it were stopped at the locations described in the crash log and functions can
1167 be disassembled and lookups can be performed using the addresses found in the crash log.'''
1168 option_parser = CreateSymbolicateCrashLogOptions(
1169 'crashlog', description, True)
1170 try:
1171 (options, args) = option_parser.parse_args(command_args)
1172 except:
1173 return
1175 if options.debug:
1176 print('command_args = %s' % command_args)
1177 print('options', options)
1178 print('args', args)
1180 if options.debug_delay > 0:
1181 print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1182 time.sleep(options.debug_delay)
1183 error = lldb.SBError()
1185 if args:
1186 if options.interactive:
1187 interactive_crashlogs(debugger, options, args)
1188 else:
1189 for crash_log_file in args:
1190 crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose)
1191 SymbolicateCrashLog(crash_log, options)
1192 if __name__ == '__main__':
1193 # Create a new debugger instance
1194 debugger = lldb.SBDebugger.Create()
1195 SymbolicateCrashLogs(debugger, sys.argv[1:])
1196 lldb.SBDebugger.Destroy(debugger)
1197 elif getattr(lldb, 'debugger', None):
1198 lldb.debugger.HandleCommand(
1199 'command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
1200 lldb.debugger.HandleCommand(
1201 'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')