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 this_thread_crashed
== False:
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
)
255 return self
.reason
is not None
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 not os
.path
.exists(dsymForUUIDBinary
):
289 dsymForUUIDBinary
= (
290 subprocess
.check_output("which dsymForUUID", shell
=True)
295 dsymForUUIDBinary
= ""
297 dwarfdump_uuid_regex
= re
.compile("UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*")
300 self
, text_addr_lo
, text_addr_hi
, identifier
, version
, uuid
, path
, verbose
302 symbolication
.Image
.__init
__(self
, path
, uuid
)
304 symbolication
.Section(text_addr_lo
, text_addr_hi
, "__TEXT")
306 self
.identifier
= identifier
307 self
.version
= version
308 self
.verbose
= verbose
310 def show_symbol_progress(self
):
312 Hide progress output and errors from system frameworks as they are plentiful.
317 self
.path
.startswith("/System/Library/")
318 or self
.path
.startswith("/usr/lib/")
321 def find_matching_slice(self
):
322 dwarfdump_cmd_output
= subprocess
.check_output(
323 'dwarfdump --uuid "%s"' % self
.path
, shell
=True
325 self_uuid
= self
.get_uuid()
326 for line
in dwarfdump_cmd_output
.splitlines():
327 match
= self
.dwarfdump_uuid_regex
.search(line
)
329 dwarf_uuid_str
= match
.group(1)
330 dwarf_uuid
= uuid
.UUID(dwarf_uuid_str
)
331 if self_uuid
== dwarf_uuid
:
332 self
.resolved_path
= self
.path
333 self
.arch
= match
.group(2)
335 if not self
.resolved_path
:
336 self
.unavailable
= True
337 if self
.show_symbol_progress():
340 "error\n error: unable to locate '%s' with UUID %s"
341 % (self
.path
, self
.get_normalized_uuid_string())
346 def locate_module_and_debug_symbols(self
):
347 # Don't load a module twice...
350 # Mark this as resolved so we don't keep trying
352 uuid_str
= self
.get_normalized_uuid_string()
353 if self
.show_symbol_progress():
355 print("Getting symbols for %s %s..." % (uuid_str
, self
.path
))
356 # Keep track of unresolved source paths.
357 unavailable_source_paths
= set()
358 if os
.path
.exists(self
.dsymForUUIDBinary
):
359 dsym_for_uuid_command
= "%s %s" % (self
.dsymForUUIDBinary
, uuid_str
)
360 s
= subprocess
.check_output(dsym_for_uuid_command
, shell
=True)
363 plist_root
= read_plist(s
)
370 " handling dsymForUUID output: \n",
376 plist
= plist_root
[uuid_str
]
378 if "DBGArchitecture" in plist
:
379 self
.arch
= plist
["DBGArchitecture"]
380 if "DBGDSYMPath" in plist
:
381 self
.symfile
= os
.path
.realpath(plist
["DBGDSYMPath"])
382 if "DBGSymbolRichExecutable" in plist
:
383 self
.path
= os
.path
.expanduser(
384 plist
["DBGSymbolRichExecutable"]
386 self
.resolved_path
= self
.path
387 if "DBGSourcePathRemapping" in plist
:
388 path_remapping
= plist
["DBGSourcePathRemapping"]
389 for _
, value
in path_remapping
.items():
390 source_path
= os
.path
.expanduser(value
)
391 if not os
.path
.exists(source_path
):
392 unavailable_source_paths
.add(source_path
)
393 if not self
.resolved_path
and os
.path
.exists(self
.path
):
394 if not self
.find_matching_slice():
396 if not self
.resolved_path
and not os
.path
.exists(self
.path
):
399 subprocess
.check_output(
402 "com_apple_xcode_dsym_uuids == %s" % uuid_str
,
408 found_matching_slice
= False
409 for dsym
in mdfind_results
:
410 dwarf_dir
= os
.path
.join(dsym
, "Contents/Resources/DWARF")
411 if not os
.path
.exists(dwarf_dir
):
412 # Not a dSYM bundle, probably an Xcode archive.
415 print('falling back to binary inside "%s"' % dsym
)
417 for filename
in os
.listdir(dwarf_dir
):
418 self
.path
= os
.path
.join(dwarf_dir
, filename
)
419 if self
.find_matching_slice():
420 found_matching_slice
= True
422 if found_matching_slice
:
426 if (self
.resolved_path
and os
.path
.exists(self
.resolved_path
)) or (
427 self
.path
and os
.path
.exists(self
.path
)
430 print("Resolved symbols for %s %s..." % (uuid_str
, self
.path
))
431 if len(unavailable_source_paths
):
432 for source_path
in unavailable_source_paths
:
434 "Could not access remapped source path for %s %s"
435 % (uuid_str
, source_path
)
439 self
.unavailable
= True
442 def __init__(self
, debugger
, path
, verbose
):
443 """CrashLog constructor that take a path to a darwin crash log file"""
444 symbolication
.Symbolicator
.__init
__(self
, debugger
)
445 self
.path
= os
.path
.expanduser(path
)
446 self
.info_lines
= list()
447 self
.system_profile
= list()
448 self
.threads
= list()
449 self
.backtraces
= list() # For application specific backtraces
452 ) # A list of the required identifiers for doing all stack backtraces
454 self
.exception
= dict()
455 self
.crashed_thread_idx
= -1
458 self
.verbose
= verbose
459 self
.process_id
= None
460 self
.process_identifier
= None
461 self
.process_path
= None
462 self
.process_arch
= None
465 print("Crash Log File: %s" % (self
.path
))
467 print("\nApplication Specific Backtraces:")
468 for thread
in self
.backtraces
:
471 for thread
in self
.threads
:
474 for image
in self
.images
:
477 def set_main_image(self
, identifier
):
478 for i
, image
in enumerate(self
.images
):
479 if image
.identifier
== identifier
:
480 self
.images
.insert(0, self
.images
.pop(i
))
483 def find_image_with_identifier(self
, identifier
):
484 for image
in self
.images
:
485 if image
.identifier
== identifier
:
487 regex_text
= "^.*\.%s$" % (re
.escape(identifier
))
488 regex
= re
.compile(regex_text
)
489 for image
in self
.images
:
490 if regex
.match(image
.identifier
):
494 def create_target(self
):
495 if self
.target
is None:
496 self
.target
= symbolication
.Symbolicator
.create_target(self
)
499 # We weren't able to open the main executable as, but we can still
501 print("crashlog.create_target()...2")
503 for ident
in self
.idents
:
504 image
= self
.find_image_with_identifier(ident
)
506 self
.target
= image
.create_target(self
.debugger
)
508 return self
.target
# success
509 print("crashlog.create_target()...3")
510 for image
in self
.images
:
511 self
.target
= image
.create_target(self
.debugger
)
513 return self
.target
# success
514 print("crashlog.create_target()...4")
515 print("error: Unable to locate any executables from the crash log.")
516 print(" Try loading the executable into lldb before running crashlog")
518 " and/or make sure the .dSYM bundles can be found by Spotlight."
522 def get_target(self
):
526 class CrashLogFormatException(Exception):
530 class CrashLogParseException(Exception):
534 class InteractiveCrashLogException(Exception):
538 class CrashLogParser
:
540 def create(debugger
, path
, options
):
541 data
= JSONCrashLogParser
.is_valid_json(path
)
543 parser
= JSONCrashLogParser(debugger
, path
, options
)
547 return TextCrashLogParser(debugger
, path
, options
)
549 def __init__(self
, debugger
, path
, options
):
550 self
.path
= os
.path
.expanduser(path
)
551 self
.options
= options
552 self
.crashlog
= CrashLog(debugger
, self
.path
, self
.options
.verbose
)
559 class JSONCrashLogParser(CrashLogParser
):
561 def is_valid_json(path
):
562 def parse_json(buffer):
564 return json
.loads(buffer)
566 # The first line can contain meta data. Try stripping it and
568 head
, _
, tail
= buffer.partition("\n")
569 return json
.loads(tail
)
571 with
open(path
, "r", encoding
="utf-8") as f
:
574 return parse_json(buffer)
578 def __init__(self
, debugger
, path
, options
):
579 super().__init
__(debugger
, path
, options
)
583 self
.parse_process_info(self
.data
)
584 self
.parse_images(self
.data
["usedImages"])
585 self
.parse_main_image(self
.data
)
586 self
.parse_threads(self
.data
["threads"])
587 if "asi" in self
.data
:
588 self
.crashlog
.asi
= self
.data
["asi"]
589 # FIXME: With the current design, we can either show the ASI or Last
590 # Exception Backtrace, not both. Is there a situation where we would
591 # like to show both ?
592 if "asiBacktraces" in self
.data
:
593 self
.parse_app_specific_backtraces(self
.data
["asiBacktraces"])
594 if "lastExceptionBacktrace" in self
.data
:
595 self
.parse_last_exception_backtraces(
596 self
.data
["lastExceptionBacktrace"]
598 self
.parse_errors(self
.data
)
599 thread
= self
.crashlog
.threads
[self
.crashlog
.crashed_thread_idx
]
600 reason
= self
.parse_crash_reason(self
.data
["exception"])
602 thread
.reason
= "{} {}".format(thread
.reason
, reason
)
604 thread
.reason
= reason
605 except (KeyError, ValueError, TypeError) as e
:
606 raise CrashLogParseException(
607 "Failed to parse JSON crashlog: {}: {}".format(type(e
).__name
__, e
)
612 def get_used_image(self
, idx
):
613 return self
.data
["usedImages"][idx
]
615 def parse_process_info(self
, json_data
):
616 self
.crashlog
.process_id
= json_data
["pid"]
617 self
.crashlog
.process_identifier
= json_data
["procName"]
618 if "procPath" in json_data
:
619 self
.crashlog
.process_path
= json_data
["procPath"]
621 def parse_crash_reason(self
, json_exception
):
622 self
.crashlog
.exception
= json_exception
623 exception_type
= json_exception
["type"]
624 exception_signal
= " "
625 if "signal" in json_exception
:
626 exception_signal
+= "({})".format(json_exception
["signal"])
628 if "codes" in json_exception
:
629 exception_extra
= " ({})".format(json_exception
["codes"])
630 elif "subtype" in json_exception
:
631 exception_extra
= " ({})".format(json_exception
["subtype"])
634 return "{}{}{}".format(exception_type
, exception_signal
, exception_extra
)
636 def parse_images(self
, json_images
):
637 for json_image
in json_images
:
638 img_uuid
= uuid
.UUID(json_image
["uuid"])
639 low
= int(json_image
["base"])
640 high
= low
+ int(json_image
["size"]) if "size" in json_image
else low
641 name
= json_image
["name"] if "name" in json_image
else ""
642 path
= json_image
["path"] if "path" in json_image
else ""
644 darwin_image
= self
.crashlog
.DarwinImage(
645 low
, high
, name
, version
, img_uuid
, path
, self
.options
.verbose
647 if "arch" in json_image
:
648 darwin_image
.arch
= json_image
["arch"]
649 if path
== self
.crashlog
.process_path
:
650 self
.crashlog
.process_arch
= darwin_image
.arch
651 self
.crashlog
.images
.append(darwin_image
)
653 def parse_main_image(self
, json_data
):
654 if "procName" in json_data
:
655 proc_name
= json_data
["procName"]
656 self
.crashlog
.set_main_image(proc_name
)
658 def parse_frames(self
, thread
, json_frames
):
660 for json_frame
in json_frames
:
661 image_id
= int(json_frame
["imageIndex"])
662 json_image
= self
.get_used_image(image_id
)
663 ident
= json_image
["name"] if "name" in json_image
else ""
664 thread
.add_ident(ident
)
665 if ident
not in self
.crashlog
.idents
:
666 self
.crashlog
.idents
.append(ident
)
668 frame_offset
= int(json_frame
["imageOffset"])
669 image_addr
= self
.get_used_image(image_id
)["base"]
670 pc
= image_addr
+ frame_offset
672 if "symbol" in json_frame
:
673 symbol
= json_frame
["symbol"]
675 if "symbolLocation" in json_frame
and json_frame
["symbolLocation"]:
676 location
= int(json_frame
["symbolLocation"])
677 image
= self
.crashlog
.images
[image_id
]
678 image
.symbols
[symbol
] = {
681 "address": frame_offset
- location
,
684 thread
.frames
.append(self
.crashlog
.Frame(idx
, pc
, frame_offset
))
686 # on arm64 systems, if it jump through a null function pointer,
687 # we end up at address 0 and the crash reporter unwinder
688 # misses the frame that actually faulted.
689 # But $lr can tell us where the last BL/BLR instruction used
690 # was at, so insert that address as the caller stack frame.
691 if idx
== 0 and pc
== 0 and "lr" in thread
.registers
:
692 pc
= thread
.registers
["lr"]
693 for image
in self
.data
["usedImages"]:
694 text_lo
= image
["base"]
695 text_hi
= text_lo
+ image
["size"]
696 if text_lo
<= pc
< text_hi
:
698 frame_offset
= pc
- text_lo
699 thread
.frames
.append(self
.crashlog
.Frame(idx
, pc
, frame_offset
))
704 def parse_threads(self
, json_threads
):
706 for json_thread
in json_threads
:
707 thread
= self
.crashlog
.Thread(idx
, False, self
.crashlog
.process_arch
)
708 if "name" in json_thread
:
709 thread
.name
= json_thread
["name"]
710 thread
.reason
= json_thread
["name"]
711 if "id" in json_thread
:
712 thread
.id = int(json_thread
["id"])
713 if json_thread
.get("triggered", False):
714 self
.crashlog
.crashed_thread_idx
= idx
715 thread
.crashed
= True
716 if "threadState" in json_thread
:
717 thread
.registers
= self
.parse_thread_registers(
718 json_thread
["threadState"]
720 if "queue" in json_thread
:
721 thread
.queue
= json_thread
.get("queue")
722 self
.parse_frames(thread
, json_thread
.get("frames", []))
723 self
.crashlog
.threads
.append(thread
)
726 def parse_asi_backtrace(self
, thread
, bt
):
727 for line
in bt
.split("\n"):
728 frame_match
= TextCrashLogParser
.frame_regex
.search(line
)
730 print("error: can't parse application specific backtrace.")
739 ) = frame_offset
= frame_file
= frame_line
= frame_column
= None
741 if len(frame_match
.groups()) == 3:
742 # Get the image UUID from the frame image name.
743 (frame_id
, frame_img_name
, frame_addr
) = frame_match
.groups()
744 elif len(frame_match
.groups()) == 5:
751 ) = frame_match
.groups()
752 elif len(frame_match
.groups()) == 7:
761 ) = frame_match
.groups()
762 elif len(frame_match
.groups()) == 8:
772 ) = frame_match
.groups()
774 thread
.add_ident(frame_img_name
)
775 if frame_img_name
not in self
.crashlog
.idents
:
776 self
.crashlog
.idents
.append(frame_img_name
)
779 if frame_img_name
and frame_addr
and frame_symbol
:
780 description
= frame_symbol
781 frame_offset_value
= 0
783 description
+= " + " + frame_offset
784 frame_offset_value
= int(frame_offset
, 0)
785 for image
in self
.crashlog
.images
:
786 if image
.identifier
== frame_img_name
:
787 image
.symbols
[frame_symbol
] = {
788 "name": frame_symbol
,
790 "address": int(frame_addr
, 0) - frame_offset_value
,
793 thread
.frames
.append(
794 self
.crashlog
.Frame(int(frame_id
), int(frame_addr
, 0), description
)
799 def parse_app_specific_backtraces(self
, json_app_specific_bts
):
800 thread
= self
.crashlog
.Thread(
801 len(self
.crashlog
.threads
), True, self
.crashlog
.process_arch
803 thread
.queue
= "Application Specific Backtrace"
804 if self
.parse_asi_backtrace(thread
, json_app_specific_bts
[0]):
805 self
.crashlog
.threads
.append(thread
)
807 print("error: Couldn't parse Application Specific Backtrace.")
809 def parse_last_exception_backtraces(self
, json_last_exc_bts
):
810 thread
= self
.crashlog
.Thread(
811 len(self
.crashlog
.threads
), True, self
.crashlog
.process_arch
813 thread
.queue
= "Last Exception Backtrace"
814 self
.parse_frames(thread
, json_last_exc_bts
)
815 self
.crashlog
.threads
.append(thread
)
817 def parse_thread_registers(self
, json_thread_state
, prefix
=None):
819 for key
, state
in json_thread_state
.items():
821 registers
.update(self
.parse_thread_registers(state
))
824 gpr_dict
= {str(idx
): reg
for idx
, reg
in enumerate(state
)}
825 registers
.update(self
.parse_thread_registers(gpr_dict
, key
))
828 if not self
.crashlog
.process_arch
:
829 if state
== "ARM_THREAD_STATE64":
830 self
.crashlog
.process_arch
= "arm64"
831 elif state
== "X86_THREAD_STATE":
832 self
.crashlog
.process_arch
= "x86_64"
835 value
= int(state
["value"])
836 registers
["{}{}".format(prefix
or "", key
)] = value
837 except (KeyError, ValueError, TypeError):
841 def parse_errors(self
, json_data
):
842 if "reportNotes" in json_data
:
843 self
.crashlog
.errors
= json_data
["reportNotes"]
846 class TextCrashLogParser(CrashLogParser
):
847 parent_process_regex
= re
.compile(r
"^Parent Process:\s*(.*)\[(\d+)\]")
848 thread_state_regex
= re
.compile(r
"^Thread \d+ crashed with")
849 thread_instrs_regex
= re
.compile(r
"^Thread \d+ instruction stream")
850 thread_regex
= re
.compile(r
"^Thread (\d+).*:")
851 app_backtrace_regex
= re
.compile(r
"^Application Specific Backtrace (\d+).*:")
854 version
= r
"\(.+\)|(?:arm|x86_)[0-9a-z]+"
856 class FrameRegex(VersionRegex
):
860 img_name
= r
"(.+?)\s+"
861 version
= r
"(?:" + super().version
+ r
"\s+)?"
862 address
= r
"(0x[0-9a-fA-F]{4,})" # 4 digits or more
870 (?P<symbol_offset>\d+)
874 (?P<file_name>[^:]+):(?P<line_number>\d+)
883 index
+ img_name
+ version
+ address
+ symbol
, flags
=re
.VERBOSE
886 frame_regex
= FrameRegex
.get()
887 null_frame_regex
= re
.compile(r
"^\d+\s+\?\?\?\s+0{4,} +")
888 image_regex_uuid
= re
.compile(
889 r
"(0x[0-9a-fA-F]+)" # img_lo
891 r
"(0x[0-9a-fA-F]+)\s+" # img_hi
892 r
"[+]?(.+?)\s+" # img_name
893 r
"(?:(" + VersionRegex
.version
+ r
")\s+)?" # img_version
894 r
"(?:<([-0-9a-fA-F]+)>\s+)?" # img_uuid
895 r
"(\?+|/.*)" # img_path
897 exception_type_regex
= re
.compile(
898 r
"^Exception Type:\s+(EXC_[A-Z_]+)(?:\s+\((.*)\))?"
900 exception_codes_regex
= re
.compile(
901 r
"^Exception Codes:\s+(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+)"
903 exception_extra_regex
= re
.compile(r
"^Exception\s+.*:\s+(.*)")
905 class CrashLogParseMode
:
913 def __init__(self
, debugger
, path
, options
):
914 super().__init
__(debugger
, path
, options
)
916 self
.app_specific_backtrace
= False
917 self
.parse_mode
= self
.CrashLogParseMode
.NORMAL
919 self
.CrashLogParseMode
.NORMAL
: self
.parse_normal
,
920 self
.CrashLogParseMode
.THREAD
: self
.parse_thread
,
921 self
.CrashLogParseMode
.IMAGES
: self
.parse_images
,
922 self
.CrashLogParseMode
.THREGS
: self
.parse_thread_registers
,
923 self
.CrashLogParseMode
.SYSTEM
: self
.parse_system
,
924 self
.CrashLogParseMode
.INSTRS
: self
.parse_instructions
,
929 with
open(self
.path
, "r", encoding
="utf-8") as f
:
930 lines
= f
.read().splitlines()
933 lines_count
= len(lines
)
935 if idx
>= lines_count
:
943 if self
.parse_mode
== self
.CrashLogParseMode
.THREAD
:
944 if self
.thread
.index
== self
.crashlog
.crashed_thread_idx
:
945 self
.thread
.reason
= ""
946 if hasattr(self
.crashlog
, "thread_exception"):
947 self
.thread
.reason
+= self
.crashlog
.thread_exception
948 if hasattr(self
.crashlog
, "thread_exception_data"):
949 self
.thread
.reason
+= (
950 " (%s)" % self
.crashlog
.thread_exception_data
952 self
.thread
.crashed
= True
953 if self
.app_specific_backtrace
:
954 self
.crashlog
.backtraces
.append(self
.thread
)
956 self
.crashlog
.threads
.append(self
.thread
)
961 idx
+ empty_lines
< lines_count
962 and len(lines
[idx
+ empty_lines
]) == 0
964 empty_lines
= empty_lines
+ 1
968 and idx
+ empty_lines
< lines_count
- 1
969 and self
.parse_mode
!= self
.CrashLogParseMode
.NORMAL
971 # check if next line can be parsed with the current parse mode
972 next_line_idx
= idx
+ empty_lines
973 if self
.parsers
[self
.parse_mode
](lines
[next_line_idx
]):
974 # If that suceeded, skip the empty line and the next line.
975 idx
= next_line_idx
+ 1
977 self
.parse_mode
= self
.CrashLogParseMode
.NORMAL
979 self
.parsers
[self
.parse_mode
](line
)
985 def parse_exception(self
, line
):
986 if not line
.startswith("Exception"):
988 if line
.startswith("Exception Type:"):
989 self
.crashlog
.thread_exception
= line
[15:].strip()
990 exception_type_match
= self
.exception_type_regex
.search(line
)
991 if exception_type_match
:
992 exc_type
, exc_signal
= exception_type_match
.groups()
993 self
.crashlog
.exception
["type"] = exc_type
995 self
.crashlog
.exception
["signal"] = exc_signal
996 elif line
.startswith("Exception Subtype:"):
997 self
.crashlog
.thread_exception_subtype
= line
[18:].strip()
998 if "type" in self
.crashlog
.exception
:
999 self
.crashlog
.exception
[
1001 ] = self
.crashlog
.thread_exception_subtype
1002 elif line
.startswith("Exception Codes:"):
1003 self
.crashlog
.thread_exception_data
= line
[16:].strip()
1004 if "type" not in self
.crashlog
.exception
:
1006 exception_codes_match
= self
.exception_codes_regex
.search(line
)
1007 if exception_codes_match
:
1008 self
.crashlog
.exception
["codes"] = self
.crashlog
.thread_exception_data
1009 code
, subcode
= exception_codes_match
.groups()
1010 self
.crashlog
.exception
["rawCodes"] = [
1012 int(subcode
, base
=16),
1015 if "type" not in self
.crashlog
.exception
:
1017 exception_extra_match
= self
.exception_extra_regex
.search(line
)
1018 if exception_extra_match
:
1019 self
.crashlog
.exception
["message"] = exception_extra_match
.group(1)
1022 def parse_normal(self
, line
):
1023 if line
.startswith("Process:"):
1024 (self
.crashlog
.process_name
, pid_with_brackets
) = (
1025 line
[8:].strip().split(" [")
1027 self
.crashlog
.process_id
= pid_with_brackets
.strip("[]")
1028 elif line
.startswith("Path:"):
1029 self
.crashlog
.process_path
= line
[5:].strip()
1030 elif line
.startswith("Identifier:"):
1031 self
.crashlog
.process_identifier
= line
[11:].strip()
1032 elif line
.startswith("Version:"):
1033 version_string
= line
[8:].strip()
1034 matched_pair
= re
.search("(.+)\((.+)\)", version_string
)
1036 self
.crashlog
.process_version
= matched_pair
.group(1)
1037 self
.crashlog
.process_compatability_version
= matched_pair
.group(2)
1039 self
.crashlog
.process
= version_string
1040 self
.crashlog
.process_compatability_version
= version_string
1041 elif line
.startswith("Code Type:"):
1042 if "ARM-64" in line
:
1043 self
.crashlog
.process_arch
= "arm64"
1044 elif "X86-64" in line
:
1045 self
.crashlog
.process_arch
= "x86_64"
1046 elif self
.parent_process_regex
.search(line
):
1047 parent_process_match
= self
.parent_process_regex
.search(line
)
1048 self
.crashlog
.parent_process_name
= parent_process_match
.group(1)
1049 self
.crashlog
.parent_process_id
= parent_process_match
.group(2)
1050 elif line
.startswith("Exception"):
1051 self
.parse_exception(line
)
1053 elif line
.startswith("Crashed Thread:"):
1054 self
.crashlog
.crashed_thread_idx
= int(line
[15:].strip().split()[0])
1056 elif line
.startswith("Triggered by Thread:"): # iOS
1057 self
.crashlog
.crashed_thread_idx
= int(line
[20:].strip().split()[0])
1059 elif line
.startswith("Report Version:"):
1060 self
.crashlog
.version
= int(line
[15:].strip())
1062 elif line
.startswith("System Profile:"):
1063 self
.parse_mode
= self
.CrashLogParseMode
.SYSTEM
1066 line
.startswith("Interval Since Last Report:")
1067 or line
.startswith("Crashes Since Last Report:")
1068 or line
.startswith("Per-App Interval Since Last Report:")
1069 or line
.startswith("Per-App Crashes Since Last Report:")
1070 or line
.startswith("Sleep/Wake UUID:")
1071 or line
.startswith("Anonymous UUID:")
1075 elif line
.startswith("Thread"):
1076 thread_state_match
= self
.thread_state_regex
.search(line
)
1077 if thread_state_match
:
1078 self
.app_specific_backtrace
= False
1079 thread_state_match
= self
.thread_regex
.search(line
)
1080 thread_idx
= int(thread_state_match
.group(1))
1081 self
.parse_mode
= self
.CrashLogParseMode
.THREGS
1082 self
.thread
= self
.crashlog
.threads
[thread_idx
]
1084 thread_insts_match
= self
.thread_instrs_regex
.search(line
)
1085 if thread_insts_match
:
1086 self
.parse_mode
= self
.CrashLogParseMode
.INSTRS
1088 thread_match
= self
.thread_regex
.search(line
)
1090 self
.app_specific_backtrace
= False
1091 self
.parse_mode
= self
.CrashLogParseMode
.THREAD
1092 thread_idx
= int(thread_match
.group(1))
1093 self
.thread
= self
.crashlog
.Thread(
1094 thread_idx
, False, self
.crashlog
.process_arch
1098 elif line
.startswith("Binary Images:"):
1099 self
.parse_mode
= self
.CrashLogParseMode
.IMAGES
1101 elif line
.startswith("Application Specific Backtrace"):
1102 app_backtrace_match
= self
.app_backtrace_regex
.search(line
)
1103 if app_backtrace_match
:
1104 self
.parse_mode
= self
.CrashLogParseMode
.THREAD
1105 self
.app_specific_backtrace
= True
1106 idx
= int(app_backtrace_match
.group(1))
1107 self
.thread
= self
.crashlog
.Thread(
1108 idx
, True, self
.crashlog
.process_arch
1110 elif line
.startswith("Last Exception Backtrace:"): # iOS
1111 self
.parse_mode
= self
.CrashLogParseMode
.THREAD
1112 self
.app_specific_backtrace
= True
1114 self
.thread
= self
.crashlog
.Thread(idx
, True, self
.crashlog
.process_arch
)
1115 self
.crashlog
.info_lines
.append(line
.strip())
1117 def parse_thread(self
, line
):
1118 if line
.startswith("Thread"):
1120 if self
.null_frame_regex
.search(line
):
1121 print('warning: thread parser ignored null-frame: "%s"' % line
)
1123 frame_match
= self
.frame_regex
.search(line
)
1125 print('error: frame regex failed for line: "%s"' % line
)
1132 ) = frame_symbol
= frame_offset
= frame_file
= frame_line
= frame_column
= None
1134 if len(frame_match
.groups()) == 3:
1135 # Get the image UUID from the frame image name.
1136 (frame_id
, frame_img_name
, frame_addr
) = frame_match
.groups()
1137 elif len(frame_match
.groups()) == 5:
1144 ) = frame_match
.groups()
1145 elif len(frame_match
.groups()) == 7:
1154 ) = frame_match
.groups()
1155 elif len(frame_match
.groups()) == 8:
1165 ) = frame_match
.groups()
1167 self
.thread
.add_ident(frame_img_name
)
1168 if frame_img_name
not in self
.crashlog
.idents
:
1169 self
.crashlog
.idents
.append(frame_img_name
)
1172 # Since images are parsed after threads, we need to build a
1173 # map for every image with a list of all the symbols and addresses
1174 if frame_img_name
and frame_addr
and frame_symbol
:
1175 description
= frame_symbol
1176 frame_offset_value
= 0
1178 description
+= " + " + frame_offset
1179 frame_offset_value
= int(frame_offset
, 0)
1180 if frame_img_name
not in self
.symbols
:
1181 self
.symbols
[frame_img_name
] = list()
1182 self
.symbols
[frame_img_name
].append(
1184 "name": frame_symbol
,
1185 "address": int(frame_addr
, 0) - frame_offset_value
,
1189 self
.thread
.frames
.append(
1190 self
.crashlog
.Frame(int(frame_id
), int(frame_addr
, 0), description
)
1195 def parse_images(self
, line
):
1196 image_match
= self
.image_regex_uuid
.search(line
)
1205 ) = image_match
.groups()
1207 image
= self
.crashlog
.DarwinImage(
1211 img_version
.strip() if img_version
else "",
1212 uuid
.UUID(img_uuid
),
1214 self
.options
.verbose
,
1216 unqualified_img_name
= os
.path
.basename(img_path
)
1217 if unqualified_img_name
in self
.symbols
:
1218 for symbol
in self
.symbols
[unqualified_img_name
]:
1219 image
.symbols
[symbol
["name"]] = {
1220 "name": symbol
["name"],
1222 # NOTE: "address" is actually the symbol image offset
1223 "address": symbol
["address"] - int(img_lo
, 0),
1226 self
.crashlog
.images
.append(image
)
1229 if self
.options
.debug
:
1230 print("error: image regex failed for: %s" % line
)
1233 def parse_thread_registers(self
, line
):
1234 # "r12: 0x00007fff6b5939c8 r13: 0x0000000007000006 r14: 0x0000000000002a03 r15: 0x0000000000000c00"
1235 reg_values
= re
.findall("([a-z0-9]+): (0x[0-9a-f]+)", line
, re
.I
)
1236 for reg
, value
in reg_values
:
1237 self
.thread
.registers
[reg
] = int(value
, 16)
1238 return len(reg_values
) != 0
1240 def parse_system(self
, line
):
1241 self
.crashlog
.system_profile
.append(line
)
1244 def parse_instructions(self
, line
):
1248 def save_crashlog(debugger
, command
, exe_ctx
, result
, dict):
1249 usage
= "save_crashlog [options] <output-path>"
1250 description
= """Export the state of current target into a crashlog file"""
1251 parser
= argparse
.ArgumentParser(
1252 description
=description
,
1253 prog
="save_crashlog",
1254 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
,
1256 parser
.add_argument(
1258 metavar
="output-file",
1259 type=argparse
.FileType("w", encoding
="utf-8"),
1262 parser
.add_argument(
1265 action
="store_true",
1267 help="display verbose debug info",
1271 options
= parser
.parse_args(shlex
.split(command
))
1272 except Exception as e
:
1273 result
.SetError(str(e
))
1275 target
= exe_ctx
.target
1277 out_file
= options
.output
[0]
1278 identifier
= target
.executable
.basename
1279 process
= exe_ctx
.process
1282 if pid
!= lldb
.LLDB_INVALID_PROCESS_ID
:
1283 out_file
.write("Process: %s [%u]\n" % (identifier
, pid
))
1284 out_file
.write("Path: %s\n" % (target
.executable
.fullpath
))
1285 out_file
.write("Identifier: %s\n" % (identifier
))
1288 % (datetime
.datetime
.now().strftime("%Y-%m-%d %H:%M:%S"))
1291 "OS Version: Mac OS X %s (%s)\n"
1293 platform
.mac_ver()[0],
1294 subprocess
.check_output("sysctl -n kern.osversion", shell
=True).decode(
1299 out_file
.write("Report Version: 9\n")
1300 for thread_idx
in range(process
.num_threads
):
1301 thread
= process
.thread
[thread_idx
]
1302 out_file
.write("\nThread %u:\n" % (thread_idx
))
1303 for frame_idx
, frame
in enumerate(thread
.frames
):
1307 block
= frame
.GetFrameBlock()
1308 block_range
= block
.range[frame
.addr
]
1310 block_start_addr
= block_range
[0]
1311 frame_offset
= frame_pc
- block_start_addr
.GetLoadAddress(
1315 frame_offset
= frame_pc
- frame
.function
.addr
.GetLoadAddress(
1319 frame_offset
= frame_pc
- frame
.symbol
.addr
.GetLoadAddress(target
)
1321 "%-3u %-32s 0x%16.16x %s"
1322 % (frame_idx
, frame
.module
.file.basename
, frame_pc
, frame
.name
)
1324 if frame_offset
> 0:
1325 out_file
.write(" + %u" % (frame_offset
))
1326 line_entry
= frame
.line_entry
1329 # This will output the fullpath + line + column
1330 out_file
.write(" %s" % (line_entry
))
1333 " %s:%u" % (line_entry
.file.basename
, line_entry
.line
)
1335 column
= line_entry
.column
1337 out_file
.write(":%u" % (column
))
1338 out_file
.write("\n")
1340 out_file
.write("\nBinary Images:\n")
1341 for module
in target
.modules
:
1342 text_segment
= module
.section
["__TEXT"]
1344 text_segment_load_addr
= text_segment
.GetLoadAddress(target
)
1345 if text_segment_load_addr
!= lldb
.LLDB_INVALID_ADDRESS
:
1346 text_segment_end_load_addr
= (
1347 text_segment_load_addr
+ text_segment
.size
1349 identifier
= module
.file.basename
1350 module_version
= "???"
1351 module_version_array
= module
.GetVersion()
1352 if module_version_array
:
1353 module_version
= ".".join(map(str, module_version_array
))
1355 " 0x%16.16x - 0x%16.16x %s (%s - ???) <%s> %s\n"
1357 text_segment_load_addr
,
1358 text_segment_end_load_addr
,
1361 module
.GetUUIDString(),
1362 module
.file.fullpath
,
1367 result
.SetError("invalid target")
1371 def __init__(self
, debugger
, internal_dict
):
1374 def __call__(self
, debugger
, command
, exe_ctx
, result
):
1375 SymbolicateCrashLogs(debugger
, shlex
.split(command
), result
, True)
1377 def get_short_help(self
):
1378 return "Symbolicate one or more darwin crash log files."
1380 def get_long_help(self
):
1381 arg_parser
= CrashLogOptionParser()
1382 return arg_parser
.format_help()
1385 def SymbolicateCrashLog(crash_log
, options
):
1388 if not crash_log
.images
:
1389 print("error: no images in crash log")
1392 if options
.dump_image_list
:
1393 print("Binary Images:")
1394 for image
in crash_log
.images
:
1396 print(image
.debug_dump())
1400 target
= crash_log
.create_target()
1404 if options
.load_all_images
:
1405 for image
in crash_log
.images
:
1406 image
.resolve
= True
1407 elif options
.crashed_only
:
1408 for thread
in crash_log
.threads
:
1409 if thread
.did_crash():
1410 for ident
in thread
.idents
:
1411 for image
in crash_log
.find_images_with_identifier(ident
):
1412 image
.resolve
= True
1416 with tempfile
.TemporaryDirectory() as obj_dir
:
1417 with concurrent
.futures
.ThreadPoolExecutor() as executor
:
1419 def add_module(image
, target
, obj_dir
):
1420 return image
, image
.add_module(target
, obj_dir
)
1422 for image
in crash_log
.images
:
1425 add_module
, image
=image
, target
=target
, obj_dir
=obj_dir
1428 for future
in concurrent
.futures
.as_completed(futures
):
1429 image
, err
= future
.result()
1433 loaded_images
.append(image
)
1435 if crash_log
.backtraces
:
1436 for thread
in crash_log
.backtraces
:
1437 thread
.dump_symbolicated(crash_log
, options
)
1440 for thread
in crash_log
.threads
:
1441 if options
.crashed_only
and not (
1442 thread
.crashed
or thread
.app_specific_backtrace
1445 thread
.dump_symbolicated(crash_log
, options
)
1448 if crash_log
.errors
:
1450 for error
in crash_log
.errors
:
1454 def load_crashlog_in_scripted_process(debugger
, crashlog_path
, options
, result
):
1455 crashlog
= CrashLogParser
.create(debugger
, crashlog_path
, options
).parse()
1457 target
= lldb
.SBTarget()
1458 # 1. Try to use the user-provided target
1459 if options
.target_path
:
1460 target
= debugger
.CreateTarget(options
.target_path
)
1462 raise InteractiveCrashLogException(
1463 "couldn't create target provided by the user (%s)" % options
.target_path
1466 # 2. If the user didn't provide a target, try to create a target using the symbolicator
1467 if not target
or not target
.IsValid():
1468 target
= crashlog
.create_target()
1469 # 3. If that didn't work, create a dummy target
1470 if target
is None or not target
.IsValid():
1471 arch
= crashlog
.process_arch
1473 raise InteractiveCrashLogException(
1474 "couldn't create find the architecture to create the target"
1476 target
= debugger
.CreateTargetWithFileAndArch(None, arch
)
1478 if target
is None or not target
.IsValid():
1479 raise InteractiveCrashLogException("couldn't create target")
1481 ci
= debugger
.GetCommandInterpreter()
1483 raise InteractiveCrashLogException("couldn't get command interpreter")
1485 ci
.HandleCommand("script from lldb.macosx import crashlog_scripted_process", result
)
1486 if not result
.Succeeded():
1487 raise InteractiveCrashLogException(
1488 "couldn't import crashlog scripted process module"
1491 structured_data
= lldb
.SBStructuredData()
1492 structured_data
.SetFromJSON(
1494 {"file_path": crashlog_path
, "load_all_images": options
.load_all_images
}
1497 launch_info
= lldb
.SBLaunchInfo(None)
1498 launch_info
.SetProcessPluginName("ScriptedProcess")
1499 launch_info
.SetScriptedProcessClassName(
1500 "crashlog_scripted_process.CrashLogScriptedProcess"
1502 launch_info
.SetScriptedProcessDictionary(structured_data
)
1503 launch_info
.SetLaunchFlags(lldb
.eLaunchFlagStopAtEntry
)
1505 error
= lldb
.SBError()
1506 process
= target
.Launch(launch_info
, error
)
1508 if not process
or error
.Fail():
1509 raise InteractiveCrashLogException("couldn't launch Scripted Process", error
)
1511 process
.GetScriptedImplementation().set_crashlog(crashlog
)
1514 if not options
.skip_status
:
1516 @contextlib.contextmanager
1517 def synchronous(debugger
):
1518 async_state
= debugger
.GetAsync()
1519 debugger
.SetAsync(False)
1523 debugger
.SetAsync(async_state
)
1525 with
synchronous(debugger
):
1526 run_options
= lldb
.SBCommandInterpreterRunOptions()
1527 run_options
.SetStopOnError(True)
1528 run_options
.SetStopOnCrash(True)
1529 run_options
.SetEchoCommands(True)
1531 commands_stream
= lldb
.SBStream()
1532 commands_stream
.Print("process status --verbose\n")
1533 commands_stream
.Print("thread backtrace --extended true\n")
1534 error
= debugger
.SetInputString(commands_stream
.GetData())
1536 debugger
.RunCommandInterpreter(True, False, run_options
, 0, False, True)
1539 def CreateSymbolicateCrashLogOptions(
1540 command_name
, description
, add_interactive_options
1542 usage
= "crashlog [options] <FILE> [FILE ...]"
1543 arg_parser
= argparse
.ArgumentParser(
1544 description
=description
,
1547 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
,
1549 arg_parser
.add_argument(
1554 help="crash report(s) to symbolicate",
1557 arg_parser
.add_argument(
1561 action
="store_true",
1562 help="Show crashlog version",
1565 arg_parser
.add_argument(
1568 action
="store_true",
1570 help="display verbose debug info",
1573 arg_parser
.add_argument(
1576 action
="store_true",
1578 help="display verbose debug logging",
1581 arg_parser
.add_argument(
1584 action
="store_true",
1585 dest
="load_all_images",
1586 help="load all executable images, not just the images found in the "
1587 "crashed stack frames, loads stackframes for all the threads in "
1588 "interactive mode.",
1591 arg_parser
.add_argument(
1593 action
="store_true",
1594 dest
="dump_image_list",
1595 help="show image list",
1598 arg_parser
.add_argument(
1603 help="pause for NSEC seconds for debugger",
1606 # NOTE: Requires python 3.9
1607 # arg_parser.add_argument(
1610 # action=argparse.BooleanOptionalAction,
1611 # dest="crashed_only",
1612 # help="only symbolicate the crashed thread",
1615 arg_parser
.add_argument(
1618 action
="store_true",
1619 dest
="crashed_only",
1620 help="only symbolicate the crashed thread",
1623 arg_parser
.add_argument(
1624 "--no-crashed-only",
1625 action
="store_false",
1626 dest
="crashed_only",
1627 help="do not symbolicate the crashed thread",
1629 arg_parser
.add_argument(
1633 dest
="disassemble_depth",
1634 help="set the depth in stack frames that should be disassembled",
1637 arg_parser
.add_argument(
1640 action
="store_true",
1641 dest
="disassemble_all_threads",
1642 help="enabled disassembly of frames on all threads (not just the crashed thread)",
1645 arg_parser
.add_argument(
1649 dest
="disassemble_before",
1650 help="the number of instructions to disassemble before the frame PC",
1653 arg_parser
.add_argument(
1657 dest
="disassemble_after",
1658 help="the number of instructions to disassemble after the frame PC",
1661 arg_parser
.add_argument(
1666 dest
="source_context",
1667 help="show NLINES source lines of source context",
1670 arg_parser
.add_argument(
1674 dest
="source_frames",
1675 help="show source for NFRAMES",
1678 arg_parser
.add_argument(
1680 action
="store_true",
1682 help="show source for all threads, not just the crashed thread",
1685 if add_interactive_options
:
1686 arg_parser
.add_argument(
1689 action
="store_true",
1690 help="parse a crash log and load it in a ScriptedProcess",
1693 arg_parser
.add_argument(
1696 action
="store_true",
1697 help="dump symbolicated stackframes without creating a debug session",
1700 arg_parser
.add_argument(
1704 help="the target binary path that should be used for interactive crashlog (optional)",
1707 arg_parser
.add_argument(
1711 action
="store_true",
1712 help="prevent the interactive crashlog to dump the process status and thread backtrace at launch",
1718 def CrashLogOptionParser():
1719 description
= """Symbolicate one or more darwin crash log files to provide source file and line information,
1720 inlined stack frames back to the concrete functions, and disassemble the location of the crash
1721 for the first frame of the crashed thread.
1722 If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1723 for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1724 created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1725 you to explore the program as if it were stopped at the locations described in the crash log and functions can
1726 be disassembled and lookups can be performed using the addresses found in the crash log."""
1727 return CreateSymbolicateCrashLogOptions("crashlog", description
, True)
1730 def SymbolicateCrashLogs(debugger
, command_args
, result
, is_command
):
1731 arg_parser
= CrashLogOptionParser()
1733 if not len(command_args
):
1734 arg_parser
.print_help()
1738 options
= arg_parser
.parse_args(command_args
)
1739 except Exception as e
:
1740 result
.SetError(str(e
))
1743 # Interactive mode requires running the crashlog command from inside lldb.
1744 if options
.interactive
and not is_command
:
1746 subprocess
.check_output(["/usr/bin/xcrun", "-f", "lldb"])
1756 "command script import lldb.macosx",
1758 "crashlog {}".format(shlex
.join(command_args
)),
1764 print(debugger
.GetVersionString())
1768 print("command_args = %s" % command_args
)
1769 print("options", options
)
1770 print("args", options
.reports
)
1772 if options
.debug_delay
> 0:
1773 print("Waiting %u seconds for debugger to attach..." % options
.debug_delay
)
1774 time
.sleep(options
.debug_delay
)
1775 error
= lldb
.SBError()
1777 def should_run_in_interactive_mode(options
, ci
):
1778 if options
.interactive
:
1782 # elif ci and ci.IsInteractive():
1787 ci
= debugger
.GetCommandInterpreter()
1790 for crashlog_file
in options
.reports
:
1791 crashlog_path
= os
.path
.expanduser(crashlog_file
)
1792 if not os
.path
.exists(crashlog_path
):
1793 raise FileNotFoundError(
1794 "crashlog file %s does not exist" % crashlog_path
1796 if should_run_in_interactive_mode(options
, ci
):
1798 load_crashlog_in_scripted_process(
1799 debugger
, crashlog_path
, options
, result
1801 except InteractiveCrashLogException
as e
:
1802 result
.SetError(str(e
))
1804 crash_log
= CrashLogParser
.create(
1805 debugger
, crashlog_path
, options
1807 SymbolicateCrashLog(crash_log
, options
)
1810 if __name__
== "__main__":
1811 # Create a new debugger instance
1812 debugger
= lldb
.SBDebugger
.Create()
1813 result
= lldb
.SBCommandReturnObject()
1814 SymbolicateCrashLogs(debugger
, sys
.argv
[1:], result
, False)
1815 lldb
.SBDebugger
.Destroy(debugger
)
1818 def __lldb_init_module(debugger
, internal_dict
):
1819 debugger
.HandleCommand(
1820 "command script add -o -c lldb.macosx.crashlog.Symbolicate -C disk-file crashlog"
1822 debugger
.HandleCommand(
1823 "command script add -o -f lldb.macosx.crashlog.save_crashlog -C disk-file save_crashlog"
1826 '"crashlog" and "save_crashlog" commands have been installed, use '
1827 'the "--help" options on these commands for detailed help.'