1be691f3bSpatrick#!/usr/bin/env python3
2061da546Spatrick
3061da546Spatrick#----------------------------------------------------------------------
4061da546Spatrick# Be sure to add the python path that points to the LLDB shared library.
5061da546Spatrick#
6061da546Spatrick# To use this in the embedded python interpreter using "lldb":
7061da546Spatrick#
8061da546Spatrick#   cd /path/containing/crashlog.py
9061da546Spatrick#   lldb
10061da546Spatrick#   (lldb) script import crashlog
11061da546Spatrick#   "crashlog" command installed, type "crashlog --help" for detailed help
12061da546Spatrick#   (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
13061da546Spatrick#
14061da546Spatrick# The benefit of running the crashlog command inside lldb in the
15061da546Spatrick# embedded python interpreter is when the command completes, there
16061da546Spatrick# will be a target with all of the files loaded at the locations
17061da546Spatrick# described in the crash log. Only the files that have stack frames
18061da546Spatrick# in the backtrace will be loaded unless the "--load-all" option
19061da546Spatrick# has been specified. This allows users to explore the program in the
20061da546Spatrick# state it was in right at crash time.
21061da546Spatrick#
22061da546Spatrick# On MacOSX csh, tcsh:
23061da546Spatrick#   ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
24061da546Spatrick#
25061da546Spatrick# On MacOSX sh, bash:
26061da546Spatrick#   PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
27061da546Spatrick#----------------------------------------------------------------------
28061da546Spatrick
29*f6aab3d8Srobertimport abc
30*f6aab3d8Srobertimport concurrent.futures
31*f6aab3d8Srobertimport contextlib
32061da546Spatrickimport datetime
33*f6aab3d8Srobertimport json
34061da546Spatrickimport optparse
35061da546Spatrickimport os
36061da546Spatrickimport platform
37061da546Spatrickimport plistlib
38061da546Spatrickimport re
39061da546Spatrickimport shlex
40061da546Spatrickimport string
41061da546Spatrickimport subprocess
42061da546Spatrickimport sys
43*f6aab3d8Srobertimport threading
44061da546Spatrickimport time
45061da546Spatrickimport uuid
46*f6aab3d8Srobert
47*f6aab3d8Srobert
48*f6aab3d8Srobertprint_lock = threading.RLock()
49be691f3bSpatrick
50be691f3bSpatricktry:
51be691f3bSpatrick    # First try for LLDB in case PYTHONPATH is already correctly setup.
52be691f3bSpatrick    import lldb
53be691f3bSpatrickexcept ImportError:
54be691f3bSpatrick    # Ask the command line driver for the path to the lldb module. Copy over
55be691f3bSpatrick    # the environment so that SDKROOT is propagated to xcrun.
56be691f3bSpatrick    command =  ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P']
57be691f3bSpatrick    # Extend the PYTHONPATH if the path exists and isn't already there.
58*f6aab3d8Srobert    lldb_python_path = subprocess.check_output(command).decode("utf-8").strip()
59be691f3bSpatrick    if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path):
60be691f3bSpatrick        sys.path.append(lldb_python_path)
61be691f3bSpatrick    # Try importing LLDB again.
62be691f3bSpatrick    try:
63be691f3bSpatrick        import lldb
64be691f3bSpatrick    except ImportError:
65be691f3bSpatrick        print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
66be691f3bSpatrick        sys.exit(1)
67be691f3bSpatrick
68be691f3bSpatrickfrom lldb.utils import symbolication
69be691f3bSpatrick
70061da546Spatrickdef read_plist(s):
71061da546Spatrick    if sys.version_info.major == 3:
72061da546Spatrick        return plistlib.loads(s)
73061da546Spatrick    else:
74061da546Spatrick        return plistlib.readPlistFromString(s)
75061da546Spatrick
76061da546Spatrickclass CrashLog(symbolication.Symbolicator):
77061da546Spatrick    class Thread:
78061da546Spatrick        """Class that represents a thread in a darwin crash log"""
79061da546Spatrick
80061da546Spatrick        def __init__(self, index, app_specific_backtrace):
81061da546Spatrick            self.index = index
82*f6aab3d8Srobert            self.id = index
83061da546Spatrick            self.frames = list()
84061da546Spatrick            self.idents = list()
85061da546Spatrick            self.registers = dict()
86061da546Spatrick            self.reason = None
87*f6aab3d8Srobert            self.name = None
88061da546Spatrick            self.queue = None
89*f6aab3d8Srobert            self.crashed = False
90061da546Spatrick            self.app_specific_backtrace = app_specific_backtrace
91061da546Spatrick
92061da546Spatrick        def dump(self, prefix):
93061da546Spatrick            if self.app_specific_backtrace:
94061da546Spatrick                print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason))
95061da546Spatrick            else:
96061da546Spatrick                print("%sThread[%u] %s" % (prefix, self.index, self.reason))
97061da546Spatrick            if self.frames:
98061da546Spatrick                print("%s  Frames:" % (prefix))
99061da546Spatrick                for frame in self.frames:
100061da546Spatrick                    frame.dump(prefix + '    ')
101061da546Spatrick            if self.registers:
102061da546Spatrick                print("%s  Registers:" % (prefix))
103061da546Spatrick                for reg in self.registers.keys():
104be691f3bSpatrick                    print("%s    %-8s = %#16.16x" % (prefix, reg, self.registers[reg]))
105061da546Spatrick
106061da546Spatrick        def dump_symbolicated(self, crash_log, options):
107061da546Spatrick            this_thread_crashed = self.app_specific_backtrace
108061da546Spatrick            if not this_thread_crashed:
109061da546Spatrick                this_thread_crashed = self.did_crash()
110061da546Spatrick                if options.crashed_only and this_thread_crashed == False:
111061da546Spatrick                    return
112061da546Spatrick
113061da546Spatrick            print("%s" % self)
114061da546Spatrick            display_frame_idx = -1
115061da546Spatrick            for frame_idx, frame in enumerate(self.frames):
116061da546Spatrick                disassemble = (
117061da546Spatrick                    this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth
118*f6aab3d8Srobert
119*f6aab3d8Srobert                # Except for the zeroth frame, we should subtract 1 from every
120*f6aab3d8Srobert                # frame pc to get the previous line entry.
121*f6aab3d8Srobert                pc = frame.pc & crash_log.addr_mask
122*f6aab3d8Srobert                pc = pc if frame_idx == 0 or pc == 0 else pc - 1
123*f6aab3d8Srobert                symbolicated_frame_addresses = crash_log.symbolicate(pc, options.verbose)
124061da546Spatrick
125061da546Spatrick                if symbolicated_frame_addresses:
126061da546Spatrick                    symbolicated_frame_address_idx = 0
127061da546Spatrick                    for symbolicated_frame_address in symbolicated_frame_addresses:
128061da546Spatrick                        display_frame_idx += 1
129061da546Spatrick                        print('[%3u] %s' % (frame_idx, symbolicated_frame_address))
130061da546Spatrick                        if (options.source_all or self.did_crash(
131061da546Spatrick                        )) and display_frame_idx < options.source_frames and options.source_context:
132061da546Spatrick                            source_context = options.source_context
133061da546Spatrick                            line_entry = symbolicated_frame_address.get_symbol_context().line_entry
134061da546Spatrick                            if line_entry.IsValid():
135061da546Spatrick                                strm = lldb.SBStream()
136061da546Spatrick                                if line_entry:
137be691f3bSpatrick                                    crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
138061da546Spatrick                                        line_entry.file, line_entry.line, source_context, source_context, "->", strm)
139061da546Spatrick                                source_text = strm.GetData()
140061da546Spatrick                                if source_text:
141061da546Spatrick                                    # Indent the source a bit
142061da546Spatrick                                    indent_str = '    '
143061da546Spatrick                                    join_str = '\n' + indent_str
144061da546Spatrick                                    print('%s%s' % (indent_str, join_str.join(source_text.split('\n'))))
145061da546Spatrick                        if symbolicated_frame_address_idx == 0:
146061da546Spatrick                            if disassemble:
147061da546Spatrick                                instructions = symbolicated_frame_address.get_instructions()
148061da546Spatrick                                if instructions:
149061da546Spatrick                                    print()
150061da546Spatrick                                    symbolication.disassemble_instructions(
151061da546Spatrick                                        crash_log.get_target(),
152061da546Spatrick                                        instructions,
153061da546Spatrick                                        frame.pc,
154061da546Spatrick                                        options.disassemble_before,
155061da546Spatrick                                        options.disassemble_after,
156061da546Spatrick                                        frame.index > 0)
157061da546Spatrick                                    print()
158061da546Spatrick                        symbolicated_frame_address_idx += 1
159061da546Spatrick                else:
160061da546Spatrick                    print(frame)
161be691f3bSpatrick            if self.registers:
162be691f3bSpatrick                print()
163be691f3bSpatrick                for reg in self.registers.keys():
164be691f3bSpatrick                    print("    %-8s = %#16.16x" % (reg, self.registers[reg]))
165*f6aab3d8Srobert            elif self.crashed:
166*f6aab3d8Srobert               print()
167*f6aab3d8Srobert               print("No thread state (register information) available")
168061da546Spatrick
169061da546Spatrick        def add_ident(self, ident):
170061da546Spatrick            if ident not in self.idents:
171061da546Spatrick                self.idents.append(ident)
172061da546Spatrick
173061da546Spatrick        def did_crash(self):
174061da546Spatrick            return self.reason is not None
175061da546Spatrick
176061da546Spatrick        def __str__(self):
177061da546Spatrick            if self.app_specific_backtrace:
178061da546Spatrick                s = "Application Specific Backtrace[%u]" % self.index
179061da546Spatrick            else:
180061da546Spatrick                s = "Thread[%u]" % self.index
181061da546Spatrick            if self.reason:
182061da546Spatrick                s += ' %s' % self.reason
183061da546Spatrick            return s
184061da546Spatrick
185061da546Spatrick    class Frame:
186061da546Spatrick        """Class that represents a stack frame in a thread in a darwin crash log"""
187061da546Spatrick
188061da546Spatrick        def __init__(self, index, pc, description):
189061da546Spatrick            self.pc = pc
190061da546Spatrick            self.description = description
191061da546Spatrick            self.index = index
192061da546Spatrick
193061da546Spatrick        def __str__(self):
194061da546Spatrick            if self.description:
195061da546Spatrick                return "[%3u] 0x%16.16x %s" % (
196061da546Spatrick                    self.index, self.pc, self.description)
197061da546Spatrick            else:
198061da546Spatrick                return "[%3u] 0x%16.16x" % (self.index, self.pc)
199061da546Spatrick
200061da546Spatrick        def dump(self, prefix):
201061da546Spatrick            print("%s%s" % (prefix, str(self)))
202061da546Spatrick
203061da546Spatrick    class DarwinImage(symbolication.Image):
204061da546Spatrick        """Class that represents a binary images in a darwin crash log"""
205061da546Spatrick        dsymForUUIDBinary = '/usr/local/bin/dsymForUUID'
206061da546Spatrick        if not os.path.exists(dsymForUUIDBinary):
207061da546Spatrick            try:
208061da546Spatrick                dsymForUUIDBinary = subprocess.check_output('which dsymForUUID',
209061da546Spatrick                                                            shell=True).decode("utf-8").rstrip('\n')
210061da546Spatrick            except:
211061da546Spatrick                dsymForUUIDBinary = ""
212061da546Spatrick
213061da546Spatrick        dwarfdump_uuid_regex = re.compile(
214061da546Spatrick            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
215061da546Spatrick
216061da546Spatrick        def __init__(
217061da546Spatrick                self,
218061da546Spatrick                text_addr_lo,
219061da546Spatrick                text_addr_hi,
220061da546Spatrick                identifier,
221061da546Spatrick                version,
222061da546Spatrick                uuid,
223061da546Spatrick                path,
224061da546Spatrick                verbose):
225061da546Spatrick            symbolication.Image.__init__(self, path, uuid)
226061da546Spatrick            self.add_section(
227061da546Spatrick                symbolication.Section(
228061da546Spatrick                    text_addr_lo,
229061da546Spatrick                    text_addr_hi,
230061da546Spatrick                    "__TEXT"))
231061da546Spatrick            self.identifier = identifier
232061da546Spatrick            self.version = version
233061da546Spatrick            self.verbose = verbose
234061da546Spatrick
235061da546Spatrick        def show_symbol_progress(self):
236061da546Spatrick            """
237061da546Spatrick            Hide progress output and errors from system frameworks as they are plentiful.
238061da546Spatrick            """
239061da546Spatrick            if self.verbose:
240061da546Spatrick                return True
241061da546Spatrick            return not (self.path.startswith("/System/Library/") or
242061da546Spatrick                        self.path.startswith("/usr/lib/"))
243061da546Spatrick
244061da546Spatrick
245061da546Spatrick        def find_matching_slice(self):
246061da546Spatrick            dwarfdump_cmd_output = subprocess.check_output(
247061da546Spatrick                'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8")
248061da546Spatrick            self_uuid = self.get_uuid()
249061da546Spatrick            for line in dwarfdump_cmd_output.splitlines():
250061da546Spatrick                match = self.dwarfdump_uuid_regex.search(line)
251061da546Spatrick                if match:
252061da546Spatrick                    dwarf_uuid_str = match.group(1)
253061da546Spatrick                    dwarf_uuid = uuid.UUID(dwarf_uuid_str)
254061da546Spatrick                    if self_uuid == dwarf_uuid:
255061da546Spatrick                        self.resolved_path = self.path
256061da546Spatrick                        self.arch = match.group(2)
257061da546Spatrick                        return True
258061da546Spatrick            if not self.resolved_path:
259061da546Spatrick                self.unavailable = True
260061da546Spatrick                if self.show_symbol_progress():
261061da546Spatrick                    print(("error\n    error: unable to locate '%s' with UUID %s"
262061da546Spatrick                           % (self.path, self.get_normalized_uuid_string())))
263061da546Spatrick                return False
264061da546Spatrick
265061da546Spatrick        def locate_module_and_debug_symbols(self):
266061da546Spatrick            # Don't load a module twice...
267061da546Spatrick            if self.resolved:
268061da546Spatrick                return True
269061da546Spatrick            # Mark this as resolved so we don't keep trying
270061da546Spatrick            self.resolved = True
271061da546Spatrick            uuid_str = self.get_normalized_uuid_string()
272061da546Spatrick            if self.show_symbol_progress():
273*f6aab3d8Srobert                with print_lock:
274*f6aab3d8Srobert                    print('Getting symbols for %s %s...' % (uuid_str, self.path))
275061da546Spatrick            if os.path.exists(self.dsymForUUIDBinary):
276061da546Spatrick                dsym_for_uuid_command = '%s %s' % (
277061da546Spatrick                    self.dsymForUUIDBinary, uuid_str)
278061da546Spatrick                s = subprocess.check_output(dsym_for_uuid_command, shell=True)
279061da546Spatrick                if s:
280061da546Spatrick                    try:
281061da546Spatrick                        plist_root = read_plist(s)
282061da546Spatrick                    except:
283*f6aab3d8Srobert                        with print_lock:
284061da546Spatrick                            print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s))
285061da546Spatrick                        raise
286061da546Spatrick                    if plist_root:
287061da546Spatrick                        plist = plist_root[uuid_str]
288061da546Spatrick                        if plist:
289061da546Spatrick                            if 'DBGArchitecture' in plist:
290061da546Spatrick                                self.arch = plist['DBGArchitecture']
291061da546Spatrick                            if 'DBGDSYMPath' in plist:
292061da546Spatrick                                self.symfile = os.path.realpath(
293061da546Spatrick                                    plist['DBGDSYMPath'])
294061da546Spatrick                            if 'DBGSymbolRichExecutable' in plist:
295061da546Spatrick                                self.path = os.path.expanduser(
296061da546Spatrick                                    plist['DBGSymbolRichExecutable'])
297061da546Spatrick                                self.resolved_path = self.path
298061da546Spatrick            if not self.resolved_path and os.path.exists(self.path):
299061da546Spatrick                if not self.find_matching_slice():
300061da546Spatrick                    return False
301061da546Spatrick            if not self.resolved_path and not os.path.exists(self.path):
302061da546Spatrick                try:
303*f6aab3d8Srobert                    mdfind_results = subprocess.check_output(
304061da546Spatrick                        ["/usr/bin/mdfind",
305*f6aab3d8Srobert                         "com_apple_xcode_dsym_uuids == %s" % uuid_str]).decode("utf-8").splitlines()
306*f6aab3d8Srobert                    found_matching_slice = False
307*f6aab3d8Srobert                    for dsym in mdfind_results:
308061da546Spatrick                        dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
309*f6aab3d8Srobert                        if not os.path.exists(dwarf_dir):
310*f6aab3d8Srobert                            # Not a dSYM bundle, probably an Xcode archive.
311*f6aab3d8Srobert                            continue
312*f6aab3d8Srobert                        with print_lock:
313*f6aab3d8Srobert                            print('falling back to binary inside "%s"' % dsym)
314*f6aab3d8Srobert                        self.symfile = dsym
315061da546Spatrick                        for filename in os.listdir(dwarf_dir):
316061da546Spatrick                           self.path = os.path.join(dwarf_dir, filename)
317*f6aab3d8Srobert                           if self.find_matching_slice():
318*f6aab3d8Srobert                              found_matching_slice = True
319*f6aab3d8Srobert                              break
320*f6aab3d8Srobert                        if found_matching_slice:
321061da546Spatrick                           break
322061da546Spatrick                except:
323061da546Spatrick                    pass
324061da546Spatrick            if (self.resolved_path and os.path.exists(self.resolved_path)) or (
325061da546Spatrick                    self.path and os.path.exists(self.path)):
326*f6aab3d8Srobert                with print_lock:
327*f6aab3d8Srobert                    print('Resolved symbols for %s %s...' % (uuid_str, self.path))
328061da546Spatrick                return True
329061da546Spatrick            else:
330061da546Spatrick                self.unavailable = True
331061da546Spatrick            return False
332061da546Spatrick
333be691f3bSpatrick    def __init__(self, debugger, path, verbose):
334061da546Spatrick        """CrashLog constructor that take a path to a darwin crash log file"""
335be691f3bSpatrick        symbolication.Symbolicator.__init__(self, debugger)
336061da546Spatrick        self.path = os.path.expanduser(path)
337061da546Spatrick        self.info_lines = list()
338061da546Spatrick        self.system_profile = list()
339061da546Spatrick        self.threads = list()
340061da546Spatrick        self.backtraces = list()  # For application specific backtraces
341061da546Spatrick        self.idents = list()  # A list of the required identifiers for doing all stack backtraces
342*f6aab3d8Srobert        self.errors = list()
343*f6aab3d8Srobert        self.exception = dict()
344061da546Spatrick        self.crashed_thread_idx = -1
345061da546Spatrick        self.version = -1
346061da546Spatrick        self.target = None
347061da546Spatrick        self.verbose = verbose
348061da546Spatrick
349061da546Spatrick    def dump(self):
350061da546Spatrick        print("Crash Log File: %s" % (self.path))
351061da546Spatrick        if self.backtraces:
352061da546Spatrick            print("\nApplication Specific Backtraces:")
353061da546Spatrick            for thread in self.backtraces:
354061da546Spatrick                thread.dump('  ')
355061da546Spatrick        print("\nThreads:")
356061da546Spatrick        for thread in self.threads:
357061da546Spatrick            thread.dump('  ')
358061da546Spatrick        print("\nImages:")
359061da546Spatrick        for image in self.images:
360061da546Spatrick            image.dump('  ')
361061da546Spatrick
362*f6aab3d8Srobert    def set_main_image(self, identifier):
363*f6aab3d8Srobert        for i, image in enumerate(self.images):
364*f6aab3d8Srobert            if image.identifier == identifier:
365*f6aab3d8Srobert                self.images.insert(0, self.images.pop(i))
366*f6aab3d8Srobert                break
367*f6aab3d8Srobert
368061da546Spatrick    def find_image_with_identifier(self, identifier):
369061da546Spatrick        for image in self.images:
370061da546Spatrick            if image.identifier == identifier:
371061da546Spatrick                return image
372061da546Spatrick        regex_text = '^.*\.%s$' % (re.escape(identifier))
373061da546Spatrick        regex = re.compile(regex_text)
374061da546Spatrick        for image in self.images:
375061da546Spatrick            if regex.match(image.identifier):
376061da546Spatrick                return image
377061da546Spatrick        return None
378061da546Spatrick
379061da546Spatrick    def create_target(self):
380061da546Spatrick        if self.target is None:
381061da546Spatrick            self.target = symbolication.Symbolicator.create_target(self)
382061da546Spatrick            if self.target:
383061da546Spatrick                return self.target
384061da546Spatrick            # We weren't able to open the main executable as, but we can still
385061da546Spatrick            # symbolicate
386061da546Spatrick            print('crashlog.create_target()...2')
387061da546Spatrick            if self.idents:
388061da546Spatrick                for ident in self.idents:
389061da546Spatrick                    image = self.find_image_with_identifier(ident)
390061da546Spatrick                    if image:
391be691f3bSpatrick                        self.target = image.create_target(self.debugger)
392061da546Spatrick                        if self.target:
393061da546Spatrick                            return self.target  # success
394061da546Spatrick            print('crashlog.create_target()...3')
395061da546Spatrick            for image in self.images:
396be691f3bSpatrick                self.target = image.create_target(self.debugger)
397061da546Spatrick                if self.target:
398061da546Spatrick                    return self.target  # success
399061da546Spatrick            print('crashlog.create_target()...4')
400061da546Spatrick            print('error: Unable to locate any executables from the crash log.')
401061da546Spatrick            print('       Try loading the executable into lldb before running crashlog')
402061da546Spatrick            print('       and/or make sure the .dSYM bundles can be found by Spotlight.')
403061da546Spatrick        return self.target
404061da546Spatrick
405061da546Spatrick    def get_target(self):
406061da546Spatrick        return self.target
407061da546Spatrick
408061da546Spatrick
409be691f3bSpatrickclass CrashLogFormatException(Exception):
410be691f3bSpatrick    pass
411be691f3bSpatrick
412be691f3bSpatrick
413be691f3bSpatrickclass CrashLogParseException(Exception):
414be691f3bSpatrick    pass
415be691f3bSpatrick
416*f6aab3d8Srobertclass InteractiveCrashLogException(Exception):
417*f6aab3d8Srobert    pass
418be691f3bSpatrick
419be691f3bSpatrickclass CrashLogParser:
420*f6aab3d8Srobert    @staticmethod
421*f6aab3d8Srobert    def create(debugger, path, verbose):
422*f6aab3d8Srobert        data = JSONCrashLogParser.is_valid_json(path)
423*f6aab3d8Srobert        if data:
424*f6aab3d8Srobert            parser = JSONCrashLogParser(debugger, path, verbose)
425*f6aab3d8Srobert            parser.data = data
426*f6aab3d8Srobert            return parser
427*f6aab3d8Srobert        else:
428*f6aab3d8Srobert            return TextCrashLogParser(debugger, path, verbose)
429be691f3bSpatrick
430be691f3bSpatrick    def __init__(self, debugger, path, verbose):
431be691f3bSpatrick        self.path = os.path.expanduser(path)
432be691f3bSpatrick        self.verbose = verbose
433be691f3bSpatrick        self.crashlog = CrashLog(debugger, self.path, self.verbose)
434be691f3bSpatrick
435*f6aab3d8Srobert    @abc.abstractmethod
436be691f3bSpatrick    def parse(self):
437*f6aab3d8Srobert        pass
438be691f3bSpatrick
439be691f3bSpatrick
440*f6aab3d8Srobertclass JSONCrashLogParser(CrashLogParser):
441*f6aab3d8Srobert    @staticmethod
442*f6aab3d8Srobert    def is_valid_json(path):
443*f6aab3d8Srobert        def parse_json(buffer):
444be691f3bSpatrick            try:
445*f6aab3d8Srobert                return json.loads(buffer)
446*f6aab3d8Srobert            except:
447*f6aab3d8Srobert                # The first line can contain meta data. Try stripping it and
448*f6aab3d8Srobert                # try again.
449*f6aab3d8Srobert                head, _, tail = buffer.partition('\n')
450*f6aab3d8Srobert                return json.loads(tail)
451be691f3bSpatrick
452*f6aab3d8Srobert        with open(path, 'r', encoding='utf-8') as f:
453*f6aab3d8Srobert            buffer = f.read()
454*f6aab3d8Srobert        try:
455*f6aab3d8Srobert            return parse_json(buffer)
456*f6aab3d8Srobert        except:
457*f6aab3d8Srobert            return None
458*f6aab3d8Srobert
459*f6aab3d8Srobert    def parse(self):
460be691f3bSpatrick        try:
461be691f3bSpatrick            self.parse_process_info(self.data)
462be691f3bSpatrick            self.parse_images(self.data['usedImages'])
463*f6aab3d8Srobert            self.parse_main_image(self.data)
464be691f3bSpatrick            self.parse_threads(self.data['threads'])
465*f6aab3d8Srobert            if 'asi' in self.data:
466*f6aab3d8Srobert                self.crashlog.asi = self.data['asi']
467*f6aab3d8Srobert            if 'asiBacktraces' in self.data:
468*f6aab3d8Srobert                self.parse_app_specific_backtraces(self.data['asiBacktraces'])
469*f6aab3d8Srobert            if 'lastExceptionBacktrace' in self.data:
470*f6aab3d8Srobert                self.crashlog.asb = self.data['lastExceptionBacktrace']
471*f6aab3d8Srobert            self.parse_errors(self.data)
472be691f3bSpatrick            thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
473be691f3bSpatrick            reason = self.parse_crash_reason(self.data['exception'])
474be691f3bSpatrick            if thread.reason:
475be691f3bSpatrick                thread.reason = '{} {}'.format(thread.reason, reason)
476be691f3bSpatrick            else:
477be691f3bSpatrick                thread.reason = reason
478be691f3bSpatrick        except (KeyError, ValueError, TypeError) as e:
479be691f3bSpatrick            raise CrashLogParseException(
480be691f3bSpatrick                'Failed to parse JSON crashlog: {}: {}'.format(
481be691f3bSpatrick                    type(e).__name__, e))
482be691f3bSpatrick
483be691f3bSpatrick        return self.crashlog
484be691f3bSpatrick
485be691f3bSpatrick    def get_used_image(self, idx):
486be691f3bSpatrick        return self.data['usedImages'][idx]
487be691f3bSpatrick
488be691f3bSpatrick    def parse_process_info(self, json_data):
489be691f3bSpatrick        self.crashlog.process_id = json_data['pid']
490be691f3bSpatrick        self.crashlog.process_identifier = json_data['procName']
491be691f3bSpatrick
492be691f3bSpatrick    def parse_crash_reason(self, json_exception):
493*f6aab3d8Srobert        self.crashlog.exception = json_exception
494be691f3bSpatrick        exception_type = json_exception['type']
495*f6aab3d8Srobert        exception_signal = " "
496*f6aab3d8Srobert        if 'signal' in json_exception:
497*f6aab3d8Srobert            exception_signal += "({})".format(json_exception['signal'])
498*f6aab3d8Srobert
499be691f3bSpatrick        if 'codes' in json_exception:
500be691f3bSpatrick            exception_extra = " ({})".format(json_exception['codes'])
501be691f3bSpatrick        elif 'subtype' in json_exception:
502be691f3bSpatrick            exception_extra = " ({})".format(json_exception['subtype'])
503be691f3bSpatrick        else:
504be691f3bSpatrick            exception_extra = ""
505*f6aab3d8Srobert        return "{}{}{}".format(exception_type, exception_signal,
506be691f3bSpatrick                                  exception_extra)
507be691f3bSpatrick
508be691f3bSpatrick    def parse_images(self, json_images):
509be691f3bSpatrick        idx = 0
510be691f3bSpatrick        for json_image in json_images:
511be691f3bSpatrick            img_uuid = uuid.UUID(json_image['uuid'])
512be691f3bSpatrick            low = int(json_image['base'])
513be691f3bSpatrick            high = int(0)
514be691f3bSpatrick            name = json_image['name'] if 'name' in json_image else ''
515be691f3bSpatrick            path = json_image['path'] if 'path' in json_image else ''
516be691f3bSpatrick            version = ''
517be691f3bSpatrick            darwin_image = self.crashlog.DarwinImage(low, high, name, version,
518be691f3bSpatrick                                                     img_uuid, path,
519be691f3bSpatrick                                                     self.verbose)
520be691f3bSpatrick            self.crashlog.images.append(darwin_image)
521be691f3bSpatrick            idx += 1
522be691f3bSpatrick
523*f6aab3d8Srobert    def parse_main_image(self, json_data):
524*f6aab3d8Srobert        if 'procName' in json_data:
525*f6aab3d8Srobert            proc_name = json_data['procName']
526*f6aab3d8Srobert            self.crashlog.set_main_image(proc_name)
527*f6aab3d8Srobert
528be691f3bSpatrick    def parse_frames(self, thread, json_frames):
529be691f3bSpatrick        idx = 0
530be691f3bSpatrick        for json_frame in json_frames:
531be691f3bSpatrick            image_id = int(json_frame['imageIndex'])
532*f6aab3d8Srobert            json_image = self.get_used_image(image_id)
533*f6aab3d8Srobert            ident = json_image['name'] if 'name' in json_image else ''
534be691f3bSpatrick            thread.add_ident(ident)
535be691f3bSpatrick            if ident not in self.crashlog.idents:
536be691f3bSpatrick                self.crashlog.idents.append(ident)
537be691f3bSpatrick
538be691f3bSpatrick            frame_offset = int(json_frame['imageOffset'])
539be691f3bSpatrick            image_addr = self.get_used_image(image_id)['base']
540be691f3bSpatrick            pc = image_addr + frame_offset
541be691f3bSpatrick            thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
542*f6aab3d8Srobert
543*f6aab3d8Srobert            # on arm64 systems, if it jump through a null function pointer,
544*f6aab3d8Srobert            # we end up at address 0 and the crash reporter unwinder
545*f6aab3d8Srobert            # misses the frame that actually faulted.
546*f6aab3d8Srobert            # But $lr can tell us where the last BL/BLR instruction used
547*f6aab3d8Srobert            # was at, so insert that address as the caller stack frame.
548*f6aab3d8Srobert            if idx == 0 and pc == 0 and "lr" in thread.registers:
549*f6aab3d8Srobert                pc = thread.registers["lr"]
550*f6aab3d8Srobert                for image in self.data['usedImages']:
551*f6aab3d8Srobert                    text_lo = image['base']
552*f6aab3d8Srobert                    text_hi = text_lo + image['size']
553*f6aab3d8Srobert                    if text_lo <= pc < text_hi:
554*f6aab3d8Srobert                      idx += 1
555*f6aab3d8Srobert                      frame_offset = pc - text_lo
556*f6aab3d8Srobert                      thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
557*f6aab3d8Srobert                      break
558*f6aab3d8Srobert
559be691f3bSpatrick            idx += 1
560be691f3bSpatrick
561be691f3bSpatrick    def parse_threads(self, json_threads):
562be691f3bSpatrick        idx = 0
563be691f3bSpatrick        for json_thread in json_threads:
564be691f3bSpatrick            thread = self.crashlog.Thread(idx, False)
565be691f3bSpatrick            if 'name' in json_thread:
566*f6aab3d8Srobert                thread.name = json_thread['name']
567be691f3bSpatrick                thread.reason = json_thread['name']
568*f6aab3d8Srobert            if 'id' in json_thread:
569*f6aab3d8Srobert                thread.id = int(json_thread['id'])
570be691f3bSpatrick            if json_thread.get('triggered', False):
571be691f3bSpatrick                self.crashlog.crashed_thread_idx = idx
572*f6aab3d8Srobert                thread.crashed = True
573*f6aab3d8Srobert                if 'threadState' in json_thread:
574be691f3bSpatrick                    thread.registers = self.parse_thread_registers(
575be691f3bSpatrick                        json_thread['threadState'])
576*f6aab3d8Srobert            if 'queue' in json_thread:
577be691f3bSpatrick                thread.queue = json_thread.get('queue')
578be691f3bSpatrick            self.parse_frames(thread, json_thread.get('frames', []))
579be691f3bSpatrick            self.crashlog.threads.append(thread)
580be691f3bSpatrick            idx += 1
581be691f3bSpatrick
582*f6aab3d8Srobert    def parse_asi_backtrace(self, thread, bt):
583*f6aab3d8Srobert        for line in bt.split('\n'):
584*f6aab3d8Srobert            frame_match = TextCrashLogParser.frame_regex.search(line)
585*f6aab3d8Srobert            if not frame_match:
586*f6aab3d8Srobert                print("error: can't parse application specific backtrace.")
587*f6aab3d8Srobert                return False
588*f6aab3d8Srobert
589*f6aab3d8Srobert            (frame_id, frame_img_name, frame_addr,
590*f6aab3d8Srobert                frame_ofs) = frame_match.groups()
591*f6aab3d8Srobert
592*f6aab3d8Srobert            thread.add_ident(frame_img_name)
593*f6aab3d8Srobert            if frame_img_name not in self.crashlog.idents:
594*f6aab3d8Srobert                self.crashlog.idents.append(frame_img_name)
595*f6aab3d8Srobert            thread.frames.append(self.crashlog.Frame(int(frame_id), int(
596*f6aab3d8Srobert                frame_addr, 0), frame_ofs))
597*f6aab3d8Srobert
598*f6aab3d8Srobert        return True
599*f6aab3d8Srobert
600*f6aab3d8Srobert    def parse_app_specific_backtraces(self, json_app_specific_bts):
601*f6aab3d8Srobert        for idx, backtrace in enumerate(json_app_specific_bts):
602*f6aab3d8Srobert            thread = self.crashlog.Thread(idx, True)
603*f6aab3d8Srobert            thread.queue = "Application Specific Backtrace"
604*f6aab3d8Srobert            if self.parse_asi_backtrace(thread, backtrace):
605*f6aab3d8Srobert                self.crashlog.threads.append(thread)
606*f6aab3d8Srobert
607*f6aab3d8Srobert    def parse_thread_registers(self, json_thread_state, prefix=None):
608be691f3bSpatrick        registers = dict()
609be691f3bSpatrick        for key, state in json_thread_state.items():
610*f6aab3d8Srobert            if key == "rosetta":
611*f6aab3d8Srobert                registers.update(self.parse_thread_registers(state))
612*f6aab3d8Srobert                continue
613*f6aab3d8Srobert            if key == "x":
614*f6aab3d8Srobert                gpr_dict = { str(idx) : reg for idx,reg in enumerate(state) }
615*f6aab3d8Srobert                registers.update(self.parse_thread_registers(gpr_dict, key))
616*f6aab3d8Srobert                continue
617be691f3bSpatrick            try:
618be691f3bSpatrick                value = int(state['value'])
619*f6aab3d8Srobert                registers["{}{}".format(prefix or '',key)] = value
620*f6aab3d8Srobert            except (KeyError, ValueError, TypeError):
621be691f3bSpatrick                pass
622be691f3bSpatrick        return registers
623be691f3bSpatrick
624*f6aab3d8Srobert    def parse_errors(self, json_data):
625*f6aab3d8Srobert       if 'reportNotes' in json_data:
626*f6aab3d8Srobert          self.crashlog.errors = json_data['reportNotes']
627*f6aab3d8Srobert
628be691f3bSpatrick
629be691f3bSpatrickclass CrashLogParseMode:
630be691f3bSpatrick    NORMAL = 0
631be691f3bSpatrick    THREAD = 1
632be691f3bSpatrick    IMAGES = 2
633be691f3bSpatrick    THREGS = 3
634be691f3bSpatrick    SYSTEM = 4
635be691f3bSpatrick    INSTRS = 5
636be691f3bSpatrick
637*f6aab3d8Srobertclass TextCrashLogParser(CrashLogParser):
638*f6aab3d8Srobert    parent_process_regex = re.compile(r'^Parent Process:\s*(.*)\[(\d+)\]')
639*f6aab3d8Srobert    thread_state_regex = re.compile(r'^Thread \d+ crashed with')
640*f6aab3d8Srobert    thread_instrs_regex = re.compile(r'^Thread \d+ instruction stream')
641*f6aab3d8Srobert    thread_regex = re.compile(r'^Thread (\d+).*:')
642*f6aab3d8Srobert    app_backtrace_regex = re.compile(r'^Application Specific Backtrace (\d+).*:')
643*f6aab3d8Srobert    version = r'\(.+\)|(?:arm|x86_)[0-9a-z]+'
644*f6aab3d8Srobert    frame_regex = re.compile(r'^(\d+)\s+'              # id
645*f6aab3d8Srobert                             r'(.+?)\s+'               # img_name
646*f6aab3d8Srobert                             r'(?:' +version+ r'\s+)?' # img_version
647*f6aab3d8Srobert                             r'(0x[0-9a-fA-F]{4,})'    # addr (4 chars or more)
648*f6aab3d8Srobert                             r'(?: +(.*))?'            # offs
649be691f3bSpatrick                            )
650*f6aab3d8Srobert    null_frame_regex = re.compile(r'^\d+\s+\?\?\?\s+0{4,} +')
651be691f3bSpatrick    image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)'          # img_lo
652*f6aab3d8Srobert                                  r'\s+-\s+'                   #   -
653*f6aab3d8Srobert                                  r'(0x[0-9a-fA-F]+)\s+'       # img_hi
654*f6aab3d8Srobert                                  r'[+]?(.+?)\s+'              # img_name
655*f6aab3d8Srobert                                  r'(?:(' +version+ r')\s+)?'  # img_version
656*f6aab3d8Srobert                                  r'(?:<([-0-9a-fA-F]+)>\s+)?' # img_uuid
657*f6aab3d8Srobert                                  r'(\?+|/.*)'                 # img_path
658be691f3bSpatrick                                 )
659*f6aab3d8Srobert    exception_type_regex = re.compile(r'^Exception Type:\s+(EXC_[A-Z_]+)(?:\s+\((.*)\))?')
660*f6aab3d8Srobert    exception_codes_regex = re.compile(r'^Exception Codes:\s+(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+)')
661*f6aab3d8Srobert    exception_extra_regex = re.compile(r'^Exception\s+.*:\s+(.*)')
662be691f3bSpatrick
663be691f3bSpatrick    def __init__(self, debugger, path, verbose):
664*f6aab3d8Srobert        super().__init__(debugger, path, verbose)
665be691f3bSpatrick        self.thread = None
666be691f3bSpatrick        self.app_specific_backtrace = False
667be691f3bSpatrick        self.parse_mode = CrashLogParseMode.NORMAL
668be691f3bSpatrick        self.parsers = {
669be691f3bSpatrick            CrashLogParseMode.NORMAL : self.parse_normal,
670be691f3bSpatrick            CrashLogParseMode.THREAD : self.parse_thread,
671be691f3bSpatrick            CrashLogParseMode.IMAGES : self.parse_images,
672be691f3bSpatrick            CrashLogParseMode.THREGS : self.parse_thread_registers,
673be691f3bSpatrick            CrashLogParseMode.SYSTEM : self.parse_system,
674be691f3bSpatrick            CrashLogParseMode.INSTRS : self.parse_instructions,
675be691f3bSpatrick        }
676be691f3bSpatrick
677be691f3bSpatrick    def parse(self):
678*f6aab3d8Srobert        with open(self.path,'r', encoding='utf-8') as f:
679be691f3bSpatrick            lines = f.read().splitlines()
680be691f3bSpatrick
681be691f3bSpatrick        for line in lines:
682be691f3bSpatrick            line_len = len(line)
683be691f3bSpatrick            if line_len == 0:
684be691f3bSpatrick                if self.thread:
685be691f3bSpatrick                    if self.parse_mode == CrashLogParseMode.THREAD:
686be691f3bSpatrick                        if self.thread.index == self.crashlog.crashed_thread_idx:
687be691f3bSpatrick                            self.thread.reason = ''
688*f6aab3d8Srobert                            if hasattr(self.crashlog, 'thread_exception'):
689be691f3bSpatrick                                self.thread.reason += self.crashlog.thread_exception
690*f6aab3d8Srobert                            if hasattr(self.crashlog, 'thread_exception_data'):
691be691f3bSpatrick                                self.thread.reason += " (%s)" % self.crashlog.thread_exception_data
692be691f3bSpatrick                        if self.app_specific_backtrace:
693be691f3bSpatrick                            self.crashlog.backtraces.append(self.thread)
694be691f3bSpatrick                        else:
695be691f3bSpatrick                            self.crashlog.threads.append(self.thread)
696be691f3bSpatrick                    self.thread = None
697be691f3bSpatrick                else:
698be691f3bSpatrick                    # only append an extra empty line if the previous line
699be691f3bSpatrick                    # in the info_lines wasn't empty
700be691f3bSpatrick                    if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]):
701be691f3bSpatrick                        self.crashlog.info_lines.append(line)
702be691f3bSpatrick                self.parse_mode = CrashLogParseMode.NORMAL
703be691f3bSpatrick            else:
704be691f3bSpatrick                self.parsers[self.parse_mode](line)
705be691f3bSpatrick
706be691f3bSpatrick        return self.crashlog
707be691f3bSpatrick
708*f6aab3d8Srobert    def parse_exception(self, line):
709*f6aab3d8Srobert        if not line.startswith('Exception'):
710*f6aab3d8Srobert            return
711*f6aab3d8Srobert        if line.startswith('Exception Type:'):
712*f6aab3d8Srobert            self.crashlog.thread_exception = line[15:].strip()
713*f6aab3d8Srobert            exception_type_match = self.exception_type_regex.search(line)
714*f6aab3d8Srobert            if exception_type_match:
715*f6aab3d8Srobert                exc_type, exc_signal = exception_type_match.groups()
716*f6aab3d8Srobert                self.crashlog.exception['type'] = exc_type
717*f6aab3d8Srobert                if exc_signal:
718*f6aab3d8Srobert                    self.crashlog.exception['signal'] = exc_signal
719*f6aab3d8Srobert        elif line.startswith('Exception Subtype:'):
720*f6aab3d8Srobert            self.crashlog.thread_exception_subtype = line[18:].strip()
721*f6aab3d8Srobert            if 'type' in self.crashlog.exception:
722*f6aab3d8Srobert                self.crashlog.exception['subtype'] = self.crashlog.thread_exception_subtype
723*f6aab3d8Srobert        elif line.startswith('Exception Codes:'):
724*f6aab3d8Srobert            self.crashlog.thread_exception_data = line[16:].strip()
725*f6aab3d8Srobert            if 'type' not in self.crashlog.exception:
726*f6aab3d8Srobert                return
727*f6aab3d8Srobert            exception_codes_match = self.exception_codes_regex.search(line)
728*f6aab3d8Srobert            if exception_codes_match:
729*f6aab3d8Srobert                self.crashlog.exception['codes'] = self.crashlog.thread_exception_data
730*f6aab3d8Srobert                code, subcode = exception_codes_match.groups()
731*f6aab3d8Srobert                self.crashlog.exception['rawCodes'] = [int(code, base=16),
732*f6aab3d8Srobert                                                       int(subcode, base=16)]
733*f6aab3d8Srobert        else:
734*f6aab3d8Srobert            if 'type' not in self.crashlog.exception:
735*f6aab3d8Srobert                return
736*f6aab3d8Srobert            exception_extra_match = self.exception_extra_regex.search(line)
737*f6aab3d8Srobert            if exception_extra_match:
738*f6aab3d8Srobert                self.crashlog.exception['message'] = exception_extra_match.group(1)
739be691f3bSpatrick
740be691f3bSpatrick    def parse_normal(self, line):
741be691f3bSpatrick        if line.startswith('Process:'):
742be691f3bSpatrick            (self.crashlog.process_name, pid_with_brackets) = line[
743be691f3bSpatrick                8:].strip().split(' [')
744be691f3bSpatrick            self.crashlog.process_id = pid_with_brackets.strip('[]')
745be691f3bSpatrick        elif line.startswith('Identifier:'):
746be691f3bSpatrick            self.crashlog.process_identifier = line[11:].strip()
747be691f3bSpatrick        elif line.startswith('Version:'):
748be691f3bSpatrick            version_string = line[8:].strip()
749be691f3bSpatrick            matched_pair = re.search("(.+)\((.+)\)", version_string)
750be691f3bSpatrick            if matched_pair:
751be691f3bSpatrick                self.crashlog.process_version = matched_pair.group(1)
752be691f3bSpatrick                self.crashlog.process_compatability_version = matched_pair.group(
753be691f3bSpatrick                    2)
754be691f3bSpatrick            else:
755be691f3bSpatrick                self.crashlog.process = version_string
756be691f3bSpatrick                self.crashlog.process_compatability_version = version_string
757be691f3bSpatrick        elif self.parent_process_regex.search(line):
758be691f3bSpatrick            parent_process_match = self.parent_process_regex.search(
759be691f3bSpatrick                line)
760be691f3bSpatrick            self.crashlog.parent_process_name = parent_process_match.group(1)
761be691f3bSpatrick            self.crashlog.parent_process_id = parent_process_match.group(2)
762*f6aab3d8Srobert        elif line.startswith('Exception'):
763*f6aab3d8Srobert            self.parse_exception(line)
764be691f3bSpatrick            return
765be691f3bSpatrick        elif line.startswith('Crashed Thread:'):
766be691f3bSpatrick            self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0])
767be691f3bSpatrick            return
768be691f3bSpatrick        elif line.startswith('Triggered by Thread:'): # iOS
769be691f3bSpatrick            self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0])
770be691f3bSpatrick            return
771be691f3bSpatrick        elif line.startswith('Report Version:'):
772be691f3bSpatrick            self.crashlog.version = int(line[15:].strip())
773be691f3bSpatrick            return
774be691f3bSpatrick        elif line.startswith('System Profile:'):
775be691f3bSpatrick            self.parse_mode = CrashLogParseMode.SYSTEM
776be691f3bSpatrick            return
777be691f3bSpatrick        elif (line.startswith('Interval Since Last Report:') or
778be691f3bSpatrick                line.startswith('Crashes Since Last Report:') or
779be691f3bSpatrick                line.startswith('Per-App Interval Since Last Report:') or
780be691f3bSpatrick                line.startswith('Per-App Crashes Since Last Report:') or
781be691f3bSpatrick                line.startswith('Sleep/Wake UUID:') or
782be691f3bSpatrick                line.startswith('Anonymous UUID:')):
783be691f3bSpatrick            # ignore these
784be691f3bSpatrick            return
785be691f3bSpatrick        elif line.startswith('Thread'):
786be691f3bSpatrick            thread_state_match = self.thread_state_regex.search(line)
787be691f3bSpatrick            if thread_state_match:
788be691f3bSpatrick                self.app_specific_backtrace = False
789be691f3bSpatrick                thread_state_match = self.thread_regex.search(line)
790be691f3bSpatrick                thread_idx = int(thread_state_match.group(1))
791be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREGS
792be691f3bSpatrick                self.thread = self.crashlog.threads[thread_idx]
793be691f3bSpatrick                return
794be691f3bSpatrick            thread_insts_match  = self.thread_instrs_regex.search(line)
795be691f3bSpatrick            if thread_insts_match:
796be691f3bSpatrick                self.parse_mode = CrashLogParseMode.INSTRS
797be691f3bSpatrick                return
798be691f3bSpatrick            thread_match = self.thread_regex.search(line)
799be691f3bSpatrick            if thread_match:
800be691f3bSpatrick                self.app_specific_backtrace = False
801be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREAD
802be691f3bSpatrick                thread_idx = int(thread_match.group(1))
803be691f3bSpatrick                self.thread = self.crashlog.Thread(thread_idx, False)
804be691f3bSpatrick                return
805be691f3bSpatrick            return
806be691f3bSpatrick        elif line.startswith('Binary Images:'):
807be691f3bSpatrick            self.parse_mode = CrashLogParseMode.IMAGES
808be691f3bSpatrick            return
809be691f3bSpatrick        elif line.startswith('Application Specific Backtrace'):
810be691f3bSpatrick            app_backtrace_match = self.app_backtrace_regex.search(line)
811be691f3bSpatrick            if app_backtrace_match:
812be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREAD
813be691f3bSpatrick                self.app_specific_backtrace = True
814be691f3bSpatrick                idx = int(app_backtrace_match.group(1))
815be691f3bSpatrick                self.thread = self.crashlog.Thread(idx, True)
816be691f3bSpatrick        elif line.startswith('Last Exception Backtrace:'): # iOS
817be691f3bSpatrick            self.parse_mode = CrashLogParseMode.THREAD
818be691f3bSpatrick            self.app_specific_backtrace = True
819be691f3bSpatrick            idx = 1
820be691f3bSpatrick            self.thread = self.crashlog.Thread(idx, True)
821be691f3bSpatrick        self.crashlog.info_lines.append(line.strip())
822be691f3bSpatrick
823be691f3bSpatrick    def parse_thread(self, line):
824be691f3bSpatrick        if line.startswith('Thread'):
825be691f3bSpatrick            return
826be691f3bSpatrick        if self.null_frame_regex.search(line):
827be691f3bSpatrick            print('warning: thread parser ignored null-frame: "%s"' % line)
828be691f3bSpatrick            return
829be691f3bSpatrick        frame_match = self.frame_regex.search(line)
830be691f3bSpatrick        if frame_match:
831*f6aab3d8Srobert            (frame_id, frame_img_name, frame_addr,
832*f6aab3d8Srobert                frame_ofs) = frame_match.groups()
833be691f3bSpatrick            ident = frame_img_name
834be691f3bSpatrick            self.thread.add_ident(ident)
835be691f3bSpatrick            if ident not in self.crashlog.idents:
836be691f3bSpatrick                self.crashlog.idents.append(ident)
837be691f3bSpatrick            self.thread.frames.append(self.crashlog.Frame(int(frame_id), int(
838be691f3bSpatrick                frame_addr, 0), frame_ofs))
839be691f3bSpatrick        else:
840be691f3bSpatrick            print('error: frame regex failed for line: "%s"' % line)
841be691f3bSpatrick
842be691f3bSpatrick    def parse_images(self, line):
843be691f3bSpatrick        image_match = self.image_regex_uuid.search(line)
844be691f3bSpatrick        if image_match:
845*f6aab3d8Srobert            (img_lo, img_hi, img_name, img_version,
846*f6aab3d8Srobert                img_uuid, img_path) = image_match.groups()
847be691f3bSpatrick            image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
848be691f3bSpatrick                                            img_name.strip(),
849be691f3bSpatrick                                            img_version.strip()
850be691f3bSpatrick                                            if img_version else "",
851be691f3bSpatrick                                            uuid.UUID(img_uuid), img_path,
852be691f3bSpatrick                                            self.verbose)
853be691f3bSpatrick            self.crashlog.images.append(image)
854be691f3bSpatrick        else:
855be691f3bSpatrick            print("error: image regex failed for: %s" % line)
856be691f3bSpatrick
857be691f3bSpatrick
858be691f3bSpatrick    def parse_thread_registers(self, line):
859be691f3bSpatrick        # "r12: 0x00007fff6b5939c8  r13: 0x0000000007000006  r14: 0x0000000000002a03  r15: 0x0000000000000c00"
860*f6aab3d8Srobert        reg_values = re.findall('([a-z0-9]+): (0x[0-9a-f]+)', line, re.I)
861*f6aab3d8Srobert        for reg, value in reg_values:
862*f6aab3d8Srobert            self.thread.registers[reg] = int(value, 16)
863be691f3bSpatrick
864be691f3bSpatrick    def parse_system(self, line):
865be691f3bSpatrick        self.crashlog.system_profile.append(line)
866be691f3bSpatrick
867be691f3bSpatrick    def parse_instructions(self, line):
868be691f3bSpatrick        pass
869be691f3bSpatrick
870be691f3bSpatrick
871061da546Spatrickdef usage():
872061da546Spatrick    print("Usage: lldb-symbolicate.py [-n name] executable-image")
873061da546Spatrick    sys.exit(0)
874061da546Spatrick
875061da546Spatrick
876061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict):
877061da546Spatrick    usage = "usage: %prog [options] <output-path>"
878061da546Spatrick    description = '''Export the state of current target into a crashlog file'''
879061da546Spatrick    parser = optparse.OptionParser(
880061da546Spatrick        description=description,
881061da546Spatrick        prog='save_crashlog',
882061da546Spatrick        usage=usage)
883061da546Spatrick    parser.add_option(
884061da546Spatrick        '-v',
885061da546Spatrick        '--verbose',
886061da546Spatrick        action='store_true',
887061da546Spatrick        dest='verbose',
888061da546Spatrick        help='display verbose debug info',
889061da546Spatrick        default=False)
890061da546Spatrick    try:
891061da546Spatrick        (options, args) = parser.parse_args(shlex.split(command))
892061da546Spatrick    except:
893061da546Spatrick        result.PutCString("error: invalid options")
894061da546Spatrick        return
895061da546Spatrick    if len(args) != 1:
896061da546Spatrick        result.PutCString(
897061da546Spatrick            "error: invalid arguments, a single output file is the only valid argument")
898061da546Spatrick        return
899*f6aab3d8Srobert    out_file = open(args[0], 'w', encoding='utf-8')
900061da546Spatrick    if not out_file:
901061da546Spatrick        result.PutCString(
902061da546Spatrick            "error: failed to open file '%s' for writing...",
903061da546Spatrick            args[0])
904061da546Spatrick        return
905061da546Spatrick    target = exe_ctx.target
906061da546Spatrick    if target:
907061da546Spatrick        identifier = target.executable.basename
908061da546Spatrick        process = exe_ctx.process
909061da546Spatrick        if process:
910061da546Spatrick            pid = process.id
911061da546Spatrick            if pid != lldb.LLDB_INVALID_PROCESS_ID:
912061da546Spatrick                out_file.write(
913061da546Spatrick                    'Process:         %s [%u]\n' %
914061da546Spatrick                    (identifier, pid))
915061da546Spatrick        out_file.write('Path:            %s\n' % (target.executable.fullpath))
916061da546Spatrick        out_file.write('Identifier:      %s\n' % (identifier))
917061da546Spatrick        out_file.write('\nDate/Time:       %s\n' %
918061da546Spatrick                       (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
919061da546Spatrick        out_file.write(
920061da546Spatrick            'OS Version:      Mac OS X %s (%s)\n' %
921061da546Spatrick            (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8")))
922061da546Spatrick        out_file.write('Report Version:  9\n')
923061da546Spatrick        for thread_idx in range(process.num_threads):
924061da546Spatrick            thread = process.thread[thread_idx]
925061da546Spatrick            out_file.write('\nThread %u:\n' % (thread_idx))
926061da546Spatrick            for (frame_idx, frame) in enumerate(thread.frames):
927061da546Spatrick                frame_pc = frame.pc
928061da546Spatrick                frame_offset = 0
929061da546Spatrick                if frame.function:
930061da546Spatrick                    block = frame.GetFrameBlock()
931061da546Spatrick                    block_range = block.range[frame.addr]
932061da546Spatrick                    if block_range:
933061da546Spatrick                        block_start_addr = block_range[0]
934dda28197Spatrick                        frame_offset = frame_pc - block_start_addr.GetLoadAddress(target)
935061da546Spatrick                    else:
936dda28197Spatrick                        frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target)
937061da546Spatrick                elif frame.symbol:
938dda28197Spatrick                    frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
939061da546Spatrick                out_file.write(
940061da546Spatrick                    '%-3u %-32s 0x%16.16x %s' %
941061da546Spatrick                    (frame_idx, frame.module.file.basename, frame_pc, frame.name))
942061da546Spatrick                if frame_offset > 0:
943061da546Spatrick                    out_file.write(' + %u' % (frame_offset))
944061da546Spatrick                line_entry = frame.line_entry
945061da546Spatrick                if line_entry:
946061da546Spatrick                    if options.verbose:
947061da546Spatrick                        # This will output the fullpath + line + column
948061da546Spatrick                        out_file.write(' %s' % (line_entry))
949061da546Spatrick                    else:
950061da546Spatrick                        out_file.write(
951061da546Spatrick                            ' %s:%u' %
952061da546Spatrick                            (line_entry.file.basename, line_entry.line))
953061da546Spatrick                        column = line_entry.column
954061da546Spatrick                        if column:
955061da546Spatrick                            out_file.write(':%u' % (column))
956061da546Spatrick                out_file.write('\n')
957061da546Spatrick
958061da546Spatrick        out_file.write('\nBinary Images:\n')
959061da546Spatrick        for module in target.modules:
960061da546Spatrick            text_segment = module.section['__TEXT']
961061da546Spatrick            if text_segment:
962061da546Spatrick                text_segment_load_addr = text_segment.GetLoadAddress(target)
963061da546Spatrick                if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
964061da546Spatrick                    text_segment_end_load_addr = text_segment_load_addr + text_segment.size
965061da546Spatrick                    identifier = module.file.basename
966061da546Spatrick                    module_version = '???'
967061da546Spatrick                    module_version_array = module.GetVersion()
968061da546Spatrick                    if module_version_array:
969061da546Spatrick                        module_version = '.'.join(
970061da546Spatrick                            map(str, module_version_array))
971061da546Spatrick                    out_file.write(
972061da546Spatrick                        '    0x%16.16x - 0x%16.16x  %s (%s - ???) <%s> %s\n' %
973061da546Spatrick                        (text_segment_load_addr,
974061da546Spatrick                         text_segment_end_load_addr,
975061da546Spatrick                         identifier,
976061da546Spatrick                         module_version,
977061da546Spatrick                         module.GetUUIDString(),
978061da546Spatrick                         module.file.fullpath))
979061da546Spatrick        out_file.close()
980061da546Spatrick    else:
981061da546Spatrick        result.PutCString("error: invalid target")
982061da546Spatrick
983061da546Spatrick
984*f6aab3d8Srobertclass Symbolicate:
985*f6aab3d8Srobert    def __init__(self, debugger, internal_dict):
986*f6aab3d8Srobert        pass
987*f6aab3d8Srobert
988*f6aab3d8Srobert    def __call__(self, debugger, command, exe_ctx, result):
989*f6aab3d8Srobert        SymbolicateCrashLogs(debugger, shlex.split(command), result)
990*f6aab3d8Srobert
991*f6aab3d8Srobert    def get_short_help(self):
992*f6aab3d8Srobert        return "Symbolicate one or more darwin crash log files."
993*f6aab3d8Srobert
994*f6aab3d8Srobert    def get_long_help(self):
995*f6aab3d8Srobert        option_parser = CrashLogOptionParser()
996*f6aab3d8Srobert        return option_parser.format_help()
997061da546Spatrick
998061da546Spatrick
999061da546Spatrickdef SymbolicateCrashLog(crash_log, options):
1000061da546Spatrick    if options.debug:
1001061da546Spatrick        crash_log.dump()
1002061da546Spatrick    if not crash_log.images:
1003061da546Spatrick        print('error: no images in crash log')
1004061da546Spatrick        return
1005061da546Spatrick
1006061da546Spatrick    if options.dump_image_list:
1007061da546Spatrick        print("Binary Images:")
1008061da546Spatrick        for image in crash_log.images:
1009061da546Spatrick            if options.verbose:
1010061da546Spatrick                print(image.debug_dump())
1011061da546Spatrick            else:
1012061da546Spatrick                print(image)
1013061da546Spatrick
1014061da546Spatrick    target = crash_log.create_target()
1015061da546Spatrick    if not target:
1016061da546Spatrick        return
1017061da546Spatrick    exe_module = target.GetModuleAtIndex(0)
1018061da546Spatrick    images_to_load = list()
1019061da546Spatrick    loaded_images = list()
1020061da546Spatrick    if options.load_all_images:
1021061da546Spatrick        # --load-all option was specified, load everything up
1022061da546Spatrick        for image in crash_log.images:
1023061da546Spatrick            images_to_load.append(image)
1024061da546Spatrick    else:
1025061da546Spatrick        # Only load the images found in stack frames for the crashed threads
1026061da546Spatrick        if options.crashed_only:
1027061da546Spatrick            for thread in crash_log.threads:
1028061da546Spatrick                if thread.did_crash():
1029061da546Spatrick                    for ident in thread.idents:
1030061da546Spatrick                        images = crash_log.find_images_with_identifier(ident)
1031061da546Spatrick                        if images:
1032061da546Spatrick                            for image in images:
1033061da546Spatrick                                images_to_load.append(image)
1034061da546Spatrick                        else:
1035061da546Spatrick                            print('error: can\'t find image for identifier "%s"' % ident)
1036061da546Spatrick        else:
1037061da546Spatrick            for ident in crash_log.idents:
1038061da546Spatrick                images = crash_log.find_images_with_identifier(ident)
1039061da546Spatrick                if images:
1040061da546Spatrick                    for image in images:
1041061da546Spatrick                        images_to_load.append(image)
1042061da546Spatrick                else:
1043061da546Spatrick                    print('error: can\'t find image for identifier "%s"' % ident)
1044061da546Spatrick
1045*f6aab3d8Srobert    futures = []
1046*f6aab3d8Srobert    with concurrent.futures.ThreadPoolExecutor() as executor:
1047*f6aab3d8Srobert        def add_module(image, target):
1048*f6aab3d8Srobert            return image, image.add_module(target)
1049*f6aab3d8Srobert
1050061da546Spatrick        for image in images_to_load:
1051*f6aab3d8Srobert            futures.append(executor.submit(add_module, image=image, target=target))
1052*f6aab3d8Srobert
1053*f6aab3d8Srobert        for future in concurrent.futures.as_completed(futures):
1054*f6aab3d8Srobert            image, err = future.result()
1055061da546Spatrick            if err:
1056061da546Spatrick                print(err)
1057061da546Spatrick            else:
1058061da546Spatrick                loaded_images.append(image)
1059061da546Spatrick
1060061da546Spatrick    if crash_log.backtraces:
1061061da546Spatrick        for thread in crash_log.backtraces:
1062061da546Spatrick            thread.dump_symbolicated(crash_log, options)
1063061da546Spatrick            print()
1064061da546Spatrick
1065061da546Spatrick    for thread in crash_log.threads:
1066061da546Spatrick        thread.dump_symbolicated(crash_log, options)
1067061da546Spatrick        print()
1068061da546Spatrick
1069*f6aab3d8Srobert    if crash_log.errors:
1070*f6aab3d8Srobert        print("Errors:")
1071*f6aab3d8Srobert        for error in crash_log.errors:
1072*f6aab3d8Srobert            print(error)
1073*f6aab3d8Srobert
1074*f6aab3d8Srobertdef load_crashlog_in_scripted_process(debugger, crash_log_file, options, result):
1075*f6aab3d8Srobert    crashlog_path = os.path.expanduser(crash_log_file)
1076*f6aab3d8Srobert    if not os.path.exists(crashlog_path):
1077*f6aab3d8Srobert        raise InteractiveCrashLogException("crashlog file %s does not exist" % crashlog_path)
1078*f6aab3d8Srobert
1079*f6aab3d8Srobert    crashlog = CrashLogParser.create(debugger, crashlog_path, False).parse()
1080*f6aab3d8Srobert
1081*f6aab3d8Srobert    target = lldb.SBTarget()
1082*f6aab3d8Srobert    # 1. Try to use the user-provided target
1083*f6aab3d8Srobert    if options.target_path:
1084*f6aab3d8Srobert        target = debugger.CreateTarget(options.target_path)
1085*f6aab3d8Srobert        if not target:
1086*f6aab3d8Srobert            raise InteractiveCrashLogException("couldn't create target provided by the user (%s)" % options.target_path)
1087*f6aab3d8Srobert
1088*f6aab3d8Srobert    # 2. If the user didn't provide a target, try to create a target using the symbolicator
1089*f6aab3d8Srobert    if not target or not target.IsValid():
1090*f6aab3d8Srobert        target = crashlog.create_target()
1091*f6aab3d8Srobert    # 3. If that didn't work, and a target is already loaded, use it
1092*f6aab3d8Srobert    if (target is None  or not target.IsValid()) and debugger.GetNumTargets() > 0:
1093*f6aab3d8Srobert        target = debugger.GetTargetAtIndex(0)
1094*f6aab3d8Srobert    # 4. Fail
1095*f6aab3d8Srobert    if target is None or not target.IsValid():
1096*f6aab3d8Srobert        raise InteractiveCrashLogException("couldn't create target")
1097*f6aab3d8Srobert
1098*f6aab3d8Srobert    ci = debugger.GetCommandInterpreter()
1099*f6aab3d8Srobert    if not ci:
1100*f6aab3d8Srobert        raise InteractiveCrashLogException("couldn't get command interpreter")
1101*f6aab3d8Srobert
1102*f6aab3d8Srobert    ci.HandleCommand('script from lldb.macosx import crashlog_scripted_process', result)
1103*f6aab3d8Srobert    if not result.Succeeded():
1104*f6aab3d8Srobert        raise InteractiveCrashLogException("couldn't import crashlog scripted process module")
1105*f6aab3d8Srobert
1106*f6aab3d8Srobert    structured_data = lldb.SBStructuredData()
1107*f6aab3d8Srobert    structured_data.SetFromJSON(json.dumps({ "file_path" : crashlog_path,
1108*f6aab3d8Srobert                                             "load_all_images": options.load_all_images }))
1109*f6aab3d8Srobert    launch_info = lldb.SBLaunchInfo(None)
1110*f6aab3d8Srobert    launch_info.SetProcessPluginName("ScriptedProcess")
1111*f6aab3d8Srobert    launch_info.SetScriptedProcessClassName("crashlog_scripted_process.CrashLogScriptedProcess")
1112*f6aab3d8Srobert    launch_info.SetScriptedProcessDictionary(structured_data)
1113*f6aab3d8Srobert    error = lldb.SBError()
1114*f6aab3d8Srobert    process = target.Launch(launch_info, error)
1115*f6aab3d8Srobert
1116*f6aab3d8Srobert    if not process or error.Fail():
1117*f6aab3d8Srobert        raise InteractiveCrashLogException("couldn't launch Scripted Process", error)
1118*f6aab3d8Srobert
1119*f6aab3d8Srobert    if not options.skip_status:
1120*f6aab3d8Srobert        @contextlib.contextmanager
1121*f6aab3d8Srobert        def synchronous(debugger):
1122*f6aab3d8Srobert            async_state = debugger.GetAsync()
1123*f6aab3d8Srobert            debugger.SetAsync(False)
1124*f6aab3d8Srobert            try:
1125*f6aab3d8Srobert                yield
1126*f6aab3d8Srobert            finally:
1127*f6aab3d8Srobert                debugger.SetAsync(async_state)
1128*f6aab3d8Srobert
1129*f6aab3d8Srobert        with synchronous(debugger):
1130*f6aab3d8Srobert            run_options = lldb.SBCommandInterpreterRunOptions()
1131*f6aab3d8Srobert            run_options.SetStopOnError(True)
1132*f6aab3d8Srobert            run_options.SetStopOnCrash(True)
1133*f6aab3d8Srobert            run_options.SetEchoCommands(True)
1134*f6aab3d8Srobert
1135*f6aab3d8Srobert            commands_stream = lldb.SBStream()
1136*f6aab3d8Srobert            commands_stream.Print("process status --verbose\n")
1137*f6aab3d8Srobert            commands_stream.Print("thread backtrace --extended true\n")
1138*f6aab3d8Srobert            error = debugger.SetInputString(commands_stream.GetData())
1139*f6aab3d8Srobert            if error.Success():
1140*f6aab3d8Srobert                debugger.RunCommandInterpreter(True, False, run_options, 0, False, True)
1141061da546Spatrick
1142061da546Spatrickdef CreateSymbolicateCrashLogOptions(
1143061da546Spatrick        command_name,
1144061da546Spatrick        description,
1145061da546Spatrick        add_interactive_options):
1146061da546Spatrick    usage = "usage: %prog [options] <FILE> [FILE ...]"
1147061da546Spatrick    option_parser = optparse.OptionParser(
1148061da546Spatrick        description=description, prog='crashlog', usage=usage)
1149061da546Spatrick    option_parser.add_option(
1150*f6aab3d8Srobert        '--version',
1151*f6aab3d8Srobert        '-V',
1152*f6aab3d8Srobert        dest='version',
1153*f6aab3d8Srobert        action='store_true',
1154*f6aab3d8Srobert        help='Show crashlog version',
1155*f6aab3d8Srobert        default=False)
1156*f6aab3d8Srobert    option_parser.add_option(
1157061da546Spatrick        '--verbose',
1158061da546Spatrick        '-v',
1159061da546Spatrick        action='store_true',
1160061da546Spatrick        dest='verbose',
1161061da546Spatrick        help='display verbose debug info',
1162061da546Spatrick        default=False)
1163061da546Spatrick    option_parser.add_option(
1164061da546Spatrick        '--debug',
1165061da546Spatrick        '-g',
1166061da546Spatrick        action='store_true',
1167061da546Spatrick        dest='debug',
1168061da546Spatrick        help='display verbose debug logging',
1169061da546Spatrick        default=False)
1170061da546Spatrick    option_parser.add_option(
1171061da546Spatrick        '--load-all',
1172061da546Spatrick        '-a',
1173061da546Spatrick        action='store_true',
1174061da546Spatrick        dest='load_all_images',
1175*f6aab3d8Srobert        help='load all executable images, not just the images found in the '
1176*f6aab3d8Srobert        'crashed stack frames, loads stackframes for all the threads in '
1177*f6aab3d8Srobert        'interactive mode.',
1178061da546Spatrick        default=False)
1179061da546Spatrick    option_parser.add_option(
1180061da546Spatrick        '--images',
1181061da546Spatrick        action='store_true',
1182061da546Spatrick        dest='dump_image_list',
1183061da546Spatrick        help='show image list',
1184061da546Spatrick        default=False)
1185061da546Spatrick    option_parser.add_option(
1186061da546Spatrick        '--debug-delay',
1187061da546Spatrick        type='int',
1188061da546Spatrick        dest='debug_delay',
1189061da546Spatrick        metavar='NSEC',
1190061da546Spatrick        help='pause for NSEC seconds for debugger',
1191061da546Spatrick        default=0)
1192061da546Spatrick    option_parser.add_option(
1193061da546Spatrick        '--crashed-only',
1194061da546Spatrick        '-c',
1195061da546Spatrick        action='store_true',
1196061da546Spatrick        dest='crashed_only',
1197061da546Spatrick        help='only symbolicate the crashed thread',
1198061da546Spatrick        default=False)
1199061da546Spatrick    option_parser.add_option(
1200061da546Spatrick        '--disasm-depth',
1201061da546Spatrick        '-d',
1202061da546Spatrick        type='int',
1203061da546Spatrick        dest='disassemble_depth',
1204061da546Spatrick        help='set the depth in stack frames that should be disassembled (default is 1)',
1205061da546Spatrick        default=1)
1206061da546Spatrick    option_parser.add_option(
1207061da546Spatrick        '--disasm-all',
1208061da546Spatrick        '-D',
1209061da546Spatrick        action='store_true',
1210061da546Spatrick        dest='disassemble_all_threads',
1211061da546Spatrick        help='enabled disassembly of frames on all threads (not just the crashed thread)',
1212061da546Spatrick        default=False)
1213061da546Spatrick    option_parser.add_option(
1214061da546Spatrick        '--disasm-before',
1215061da546Spatrick        '-B',
1216061da546Spatrick        type='int',
1217061da546Spatrick        dest='disassemble_before',
1218061da546Spatrick        help='the number of instructions to disassemble before the frame PC',
1219061da546Spatrick        default=4)
1220061da546Spatrick    option_parser.add_option(
1221061da546Spatrick        '--disasm-after',
1222061da546Spatrick        '-A',
1223061da546Spatrick        type='int',
1224061da546Spatrick        dest='disassemble_after',
1225061da546Spatrick        help='the number of instructions to disassemble after the frame PC',
1226061da546Spatrick        default=4)
1227061da546Spatrick    option_parser.add_option(
1228061da546Spatrick        '--source-context',
1229061da546Spatrick        '-C',
1230061da546Spatrick        type='int',
1231061da546Spatrick        metavar='NLINES',
1232061da546Spatrick        dest='source_context',
1233061da546Spatrick        help='show NLINES source lines of source context (default = 4)',
1234061da546Spatrick        default=4)
1235061da546Spatrick    option_parser.add_option(
1236061da546Spatrick        '--source-frames',
1237061da546Spatrick        type='int',
1238061da546Spatrick        metavar='NFRAMES',
1239061da546Spatrick        dest='source_frames',
1240061da546Spatrick        help='show source for NFRAMES (default = 4)',
1241061da546Spatrick        default=4)
1242061da546Spatrick    option_parser.add_option(
1243061da546Spatrick        '--source-all',
1244061da546Spatrick        action='store_true',
1245061da546Spatrick        dest='source_all',
1246061da546Spatrick        help='show source for all threads, not just the crashed thread',
1247061da546Spatrick        default=False)
1248061da546Spatrick    if add_interactive_options:
1249061da546Spatrick        option_parser.add_option(
1250061da546Spatrick            '-i',
1251061da546Spatrick            '--interactive',
1252061da546Spatrick            action='store_true',
1253*f6aab3d8Srobert            help='parse a crash log and load it in a ScriptedProcess',
1254*f6aab3d8Srobert            default=False)
1255*f6aab3d8Srobert        option_parser.add_option(
1256*f6aab3d8Srobert            '-b',
1257*f6aab3d8Srobert            '--batch',
1258*f6aab3d8Srobert            action='store_true',
1259*f6aab3d8Srobert            help='dump symbolicated stackframes without creating a debug session',
1260*f6aab3d8Srobert            default=True)
1261*f6aab3d8Srobert        option_parser.add_option(
1262*f6aab3d8Srobert            '--target',
1263*f6aab3d8Srobert            '-t',
1264*f6aab3d8Srobert            dest='target_path',
1265*f6aab3d8Srobert            help='the target binary path that should be used for interactive crashlog (optional)',
1266*f6aab3d8Srobert            default=None)
1267*f6aab3d8Srobert        option_parser.add_option(
1268*f6aab3d8Srobert            '--skip-status',
1269*f6aab3d8Srobert            '-s',
1270*f6aab3d8Srobert            dest='skip_status',
1271*f6aab3d8Srobert            action='store_true',
1272*f6aab3d8Srobert            help='prevent the interactive crashlog to dump the process status and thread backtrace at launch',
1273061da546Spatrick            default=False)
1274061da546Spatrick    return option_parser
1275061da546Spatrick
1276061da546Spatrick
1277*f6aab3d8Srobertdef CrashLogOptionParser():
1278061da546Spatrick    description = '''Symbolicate one or more darwin crash log files to provide source file and line information,
1279061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash
1280061da546Spatrickfor the first frame of the crashed thread.
1281061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1282061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1283061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1284061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can
1285061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.'''
1286*f6aab3d8Srobert    return CreateSymbolicateCrashLogOptions('crashlog', description, True)
1287*f6aab3d8Srobert
1288*f6aab3d8Srobertdef SymbolicateCrashLogs(debugger, command_args, result):
1289*f6aab3d8Srobert    option_parser = CrashLogOptionParser()
1290*f6aab3d8Srobert
1291*f6aab3d8Srobert    if not len(command_args):
1292*f6aab3d8Srobert        option_parser.print_help()
1293*f6aab3d8Srobert        return
1294*f6aab3d8Srobert
1295061da546Spatrick    try:
1296061da546Spatrick        (options, args) = option_parser.parse_args(command_args)
1297061da546Spatrick    except:
1298061da546Spatrick        return
1299061da546Spatrick
1300*f6aab3d8Srobert    if options.version:
1301*f6aab3d8Srobert        print(debugger.GetVersionString())
1302*f6aab3d8Srobert        return
1303*f6aab3d8Srobert
1304061da546Spatrick    if options.debug:
1305061da546Spatrick        print('command_args = %s' % command_args)
1306061da546Spatrick        print('options', options)
1307061da546Spatrick        print('args', args)
1308061da546Spatrick
1309061da546Spatrick    if options.debug_delay > 0:
1310061da546Spatrick        print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1311061da546Spatrick        time.sleep(options.debug_delay)
1312061da546Spatrick    error = lldb.SBError()
1313061da546Spatrick
1314*f6aab3d8Srobert    def should_run_in_interactive_mode(options, ci):
1315061da546Spatrick        if options.interactive:
1316*f6aab3d8Srobert            return True
1317*f6aab3d8Srobert        elif options.batch:
1318*f6aab3d8Srobert            return False
1319*f6aab3d8Srobert        # elif ci and ci.IsInteractive():
1320*f6aab3d8Srobert        #     return True
1321061da546Spatrick        else:
1322*f6aab3d8Srobert            return False
1323*f6aab3d8Srobert
1324*f6aab3d8Srobert    ci = debugger.GetCommandInterpreter()
1325*f6aab3d8Srobert
1326*f6aab3d8Srobert    if args:
1327061da546Spatrick        for crash_log_file in args:
1328*f6aab3d8Srobert            if should_run_in_interactive_mode(options, ci):
1329*f6aab3d8Srobert                try:
1330*f6aab3d8Srobert                    load_crashlog_in_scripted_process(debugger, crash_log_file,
1331*f6aab3d8Srobert                                                      options, result)
1332*f6aab3d8Srobert                except InteractiveCrashLogException as e:
1333*f6aab3d8Srobert                    result.SetError(str(e))
1334*f6aab3d8Srobert            else:
1335*f6aab3d8Srobert                crash_log = CrashLogParser.create(debugger, crash_log_file, options.verbose).parse()
1336061da546Spatrick                SymbolicateCrashLog(crash_log, options)
1337*f6aab3d8Srobert
1338061da546Spatrickif __name__ == '__main__':
1339061da546Spatrick    # Create a new debugger instance
1340be691f3bSpatrick    debugger = lldb.SBDebugger.Create()
1341*f6aab3d8Srobert    result = lldb.SBCommandReturnObject()
1342*f6aab3d8Srobert    SymbolicateCrashLogs(debugger, sys.argv[1:], result)
1343be691f3bSpatrick    lldb.SBDebugger.Destroy(debugger)
1344*f6aab3d8Srobert
1345*f6aab3d8Srobertdef __lldb_init_module(debugger, internal_dict):
1346*f6aab3d8Srobert    debugger.HandleCommand(
1347*f6aab3d8Srobert        'command script add -o -c lldb.macosx.crashlog.Symbolicate crashlog')
1348*f6aab3d8Srobert    debugger.HandleCommand(
1349*f6aab3d8Srobert        'command script add -o -f lldb.macosx.crashlog.save_crashlog save_crashlog')
1350*f6aab3d8Srobert    print('"crashlog" and "save_crashlog" commands have been installed, use '
1351*f6aab3d8Srobert          'the "--help" options on these commands for detailed help.')
1352