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
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 )
26 # PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
27 # ----------------------------------------------------------------------
31 import concurrent
.futures
49 print_lock
= threading
.RLock()
52 # First try for LLDB in case PYTHONPATH is already correctly setup.
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.
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.
69 "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
73 from lldb
.utils
import symbolication
74 from lldb
.plugins
.scripted_process
import INTEL64_GPR
, ARM64_GPR
78 if sys
.version_info
.major
== 3:
79 return plistlib
.loads(s
)
81 return plistlib
.readPlistFromString(s
)
84 class CrashLog(symbolication
.Symbolicator
):
86 """Class that represents a thread in a darwin crash log"""
88 def __init__(self
, index
, app_specific_backtrace
, arch
):
94 self
.registers
= dict()
99 self
.app_specific_backtrace
= app_specific_backtrace
102 def dump_registers(self
, prefix
=""):
103 registers_info
= None
104 sorted_registers
= {}
107 sorted_keys
= list(d
.keys())
109 return {k
: d
[k
] for k
in sorted_keys
}
112 if "x86_64" == self
.arch
:
113 registers_info
= INTEL64_GPR
114 elif "arm64" in self
.arch
:
115 registers_info
= ARM64_GPR
117 print("unknown target architecture: %s" % self
.arch
)
120 # Add registers available in the register information dictionary.
121 for reg_info
in registers_info
:
123 if reg_info
["name"] in self
.registers
:
124 reg_name
= reg_info
["name"]
126 "generic" in reg_info
and reg_info
["generic"] in self
.registers
128 reg_name
= reg_info
["generic"]
130 # Skip register that are present in the register information dictionary but not present in the report.
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
))
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
:
152 "%Application Specific Backtrace[%u] %s"
153 % (prefix
, self
.index
, self
.reason
)
156 print("%sThread[%u] %s" % (prefix
, self
.index
, self
.reason
))
158 print("%s Frames:" % (prefix
))
159 for frame
in self
.frames
:
160 frame
.dump(prefix
+ " ")
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
:
173 display_frame_idx
= -1
174 for frame_idx
, frame
in enumerate(self
.frames
):
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(
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
))
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
199 symbolicated_frame_address
.get_symbol_context().line_entry
201 if line_entry
.IsValid():
202 strm
= lldb
.SBStream()
204 crash_log
.debugger
.GetSourceManager().DisplaySourceLinesWithLineNumbers(
212 source_text
= strm
.GetData()
214 # Indent the source a bit
216 join_str
= "\n" + indent_str
221 join_str
.join(source_text
.split("\n")),
224 if symbolicated_frame_address_idx
== 0:
227 symbolicated_frame_address
.get_instructions()
231 symbolication
.disassemble_instructions(
232 crash_log
.get_target(),
235 options
.disassemble_before
,
236 options
.disassemble_after
,
240 symbolicated_frame_address_idx
+= 1
245 self
.dump_registers()
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
)
258 if self
.app_specific_backtrace
:
259 s
= "Application Specific Backtrace[%u]" % self
.index
261 s
= "Thread[%u]" % self
.index
263 s
+= " %s" % self
.reason
267 """Class that represents a stack frame in a thread in a darwin crash log"""
269 def __init__(self
, index
, pc
, description
):
271 self
.description
= description
276 return "[%3u] 0x%16.16x %s" % (self
.index
, self
.pc
, self
.description
)
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
):
291 dsymForUUIDBinary
= (
292 subprocess
.check_output("which dsymForUUID", shell
=True)
297 dsymForUUIDBinary
= ""
299 dwarfdump_uuid_regex
= re
.compile("UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*")
302 self
, text_addr_lo
, text_addr_hi
, identifier
, version
, uuid
, path
, verbose
304 symbolication
.Image
.__init
__(self
, path
, uuid
)
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.
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
327 self_uuid
= self
.get_uuid()
328 for line
in dwarfdump_cmd_output
.splitlines():
329 match
= self
.dwarfdump_uuid_regex
.search(line
)
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)
337 if not self
.resolved_path
:
338 self
.unavailable
= True
339 if self
.show_symbol_progress():
342 "error\n error: unable to locate '%s' with UUID %s"
343 % (self
.path
, self
.get_normalized_uuid_string())
348 def locate_module_and_debug_symbols(self
):
349 # Don't load a module twice...
352 # Mark this as resolved so we don't keep trying
354 uuid_str
= self
.get_normalized_uuid_string()
355 if self
.show_symbol_progress():
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)
369 plist_root
= read_plist(s
)
376 " handling dsymForUUID output: \n",
382 plist
= plist_root
[uuid_str
]
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():
402 if not self
.resolved_path
and not os
.path
.exists(self
.path
):
405 subprocess
.check_output(
408 "com_apple_xcode_dsym_uuids == %s" % uuid_str
,
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.
421 print('falling back to binary inside "%s"' % dsym
)
423 # Look for the executable next to the dSYM bundle.
424 parent_dir
= os
.path
.dirname(dsym
)
426 for root
, _
, files
in os
.walk(parent_dir
):
428 abs_path
= os
.path
.join(root
, file)
429 if os
.path
.isfile(abs_path
) and os
.access(
432 executables
.append(abs_path
)
433 for binary
in executables
:
434 basename
= os
.path
.basename(binary
)
435 if basename
== self
.identifier
:
437 found_matching_slice
= True
439 if found_matching_slice
:
443 if (self
.resolved_path
and os
.path
.exists(self
.resolved_path
)) or (
444 self
.path
and os
.path
.exists(self
.path
)
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
:
451 "Could not access remapped source path for %s %s"
452 % (uuid_str
, source_path
)
456 self
.unavailable
= True
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
469 ) # A list of the required identifiers for doing all stack backtraces
471 self
.exception
= dict()
472 self
.crashed_thread_idx
= -1
475 self
.verbose
= verbose
476 self
.process_id
= None
477 self
.process_identifier
= None
478 self
.process_path
= None
479 self
.process_arch
= None
482 print("Crash Log File: %s" % (self
.path
))
484 print("\nApplication Specific Backtraces:")
485 for thread
in self
.backtraces
:
488 for thread
in self
.threads
:
491 for image
in self
.images
:
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
))
500 def find_image_with_identifier(self
, identifier
):
501 for image
in self
.images
:
502 if image
.identifier
== identifier
:
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
):
511 def create_target(self
):
512 if self
.target
is None:
513 self
.target
= symbolication
.Symbolicator
.create_target(self
)
516 # We weren't able to open the main executable as, but we can still
518 print("crashlog.create_target()...2")
520 for ident
in self
.idents
:
521 image
= self
.find_image_with_identifier(ident
)
523 self
.target
= image
.create_target(self
.debugger
)
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
)
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")
535 " and/or make sure the .dSYM bundles can be found by Spotlight."
539 def get_target(self
):
542 def load_images(self
, options
, loaded_images
=None):
543 if not loaded_images
:
545 images_to_load
= self
.images
546 if options
.load_all_images
:
547 for image
in self
.images
:
549 elif options
.crashed_only
:
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
):
556 images_to_load
.append(image
)
559 with tempfile
.TemporaryDirectory() as obj_dir
:
561 def add_module(image
, target
, obj_dir
):
562 return image
, image
.add_module(target
, obj_dir
)
565 if options
.no_parallel_image_loading
:
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):
582 for future
in concurrent
.futures
.as_completed(futures
):
583 image
, err
= future
.result()
587 loaded_images
.append(image
)
590 class CrashLogFormatException(Exception):
594 class CrashLogParseException(Exception):
598 class InteractiveCrashLogException(Exception):
602 class CrashLogParser
:
604 def create(debugger
, path
, options
):
605 data
= JSONCrashLogParser
.is_valid_json(path
)
607 parser
= JSONCrashLogParser(debugger
, path
, options
)
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
)
623 class JSONCrashLogParser(CrashLogParser
):
625 def is_valid_json(path
):
626 def parse_json(buffer):
628 return json
.loads(buffer)
630 # The first line can contain meta data. Try stripping it and
632 head
, _
, tail
= buffer.partition("\n")
633 return json
.loads(tail
)
635 with
open(path
, "r", encoding
="utf-8") as f
:
638 return parse_json(buffer)
642 def __init__(self
, debugger
, path
, options
):
643 super().__init
__(debugger
, path
, options
)
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"])
666 thread
.reason
= "{} {}".format(thread
.reason
, reason
)
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
)
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"])
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 ""
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
):
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"]
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
] = {
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
:
762 frame_offset
= pc
- text_lo
763 thread
.frames
.append(self
.crashlog
.Frame(idx
, pc
, frame_offset
))
768 def parse_threads(self
, json_threads
):
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
)
790 def parse_asi_backtrace(self
, thread
, bt
):
791 for line
in bt
.split("\n"):
792 frame_match
= TextCrashLogParser
.frame_regex
.search(line
)
794 print("error: can't parse application specific backtrace.")
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:
815 ) = frame_match
.groups()
816 elif len(frame_match
.groups()) == 7:
825 ) = frame_match
.groups()
826 elif len(frame_match
.groups()) == 8:
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
)
843 if frame_img_name
and frame_addr
and frame_symbol
:
844 description
= frame_symbol
845 frame_offset_value
= 0
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
,
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
)
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
)
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):
883 for key
, state
in json_thread_state
.items():
885 registers
.update(self
.parse_thread_registers(state
))
888 gpr_dict
= {str(idx
): reg
for idx
, reg
in enumerate(state
)}
889 registers
.update(self
.parse_thread_registers(gpr_dict
, key
))
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"
899 value
= int(state
["value"])
900 registers
["{}{}".format(prefix
or "", key
)] = value
901 except (KeyError, ValueError, TypeError):
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+).*")
918 version
= r
"\(.+\)|(?:arm|x86_)[0-9a-z]+"
920 class FrameRegex(VersionRegex
):
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
934 (?P<symbol_offset>\d+)
938 (?P<file_name>[^:]+):(?P<line_number>\d+)
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
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
:
977 def __init__(self
, debugger
, path
, options
):
978 super().__init
__(debugger
, path
, options
)
980 self
.app_specific_backtrace
= False
981 self
.parse_mode
= self
.CrashLogParseMode
.NORMAL
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
,
993 with
open(self
.path
, "r", encoding
="utf-8") as f
:
994 lines
= f
.read().splitlines()
997 lines_count
= len(lines
)
999 if idx
>= lines_count
:
1003 line_len
= len(line
)
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
)
1020 self
.crashlog
.threads
.append(self
.thread
)
1025 idx
+ empty_lines
< lines_count
1026 and len(lines
[idx
+ empty_lines
]) == 0
1028 empty_lines
= 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
1041 self
.parse_mode
= self
.CrashLogParseMode
.NORMAL
1043 self
.parsers
[self
.parse_mode
](line
)
1047 return self
.crashlog
1049 def parse_exception(self
, line
):
1050 if not line
.startswith("Exception"):
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
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
[
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
:
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"] = [
1076 int(subcode
, base
=16),
1079 if "type" not in self
.crashlog
.exception
:
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)
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
)
1100 self
.crashlog
.process_version
= matched_pair
.group(1)
1101 self
.crashlog
.process_compatability_version
= matched_pair
.group(2)
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
)
1117 elif line
.startswith("Crashed Thread:"):
1118 self
.crashlog
.crashed_thread_idx
= int(line
[15:].strip().split()[0])
1120 elif line
.startswith("Triggered by Thread:"): # iOS
1121 self
.crashlog
.crashed_thread_idx
= int(line
[20:].strip().split()[0])
1123 elif line
.startswith("Report Version:"):
1124 self
.crashlog
.version
= int(line
[15:].strip())
1126 elif line
.startswith("System Profile:"):
1127 self
.parse_mode
= self
.CrashLogParseMode
.SYSTEM
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:")
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))
1147 thread_idx
= self
.crashlog
.crashed_thread_idx
1148 self
.parse_mode
= self
.CrashLogParseMode
.THREGS
1149 self
.thread
= self
.crashlog
.threads
[thread_idx
]
1151 thread_insts_match
= self
.thread_instrs_regex
.search(line
)
1152 if thread_insts_match
:
1153 self
.parse_mode
= self
.CrashLogParseMode
.INSTRS
1155 thread_match
= self
.thread_regex
.search(line
)
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
1165 elif line
.startswith("Binary Images:"):
1166 self
.parse_mode
= self
.CrashLogParseMode
.IMAGES
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
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"):
1189 if self
.null_frame_regex
.search(line
):
1190 print('warning: thread parser ignored null-frame: "%s"' % line
)
1192 frame_match
= self
.frame_regex
.search(line
)
1194 print('error: frame regex failed for line: "%s"' % line
)
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:
1213 ) = frame_match
.groups()
1214 elif len(frame_match
.groups()) == 7:
1223 ) = frame_match
.groups()
1224 elif len(frame_match
.groups()) == 8:
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
)
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
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
)
1264 def parse_images(self
, line
):
1265 image_match
= self
.image_regex_uuid
.search(line
)
1274 ) = image_match
.groups()
1276 image
= self
.crashlog
.DarwinImage(
1280 img_version
.strip() if img_version
else "",
1281 uuid
.UUID(img_uuid
),
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"],
1291 # NOTE: "address" is actually the symbol image offset
1292 "address": symbol
["address"] - int(img_lo
, 0),
1295 self
.crashlog
.images
.append(image
)
1298 if self
.options
.debug
:
1299 print("error: image regex failed for: %s" % line
)
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
)
1313 def parse_instructions(self
, line
):
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(
1327 metavar
="output-file",
1328 type=argparse
.FileType("w", encoding
="utf-8"),
1331 parser
.add_argument(
1334 action
="store_true",
1336 help="display verbose debug info",
1340 options
= parser
.parse_args(shlex
.split(command
))
1341 except Exception as e
:
1342 result
.SetError(str(e
))
1344 target
= exe_ctx
.target
1346 out_file
= options
.output
[0]
1347 identifier
= target
.executable
.basename
1348 process
= exe_ctx
.process
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
))
1357 % (datetime
.datetime
.now().strftime("%Y-%m-%d %H:%M:%S"))
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(
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
):
1376 block
= frame
.GetFrameBlock()
1377 block_range
= block
.range[frame
.addr
]
1379 block_start_addr
= block_range
[0]
1380 frame_offset
= frame_pc
- block_start_addr
.GetLoadAddress(
1384 frame_offset
= frame_pc
- frame
.function
.addr
.GetLoadAddress(
1388 frame_offset
= frame_pc
- frame
.symbol
.addr
.GetLoadAddress(target
)
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
1398 # This will output the fullpath + line + column
1399 out_file
.write(" %s" % (line_entry
))
1402 " %s:%u" % (line_entry
.file.basename
, line_entry
.line
)
1404 column
= line_entry
.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"]
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
))
1424 " 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n"
1426 text_segment_load_addr
,
1427 text_segment_end_load_addr
,
1430 module
.GetUUIDString(),
1431 module
.file.fullpath
,
1436 result
.SetError("invalid target")
1440 def __init__(self
, debugger
, internal_dict
):
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
):
1457 if not crash_log
.images
:
1458 print("error: no images in crash log")
1461 if options
.dump_image_list
:
1462 print("Binary Images:")
1463 for image
in crash_log
.images
:
1465 print(image
.debug_dump())
1469 target
= crash_log
.create_target()
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
)
1480 for thread
in crash_log
.threads
:
1481 if options
.crashed_only
and not (
1482 thread
.crashed
or thread
.app_specific_backtrace
1485 thread
.dump_symbolicated(crash_log
, options
)
1488 if crash_log
.errors
:
1490 for error
in crash_log
.errors
:
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
)
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
1514 raise InteractiveCrashLogException(
1515 "couldn't create find the architecture to create the target"
1517 target
= debugger
.CreateTargetWithFileAndArch(None, arch
)
1519 if target
is None or not target
.IsValid():
1520 raise InteractiveCrashLogException("couldn't create target")
1522 ci
= debugger
.GetCommandInterpreter()
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(
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
)
1560 if not options
.skip_status
:
1562 @contextlib.contextmanager
1563 def synchronous(debugger
):
1564 async_state
= debugger
.GetAsync()
1565 debugger
.SetAsync(False)
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())
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
,
1593 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
,
1595 arg_parser
.add_argument(
1600 help="crash report(s) to symbolicate",
1603 arg_parser
.add_argument(
1607 action
="store_true",
1608 help="Show crashlog version",
1611 arg_parser
.add_argument(
1614 action
="store_true",
1616 help="display verbose debug info",
1619 arg_parser
.add_argument(
1622 action
="store_true",
1624 help="display verbose debug logging",
1627 arg_parser
.add_argument(
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.",
1637 arg_parser
.add_argument(
1639 action
="store_true",
1640 dest
="dump_image_list",
1641 help="show image list",
1644 arg_parser
.add_argument(
1649 help="pause for NSEC seconds for debugger",
1652 # NOTE: Requires python 3.9
1653 # arg_parser.add_argument(
1656 # action=argparse.BooleanOptionalAction,
1657 # dest="crashed_only",
1658 # help="only symbolicate the crashed thread",
1661 arg_parser
.add_argument(
1664 action
="store_true",
1665 dest
="crashed_only",
1666 help="only symbolicate the crashed thread",
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",
1676 arg_parser
.add_argument(
1680 dest
="disassemble_depth",
1681 help="set the depth in stack frames that should be disassembled",
1684 arg_parser
.add_argument(
1687 action
="store_true",
1688 dest
="disassemble_all_threads",
1689 help="enabled disassembly of frames on all threads (not just the crashed thread)",
1692 arg_parser
.add_argument(
1696 dest
="disassemble_before",
1697 help="the number of instructions to disassemble before the frame PC",
1700 arg_parser
.add_argument(
1704 dest
="disassemble_after",
1705 help="the number of instructions to disassemble after the frame PC",
1708 arg_parser
.add_argument(
1713 dest
="source_context",
1714 help="show NLINES source lines of source context",
1717 arg_parser
.add_argument(
1721 dest
="source_frames",
1722 help="show source for NFRAMES",
1725 arg_parser
.add_argument(
1727 action
="store_true",
1729 help="show source for all threads, not just the crashed thread",
1732 arg_parser
.add_argument(
1733 "--no-parallel-image-loading",
1734 dest
="no_parallel_image_loading",
1735 action
="store_true",
1736 help=argparse
.SUPPRESS
,
1739 if add_interactive_options
:
1740 arg_parser
.add_argument(
1743 action
="store_true",
1744 help="parse a crash log and load it in a ScriptedProcess",
1747 arg_parser
.add_argument(
1750 action
="store_true",
1751 help="dump symbolicated stackframes without creating a debug session",
1754 arg_parser
.add_argument(
1758 help="the target binary path that should be used for interactive crashlog (optional)",
1761 arg_parser
.add_argument(
1765 action
="store_true",
1766 help="prevent the interactive crashlog to dump the process status and thread backtrace at launch",
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()
1792 options
= arg_parser
.parse_args(command_args
)
1793 except Exception as e
:
1794 result
.SetError(str(e
))
1797 # Interactive mode requires running the crashlog command from inside lldb.
1798 if options
.interactive
and not is_command
:
1800 subprocess
.check_output(["/usr/bin/xcrun", "-f", "lldb"])
1810 "command script import lldb.macosx",
1812 "crashlog {}".format(shlex
.join(command_args
)),
1817 if "NO_PARALLEL_IMG_LOADING" in os
.environ
:
1818 options
.no_parallel_image_loading
= True
1821 print(debugger
.GetVersionString())
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
:
1839 # elif ci and ci.IsInteractive():
1844 ci
= debugger
.GetCommandInterpreter()
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
):
1855 load_crashlog_in_scripted_process(
1856 debugger
, crashlog_path
, options
, result
1858 except InteractiveCrashLogException
as e
:
1859 result
.SetError(str(e
))
1861 crash_log
= CrashLogParser
.create(
1862 debugger
, crashlog_path
, options
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"
1883 '"crashlog" and "save_crashlog" commands have been installed, use '
1884 'the "--help" options on these commands for detailed help.'