[AArch64] Fix SDNode type mismatches between *.td files and ISel (#116523)
[llvm-project.git] / lldb / examples / python / crashlog.py
blob368437ed63e46bc35f82f6c8d1373741fe4fb2b9
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 import abc
30 import argparse
31 import concurrent.futures
32 import contextlib
33 import datetime
34 import json
35 import os
36 import platform
37 import plistlib
38 import re
39 import shlex
40 import string
41 import subprocess
42 import sys
43 import tempfile
44 import threading
45 import time
46 import uuid
49 print_lock = threading.RLock()
51 try:
52 # First try for LLDB in case PYTHONPATH is already correctly setup.
53 import lldb
54 except ImportError:
55 # Ask the command line driver for the path to the lldb module. Copy over
56 # the environment so that SDKROOT is propagated to xcrun.
57 command = (
58 ["xcrun", "lldb", "-P"] if platform.system() == "Darwin" else ["lldb", "-P"]
60 # Extend the PYTHONPATH if the path exists and isn't already there.
61 lldb_python_path = subprocess.check_output(command).decode("utf-8").strip()
62 if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path):
63 sys.path.append(lldb_python_path)
64 # Try importing LLDB again.
65 try:
66 import lldb
67 except ImportError:
68 print(
69 "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
71 sys.exit(1)
73 from lldb.utils import symbolication
74 from lldb.plugins.scripted_process import INTEL64_GPR, ARM64_GPR
77 def read_plist(s):
78 if sys.version_info.major == 3:
79 return plistlib.loads(s)
80 else:
81 return plistlib.readPlistFromString(s)
84 class CrashLog(symbolication.Symbolicator):
85 class Thread:
86 """Class that represents a thread in a darwin crash log"""
88 def __init__(self, index, app_specific_backtrace, arch):
89 self.index = index
90 self.id = index
91 self.images = list()
92 self.frames = list()
93 self.idents = list()
94 self.registers = dict()
95 self.reason = None
96 self.name = None
97 self.queue = None
98 self.crashed = False
99 self.app_specific_backtrace = app_specific_backtrace
100 self.arch = arch
102 def dump_registers(self, prefix=""):
103 registers_info = None
104 sorted_registers = {}
106 def sort_dict(d):
107 sorted_keys = list(d.keys())
108 sorted_keys.sort()
109 return {k: d[k] for k in sorted_keys}
111 if self.arch:
112 if "x86_64" == self.arch:
113 registers_info = INTEL64_GPR
114 elif "arm64" in self.arch:
115 registers_info = ARM64_GPR
116 else:
117 print("unknown target architecture: %s" % self.arch)
118 return
120 # Add registers available in the register information dictionary.
121 for reg_info in registers_info:
122 reg_name = None
123 if reg_info["name"] in self.registers:
124 reg_name = reg_info["name"]
125 elif (
126 "generic" in reg_info and reg_info["generic"] in self.registers
128 reg_name = reg_info["generic"]
129 else:
130 # Skip register that are present in the register information dictionary but not present in the report.
131 continue
133 reg_val = self.registers[reg_name]
134 sorted_registers[reg_name] = reg_val
136 unknown_parsed_registers = {}
137 for reg_name in self.registers:
138 if reg_name not in sorted_registers:
139 unknown_parsed_registers[reg_name] = self.registers[reg_name]
141 sorted_registers.update(sort_dict(unknown_parsed_registers))
143 else:
144 sorted_registers = sort_dict(self.registers)
146 for reg_name, reg_val in sorted_registers.items():
147 print("%s %-8s = %#16.16x" % (prefix, reg_name, reg_val))
149 def dump(self, prefix=""):
150 if self.app_specific_backtrace:
151 print(
152 "%Application Specific Backtrace[%u] %s"
153 % (prefix, self.index, self.reason)
155 else:
156 print("%sThread[%u] %s" % (prefix, self.index, self.reason))
157 if self.frames:
158 print("%s Frames:" % (prefix))
159 for frame in self.frames:
160 frame.dump(prefix + " ")
161 if self.registers:
162 print("%s Registers:" % (prefix))
163 self.dump_registers(prefix)
165 def dump_symbolicated(self, crash_log, options):
166 this_thread_crashed = self.app_specific_backtrace
167 if not this_thread_crashed:
168 this_thread_crashed = self.did_crash()
169 if options.crashed_only and not this_thread_crashed:
170 return
172 print("%s" % self)
173 display_frame_idx = -1
174 for frame_idx, frame in enumerate(self.frames):
175 disassemble = (
176 this_thread_crashed or options.disassemble_all_threads
177 ) and frame_idx < options.disassemble_depth
179 # Except for the zeroth frame, we should subtract 1 from every
180 # frame pc to get the previous line entry.
181 pc = frame.pc & crash_log.addr_mask
182 pc = pc if frame_idx == 0 or pc == 0 else pc - 1
183 symbolicated_frame_addresses = crash_log.symbolicate(
184 pc, options.verbose
187 if symbolicated_frame_addresses:
188 symbolicated_frame_address_idx = 0
189 for symbolicated_frame_address in symbolicated_frame_addresses:
190 display_frame_idx += 1
191 print("[%3u] %s" % (frame_idx, symbolicated_frame_address))
192 if (
193 (options.source_all or self.did_crash())
194 and display_frame_idx < options.source_frames
195 and options.source_context
197 source_context = options.source_context
198 line_entry = (
199 symbolicated_frame_address.get_symbol_context().line_entry
201 if line_entry.IsValid():
202 strm = lldb.SBStream()
203 if line_entry:
204 crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
205 line_entry.file,
206 line_entry.line,
207 source_context,
208 source_context,
209 "->",
210 strm,
212 source_text = strm.GetData()
213 if source_text:
214 # Indent the source a bit
215 indent_str = " "
216 join_str = "\n" + indent_str
217 print(
218 "%s%s"
220 indent_str,
221 join_str.join(source_text.split("\n")),
224 if symbolicated_frame_address_idx == 0:
225 if disassemble:
226 instructions = (
227 symbolicated_frame_address.get_instructions()
229 if instructions:
230 print()
231 symbolication.disassemble_instructions(
232 crash_log.get_target(),
233 instructions,
234 frame.pc,
235 options.disassemble_before,
236 options.disassemble_after,
237 frame.index > 0,
239 print()
240 symbolicated_frame_address_idx += 1
241 else:
242 print(frame)
243 if self.registers:
244 print()
245 self.dump_registers()
246 elif self.crashed:
247 print()
248 print("No thread state (register information) available")
250 def add_ident(self, ident):
251 if ident not in self.idents:
252 self.idents.append(ident)
254 def did_crash(self):
255 return self.crashed
257 def __str__(self):
258 if self.app_specific_backtrace:
259 s = "Application Specific Backtrace[%u]" % self.index
260 else:
261 s = "Thread[%u]" % self.index
262 if self.reason:
263 s += " %s" % self.reason
264 return s
266 class Frame:
267 """Class that represents a stack frame in a thread in a darwin crash log"""
269 def __init__(self, index, pc, description):
270 self.pc = pc
271 self.description = description
272 self.index = index
274 def __str__(self):
275 if self.description:
276 return "[%3u] 0x%16.16x %s" % (self.index, self.pc, self.description)
277 else:
278 return "[%3u] 0x%16.16x" % (self.index, self.pc)
280 def dump(self, prefix):
281 print("%s%s" % (prefix, str(self)))
283 class DarwinImage(symbolication.Image):
284 """Class that represents a binary images in a darwin crash log"""
286 dsymForUUIDBinary = "/usr/local/bin/dsymForUUID"
287 if "LLDB_APPLE_DSYMFORUUID_EXECUTABLE" in os.environ:
288 dsymForUUIDBinary = os.environ["LLDB_APPLE_DSYMFORUUID_EXECUTABLE"]
289 elif not os.path.exists(dsymForUUIDBinary):
290 try:
291 dsymForUUIDBinary = (
292 subprocess.check_output("which dsymForUUID", shell=True)
293 .decode("utf-8")
294 .rstrip("\n")
296 except:
297 dsymForUUIDBinary = ""
299 dwarfdump_uuid_regex = re.compile("UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*")
301 def __init__(
302 self, text_addr_lo, text_addr_hi, identifier, version, uuid, path, verbose
304 symbolication.Image.__init__(self, path, uuid)
305 self.add_section(
306 symbolication.Section(text_addr_lo, text_addr_hi, "__TEXT")
308 self.identifier = identifier
309 self.version = version
310 self.verbose = verbose
312 def show_symbol_progress(self):
314 Hide progress output and errors from system frameworks as they are plentiful.
316 if self.verbose:
317 return True
318 return not (
319 self.path.startswith("/System/Library/")
320 or self.path.startswith("/usr/lib/")
323 def find_matching_slice(self):
324 dwarfdump_cmd_output = subprocess.check_output(
325 'dwarfdump --uuid "%s"' % self.path, shell=True
326 ).decode("utf-8")
327 self_uuid = self.get_uuid()
328 for line in dwarfdump_cmd_output.splitlines():
329 match = self.dwarfdump_uuid_regex.search(line)
330 if match:
331 dwarf_uuid_str = match.group(1)
332 dwarf_uuid = uuid.UUID(dwarf_uuid_str)
333 if self_uuid == dwarf_uuid:
334 self.resolved_path = self.path
335 self.arch = match.group(2)
336 return True
337 if not self.resolved_path:
338 self.unavailable = True
339 if self.show_symbol_progress():
340 print(
342 "error\n error: unable to locate '%s' with UUID %s"
343 % (self.path, self.get_normalized_uuid_string())
346 return False
348 def locate_module_and_debug_symbols(self):
349 # Don't load a module twice...
350 if self.resolved:
351 return True
352 # Mark this as resolved so we don't keep trying
353 self.resolved = True
354 uuid_str = self.get_normalized_uuid_string()
355 if self.show_symbol_progress():
356 with print_lock:
357 print("Getting symbols for %s %s..." % (uuid_str, self.path))
358 # Keep track of unresolved source paths.
359 unavailable_source_paths = set()
360 if os.path.exists(self.dsymForUUIDBinary):
361 dsym_for_uuid_command = (
362 "{} --copyExecutable --ignoreNegativeCache {}".format(
363 self.dsymForUUIDBinary, uuid_str
366 s = subprocess.check_output(dsym_for_uuid_command, shell=True)
367 if s:
368 try:
369 plist_root = read_plist(s)
370 except:
371 with print_lock:
372 print(
374 "Got exception: ",
375 sys.exc_info()[1],
376 " handling dsymForUUID output: \n",
380 raise
381 if plist_root:
382 plist = plist_root[uuid_str]
383 if plist:
384 if "DBGArchitecture" in plist:
385 self.arch = plist["DBGArchitecture"]
386 if "DBGDSYMPath" in plist:
387 self.symfile = os.path.realpath(plist["DBGDSYMPath"])
388 if "DBGSymbolRichExecutable" in plist:
389 self.path = os.path.expanduser(
390 plist["DBGSymbolRichExecutable"]
392 self.resolved_path = self.path
393 if "DBGSourcePathRemapping" in plist:
394 path_remapping = plist["DBGSourcePathRemapping"]
395 for _, value in path_remapping.items():
396 source_path = os.path.expanduser(value)
397 if not os.path.exists(source_path):
398 unavailable_source_paths.add(source_path)
399 if not self.resolved_path and os.path.exists(self.path):
400 if not self.find_matching_slice():
401 return False
402 if not self.resolved_path and not os.path.exists(self.path):
403 try:
404 mdfind_results = (
405 subprocess.check_output(
407 "/usr/bin/mdfind",
408 "com_apple_xcode_dsym_uuids == %s" % uuid_str,
411 .decode("utf-8")
412 .splitlines()
414 found_matching_slice = False
415 for dsym in mdfind_results:
416 dwarf_dir = os.path.join(dsym, "Contents/Resources/DWARF")
417 if not os.path.exists(dwarf_dir):
418 # Not a dSYM bundle, probably an Xcode archive.
419 continue
420 with print_lock:
421 print('falling back to binary inside "%s"' % dsym)
422 self.symfile = dsym
423 # Look for the executable next to the dSYM bundle.
424 parent_dir = os.path.dirname(dsym)
425 executables = []
426 for root, _, files in os.walk(parent_dir):
427 for file in files:
428 abs_path = os.path.join(root, file)
429 if os.path.isfile(abs_path) and os.access(
430 abs_path, os.X_OK
432 executables.append(abs_path)
433 for binary in executables:
434 basename = os.path.basename(binary)
435 if basename == self.identifier:
436 self.path = binary
437 found_matching_slice = True
438 break
439 if found_matching_slice:
440 break
441 except:
442 pass
443 if (self.resolved_path and os.path.exists(self.resolved_path)) or (
444 self.path and os.path.exists(self.path)
446 with print_lock:
447 print("Resolved symbols for %s %s..." % (uuid_str, self.path))
448 if len(unavailable_source_paths):
449 for source_path in unavailable_source_paths:
450 print(
451 "Could not access remapped source path for %s %s"
452 % (uuid_str, source_path)
454 return True
455 else:
456 self.unavailable = True
457 return False
459 def __init__(self, debugger, path, verbose):
460 """CrashLog constructor that take a path to a darwin crash log file"""
461 symbolication.Symbolicator.__init__(self, debugger)
462 self.path = os.path.expanduser(path)
463 self.info_lines = list()
464 self.system_profile = list()
465 self.threads = list()
466 self.backtraces = list() # For application specific backtraces
467 self.idents = (
468 list()
469 ) # A list of the required identifiers for doing all stack backtraces
470 self.errors = list()
471 self.exception = dict()
472 self.crashed_thread_idx = -1
473 self.version = -1
474 self.target = None
475 self.verbose = verbose
476 self.process_id = None
477 self.process_identifier = None
478 self.process_path = None
479 self.process_arch = None
481 def dump(self):
482 print("Crash Log File: %s" % (self.path))
483 if self.backtraces:
484 print("\nApplication Specific Backtraces:")
485 for thread in self.backtraces:
486 thread.dump(" ")
487 print("\nThreads:")
488 for thread in self.threads:
489 thread.dump(" ")
490 print("\nImages:")
491 for image in self.images:
492 image.dump(" ")
494 def set_main_image(self, identifier):
495 for i, image in enumerate(self.images):
496 if image.identifier == identifier:
497 self.images.insert(0, self.images.pop(i))
498 break
500 def find_image_with_identifier(self, identifier):
501 for image in self.images:
502 if image.identifier == identifier:
503 return image
504 regex_text = "^.*\.%s$" % (re.escape(identifier))
505 regex = re.compile(regex_text)
506 for image in self.images:
507 if regex.match(image.identifier):
508 return image
509 return None
511 def create_target(self):
512 if self.target is None:
513 self.target = symbolication.Symbolicator.create_target(self)
514 if self.target:
515 return self.target
516 # We weren't able to open the main executable as, but we can still
517 # symbolicate
518 print("crashlog.create_target()...2")
519 if self.idents:
520 for ident in self.idents:
521 image = self.find_image_with_identifier(ident)
522 if image:
523 self.target = image.create_target(self.debugger)
524 if self.target:
525 return self.target # success
526 print("crashlog.create_target()...3")
527 for image in self.images:
528 self.target = image.create_target(self.debugger)
529 if self.target:
530 return self.target # success
531 print("crashlog.create_target()...4")
532 print("error: Unable to locate any executables from the crash log.")
533 print(" Try loading the executable into lldb before running crashlog")
534 print(
535 " and/or make sure the .dSYM bundles can be found by Spotlight."
537 return self.target
539 def get_target(self):
540 return self.target
542 def load_images(self, options, loaded_images=None):
543 if not loaded_images:
544 loaded_images = []
545 images_to_load = self.images
546 if options.load_all_images:
547 for image in self.images:
548 image.resolve = True
549 elif options.crashed_only:
550 images_to_load = []
551 for thread in self.threads:
552 if thread.did_crash() or thread.app_specific_backtrace:
553 for ident in thread.idents:
554 for image in self.find_images_with_identifier(ident):
555 image.resolve = True
556 images_to_load.append(image)
558 futures = []
559 with tempfile.TemporaryDirectory() as obj_dir:
561 def add_module(image, target, obj_dir):
562 return image, image.add_module(target, obj_dir)
564 max_worker = None
565 if options.no_parallel_image_loading:
566 max_worker = 1
568 with concurrent.futures.ThreadPoolExecutor(max_worker) as executor:
569 for image in images_to_load:
570 if image not in loaded_images:
571 if image.uuid == uuid.UUID(int=0):
572 continue
573 futures.append(
574 executor.submit(
575 add_module,
576 image=image,
577 target=self.target,
578 obj_dir=obj_dir,
582 for future in concurrent.futures.as_completed(futures):
583 image, err = future.result()
584 if err:
585 print(err)
586 else:
587 loaded_images.append(image)
590 class CrashLogFormatException(Exception):
591 pass
594 class CrashLogParseException(Exception):
595 pass
598 class InteractiveCrashLogException(Exception):
599 pass
602 class CrashLogParser:
603 @staticmethod
604 def create(debugger, path, options):
605 data = JSONCrashLogParser.is_valid_json(path)
606 if data:
607 parser = JSONCrashLogParser(debugger, path, options)
608 parser.data = data
609 return parser
610 else:
611 return TextCrashLogParser(debugger, path, options)
613 def __init__(self, debugger, path, options):
614 self.path = os.path.expanduser(path)
615 self.options = options
616 self.crashlog = CrashLog(debugger, self.path, self.options.verbose)
618 @abc.abstractmethod
619 def parse(self):
620 pass
623 class JSONCrashLogParser(CrashLogParser):
624 @staticmethod
625 def is_valid_json(path):
626 def parse_json(buffer):
627 try:
628 return json.loads(buffer)
629 except:
630 # The first line can contain meta data. Try stripping it and
631 # try again.
632 head, _, tail = buffer.partition("\n")
633 return json.loads(tail)
635 with open(path, "r", encoding="utf-8") as f:
636 buffer = f.read()
637 try:
638 return parse_json(buffer)
639 except:
640 return None
642 def __init__(self, debugger, path, options):
643 super().__init__(debugger, path, options)
645 def parse(self):
646 try:
647 self.parse_process_info(self.data)
648 self.parse_images(self.data["usedImages"])
649 self.parse_main_image(self.data)
650 self.parse_threads(self.data["threads"])
651 if "asi" in self.data:
652 self.crashlog.asi = self.data["asi"]
653 # FIXME: With the current design, we can either show the ASI or Last
654 # Exception Backtrace, not both. Is there a situation where we would
655 # like to show both ?
656 if "asiBacktraces" in self.data:
657 self.parse_app_specific_backtraces(self.data["asiBacktraces"])
658 if "lastExceptionBacktrace" in self.data:
659 self.parse_last_exception_backtraces(
660 self.data["lastExceptionBacktrace"]
662 self.parse_errors(self.data)
663 thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
664 reason = self.parse_crash_reason(self.data["exception"])
665 if thread.reason:
666 thread.reason = "{} {}".format(thread.reason, reason)
667 else:
668 thread.reason = reason
669 except (KeyError, ValueError, TypeError) as e:
670 raise CrashLogParseException(
671 "Failed to parse JSON crashlog: {}: {}".format(type(e).__name__, e)
674 return self.crashlog
676 def get_used_image(self, idx):
677 return self.data["usedImages"][idx]
679 def parse_process_info(self, json_data):
680 self.crashlog.process_id = json_data["pid"]
681 self.crashlog.process_identifier = json_data["procName"]
682 if "procPath" in json_data:
683 self.crashlog.process_path = json_data["procPath"]
685 def parse_crash_reason(self, json_exception):
686 self.crashlog.exception = json_exception
687 exception_type = json_exception["type"]
688 exception_signal = " "
689 if "signal" in json_exception:
690 exception_signal += "({})".format(json_exception["signal"])
692 if "codes" in json_exception:
693 exception_extra = " ({})".format(json_exception["codes"])
694 elif "subtype" in json_exception:
695 exception_extra = " ({})".format(json_exception["subtype"])
696 else:
697 exception_extra = ""
698 return "{}{}{}".format(exception_type, exception_signal, exception_extra)
700 def parse_images(self, json_images):
701 for json_image in json_images:
702 img_uuid = uuid.UUID(json_image["uuid"])
703 low = int(json_image["base"])
704 high = low + int(json_image["size"]) if "size" in json_image else low
705 name = json_image["name"] if "name" in json_image else ""
706 path = json_image["path"] if "path" in json_image else ""
707 version = ""
708 darwin_image = self.crashlog.DarwinImage(
709 low, high, name, version, img_uuid, path, self.options.verbose
711 if "arch" in json_image:
712 darwin_image.arch = json_image["arch"]
713 if path == self.crashlog.process_path:
714 self.crashlog.process_arch = darwin_image.arch
715 self.crashlog.images.append(darwin_image)
717 def parse_main_image(self, json_data):
718 if "procName" in json_data:
719 proc_name = json_data["procName"]
720 self.crashlog.set_main_image(proc_name)
722 def parse_frames(self, thread, json_frames):
723 idx = 0
724 for json_frame in json_frames:
725 image_id = int(json_frame["imageIndex"])
726 json_image = self.get_used_image(image_id)
727 ident = json_image["name"] if "name" in json_image else ""
728 thread.add_ident(ident)
729 if ident not in self.crashlog.idents:
730 self.crashlog.idents.append(ident)
732 frame_offset = int(json_frame["imageOffset"])
733 image_addr = self.get_used_image(image_id)["base"]
734 pc = image_addr + frame_offset
736 if "symbol" in json_frame:
737 symbol = json_frame["symbol"]
738 location = 0
739 if "symbolLocation" in json_frame and json_frame["symbolLocation"]:
740 location = int(json_frame["symbolLocation"])
741 image = self.crashlog.images[image_id]
742 image.symbols[symbol] = {
743 "name": symbol,
744 "type": "code",
745 "address": frame_offset - location,
748 thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
750 # on arm64 systems, if it jump through a null function pointer,
751 # we end up at address 0 and the crash reporter unwinder
752 # misses the frame that actually faulted.
753 # But $lr can tell us where the last BL/BLR instruction used
754 # was at, so insert that address as the caller stack frame.
755 if idx == 0 and pc == 0 and "lr" in thread.registers:
756 pc = thread.registers["lr"]
757 for image in self.data["usedImages"]:
758 text_lo = image["base"]
759 text_hi = text_lo + image["size"]
760 if text_lo <= pc < text_hi:
761 idx += 1
762 frame_offset = pc - text_lo
763 thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
764 break
766 idx += 1
768 def parse_threads(self, json_threads):
769 idx = 0
770 for json_thread in json_threads:
771 thread = self.crashlog.Thread(idx, False, self.crashlog.process_arch)
772 if "name" in json_thread:
773 thread.name = json_thread["name"]
774 thread.reason = json_thread["name"]
775 if "id" in json_thread:
776 thread.id = int(json_thread["id"])
777 if json_thread.get("triggered", False):
778 self.crashlog.crashed_thread_idx = idx
779 thread.crashed = True
780 if "threadState" in json_thread:
781 thread.registers = self.parse_thread_registers(
782 json_thread["threadState"]
784 if "queue" in json_thread:
785 thread.queue = json_thread.get("queue")
786 self.parse_frames(thread, json_thread.get("frames", []))
787 self.crashlog.threads.append(thread)
788 idx += 1
790 def parse_asi_backtrace(self, thread, bt):
791 for line in bt.split("\n"):
792 frame_match = TextCrashLogParser.frame_regex.search(line)
793 if not frame_match:
794 print("error: can't parse application specific backtrace.")
795 return False
797 frame_id = (
798 frame_img_name
799 ) = (
800 frame_addr
801 ) = (
802 frame_symbol
803 ) = frame_offset = frame_file = frame_line = frame_column = None
805 if len(frame_match.groups()) == 3:
806 # Get the image UUID from the frame image name.
807 (frame_id, frame_img_name, frame_addr) = frame_match.groups()
808 elif len(frame_match.groups()) == 5:
810 frame_id,
811 frame_img_name,
812 frame_addr,
813 frame_symbol,
814 frame_offset,
815 ) = frame_match.groups()
816 elif len(frame_match.groups()) == 7:
818 frame_id,
819 frame_img_name,
820 frame_addr,
821 frame_symbol,
822 frame_offset,
823 frame_file,
824 frame_line,
825 ) = frame_match.groups()
826 elif len(frame_match.groups()) == 8:
828 frame_id,
829 frame_img_name,
830 frame_addr,
831 frame_symbol,
832 frame_offset,
833 frame_file,
834 frame_line,
835 frame_column,
836 ) = frame_match.groups()
838 thread.add_ident(frame_img_name)
839 if frame_img_name not in self.crashlog.idents:
840 self.crashlog.idents.append(frame_img_name)
842 description = ""
843 if frame_img_name and frame_addr and frame_symbol:
844 description = frame_symbol
845 frame_offset_value = 0
846 if frame_offset:
847 description += " + " + frame_offset
848 frame_offset_value = int(frame_offset, 0)
849 for image in self.crashlog.images:
850 if image.identifier == frame_img_name:
851 image.symbols[frame_symbol] = {
852 "name": frame_symbol,
853 "type": "code",
854 "address": int(frame_addr, 0) - frame_offset_value,
857 thread.frames.append(
858 self.crashlog.Frame(int(frame_id), int(frame_addr, 0), description)
861 return True
863 def parse_app_specific_backtraces(self, json_app_specific_bts):
864 thread = self.crashlog.Thread(
865 len(self.crashlog.threads), True, self.crashlog.process_arch
867 thread.name = "Application Specific Backtrace"
868 if self.parse_asi_backtrace(thread, json_app_specific_bts[0]):
869 self.crashlog.threads.append(thread)
870 else:
871 print("error: Couldn't parse Application Specific Backtrace.")
873 def parse_last_exception_backtraces(self, json_last_exc_bts):
874 thread = self.crashlog.Thread(
875 len(self.crashlog.threads), True, self.crashlog.process_arch
877 thread.name = "Last Exception Backtrace"
878 self.parse_frames(thread, json_last_exc_bts)
879 self.crashlog.threads.append(thread)
881 def parse_thread_registers(self, json_thread_state, prefix=None):
882 registers = dict()
883 for key, state in json_thread_state.items():
884 if key == "rosetta":
885 registers.update(self.parse_thread_registers(state))
886 continue
887 if key == "x":
888 gpr_dict = {str(idx): reg for idx, reg in enumerate(state)}
889 registers.update(self.parse_thread_registers(gpr_dict, key))
890 continue
891 if key == "flavor":
892 if not self.crashlog.process_arch:
893 if state == "ARM_THREAD_STATE64":
894 self.crashlog.process_arch = "arm64"
895 elif state == "X86_THREAD_STATE":
896 self.crashlog.process_arch = "x86_64"
897 continue
898 try:
899 value = int(state["value"])
900 registers["{}{}".format(prefix or "", key)] = value
901 except (KeyError, ValueError, TypeError):
902 pass
903 return registers
905 def parse_errors(self, json_data):
906 if "reportNotes" in json_data:
907 self.crashlog.errors = json_data["reportNotes"]
910 class TextCrashLogParser(CrashLogParser):
911 parent_process_regex = re.compile(r"^Parent Process:\s*(.*)\[(\d+)\]")
912 thread_state_regex = re.compile(r"^Thread (\d+ crashed with|State)")
913 thread_instrs_regex = re.compile(r"^Thread \d+ instruction stream")
914 thread_regex = re.compile(r"^Thread (\d+).*")
915 app_backtrace_regex = re.compile(r"^Application Specific Backtrace (\d+).*")
917 class VersionRegex:
918 version = r"\(.+\)|(?:arm|x86_)[0-9a-z]+"
920 class FrameRegex(VersionRegex):
921 @classmethod
922 def get(cls):
923 index = r"^(\d+)\s+"
924 img_name = r"(.+?)\s+"
925 version = r"(?:" + super().version + r"\s+)?"
926 address = r"(0x[0-9a-fA-F]{4,})" # 4 digits or more
928 symbol = """
930 [ ]+
931 (?P<symbol>.+)
933 [ ]\+[ ]
934 (?P<symbol_offset>\d+)
937 [ ]\(
938 (?P<file_name>[^:]+):(?P<line_number>\d+)
940 :(?P<column_num>\d+)
946 return re.compile(
947 index + img_name + version + address + symbol, flags=re.VERBOSE
950 frame_regex = FrameRegex.get()
951 null_frame_regex = re.compile(r"^\d+\s+\?\?\?\s+0{4,} +")
952 image_regex_uuid = re.compile(
953 r"(0x[0-9a-fA-F]+)" # img_lo
954 r"\s+-\s+" # -
955 r"(0x[0-9a-fA-F]+)\s+" # img_hi
956 r"[+]?(.+?)\s+" # img_name
957 r"(?:(" + VersionRegex.version + r")\s+)?" # img_version
958 r"(?:<([-0-9a-fA-F]+)>\s+)?" # img_uuid
959 r"(\?+|/.*)" # img_path
961 exception_type_regex = re.compile(
962 r"^Exception Type:\s+(EXC_[A-Z_]+)(?:\s+\((.*)\))?"
964 exception_codes_regex = re.compile(
965 r"^Exception Codes:\s+(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+)"
967 exception_extra_regex = re.compile(r"^Exception\s+.*:\s+(.*)")
969 class CrashLogParseMode:
970 NORMAL = 0
971 THREAD = 1
972 IMAGES = 2
973 THREGS = 3
974 SYSTEM = 4
975 INSTRS = 5
977 def __init__(self, debugger, path, options):
978 super().__init__(debugger, path, options)
979 self.thread = None
980 self.app_specific_backtrace = False
981 self.parse_mode = self.CrashLogParseMode.NORMAL
982 self.parsers = {
983 self.CrashLogParseMode.NORMAL: self.parse_normal,
984 self.CrashLogParseMode.THREAD: self.parse_thread,
985 self.CrashLogParseMode.IMAGES: self.parse_images,
986 self.CrashLogParseMode.THREGS: self.parse_thread_registers,
987 self.CrashLogParseMode.SYSTEM: self.parse_system,
988 self.CrashLogParseMode.INSTRS: self.parse_instructions,
990 self.symbols = {}
992 def parse(self):
993 with open(self.path, "r", encoding="utf-8") as f:
994 lines = f.read().splitlines()
996 idx = 0
997 lines_count = len(lines)
998 while True:
999 if idx >= lines_count:
1000 break
1002 line = lines[idx]
1003 line_len = len(line)
1005 if line_len == 0:
1006 if self.thread:
1007 if self.parse_mode == self.CrashLogParseMode.THREAD:
1008 if self.thread.index == self.crashlog.crashed_thread_idx:
1009 self.thread.reason = ""
1010 if hasattr(self.crashlog, "thread_exception"):
1011 self.thread.reason += self.crashlog.thread_exception
1012 if hasattr(self.crashlog, "thread_exception_data"):
1013 self.thread.reason += (
1014 " (%s)" % self.crashlog.thread_exception_data
1016 self.thread.crashed = True
1017 if self.app_specific_backtrace:
1018 self.crashlog.backtraces.append(self.thread)
1019 else:
1020 self.crashlog.threads.append(self.thread)
1021 self.thread = None
1023 empty_lines = 1
1024 while (
1025 idx + empty_lines < lines_count
1026 and len(lines[idx + empty_lines]) == 0
1028 empty_lines = empty_lines + 1
1030 if (
1031 empty_lines == 1
1032 and idx + empty_lines < lines_count - 1
1033 and self.parse_mode != self.CrashLogParseMode.NORMAL
1035 # check if next line can be parsed with the current parse mode
1036 next_line_idx = idx + empty_lines
1037 if self.parsers[self.parse_mode](lines[next_line_idx]):
1038 # If that suceeded, skip the empty line and the next line.
1039 idx = next_line_idx + 1
1040 continue
1041 self.parse_mode = self.CrashLogParseMode.NORMAL
1043 self.parsers[self.parse_mode](line)
1045 idx = idx + 1
1047 return self.crashlog
1049 def parse_exception(self, line):
1050 if not line.startswith("Exception"):
1051 return False
1052 if line.startswith("Exception Type:"):
1053 self.crashlog.thread_exception = line[15:].strip()
1054 exception_type_match = self.exception_type_regex.search(line)
1055 if exception_type_match:
1056 exc_type, exc_signal = exception_type_match.groups()
1057 self.crashlog.exception["type"] = exc_type
1058 if exc_signal:
1059 self.crashlog.exception["signal"] = exc_signal
1060 elif line.startswith("Exception Subtype:"):
1061 self.crashlog.thread_exception_subtype = line[18:].strip()
1062 if "type" in self.crashlog.exception:
1063 self.crashlog.exception[
1064 "subtype"
1065 ] = self.crashlog.thread_exception_subtype
1066 elif line.startswith("Exception Codes:"):
1067 self.crashlog.thread_exception_data = line[16:].strip()
1068 if "type" not in self.crashlog.exception:
1069 return False
1070 exception_codes_match = self.exception_codes_regex.search(line)
1071 if exception_codes_match:
1072 self.crashlog.exception["codes"] = self.crashlog.thread_exception_data
1073 code, subcode = exception_codes_match.groups()
1074 self.crashlog.exception["rawCodes"] = [
1075 int(code, base=16),
1076 int(subcode, base=16),
1078 else:
1079 if "type" not in self.crashlog.exception:
1080 return False
1081 exception_extra_match = self.exception_extra_regex.search(line)
1082 if exception_extra_match:
1083 self.crashlog.exception["message"] = exception_extra_match.group(1)
1084 return True
1086 def parse_normal(self, line):
1087 if line.startswith("Process:"):
1088 (self.crashlog.process_name, pid_with_brackets) = (
1089 line[8:].strip().split(" [")
1091 self.crashlog.process_id = pid_with_brackets.strip("[]")
1092 elif line.startswith("Path:"):
1093 self.crashlog.process_path = line[5:].strip()
1094 elif line.startswith("Identifier:"):
1095 self.crashlog.process_identifier = line[11:].strip()
1096 elif line.startswith("Version:"):
1097 version_string = line[8:].strip()
1098 matched_pair = re.search("(.+)\((.+)\)", version_string)
1099 if matched_pair:
1100 self.crashlog.process_version = matched_pair.group(1)
1101 self.crashlog.process_compatability_version = matched_pair.group(2)
1102 else:
1103 self.crashlog.process = version_string
1104 self.crashlog.process_compatability_version = version_string
1105 elif line.startswith("Code Type:"):
1106 if "ARM-64" in line:
1107 self.crashlog.process_arch = "arm64"
1108 elif "X86-64" in line:
1109 self.crashlog.process_arch = "x86_64"
1110 elif self.parent_process_regex.search(line):
1111 parent_process_match = self.parent_process_regex.search(line)
1112 self.crashlog.parent_process_name = parent_process_match.group(1)
1113 self.crashlog.parent_process_id = parent_process_match.group(2)
1114 elif line.startswith("Exception"):
1115 self.parse_exception(line)
1116 return
1117 elif line.startswith("Crashed Thread:"):
1118 self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0])
1119 return
1120 elif line.startswith("Triggered by Thread:"): # iOS
1121 self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0])
1122 return
1123 elif line.startswith("Report Version:"):
1124 self.crashlog.version = int(line[15:].strip())
1125 return
1126 elif line.startswith("System Profile:"):
1127 self.parse_mode = self.CrashLogParseMode.SYSTEM
1128 return
1129 elif (
1130 line.startswith("Interval Since Last Report:")
1131 or line.startswith("Crashes Since Last Report:")
1132 or line.startswith("Per-App Interval Since Last Report:")
1133 or line.startswith("Per-App Crashes Since Last Report:")
1134 or line.startswith("Sleep/Wake UUID:")
1135 or line.startswith("Anonymous UUID:")
1137 # ignore these
1138 return
1139 elif line.startswith("Thread"):
1140 thread_state_match = self.thread_state_regex.search(line)
1141 if thread_state_match:
1142 self.app_specific_backtrace = False
1143 thread_state_match = self.thread_regex.search(line)
1144 if thread_state_match:
1145 thread_idx = int(thread_state_match.group(1))
1146 else:
1147 thread_idx = self.crashlog.crashed_thread_idx
1148 self.parse_mode = self.CrashLogParseMode.THREGS
1149 self.thread = self.crashlog.threads[thread_idx]
1150 return
1151 thread_insts_match = self.thread_instrs_regex.search(line)
1152 if thread_insts_match:
1153 self.parse_mode = self.CrashLogParseMode.INSTRS
1154 return
1155 thread_match = self.thread_regex.search(line)
1156 if thread_match:
1157 self.app_specific_backtrace = False
1158 self.parse_mode = self.CrashLogParseMode.THREAD
1159 thread_idx = int(thread_match.group(1))
1160 self.thread = self.crashlog.Thread(
1161 thread_idx, False, self.crashlog.process_arch
1163 return
1164 return
1165 elif line.startswith("Binary Images:"):
1166 self.parse_mode = self.CrashLogParseMode.IMAGES
1167 return
1168 elif line.startswith("Application Specific Backtrace"):
1169 app_backtrace_match = self.app_backtrace_regex.search(line)
1170 if app_backtrace_match:
1171 self.parse_mode = self.CrashLogParseMode.THREAD
1172 self.app_specific_backtrace = True
1173 idx = int(app_backtrace_match.group(1))
1174 self.thread = self.crashlog.Thread(
1175 idx, True, self.crashlog.process_arch
1177 self.thread.name = "Application Specific Backtrace"
1178 elif line.startswith("Last Exception Backtrace:"): # iOS
1179 self.parse_mode = self.CrashLogParseMode.THREAD
1180 self.app_specific_backtrace = True
1181 idx = 1
1182 self.thread = self.crashlog.Thread(idx, True, self.crashlog.process_arch)
1183 self.thread.name = "Last Exception Backtrace"
1184 self.crashlog.info_lines.append(line.strip())
1186 def parse_thread(self, line):
1187 if line.startswith("Thread"):
1188 return False
1189 if self.null_frame_regex.search(line):
1190 print('warning: thread parser ignored null-frame: "%s"' % line)
1191 return False
1192 frame_match = self.frame_regex.search(line)
1193 if not frame_match:
1194 print('error: frame regex failed for line: "%s"' % line)
1195 return False
1197 frame_id = (
1198 frame_img_name
1199 ) = (
1200 frame_addr
1201 ) = frame_symbol = frame_offset = frame_file = frame_line = frame_column = None
1203 if len(frame_match.groups()) == 3:
1204 # Get the image UUID from the frame image name.
1205 (frame_id, frame_img_name, frame_addr) = frame_match.groups()
1206 elif len(frame_match.groups()) == 5:
1208 frame_id,
1209 frame_img_name,
1210 frame_addr,
1211 frame_symbol,
1212 frame_offset,
1213 ) = frame_match.groups()
1214 elif len(frame_match.groups()) == 7:
1216 frame_id,
1217 frame_img_name,
1218 frame_addr,
1219 frame_symbol,
1220 frame_offset,
1221 frame_file,
1222 frame_line,
1223 ) = frame_match.groups()
1224 elif len(frame_match.groups()) == 8:
1226 frame_id,
1227 frame_img_name,
1228 frame_addr,
1229 frame_symbol,
1230 frame_offset,
1231 frame_file,
1232 frame_line,
1233 frame_column,
1234 ) = frame_match.groups()
1236 self.thread.add_ident(frame_img_name)
1237 if frame_img_name not in self.crashlog.idents:
1238 self.crashlog.idents.append(frame_img_name)
1240 description = ""
1241 # Since images are parsed after threads, we need to build a
1242 # map for every image with a list of all the symbols and addresses
1243 if frame_img_name and frame_addr and frame_symbol:
1244 description = frame_symbol
1245 frame_offset_value = 0
1246 if frame_offset:
1247 description += " + " + frame_offset
1248 frame_offset_value = int(frame_offset, 0)
1249 if frame_img_name not in self.symbols:
1250 self.symbols[frame_img_name] = list()
1251 self.symbols[frame_img_name].append(
1253 "name": frame_symbol,
1254 "address": int(frame_addr, 0) - frame_offset_value,
1258 self.thread.frames.append(
1259 self.crashlog.Frame(int(frame_id), int(frame_addr, 0), description)
1262 return True
1264 def parse_images(self, line):
1265 image_match = self.image_regex_uuid.search(line)
1266 if image_match:
1268 img_lo,
1269 img_hi,
1270 img_name,
1271 img_version,
1272 img_uuid,
1273 img_path,
1274 ) = image_match.groups()
1276 image = self.crashlog.DarwinImage(
1277 int(img_lo, 0),
1278 int(img_hi, 0),
1279 img_name.strip(),
1280 img_version.strip() if img_version else "",
1281 uuid.UUID(img_uuid),
1282 img_path,
1283 self.options.verbose,
1285 unqualified_img_name = os.path.basename(img_path)
1286 if unqualified_img_name in self.symbols:
1287 for symbol in self.symbols[unqualified_img_name]:
1288 image.symbols[symbol["name"]] = {
1289 "name": symbol["name"],
1290 "type": "code",
1291 # NOTE: "address" is actually the symbol image offset
1292 "address": symbol["address"] - int(img_lo, 0),
1295 self.crashlog.images.append(image)
1296 return True
1297 else:
1298 if self.options.debug:
1299 print("error: image regex failed for: %s" % line)
1300 return False
1302 def parse_thread_registers(self, line):
1303 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
1304 reg_values = re.findall("([a-z0-9]+): (0x[0-9a-f]+)", line, re.I)
1305 for reg, value in reg_values:
1306 self.thread.registers[reg] = int(value, 16)
1307 return len(reg_values) != 0
1309 def parse_system(self, line):
1310 self.crashlog.system_profile.append(line)
1311 return True
1313 def parse_instructions(self, line):
1314 pass
1317 def save_crashlog(debugger, command, exe_ctx, result, dict):
1318 usage = "save_crashlog [options] <output-path>"
1319 description = """Export the state of current target into a crashlog file"""
1320 parser = argparse.ArgumentParser(
1321 description=description,
1322 prog="save_crashlog",
1323 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1325 parser.add_argument(
1326 "output",
1327 metavar="output-file",
1328 type=argparse.FileType("w", encoding="utf-8"),
1329 nargs=1,
1331 parser.add_argument(
1332 "-v",
1333 "--verbose",
1334 action="store_true",
1335 dest="verbose",
1336 help="display verbose debug info",
1337 default=False,
1339 try:
1340 options = parser.parse_args(shlex.split(command))
1341 except Exception as e:
1342 result.SetError(str(e))
1343 return
1344 target = exe_ctx.target
1345 if target:
1346 out_file = options.output[0]
1347 identifier = target.executable.basename
1348 process = exe_ctx.process
1349 if process:
1350 pid = process.id
1351 if pid != lldb.LLDB_INVALID_PROCESS_ID:
1352 out_file.write("Process: %s [%u]\n" % (identifier, pid))
1353 out_file.write("Path: %s\n" % (target.executable.fullpath))
1354 out_file.write("Identifier: %s\n" % (identifier))
1355 out_file.write(
1356 "\nDate/Time: %s\n"
1357 % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
1359 out_file.write(
1360 "OS Version: Mac OS X %s (%s)\n"
1362 platform.mac_ver()[0],
1363 subprocess.check_output("sysctl -n kern.osversion", shell=True).decode(
1364 "utf-8"
1368 out_file.write("Report Version: 9\n")
1369 for thread_idx in range(process.num_threads):
1370 thread = process.thread[thread_idx]
1371 out_file.write("\nThread %u:\n" % (thread_idx))
1372 for frame_idx, frame in enumerate(thread.frames):
1373 frame_pc = frame.pc
1374 frame_offset = 0
1375 if frame.function:
1376 block = frame.GetFrameBlock()
1377 block_range = block.range[frame.addr]
1378 if block_range:
1379 block_start_addr = block_range[0]
1380 frame_offset = frame_pc - block_start_addr.GetLoadAddress(
1381 target
1383 else:
1384 frame_offset = frame_pc - frame.function.addr.GetLoadAddress(
1385 target
1387 elif frame.symbol:
1388 frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
1389 out_file.write(
1390 "%-3u %-32s 0x%16.16x %s"
1391 % (frame_idx, frame.module.file.basename, frame_pc, frame.name)
1393 if frame_offset > 0:
1394 out_file.write(" + %u" % (frame_offset))
1395 line_entry = frame.line_entry
1396 if line_entry:
1397 if options.verbose:
1398 # This will output the fullpath + line + column
1399 out_file.write(" %s" % (line_entry))
1400 else:
1401 out_file.write(
1402 " %s:%u" % (line_entry.file.basename, line_entry.line)
1404 column = line_entry.column
1405 if column:
1406 out_file.write(":%u" % (column))
1407 out_file.write("\n")
1409 out_file.write("\nBinary Images:\n")
1410 for module in target.modules:
1411 text_segment = module.section["__TEXT"]
1412 if text_segment:
1413 text_segment_load_addr = text_segment.GetLoadAddress(target)
1414 if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
1415 text_segment_end_load_addr = (
1416 text_segment_load_addr + text_segment.size
1418 identifier = module.file.basename
1419 module_version = "???"
1420 module_version_array = module.GetVersion()
1421 if module_version_array:
1422 module_version = ".".join(map(str, module_version_array))
1423 out_file.write(
1424 " 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n"
1426 text_segment_load_addr,
1427 text_segment_end_load_addr,
1428 identifier,
1429 module_version,
1430 module.GetUUIDString(),
1431 module.file.fullpath,
1434 out_file.close()
1435 else:
1436 result.SetError("invalid target")
1439 class Symbolicate:
1440 def __init__(self, debugger, internal_dict):
1441 pass
1443 def __call__(self, debugger, command, exe_ctx, result):
1444 SymbolicateCrashLogs(debugger, shlex.split(command), result, True)
1446 def get_short_help(self):
1447 return "Symbolicate one or more darwin crash log files."
1449 def get_long_help(self):
1450 arg_parser = CrashLogOptionParser()
1451 return arg_parser.format_help()
1454 def SymbolicateCrashLog(crash_log, options):
1455 if options.debug:
1456 crash_log.dump()
1457 if not crash_log.images:
1458 print("error: no images in crash log")
1459 return
1461 if options.dump_image_list:
1462 print("Binary Images:")
1463 for image in crash_log.images:
1464 if options.verbose:
1465 print(image.debug_dump())
1466 else:
1467 print(image)
1469 target = crash_log.create_target()
1470 if not target:
1471 return
1473 crash_log.load_images(options)
1475 if crash_log.backtraces:
1476 for thread in crash_log.backtraces:
1477 thread.dump_symbolicated(crash_log, options)
1478 print()
1480 for thread in crash_log.threads:
1481 if options.crashed_only and not (
1482 thread.crashed or thread.app_specific_backtrace
1484 continue
1485 thread.dump_symbolicated(crash_log, options)
1486 print()
1488 if crash_log.errors:
1489 print("Errors:")
1490 for error in crash_log.errors:
1491 print(error)
1494 def load_crashlog_in_scripted_process(debugger, crashlog_path, options, result):
1495 crashlog = CrashLogParser.create(debugger, crashlog_path, options).parse()
1497 target = lldb.SBTarget()
1498 # 1. Try to use the user-provided target
1499 if options.target_path:
1500 target = debugger.CreateTarget(options.target_path)
1501 if not target:
1502 raise InteractiveCrashLogException(
1503 "couldn't create target provided by the user (%s)" % options.target_path
1505 crashlog.target = target
1507 # 2. If the user didn't provide a target, try to create a target using the symbolicator
1508 if not target or not target.IsValid():
1509 target = crashlog.create_target()
1510 # 3. If that didn't work, create a dummy target
1511 if target is None or not target.IsValid():
1512 arch = crashlog.process_arch
1513 if not arch:
1514 raise InteractiveCrashLogException(
1515 "couldn't create find the architecture to create the target"
1517 target = debugger.CreateTargetWithFileAndArch(None, arch)
1518 # 4. Fail
1519 if target is None or not target.IsValid():
1520 raise InteractiveCrashLogException("couldn't create target")
1522 ci = debugger.GetCommandInterpreter()
1523 if not ci:
1524 raise InteractiveCrashLogException("couldn't get command interpreter")
1526 ci.HandleCommand("script from lldb.macosx import crashlog_scripted_process", result)
1527 if not result.Succeeded():
1528 raise InteractiveCrashLogException(
1529 "couldn't import crashlog scripted process module"
1532 structured_data = lldb.SBStructuredData()
1533 structured_data.SetFromJSON(
1534 json.dumps(
1536 "file_path": crashlog_path,
1537 "load_all_images": options.load_all_images,
1538 "crashed_only": options.crashed_only,
1539 "no_parallel_image_loading": options.no_parallel_image_loading,
1543 launch_info = lldb.SBLaunchInfo(None)
1544 launch_info.SetProcessPluginName("ScriptedProcess")
1545 launch_info.SetScriptedProcessClassName(
1546 "crashlog_scripted_process.CrashLogScriptedProcess"
1548 launch_info.SetScriptedProcessDictionary(structured_data)
1549 launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry)
1551 error = lldb.SBError()
1552 process = target.Launch(launch_info, error)
1554 if not process or error.Fail():
1555 raise InteractiveCrashLogException("couldn't launch Scripted Process", error)
1557 process.GetScriptedImplementation().set_crashlog(crashlog)
1558 process.Continue()
1560 if not options.skip_status:
1562 @contextlib.contextmanager
1563 def synchronous(debugger):
1564 async_state = debugger.GetAsync()
1565 debugger.SetAsync(False)
1566 try:
1567 yield
1568 finally:
1569 debugger.SetAsync(async_state)
1571 with synchronous(debugger):
1572 run_options = lldb.SBCommandInterpreterRunOptions()
1573 run_options.SetStopOnError(True)
1574 run_options.SetStopOnCrash(True)
1575 run_options.SetEchoCommands(True)
1577 commands_stream = lldb.SBStream()
1578 commands_stream.Print("process status --verbose\n")
1579 commands_stream.Print("thread backtrace --extended true\n")
1580 error = debugger.SetInputString(commands_stream.GetData())
1581 if error.Success():
1582 debugger.RunCommandInterpreter(True, False, run_options, 0, False, True)
1585 def CreateSymbolicateCrashLogOptions(
1586 command_name, description, add_interactive_options
1588 usage = "crashlog [options] <FILE> [FILE ...]"
1589 arg_parser = argparse.ArgumentParser(
1590 description=description,
1591 prog="crashlog",
1592 usage=usage,
1593 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
1595 arg_parser.add_argument(
1596 "reports",
1597 metavar="FILE",
1598 type=str,
1599 nargs="*",
1600 help="crash report(s) to symbolicate",
1603 arg_parser.add_argument(
1604 "--version",
1605 "-V",
1606 dest="version",
1607 action="store_true",
1608 help="Show crashlog version",
1609 default=False,
1611 arg_parser.add_argument(
1612 "--verbose",
1613 "-v",
1614 action="store_true",
1615 dest="verbose",
1616 help="display verbose debug info",
1617 default=False,
1619 arg_parser.add_argument(
1620 "--debug",
1621 "-g",
1622 action="store_true",
1623 dest="debug",
1624 help="display verbose debug logging",
1625 default=False,
1627 arg_parser.add_argument(
1628 "--load-all",
1629 "-a",
1630 action="store_true",
1631 dest="load_all_images",
1632 help="load all executable images, not just the images found in the "
1633 "crashed stack frames, loads stackframes for all the threads in "
1634 "interactive mode.",
1635 default=False,
1637 arg_parser.add_argument(
1638 "--images",
1639 action="store_true",
1640 dest="dump_image_list",
1641 help="show image list",
1642 default=False,
1644 arg_parser.add_argument(
1645 "--debug-delay",
1646 type=int,
1647 dest="debug_delay",
1648 metavar="NSEC",
1649 help="pause for NSEC seconds for debugger",
1650 default=0,
1652 # NOTE: Requires python 3.9
1653 # arg_parser.add_argument(
1654 # "--crashed-only",
1655 # "-c",
1656 # action=argparse.BooleanOptionalAction,
1657 # dest="crashed_only",
1658 # help="only symbolicate the crashed thread",
1659 # default=True,
1661 arg_parser.add_argument(
1662 "--crashed-only",
1663 "-c",
1664 action="store_true",
1665 dest="crashed_only",
1666 help="only symbolicate the crashed thread",
1667 default=True,
1669 arg_parser.add_argument(
1670 "--no-crashed-only",
1671 action="store_false",
1672 dest="crashed_only",
1673 help="in batch mode, symbolicate all threads, not only the crashed one",
1674 default=False,
1676 arg_parser.add_argument(
1677 "--disasm-depth",
1678 "-d",
1679 type=int,
1680 dest="disassemble_depth",
1681 help="set the depth in stack frames that should be disassembled",
1682 default=1,
1684 arg_parser.add_argument(
1685 "--disasm-all",
1686 "-D",
1687 action="store_true",
1688 dest="disassemble_all_threads",
1689 help="enabled disassembly of frames on all threads (not just the crashed thread)",
1690 default=False,
1692 arg_parser.add_argument(
1693 "--disasm-before",
1694 "-B",
1695 type=int,
1696 dest="disassemble_before",
1697 help="the number of instructions to disassemble before the frame PC",
1698 default=4,
1700 arg_parser.add_argument(
1701 "--disasm-after",
1702 "-A",
1703 type=int,
1704 dest="disassemble_after",
1705 help="the number of instructions to disassemble after the frame PC",
1706 default=4,
1708 arg_parser.add_argument(
1709 "--source-context",
1710 "-C",
1711 type=int,
1712 metavar="NLINES",
1713 dest="source_context",
1714 help="show NLINES source lines of source context",
1715 default=4,
1717 arg_parser.add_argument(
1718 "--source-frames",
1719 type=int,
1720 metavar="NFRAMES",
1721 dest="source_frames",
1722 help="show source for NFRAMES",
1723 default=4,
1725 arg_parser.add_argument(
1726 "--source-all",
1727 action="store_true",
1728 dest="source_all",
1729 help="show source for all threads, not just the crashed thread",
1730 default=False,
1732 arg_parser.add_argument(
1733 "--no-parallel-image-loading",
1734 dest="no_parallel_image_loading",
1735 action="store_true",
1736 help=argparse.SUPPRESS,
1737 default=False,
1739 if add_interactive_options:
1740 arg_parser.add_argument(
1741 "-i",
1742 "--interactive",
1743 action="store_true",
1744 help="parse a crash log and load it in a ScriptedProcess",
1745 default=False,
1747 arg_parser.add_argument(
1748 "-b",
1749 "--batch",
1750 action="store_true",
1751 help="dump symbolicated stackframes without creating a debug session",
1752 default=True,
1754 arg_parser.add_argument(
1755 "--target",
1756 "-t",
1757 dest="target_path",
1758 help="the target binary path that should be used for interactive crashlog (optional)",
1759 default=None,
1761 arg_parser.add_argument(
1762 "--skip-status",
1763 "-s",
1764 dest="skip_status",
1765 action="store_true",
1766 help="prevent the interactive crashlog to dump the process status and thread backtrace at launch",
1767 default=False,
1769 return arg_parser
1772 def CrashLogOptionParser():
1773 description = """Symbolicate one or more darwin crash log files to provide source file and line information,
1774 inlined stack frames back to the concrete functions, and disassemble the location of the crash
1775 for the first frame of the crashed thread.
1776 If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1777 for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1778 created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1779 you to explore the program as if it were stopped at the locations described in the crash log and functions can
1780 be disassembled and lookups can be performed using the addresses found in the crash log."""
1781 return CreateSymbolicateCrashLogOptions("crashlog", description, True)
1784 def SymbolicateCrashLogs(debugger, command_args, result, is_command):
1785 arg_parser = CrashLogOptionParser()
1787 if not len(command_args):
1788 arg_parser.print_help()
1789 return
1791 try:
1792 options = arg_parser.parse_args(command_args)
1793 except Exception as e:
1794 result.SetError(str(e))
1795 return
1797 # Interactive mode requires running the crashlog command from inside lldb.
1798 if options.interactive and not is_command:
1799 lldb_exec = (
1800 subprocess.check_output(["/usr/bin/xcrun", "-f", "lldb"])
1801 .decode("utf-8")
1802 .strip()
1804 sys.exit(
1805 os.execv(
1806 lldb_exec,
1808 lldb_exec,
1809 "-o",
1810 "command script import lldb.macosx",
1811 "-o",
1812 "crashlog {}".format(shlex.join(command_args)),
1817 if "NO_PARALLEL_IMG_LOADING" in os.environ:
1818 options.no_parallel_image_loading = True
1820 if options.version:
1821 print(debugger.GetVersionString())
1822 return
1824 if options.debug:
1825 print("command_args = %s" % command_args)
1826 print("options", options)
1827 print("args", options.reports)
1829 if options.debug_delay > 0:
1830 print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1831 time.sleep(options.debug_delay)
1832 error = lldb.SBError()
1834 def should_run_in_interactive_mode(options, ci):
1835 if options.interactive:
1836 return True
1837 elif options.batch:
1838 return False
1839 # elif ci and ci.IsInteractive():
1840 # return True
1841 else:
1842 return False
1844 ci = debugger.GetCommandInterpreter()
1846 if options.reports:
1847 for crashlog_file in options.reports:
1848 crashlog_path = os.path.expanduser(crashlog_file)
1849 if not os.path.exists(crashlog_path):
1850 raise FileNotFoundError(
1851 "crashlog file %s does not exist" % crashlog_path
1853 if should_run_in_interactive_mode(options, ci):
1854 try:
1855 load_crashlog_in_scripted_process(
1856 debugger, crashlog_path, options, result
1858 except InteractiveCrashLogException as e:
1859 result.SetError(str(e))
1860 else:
1861 crash_log = CrashLogParser.create(
1862 debugger, crashlog_path, options
1863 ).parse()
1864 SymbolicateCrashLog(crash_log, options)
1867 if __name__ == "__main__":
1868 # Create a new debugger instance
1869 debugger = lldb.SBDebugger.Create()
1870 result = lldb.SBCommandReturnObject()
1871 SymbolicateCrashLogs(debugger, sys.argv[1:], result, False)
1872 lldb.SBDebugger.Destroy(debugger)
1875 def __lldb_init_module(debugger, internal_dict):
1876 debugger.HandleCommand(
1877 "command script add -o -c lldb.macosx.crashlog.Symbolicate -C disk-file crashlog"
1879 debugger.HandleCommand(
1880 "command script add -o -f lldb.macosx.crashlog.save_crashlog -C disk-file save_crashlog"
1882 print(
1883 '"crashlog" and "save_crashlog" commands have been installed, use '
1884 'the "--help" options on these commands for detailed help.'