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