1*be691f3bSpatrick#!/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
29061da546Spatrickfrom __future__ import print_function
30061da546Spatrickimport cmd
31061da546Spatrickimport datetime
32061da546Spatrickimport glob
33061da546Spatrickimport optparse
34061da546Spatrickimport os
35061da546Spatrickimport platform
36061da546Spatrickimport plistlib
37061da546Spatrickimport re
38061da546Spatrickimport shlex
39061da546Spatrickimport string
40061da546Spatrickimport subprocess
41061da546Spatrickimport sys
42061da546Spatrickimport time
43061da546Spatrickimport uuid
44*be691f3bSpatrickimport json
45*be691f3bSpatrick
46*be691f3bSpatricktry:
47*be691f3bSpatrick    # First try for LLDB in case PYTHONPATH is already correctly setup.
48*be691f3bSpatrick    import lldb
49*be691f3bSpatrickexcept ImportError:
50*be691f3bSpatrick    # Ask the command line driver for the path to the lldb module. Copy over
51*be691f3bSpatrick    # the environment so that SDKROOT is propagated to xcrun.
52*be691f3bSpatrick    env = os.environ.copy()
53*be691f3bSpatrick    env['LLDB_DEFAULT_PYTHON_VERSION'] = str(sys.version_info.major)
54*be691f3bSpatrick    command =  ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P']
55*be691f3bSpatrick    # Extend the PYTHONPATH if the path exists and isn't already there.
56*be691f3bSpatrick    lldb_python_path = subprocess.check_output(command, env=env).decode("utf-8").strip()
57*be691f3bSpatrick    if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path):
58*be691f3bSpatrick        sys.path.append(lldb_python_path)
59*be691f3bSpatrick    # Try importing LLDB again.
60*be691f3bSpatrick    try:
61*be691f3bSpatrick        import lldb
62*be691f3bSpatrick    except ImportError:
63*be691f3bSpatrick        print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
64*be691f3bSpatrick        sys.exit(1)
65*be691f3bSpatrick
66*be691f3bSpatrickfrom lldb.utils import symbolication
67*be691f3bSpatrick
68061da546Spatrick
69061da546Spatrickdef read_plist(s):
70061da546Spatrick    if sys.version_info.major == 3:
71061da546Spatrick        return plistlib.loads(s)
72061da546Spatrick    else:
73061da546Spatrick        return plistlib.readPlistFromString(s)
74061da546Spatrick
75061da546Spatrickclass CrashLog(symbolication.Symbolicator):
76061da546Spatrick    class Thread:
77061da546Spatrick        """Class that represents a thread in a darwin crash log"""
78061da546Spatrick
79061da546Spatrick        def __init__(self, index, app_specific_backtrace):
80061da546Spatrick            self.index = index
81061da546Spatrick            self.frames = list()
82061da546Spatrick            self.idents = list()
83061da546Spatrick            self.registers = dict()
84061da546Spatrick            self.reason = None
85061da546Spatrick            self.queue = None
86061da546Spatrick            self.app_specific_backtrace = app_specific_backtrace
87061da546Spatrick
88061da546Spatrick        def dump(self, prefix):
89061da546Spatrick            if self.app_specific_backtrace:
90061da546Spatrick                print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason))
91061da546Spatrick            else:
92061da546Spatrick                print("%sThread[%u] %s" % (prefix, self.index, self.reason))
93061da546Spatrick            if self.frames:
94061da546Spatrick                print("%s  Frames:" % (prefix))
95061da546Spatrick                for frame in self.frames:
96061da546Spatrick                    frame.dump(prefix + '    ')
97061da546Spatrick            if self.registers:
98061da546Spatrick                print("%s  Registers:" % (prefix))
99061da546Spatrick                for reg in self.registers.keys():
100*be691f3bSpatrick                    print("%s    %-8s = %#16.16x" % (prefix, reg, self.registers[reg]))
101061da546Spatrick
102061da546Spatrick        def dump_symbolicated(self, crash_log, options):
103061da546Spatrick            this_thread_crashed = self.app_specific_backtrace
104061da546Spatrick            if not this_thread_crashed:
105061da546Spatrick                this_thread_crashed = self.did_crash()
106061da546Spatrick                if options.crashed_only and this_thread_crashed == False:
107061da546Spatrick                    return
108061da546Spatrick
109061da546Spatrick            print("%s" % self)
110061da546Spatrick            display_frame_idx = -1
111061da546Spatrick            for frame_idx, frame in enumerate(self.frames):
112061da546Spatrick                disassemble = (
113061da546Spatrick                    this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth
114061da546Spatrick                if frame_idx == 0:
115061da546Spatrick                    symbolicated_frame_addresses = crash_log.symbolicate(
116061da546Spatrick                        frame.pc & crash_log.addr_mask, options.verbose)
117061da546Spatrick                else:
118061da546Spatrick                    # Any frame above frame zero and we have to subtract one to
119061da546Spatrick                    # get the previous line entry
120061da546Spatrick                    symbolicated_frame_addresses = crash_log.symbolicate(
121061da546Spatrick                        (frame.pc & crash_log.addr_mask) - 1, options.verbose)
122061da546Spatrick
123061da546Spatrick                if symbolicated_frame_addresses:
124061da546Spatrick                    symbolicated_frame_address_idx = 0
125061da546Spatrick                    for symbolicated_frame_address in symbolicated_frame_addresses:
126061da546Spatrick                        display_frame_idx += 1
127061da546Spatrick                        print('[%3u] %s' % (frame_idx, symbolicated_frame_address))
128061da546Spatrick                        if (options.source_all or self.did_crash(
129061da546Spatrick                        )) and display_frame_idx < options.source_frames and options.source_context:
130061da546Spatrick                            source_context = options.source_context
131061da546Spatrick                            line_entry = symbolicated_frame_address.get_symbol_context().line_entry
132061da546Spatrick                            if line_entry.IsValid():
133061da546Spatrick                                strm = lldb.SBStream()
134061da546Spatrick                                if line_entry:
135*be691f3bSpatrick                                    crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
136061da546Spatrick                                        line_entry.file, line_entry.line, source_context, source_context, "->", strm)
137061da546Spatrick                                source_text = strm.GetData()
138061da546Spatrick                                if source_text:
139061da546Spatrick                                    # Indent the source a bit
140061da546Spatrick                                    indent_str = '    '
141061da546Spatrick                                    join_str = '\n' + indent_str
142061da546Spatrick                                    print('%s%s' % (indent_str, join_str.join(source_text.split('\n'))))
143061da546Spatrick                        if symbolicated_frame_address_idx == 0:
144061da546Spatrick                            if disassemble:
145061da546Spatrick                                instructions = symbolicated_frame_address.get_instructions()
146061da546Spatrick                                if instructions:
147061da546Spatrick                                    print()
148061da546Spatrick                                    symbolication.disassemble_instructions(
149061da546Spatrick                                        crash_log.get_target(),
150061da546Spatrick                                        instructions,
151061da546Spatrick                                        frame.pc,
152061da546Spatrick                                        options.disassemble_before,
153061da546Spatrick                                        options.disassemble_after,
154061da546Spatrick                                        frame.index > 0)
155061da546Spatrick                                    print()
156061da546Spatrick                        symbolicated_frame_address_idx += 1
157061da546Spatrick                else:
158061da546Spatrick                    print(frame)
159*be691f3bSpatrick            if self.registers:
160*be691f3bSpatrick                print()
161*be691f3bSpatrick                for reg in self.registers.keys():
162*be691f3bSpatrick                    print("    %-8s = %#16.16x" % (reg, self.registers[reg]))
163061da546Spatrick
164061da546Spatrick        def add_ident(self, ident):
165061da546Spatrick            if ident not in self.idents:
166061da546Spatrick                self.idents.append(ident)
167061da546Spatrick
168061da546Spatrick        def did_crash(self):
169061da546Spatrick            return self.reason is not None
170061da546Spatrick
171061da546Spatrick        def __str__(self):
172061da546Spatrick            if self.app_specific_backtrace:
173061da546Spatrick                s = "Application Specific Backtrace[%u]" % self.index
174061da546Spatrick            else:
175061da546Spatrick                s = "Thread[%u]" % self.index
176061da546Spatrick            if self.reason:
177061da546Spatrick                s += ' %s' % self.reason
178061da546Spatrick            return s
179061da546Spatrick
180061da546Spatrick    class Frame:
181061da546Spatrick        """Class that represents a stack frame in a thread in a darwin crash log"""
182061da546Spatrick
183061da546Spatrick        def __init__(self, index, pc, description):
184061da546Spatrick            self.pc = pc
185061da546Spatrick            self.description = description
186061da546Spatrick            self.index = index
187061da546Spatrick
188061da546Spatrick        def __str__(self):
189061da546Spatrick            if self.description:
190061da546Spatrick                return "[%3u] 0x%16.16x %s" % (
191061da546Spatrick                    self.index, self.pc, self.description)
192061da546Spatrick            else:
193061da546Spatrick                return "[%3u] 0x%16.16x" % (self.index, self.pc)
194061da546Spatrick
195061da546Spatrick        def dump(self, prefix):
196061da546Spatrick            print("%s%s" % (prefix, str(self)))
197061da546Spatrick
198061da546Spatrick    class DarwinImage(symbolication.Image):
199061da546Spatrick        """Class that represents a binary images in a darwin crash log"""
200061da546Spatrick        dsymForUUIDBinary = '/usr/local/bin/dsymForUUID'
201061da546Spatrick        if not os.path.exists(dsymForUUIDBinary):
202061da546Spatrick            try:
203061da546Spatrick                dsymForUUIDBinary = subprocess.check_output('which dsymForUUID',
204061da546Spatrick                                                            shell=True).decode("utf-8").rstrip('\n')
205061da546Spatrick            except:
206061da546Spatrick                dsymForUUIDBinary = ""
207061da546Spatrick
208061da546Spatrick        dwarfdump_uuid_regex = re.compile(
209061da546Spatrick            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
210061da546Spatrick
211061da546Spatrick        def __init__(
212061da546Spatrick                self,
213061da546Spatrick                text_addr_lo,
214061da546Spatrick                text_addr_hi,
215061da546Spatrick                identifier,
216061da546Spatrick                version,
217061da546Spatrick                uuid,
218061da546Spatrick                path,
219061da546Spatrick                verbose):
220061da546Spatrick            symbolication.Image.__init__(self, path, uuid)
221061da546Spatrick            self.add_section(
222061da546Spatrick                symbolication.Section(
223061da546Spatrick                    text_addr_lo,
224061da546Spatrick                    text_addr_hi,
225061da546Spatrick                    "__TEXT"))
226061da546Spatrick            self.identifier = identifier
227061da546Spatrick            self.version = version
228061da546Spatrick            self.verbose = verbose
229061da546Spatrick
230061da546Spatrick        def show_symbol_progress(self):
231061da546Spatrick            """
232061da546Spatrick            Hide progress output and errors from system frameworks as they are plentiful.
233061da546Spatrick            """
234061da546Spatrick            if self.verbose:
235061da546Spatrick                return True
236061da546Spatrick            return not (self.path.startswith("/System/Library/") or
237061da546Spatrick                        self.path.startswith("/usr/lib/"))
238061da546Spatrick
239061da546Spatrick
240061da546Spatrick        def find_matching_slice(self):
241061da546Spatrick            dwarfdump_cmd_output = subprocess.check_output(
242061da546Spatrick                'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8")
243061da546Spatrick            self_uuid = self.get_uuid()
244061da546Spatrick            for line in dwarfdump_cmd_output.splitlines():
245061da546Spatrick                match = self.dwarfdump_uuid_regex.search(line)
246061da546Spatrick                if match:
247061da546Spatrick                    dwarf_uuid_str = match.group(1)
248061da546Spatrick                    dwarf_uuid = uuid.UUID(dwarf_uuid_str)
249061da546Spatrick                    if self_uuid == dwarf_uuid:
250061da546Spatrick                        self.resolved_path = self.path
251061da546Spatrick                        self.arch = match.group(2)
252061da546Spatrick                        return True
253061da546Spatrick            if not self.resolved_path:
254061da546Spatrick                self.unavailable = True
255061da546Spatrick                if self.show_symbol_progress():
256061da546Spatrick                    print(("error\n    error: unable to locate '%s' with UUID %s"
257061da546Spatrick                           % (self.path, self.get_normalized_uuid_string())))
258061da546Spatrick                return False
259061da546Spatrick
260061da546Spatrick        def locate_module_and_debug_symbols(self):
261061da546Spatrick            # Don't load a module twice...
262061da546Spatrick            if self.resolved:
263061da546Spatrick                return True
264061da546Spatrick            # Mark this as resolved so we don't keep trying
265061da546Spatrick            self.resolved = True
266061da546Spatrick            uuid_str = self.get_normalized_uuid_string()
267061da546Spatrick            if self.show_symbol_progress():
268061da546Spatrick                print('Getting symbols for %s %s...' % (uuid_str, self.path), end=' ')
269061da546Spatrick            if os.path.exists(self.dsymForUUIDBinary):
270061da546Spatrick                dsym_for_uuid_command = '%s %s' % (
271061da546Spatrick                    self.dsymForUUIDBinary, uuid_str)
272061da546Spatrick                s = subprocess.check_output(dsym_for_uuid_command, shell=True)
273061da546Spatrick                if s:
274061da546Spatrick                    try:
275061da546Spatrick                        plist_root = read_plist(s)
276061da546Spatrick                    except:
277061da546Spatrick                        print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s))
278061da546Spatrick                        raise
279061da546Spatrick                    if plist_root:
280061da546Spatrick                        plist = plist_root[uuid_str]
281061da546Spatrick                        if plist:
282061da546Spatrick                            if 'DBGArchitecture' in plist:
283061da546Spatrick                                self.arch = plist['DBGArchitecture']
284061da546Spatrick                            if 'DBGDSYMPath' in plist:
285061da546Spatrick                                self.symfile = os.path.realpath(
286061da546Spatrick                                    plist['DBGDSYMPath'])
287061da546Spatrick                            if 'DBGSymbolRichExecutable' in plist:
288061da546Spatrick                                self.path = os.path.expanduser(
289061da546Spatrick                                    plist['DBGSymbolRichExecutable'])
290061da546Spatrick                                self.resolved_path = self.path
291061da546Spatrick            if not self.resolved_path and os.path.exists(self.path):
292061da546Spatrick                if not self.find_matching_slice():
293061da546Spatrick                    return False
294061da546Spatrick            if not self.resolved_path and not os.path.exists(self.path):
295061da546Spatrick                try:
296061da546Spatrick                    dsym = subprocess.check_output(
297061da546Spatrick                        ["/usr/bin/mdfind",
298061da546Spatrick                         "com_apple_xcode_dsym_uuids == %s"%uuid_str]).decode("utf-8")[:-1]
299061da546Spatrick                    if dsym and os.path.exists(dsym):
300061da546Spatrick                        print(('falling back to binary inside "%s"'%dsym))
301061da546Spatrick                        self.symfile = dsym
302061da546Spatrick                        dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
303061da546Spatrick                        for filename in os.listdir(dwarf_dir):
304061da546Spatrick                            self.path = os.path.join(dwarf_dir, filename)
305061da546Spatrick                            if not self.find_matching_slice():
306061da546Spatrick                                return False
307061da546Spatrick                            break
308061da546Spatrick                except:
309061da546Spatrick                    pass
310061da546Spatrick            if (self.resolved_path and os.path.exists(self.resolved_path)) or (
311061da546Spatrick                    self.path and os.path.exists(self.path)):
312061da546Spatrick                print('ok')
313061da546Spatrick                return True
314061da546Spatrick            else:
315061da546Spatrick                self.unavailable = True
316061da546Spatrick            return False
317061da546Spatrick
318*be691f3bSpatrick    def __init__(self, debugger, path, verbose):
319061da546Spatrick        """CrashLog constructor that take a path to a darwin crash log file"""
320*be691f3bSpatrick        symbolication.Symbolicator.__init__(self, debugger)
321061da546Spatrick        self.path = os.path.expanduser(path)
322061da546Spatrick        self.info_lines = list()
323061da546Spatrick        self.system_profile = list()
324061da546Spatrick        self.threads = list()
325061da546Spatrick        self.backtraces = list()  # For application specific backtraces
326061da546Spatrick        self.idents = list()  # A list of the required identifiers for doing all stack backtraces
327061da546Spatrick        self.crashed_thread_idx = -1
328061da546Spatrick        self.version = -1
329061da546Spatrick        self.target = None
330061da546Spatrick        self.verbose = verbose
331061da546Spatrick
332061da546Spatrick    def dump(self):
333061da546Spatrick        print("Crash Log File: %s" % (self.path))
334061da546Spatrick        if self.backtraces:
335061da546Spatrick            print("\nApplication Specific Backtraces:")
336061da546Spatrick            for thread in self.backtraces:
337061da546Spatrick                thread.dump('  ')
338061da546Spatrick        print("\nThreads:")
339061da546Spatrick        for thread in self.threads:
340061da546Spatrick            thread.dump('  ')
341061da546Spatrick        print("\nImages:")
342061da546Spatrick        for image in self.images:
343061da546Spatrick            image.dump('  ')
344061da546Spatrick
345061da546Spatrick    def find_image_with_identifier(self, identifier):
346061da546Spatrick        for image in self.images:
347061da546Spatrick            if image.identifier == identifier:
348061da546Spatrick                return image
349061da546Spatrick        regex_text = '^.*\.%s$' % (re.escape(identifier))
350061da546Spatrick        regex = re.compile(regex_text)
351061da546Spatrick        for image in self.images:
352061da546Spatrick            if regex.match(image.identifier):
353061da546Spatrick                return image
354061da546Spatrick        return None
355061da546Spatrick
356061da546Spatrick    def create_target(self):
357061da546Spatrick        if self.target is None:
358061da546Spatrick            self.target = symbolication.Symbolicator.create_target(self)
359061da546Spatrick            if self.target:
360061da546Spatrick                return self.target
361061da546Spatrick            # We weren't able to open the main executable as, but we can still
362061da546Spatrick            # symbolicate
363061da546Spatrick            print('crashlog.create_target()...2')
364061da546Spatrick            if self.idents:
365061da546Spatrick                for ident in self.idents:
366061da546Spatrick                    image = self.find_image_with_identifier(ident)
367061da546Spatrick                    if image:
368*be691f3bSpatrick                        self.target = image.create_target(self.debugger)
369061da546Spatrick                        if self.target:
370061da546Spatrick                            return self.target  # success
371061da546Spatrick            print('crashlog.create_target()...3')
372061da546Spatrick            for image in self.images:
373*be691f3bSpatrick                self.target = image.create_target(self.debugger)
374061da546Spatrick                if self.target:
375061da546Spatrick                    return self.target  # success
376061da546Spatrick            print('crashlog.create_target()...4')
377061da546Spatrick            print('error: Unable to locate any executables from the crash log.')
378061da546Spatrick            print('       Try loading the executable into lldb before running crashlog')
379061da546Spatrick            print('       and/or make sure the .dSYM bundles can be found by Spotlight.')
380061da546Spatrick        return self.target
381061da546Spatrick
382061da546Spatrick    def get_target(self):
383061da546Spatrick        return self.target
384061da546Spatrick
385061da546Spatrick
386*be691f3bSpatrickclass CrashLogFormatException(Exception):
387*be691f3bSpatrick    pass
388*be691f3bSpatrick
389*be691f3bSpatrick
390*be691f3bSpatrickclass CrashLogParseException(Exception):
391*be691f3bSpatrick   pass
392*be691f3bSpatrick
393*be691f3bSpatrick
394*be691f3bSpatrickclass CrashLogParser:
395*be691f3bSpatrick    def parse(self, debugger, path, verbose):
396*be691f3bSpatrick        try:
397*be691f3bSpatrick            return JSONCrashLogParser(debugger, path, verbose).parse()
398*be691f3bSpatrick        except CrashLogFormatException:
399*be691f3bSpatrick            return TextCrashLogParser(debugger, path, verbose).parse()
400*be691f3bSpatrick
401*be691f3bSpatrick
402*be691f3bSpatrickclass JSONCrashLogParser:
403*be691f3bSpatrick    def __init__(self, debugger, path, verbose):
404*be691f3bSpatrick        self.path = os.path.expanduser(path)
405*be691f3bSpatrick        self.verbose = verbose
406*be691f3bSpatrick        self.crashlog = CrashLog(debugger, self.path, self.verbose)
407*be691f3bSpatrick
408*be691f3bSpatrick    def parse(self):
409*be691f3bSpatrick        with open(self.path, 'r') as f:
410*be691f3bSpatrick            buffer = f.read()
411*be691f3bSpatrick
412*be691f3bSpatrick        # First line is meta-data.
413*be691f3bSpatrick        buffer = buffer[buffer.index('\n') + 1:]
414*be691f3bSpatrick
415*be691f3bSpatrick        try:
416*be691f3bSpatrick            self.data = json.loads(buffer)
417*be691f3bSpatrick        except ValueError:
418*be691f3bSpatrick            raise CrashLogFormatException()
419*be691f3bSpatrick
420*be691f3bSpatrick        try:
421*be691f3bSpatrick            self.parse_process_info(self.data)
422*be691f3bSpatrick            self.parse_images(self.data['usedImages'])
423*be691f3bSpatrick            self.parse_threads(self.data['threads'])
424*be691f3bSpatrick            thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
425*be691f3bSpatrick            reason = self.parse_crash_reason(self.data['exception'])
426*be691f3bSpatrick            if thread.reason:
427*be691f3bSpatrick                thread.reason = '{} {}'.format(thread.reason, reason)
428*be691f3bSpatrick            else:
429*be691f3bSpatrick                thread.reason = reason
430*be691f3bSpatrick        except (KeyError, ValueError, TypeError) as e:
431*be691f3bSpatrick            raise CrashLogParseException(
432*be691f3bSpatrick                'Failed to parse JSON crashlog: {}: {}'.format(
433*be691f3bSpatrick                    type(e).__name__, e))
434*be691f3bSpatrick
435*be691f3bSpatrick        return self.crashlog
436*be691f3bSpatrick
437*be691f3bSpatrick    def get_used_image(self, idx):
438*be691f3bSpatrick        return self.data['usedImages'][idx]
439*be691f3bSpatrick
440*be691f3bSpatrick    def parse_process_info(self, json_data):
441*be691f3bSpatrick        self.crashlog.process_id = json_data['pid']
442*be691f3bSpatrick        self.crashlog.process_identifier = json_data['procName']
443*be691f3bSpatrick        self.crashlog.process_path = json_data['procPath']
444*be691f3bSpatrick
445*be691f3bSpatrick    def parse_crash_reason(self, json_exception):
446*be691f3bSpatrick        exception_type = json_exception['type']
447*be691f3bSpatrick        exception_signal = json_exception['signal']
448*be691f3bSpatrick        if 'codes' in json_exception:
449*be691f3bSpatrick            exception_extra = " ({})".format(json_exception['codes'])
450*be691f3bSpatrick        elif 'subtype' in json_exception:
451*be691f3bSpatrick            exception_extra = " ({})".format(json_exception['subtype'])
452*be691f3bSpatrick        else:
453*be691f3bSpatrick            exception_extra = ""
454*be691f3bSpatrick        return "{} ({}){}".format(exception_type, exception_signal,
455*be691f3bSpatrick                                  exception_extra)
456*be691f3bSpatrick
457*be691f3bSpatrick    def parse_images(self, json_images):
458*be691f3bSpatrick        idx = 0
459*be691f3bSpatrick        for json_image in json_images:
460*be691f3bSpatrick            img_uuid = uuid.UUID(json_image['uuid'])
461*be691f3bSpatrick            low = int(json_image['base'])
462*be691f3bSpatrick            high = int(0)
463*be691f3bSpatrick            name = json_image['name'] if 'name' in json_image else ''
464*be691f3bSpatrick            path = json_image['path'] if 'path' in json_image else ''
465*be691f3bSpatrick            version = ''
466*be691f3bSpatrick            darwin_image = self.crashlog.DarwinImage(low, high, name, version,
467*be691f3bSpatrick                                                     img_uuid, path,
468*be691f3bSpatrick                                                     self.verbose)
469*be691f3bSpatrick            self.crashlog.images.append(darwin_image)
470*be691f3bSpatrick            idx += 1
471*be691f3bSpatrick
472*be691f3bSpatrick    def parse_frames(self, thread, json_frames):
473*be691f3bSpatrick        idx = 0
474*be691f3bSpatrick        for json_frame in json_frames:
475*be691f3bSpatrick            image_id = int(json_frame['imageIndex'])
476*be691f3bSpatrick            ident = self.get_used_image(image_id)['name']
477*be691f3bSpatrick            thread.add_ident(ident)
478*be691f3bSpatrick            if ident not in self.crashlog.idents:
479*be691f3bSpatrick                self.crashlog.idents.append(ident)
480*be691f3bSpatrick
481*be691f3bSpatrick            frame_offset = int(json_frame['imageOffset'])
482*be691f3bSpatrick            image_addr = self.get_used_image(image_id)['base']
483*be691f3bSpatrick            pc = image_addr + frame_offset
484*be691f3bSpatrick            thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
485*be691f3bSpatrick            idx += 1
486*be691f3bSpatrick
487*be691f3bSpatrick    def parse_threads(self, json_threads):
488*be691f3bSpatrick        idx = 0
489*be691f3bSpatrick        for json_thread in json_threads:
490*be691f3bSpatrick            thread = self.crashlog.Thread(idx, False)
491*be691f3bSpatrick            if 'name' in json_thread:
492*be691f3bSpatrick                thread.reason = json_thread['name']
493*be691f3bSpatrick            if json_thread.get('triggered', False):
494*be691f3bSpatrick                self.crashlog.crashed_thread_idx = idx
495*be691f3bSpatrick                thread.registers = self.parse_thread_registers(
496*be691f3bSpatrick                    json_thread['threadState'])
497*be691f3bSpatrick            thread.queue = json_thread.get('queue')
498*be691f3bSpatrick            self.parse_frames(thread, json_thread.get('frames', []))
499*be691f3bSpatrick            self.crashlog.threads.append(thread)
500*be691f3bSpatrick            idx += 1
501*be691f3bSpatrick
502*be691f3bSpatrick    def parse_thread_registers(self, json_thread_state):
503*be691f3bSpatrick        registers = dict()
504*be691f3bSpatrick        for key, state in json_thread_state.items():
505*be691f3bSpatrick            try:
506*be691f3bSpatrick               value = int(state['value'])
507*be691f3bSpatrick               registers[key] = value
508*be691f3bSpatrick            except (TypeError, ValueError):
509*be691f3bSpatrick               pass
510*be691f3bSpatrick        return registers
511*be691f3bSpatrick
512*be691f3bSpatrick
513*be691f3bSpatrickclass CrashLogParseMode:
514*be691f3bSpatrick    NORMAL = 0
515*be691f3bSpatrick    THREAD = 1
516*be691f3bSpatrick    IMAGES = 2
517*be691f3bSpatrick    THREGS = 3
518*be691f3bSpatrick    SYSTEM = 4
519*be691f3bSpatrick    INSTRS = 5
520*be691f3bSpatrick
521*be691f3bSpatrick
522*be691f3bSpatrickclass TextCrashLogParser:
523*be691f3bSpatrick    parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]')
524*be691f3bSpatrick    thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
525*be691f3bSpatrick    thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream')
526*be691f3bSpatrick    thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
527*be691f3bSpatrick    app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
528*be691f3bSpatrick    version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+'
529*be691f3bSpatrick    frame_regex = re.compile(r'^([0-9]+)' r'\s'                # id
530*be691f3bSpatrick                             r'+(.+?)'    r'\s+'               # img_name
531*be691f3bSpatrick                             r'(' +version+ r')?'              # img_version
532*be691f3bSpatrick                             r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr
533*be691f3bSpatrick                             r' +(.*)'                         # offs
534*be691f3bSpatrick                            )
535*be691f3bSpatrick    null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)')
536*be691f3bSpatrick    image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)'            # img_lo
537*be691f3bSpatrick                                  r'\s+' '-' r'\s+'              #   -
538*be691f3bSpatrick                                  r'(0x[0-9a-fA-F]+)'     r'\s+' # img_hi
539*be691f3bSpatrick                                  r'[+]?(.+?)'            r'\s+' # img_name
540*be691f3bSpatrick                                  r'(' +version+ ')?'            # img_version
541*be691f3bSpatrick                                  r'(<([-0-9a-fA-F]+)>\s+)?'     # img_uuid
542*be691f3bSpatrick                                  r'(/.*)'                       # img_path
543*be691f3bSpatrick                                 )
544*be691f3bSpatrick
545*be691f3bSpatrick
546*be691f3bSpatrick    def __init__(self, debugger, path, verbose):
547*be691f3bSpatrick        self.path = os.path.expanduser(path)
548*be691f3bSpatrick        self.verbose = verbose
549*be691f3bSpatrick        self.thread = None
550*be691f3bSpatrick        self.app_specific_backtrace = False
551*be691f3bSpatrick        self.crashlog = CrashLog(debugger, self.path, self.verbose)
552*be691f3bSpatrick        self.parse_mode = CrashLogParseMode.NORMAL
553*be691f3bSpatrick        self.parsers = {
554*be691f3bSpatrick            CrashLogParseMode.NORMAL : self.parse_normal,
555*be691f3bSpatrick            CrashLogParseMode.THREAD : self.parse_thread,
556*be691f3bSpatrick            CrashLogParseMode.IMAGES : self.parse_images,
557*be691f3bSpatrick            CrashLogParseMode.THREGS : self.parse_thread_registers,
558*be691f3bSpatrick            CrashLogParseMode.SYSTEM : self.parse_system,
559*be691f3bSpatrick            CrashLogParseMode.INSTRS : self.parse_instructions,
560*be691f3bSpatrick        }
561*be691f3bSpatrick
562*be691f3bSpatrick    def parse(self):
563*be691f3bSpatrick        with open(self.path,'r') as f:
564*be691f3bSpatrick            lines = f.read().splitlines()
565*be691f3bSpatrick
566*be691f3bSpatrick        for line in lines:
567*be691f3bSpatrick            line_len = len(line)
568*be691f3bSpatrick            if line_len == 0:
569*be691f3bSpatrick                if self.thread:
570*be691f3bSpatrick                    if self.parse_mode == CrashLogParseMode.THREAD:
571*be691f3bSpatrick                        if self.thread.index == self.crashlog.crashed_thread_idx:
572*be691f3bSpatrick                            self.thread.reason = ''
573*be691f3bSpatrick                            if self.crashlog.thread_exception:
574*be691f3bSpatrick                                self.thread.reason += self.crashlog.thread_exception
575*be691f3bSpatrick                            if self.crashlog.thread_exception_data:
576*be691f3bSpatrick                                self.thread.reason += " (%s)" % self.crashlog.thread_exception_data
577*be691f3bSpatrick                        if self.app_specific_backtrace:
578*be691f3bSpatrick                            self.crashlog.backtraces.append(self.thread)
579*be691f3bSpatrick                        else:
580*be691f3bSpatrick                            self.crashlog.threads.append(self.thread)
581*be691f3bSpatrick                    self.thread = None
582*be691f3bSpatrick                else:
583*be691f3bSpatrick                    # only append an extra empty line if the previous line
584*be691f3bSpatrick                    # in the info_lines wasn't empty
585*be691f3bSpatrick                    if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]):
586*be691f3bSpatrick                        self.crashlog.info_lines.append(line)
587*be691f3bSpatrick                self.parse_mode = CrashLogParseMode.NORMAL
588*be691f3bSpatrick            else:
589*be691f3bSpatrick                self.parsers[self.parse_mode](line)
590*be691f3bSpatrick
591*be691f3bSpatrick        return self.crashlog
592*be691f3bSpatrick
593*be691f3bSpatrick
594*be691f3bSpatrick    def parse_normal(self, line):
595*be691f3bSpatrick        if line.startswith('Process:'):
596*be691f3bSpatrick            (self.crashlog.process_name, pid_with_brackets) = line[
597*be691f3bSpatrick                8:].strip().split(' [')
598*be691f3bSpatrick            self.crashlog.process_id = pid_with_brackets.strip('[]')
599*be691f3bSpatrick        elif line.startswith('Path:'):
600*be691f3bSpatrick            self.crashlog.process_path = line[5:].strip()
601*be691f3bSpatrick        elif line.startswith('Identifier:'):
602*be691f3bSpatrick            self.crashlog.process_identifier = line[11:].strip()
603*be691f3bSpatrick        elif line.startswith('Version:'):
604*be691f3bSpatrick            version_string = line[8:].strip()
605*be691f3bSpatrick            matched_pair = re.search("(.+)\((.+)\)", version_string)
606*be691f3bSpatrick            if matched_pair:
607*be691f3bSpatrick                self.crashlog.process_version = matched_pair.group(1)
608*be691f3bSpatrick                self.crashlog.process_compatability_version = matched_pair.group(
609*be691f3bSpatrick                    2)
610*be691f3bSpatrick            else:
611*be691f3bSpatrick                self.crashlog.process = version_string
612*be691f3bSpatrick                self.crashlog.process_compatability_version = version_string
613*be691f3bSpatrick        elif self.parent_process_regex.search(line):
614*be691f3bSpatrick            parent_process_match = self.parent_process_regex.search(
615*be691f3bSpatrick                line)
616*be691f3bSpatrick            self.crashlog.parent_process_name = parent_process_match.group(1)
617*be691f3bSpatrick            self.crashlog.parent_process_id = parent_process_match.group(2)
618*be691f3bSpatrick        elif line.startswith('Exception Type:'):
619*be691f3bSpatrick            self.crashlog.thread_exception = line[15:].strip()
620*be691f3bSpatrick            return
621*be691f3bSpatrick        elif line.startswith('Exception Codes:'):
622*be691f3bSpatrick            self.crashlog.thread_exception_data = line[16:].strip()
623*be691f3bSpatrick            return
624*be691f3bSpatrick        elif line.startswith('Exception Subtype:'): # iOS
625*be691f3bSpatrick            self.crashlog.thread_exception_data = line[18:].strip()
626*be691f3bSpatrick            return
627*be691f3bSpatrick        elif line.startswith('Crashed Thread:'):
628*be691f3bSpatrick            self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0])
629*be691f3bSpatrick            return
630*be691f3bSpatrick        elif line.startswith('Triggered by Thread:'): # iOS
631*be691f3bSpatrick            self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0])
632*be691f3bSpatrick            return
633*be691f3bSpatrick        elif line.startswith('Report Version:'):
634*be691f3bSpatrick            self.crashlog.version = int(line[15:].strip())
635*be691f3bSpatrick            return
636*be691f3bSpatrick        elif line.startswith('System Profile:'):
637*be691f3bSpatrick            self.parse_mode = CrashLogParseMode.SYSTEM
638*be691f3bSpatrick            return
639*be691f3bSpatrick        elif (line.startswith('Interval Since Last Report:') or
640*be691f3bSpatrick                line.startswith('Crashes Since Last Report:') or
641*be691f3bSpatrick                line.startswith('Per-App Interval Since Last Report:') or
642*be691f3bSpatrick                line.startswith('Per-App Crashes Since Last Report:') or
643*be691f3bSpatrick                line.startswith('Sleep/Wake UUID:') or
644*be691f3bSpatrick                line.startswith('Anonymous UUID:')):
645*be691f3bSpatrick            # ignore these
646*be691f3bSpatrick            return
647*be691f3bSpatrick        elif line.startswith('Thread'):
648*be691f3bSpatrick            thread_state_match = self.thread_state_regex.search(line)
649*be691f3bSpatrick            if thread_state_match:
650*be691f3bSpatrick                self.app_specific_backtrace = False
651*be691f3bSpatrick                thread_state_match = self.thread_regex.search(line)
652*be691f3bSpatrick                thread_idx = int(thread_state_match.group(1))
653*be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREGS
654*be691f3bSpatrick                self.thread = self.crashlog.threads[thread_idx]
655*be691f3bSpatrick                return
656*be691f3bSpatrick            thread_insts_match  = self.thread_instrs_regex.search(line)
657*be691f3bSpatrick            if thread_insts_match:
658*be691f3bSpatrick                self.parse_mode = CrashLogParseMode.INSTRS
659*be691f3bSpatrick                return
660*be691f3bSpatrick            thread_match = self.thread_regex.search(line)
661*be691f3bSpatrick            if thread_match:
662*be691f3bSpatrick                self.app_specific_backtrace = False
663*be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREAD
664*be691f3bSpatrick                thread_idx = int(thread_match.group(1))
665*be691f3bSpatrick                self.thread = self.crashlog.Thread(thread_idx, False)
666*be691f3bSpatrick                return
667*be691f3bSpatrick            return
668*be691f3bSpatrick        elif line.startswith('Binary Images:'):
669*be691f3bSpatrick            self.parse_mode = CrashLogParseMode.IMAGES
670*be691f3bSpatrick            return
671*be691f3bSpatrick        elif line.startswith('Application Specific Backtrace'):
672*be691f3bSpatrick            app_backtrace_match = self.app_backtrace_regex.search(line)
673*be691f3bSpatrick            if app_backtrace_match:
674*be691f3bSpatrick                self.parse_mode = CrashLogParseMode.THREAD
675*be691f3bSpatrick                self.app_specific_backtrace = True
676*be691f3bSpatrick                idx = int(app_backtrace_match.group(1))
677*be691f3bSpatrick                self.thread = self.crashlog.Thread(idx, True)
678*be691f3bSpatrick        elif line.startswith('Last Exception Backtrace:'): # iOS
679*be691f3bSpatrick            self.parse_mode = CrashLogParseMode.THREAD
680*be691f3bSpatrick            self.app_specific_backtrace = True
681*be691f3bSpatrick            idx = 1
682*be691f3bSpatrick            self.thread = self.crashlog.Thread(idx, True)
683*be691f3bSpatrick        self.crashlog.info_lines.append(line.strip())
684*be691f3bSpatrick
685*be691f3bSpatrick    def parse_thread(self, line):
686*be691f3bSpatrick        if line.startswith('Thread'):
687*be691f3bSpatrick            return
688*be691f3bSpatrick        if self.null_frame_regex.search(line):
689*be691f3bSpatrick            print('warning: thread parser ignored null-frame: "%s"' % line)
690*be691f3bSpatrick            return
691*be691f3bSpatrick        frame_match = self.frame_regex.search(line)
692*be691f3bSpatrick        if frame_match:
693*be691f3bSpatrick            (frame_id, frame_img_name, _, frame_img_version, _,
694*be691f3bSpatrick                frame_addr, frame_ofs) = frame_match.groups()
695*be691f3bSpatrick            ident = frame_img_name
696*be691f3bSpatrick            self.thread.add_ident(ident)
697*be691f3bSpatrick            if ident not in self.crashlog.idents:
698*be691f3bSpatrick                self.crashlog.idents.append(ident)
699*be691f3bSpatrick            self.thread.frames.append(self.crashlog.Frame(int(frame_id), int(
700*be691f3bSpatrick                frame_addr, 0), frame_ofs))
701*be691f3bSpatrick        else:
702*be691f3bSpatrick            print('error: frame regex failed for line: "%s"' % line)
703*be691f3bSpatrick
704*be691f3bSpatrick    def parse_images(self, line):
705*be691f3bSpatrick        image_match = self.image_regex_uuid.search(line)
706*be691f3bSpatrick        if image_match:
707*be691f3bSpatrick            (img_lo, img_hi, img_name, _, img_version, _,
708*be691f3bSpatrick                _, img_uuid, img_path) = image_match.groups()
709*be691f3bSpatrick            image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
710*be691f3bSpatrick                                            img_name.strip(),
711*be691f3bSpatrick                                            img_version.strip()
712*be691f3bSpatrick                                            if img_version else "",
713*be691f3bSpatrick                                            uuid.UUID(img_uuid), img_path,
714*be691f3bSpatrick                                            self.verbose)
715*be691f3bSpatrick            self.crashlog.images.append(image)
716*be691f3bSpatrick        else:
717*be691f3bSpatrick            print("error: image regex failed for: %s" % line)
718*be691f3bSpatrick
719*be691f3bSpatrick
720*be691f3bSpatrick    def parse_thread_registers(self, line):
721*be691f3bSpatrick        stripped_line = line.strip()
722*be691f3bSpatrick        # "r12: 0x00007fff6b5939c8  r13: 0x0000000007000006  r14: 0x0000000000002a03  r15: 0x0000000000000c00"
723*be691f3bSpatrick        reg_values = re.findall(
724*be691f3bSpatrick            '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line)
725*be691f3bSpatrick        for reg_value in reg_values:
726*be691f3bSpatrick            (reg, value) = reg_value.split(': ')
727*be691f3bSpatrick            self.thread.registers[reg.strip()] = int(value, 0)
728*be691f3bSpatrick
729*be691f3bSpatrick    def parse_system(self, line):
730*be691f3bSpatrick        self.crashlog.system_profile.append(line)
731*be691f3bSpatrick
732*be691f3bSpatrick    def parse_instructions(self, line):
733*be691f3bSpatrick        pass
734*be691f3bSpatrick
735*be691f3bSpatrick
736061da546Spatrickdef usage():
737061da546Spatrick    print("Usage: lldb-symbolicate.py [-n name] executable-image")
738061da546Spatrick    sys.exit(0)
739061da546Spatrick
740061da546Spatrick
741061da546Spatrickclass Interactive(cmd.Cmd):
742061da546Spatrick    '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
743061da546Spatrick    image_option_parser = None
744061da546Spatrick
745061da546Spatrick    def __init__(self, crash_logs):
746061da546Spatrick        cmd.Cmd.__init__(self)
747061da546Spatrick        self.use_rawinput = False
748061da546Spatrick        self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
749061da546Spatrick        self.crash_logs = crash_logs
750061da546Spatrick        self.prompt = '% '
751061da546Spatrick
752061da546Spatrick    def default(self, line):
753061da546Spatrick        '''Catch all for unknown command, which will exit the interpreter.'''
754061da546Spatrick        print("uknown command: %s" % line)
755061da546Spatrick        return True
756061da546Spatrick
757061da546Spatrick    def do_q(self, line):
758061da546Spatrick        '''Quit command'''
759061da546Spatrick        return True
760061da546Spatrick
761061da546Spatrick    def do_quit(self, line):
762061da546Spatrick        '''Quit command'''
763061da546Spatrick        return True
764061da546Spatrick
765061da546Spatrick    def do_symbolicate(self, line):
766061da546Spatrick        description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information,
767061da546Spatrick        inlined stack frames back to the concrete functions, and disassemble the location of the crash
768061da546Spatrick        for the first frame of the crashed thread.'''
769061da546Spatrick        option_parser = CreateSymbolicateCrashLogOptions(
770061da546Spatrick            'symbolicate', description, False)
771061da546Spatrick        command_args = shlex.split(line)
772061da546Spatrick        try:
773061da546Spatrick            (options, args) = option_parser.parse_args(command_args)
774061da546Spatrick        except:
775061da546Spatrick            return
776061da546Spatrick
777061da546Spatrick        if args:
778061da546Spatrick            # We have arguments, they must valid be crash log file indexes
779061da546Spatrick            for idx_str in args:
780061da546Spatrick                idx = int(idx_str)
781061da546Spatrick                if idx < len(self.crash_logs):
782061da546Spatrick                    SymbolicateCrashLog(self.crash_logs[idx], options)
783061da546Spatrick                else:
784061da546Spatrick                    print('error: crash log index %u is out of range' % (idx))
785061da546Spatrick        else:
786061da546Spatrick            # No arguments, symbolicate all crash logs using the options
787061da546Spatrick            # provided
788061da546Spatrick            for idx in range(len(self.crash_logs)):
789061da546Spatrick                SymbolicateCrashLog(self.crash_logs[idx], options)
790061da546Spatrick
791061da546Spatrick    def do_list(self, line=None):
792061da546Spatrick        '''Dump a list of all crash logs that are currently loaded.
793061da546Spatrick
794061da546Spatrick        USAGE: list'''
795061da546Spatrick        print('%u crash logs are loaded:' % len(self.crash_logs))
796061da546Spatrick        for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
797061da546Spatrick            print('[%u] = %s' % (crash_log_idx, crash_log.path))
798061da546Spatrick
799061da546Spatrick    def do_image(self, line):
800061da546Spatrick        '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
801061da546Spatrick        usage = "usage: %prog [options] <PATH> [PATH ...]"
802061da546Spatrick        description = '''Dump information about one or more images in all crash logs. The <PATH> can be a full path, image basename, or partial path. Searches are done in this order.'''
803061da546Spatrick        command_args = shlex.split(line)
804061da546Spatrick        if not self.image_option_parser:
805061da546Spatrick            self.image_option_parser = optparse.OptionParser(
806061da546Spatrick                description=description, prog='image', usage=usage)
807061da546Spatrick            self.image_option_parser.add_option(
808061da546Spatrick                '-a',
809061da546Spatrick                '--all',
810061da546Spatrick                action='store_true',
811061da546Spatrick                help='show all images',
812061da546Spatrick                default=False)
813061da546Spatrick        try:
814061da546Spatrick            (options, args) = self.image_option_parser.parse_args(command_args)
815061da546Spatrick        except:
816061da546Spatrick            return
817061da546Spatrick
818061da546Spatrick        if args:
819061da546Spatrick            for image_path in args:
820061da546Spatrick                fullpath_search = image_path[0] == '/'
821061da546Spatrick                for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
822061da546Spatrick                    matches_found = 0
823061da546Spatrick                    for (image_idx, image) in enumerate(crash_log.images):
824061da546Spatrick                        if fullpath_search:
825061da546Spatrick                            if image.get_resolved_path() == image_path:
826061da546Spatrick                                matches_found += 1
827061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
828061da546Spatrick                        else:
829061da546Spatrick                            image_basename = image.get_resolved_path_basename()
830061da546Spatrick                            if image_basename == image_path:
831061da546Spatrick                                matches_found += 1
832061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
833061da546Spatrick                    if matches_found == 0:
834061da546Spatrick                        for (image_idx, image) in enumerate(crash_log.images):
835061da546Spatrick                            resolved_image_path = image.get_resolved_path()
836061da546Spatrick                            if resolved_image_path and string.find(
837061da546Spatrick                                    image.get_resolved_path(), image_path) >= 0:
838061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
839061da546Spatrick        else:
840061da546Spatrick            for crash_log in self.crash_logs:
841061da546Spatrick                for (image_idx, image) in enumerate(crash_log.images):
842061da546Spatrick                    print('[%u] %s' % (image_idx, image))
843061da546Spatrick        return False
844061da546Spatrick
845061da546Spatrick
846*be691f3bSpatrickdef interactive_crashlogs(debugger, options, args):
847061da546Spatrick    crash_log_files = list()
848061da546Spatrick    for arg in args:
849061da546Spatrick        for resolved_path in glob.glob(arg):
850061da546Spatrick            crash_log_files.append(resolved_path)
851061da546Spatrick
852061da546Spatrick    crash_logs = list()
853061da546Spatrick    for crash_log_file in crash_log_files:
854*be691f3bSpatrick        try:
855*be691f3bSpatrick            crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose)
856*be691f3bSpatrick        except Exception as e:
857*be691f3bSpatrick            print(e)
858061da546Spatrick            continue
859061da546Spatrick        if options.debug:
860061da546Spatrick            crash_log.dump()
861061da546Spatrick        if not crash_log.images:
862061da546Spatrick            print('error: no images in crash log "%s"' % (crash_log))
863061da546Spatrick            continue
864061da546Spatrick        else:
865061da546Spatrick            crash_logs.append(crash_log)
866061da546Spatrick
867061da546Spatrick    interpreter = Interactive(crash_logs)
868061da546Spatrick    # List all crash logs that were imported
869061da546Spatrick    interpreter.do_list()
870061da546Spatrick    interpreter.cmdloop()
871061da546Spatrick
872061da546Spatrick
873061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict):
874061da546Spatrick    usage = "usage: %prog [options] <output-path>"
875061da546Spatrick    description = '''Export the state of current target into a crashlog file'''
876061da546Spatrick    parser = optparse.OptionParser(
877061da546Spatrick        description=description,
878061da546Spatrick        prog='save_crashlog',
879061da546Spatrick        usage=usage)
880061da546Spatrick    parser.add_option(
881061da546Spatrick        '-v',
882061da546Spatrick        '--verbose',
883061da546Spatrick        action='store_true',
884061da546Spatrick        dest='verbose',
885061da546Spatrick        help='display verbose debug info',
886061da546Spatrick        default=False)
887061da546Spatrick    try:
888061da546Spatrick        (options, args) = parser.parse_args(shlex.split(command))
889061da546Spatrick    except:
890061da546Spatrick        result.PutCString("error: invalid options")
891061da546Spatrick        return
892061da546Spatrick    if len(args) != 1:
893061da546Spatrick        result.PutCString(
894061da546Spatrick            "error: invalid arguments, a single output file is the only valid argument")
895061da546Spatrick        return
896061da546Spatrick    out_file = open(args[0], 'w')
897061da546Spatrick    if not out_file:
898061da546Spatrick        result.PutCString(
899061da546Spatrick            "error: failed to open file '%s' for writing...",
900061da546Spatrick            args[0])
901061da546Spatrick        return
902061da546Spatrick    target = exe_ctx.target
903061da546Spatrick    if target:
904061da546Spatrick        identifier = target.executable.basename
905061da546Spatrick        process = exe_ctx.process
906061da546Spatrick        if process:
907061da546Spatrick            pid = process.id
908061da546Spatrick            if pid != lldb.LLDB_INVALID_PROCESS_ID:
909061da546Spatrick                out_file.write(
910061da546Spatrick                    'Process:         %s [%u]\n' %
911061da546Spatrick                    (identifier, pid))
912061da546Spatrick        out_file.write('Path:            %s\n' % (target.executable.fullpath))
913061da546Spatrick        out_file.write('Identifier:      %s\n' % (identifier))
914061da546Spatrick        out_file.write('\nDate/Time:       %s\n' %
915061da546Spatrick                       (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
916061da546Spatrick        out_file.write(
917061da546Spatrick            'OS Version:      Mac OS X %s (%s)\n' %
918061da546Spatrick            (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8")))
919061da546Spatrick        out_file.write('Report Version:  9\n')
920061da546Spatrick        for thread_idx in range(process.num_threads):
921061da546Spatrick            thread = process.thread[thread_idx]
922061da546Spatrick            out_file.write('\nThread %u:\n' % (thread_idx))
923061da546Spatrick            for (frame_idx, frame) in enumerate(thread.frames):
924061da546Spatrick                frame_pc = frame.pc
925061da546Spatrick                frame_offset = 0
926061da546Spatrick                if frame.function:
927061da546Spatrick                    block = frame.GetFrameBlock()
928061da546Spatrick                    block_range = block.range[frame.addr]
929061da546Spatrick                    if block_range:
930061da546Spatrick                        block_start_addr = block_range[0]
931dda28197Spatrick                        frame_offset = frame_pc - block_start_addr.GetLoadAddress(target)
932061da546Spatrick                    else:
933dda28197Spatrick                        frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target)
934061da546Spatrick                elif frame.symbol:
935dda28197Spatrick                    frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
936061da546Spatrick                out_file.write(
937061da546Spatrick                    '%-3u %-32s 0x%16.16x %s' %
938061da546Spatrick                    (frame_idx, frame.module.file.basename, frame_pc, frame.name))
939061da546Spatrick                if frame_offset > 0:
940061da546Spatrick                    out_file.write(' + %u' % (frame_offset))
941061da546Spatrick                line_entry = frame.line_entry
942061da546Spatrick                if line_entry:
943061da546Spatrick                    if options.verbose:
944061da546Spatrick                        # This will output the fullpath + line + column
945061da546Spatrick                        out_file.write(' %s' % (line_entry))
946061da546Spatrick                    else:
947061da546Spatrick                        out_file.write(
948061da546Spatrick                            ' %s:%u' %
949061da546Spatrick                            (line_entry.file.basename, line_entry.line))
950061da546Spatrick                        column = line_entry.column
951061da546Spatrick                        if column:
952061da546Spatrick                            out_file.write(':%u' % (column))
953061da546Spatrick                out_file.write('\n')
954061da546Spatrick
955061da546Spatrick        out_file.write('\nBinary Images:\n')
956061da546Spatrick        for module in target.modules:
957061da546Spatrick            text_segment = module.section['__TEXT']
958061da546Spatrick            if text_segment:
959061da546Spatrick                text_segment_load_addr = text_segment.GetLoadAddress(target)
960061da546Spatrick                if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
961061da546Spatrick                    text_segment_end_load_addr = text_segment_load_addr + text_segment.size
962061da546Spatrick                    identifier = module.file.basename
963061da546Spatrick                    module_version = '???'
964061da546Spatrick                    module_version_array = module.GetVersion()
965061da546Spatrick                    if module_version_array:
966061da546Spatrick                        module_version = '.'.join(
967061da546Spatrick                            map(str, module_version_array))
968061da546Spatrick                    out_file.write(
969061da546Spatrick                        '    0x%16.16x - 0x%16.16x  %s (%s - ???) <%s> %s\n' %
970061da546Spatrick                        (text_segment_load_addr,
971061da546Spatrick                         text_segment_end_load_addr,
972061da546Spatrick                         identifier,
973061da546Spatrick                         module_version,
974061da546Spatrick                         module.GetUUIDString(),
975061da546Spatrick                         module.file.fullpath))
976061da546Spatrick        out_file.close()
977061da546Spatrick    else:
978061da546Spatrick        result.PutCString("error: invalid target")
979061da546Spatrick
980061da546Spatrick
981061da546Spatrickdef Symbolicate(debugger, command, result, dict):
982061da546Spatrick    try:
983*be691f3bSpatrick        SymbolicateCrashLogs(debugger, shlex.split(command))
984*be691f3bSpatrick    except Exception as e:
985*be691f3bSpatrick        result.PutCString("error: python exception: %s" % e)
986061da546Spatrick
987061da546Spatrick
988061da546Spatrickdef SymbolicateCrashLog(crash_log, options):
989061da546Spatrick    if options.debug:
990061da546Spatrick        crash_log.dump()
991061da546Spatrick    if not crash_log.images:
992061da546Spatrick        print('error: no images in crash log')
993061da546Spatrick        return
994061da546Spatrick
995061da546Spatrick    if options.dump_image_list:
996061da546Spatrick        print("Binary Images:")
997061da546Spatrick        for image in crash_log.images:
998061da546Spatrick            if options.verbose:
999061da546Spatrick                print(image.debug_dump())
1000061da546Spatrick            else:
1001061da546Spatrick                print(image)
1002061da546Spatrick
1003061da546Spatrick    target = crash_log.create_target()
1004061da546Spatrick    if not target:
1005061da546Spatrick        return
1006061da546Spatrick    exe_module = target.GetModuleAtIndex(0)
1007061da546Spatrick    images_to_load = list()
1008061da546Spatrick    loaded_images = list()
1009061da546Spatrick    if options.load_all_images:
1010061da546Spatrick        # --load-all option was specified, load everything up
1011061da546Spatrick        for image in crash_log.images:
1012061da546Spatrick            images_to_load.append(image)
1013061da546Spatrick    else:
1014061da546Spatrick        # Only load the images found in stack frames for the crashed threads
1015061da546Spatrick        if options.crashed_only:
1016061da546Spatrick            for thread in crash_log.threads:
1017061da546Spatrick                if thread.did_crash():
1018061da546Spatrick                    for ident in thread.idents:
1019061da546Spatrick                        images = crash_log.find_images_with_identifier(ident)
1020061da546Spatrick                        if images:
1021061da546Spatrick                            for image in images:
1022061da546Spatrick                                images_to_load.append(image)
1023061da546Spatrick                        else:
1024061da546Spatrick                            print('error: can\'t find image for identifier "%s"' % ident)
1025061da546Spatrick        else:
1026061da546Spatrick            for ident in crash_log.idents:
1027061da546Spatrick                images = crash_log.find_images_with_identifier(ident)
1028061da546Spatrick                if images:
1029061da546Spatrick                    for image in images:
1030061da546Spatrick                        images_to_load.append(image)
1031061da546Spatrick                else:
1032061da546Spatrick                    print('error: can\'t find image for identifier "%s"' % ident)
1033061da546Spatrick
1034061da546Spatrick    for image in images_to_load:
1035061da546Spatrick        if image not in loaded_images:
1036061da546Spatrick            err = image.add_module(target)
1037061da546Spatrick            if err:
1038061da546Spatrick                print(err)
1039061da546Spatrick            else:
1040061da546Spatrick                loaded_images.append(image)
1041061da546Spatrick
1042061da546Spatrick    if crash_log.backtraces:
1043061da546Spatrick        for thread in crash_log.backtraces:
1044061da546Spatrick            thread.dump_symbolicated(crash_log, options)
1045061da546Spatrick            print()
1046061da546Spatrick
1047061da546Spatrick    for thread in crash_log.threads:
1048061da546Spatrick        thread.dump_symbolicated(crash_log, options)
1049061da546Spatrick        print()
1050061da546Spatrick
1051061da546Spatrick
1052061da546Spatrickdef CreateSymbolicateCrashLogOptions(
1053061da546Spatrick        command_name,
1054061da546Spatrick        description,
1055061da546Spatrick        add_interactive_options):
1056061da546Spatrick    usage = "usage: %prog [options] <FILE> [FILE ...]"
1057061da546Spatrick    option_parser = optparse.OptionParser(
1058061da546Spatrick        description=description, prog='crashlog', usage=usage)
1059061da546Spatrick    option_parser.add_option(
1060061da546Spatrick        '--verbose',
1061061da546Spatrick        '-v',
1062061da546Spatrick        action='store_true',
1063061da546Spatrick        dest='verbose',
1064061da546Spatrick        help='display verbose debug info',
1065061da546Spatrick        default=False)
1066061da546Spatrick    option_parser.add_option(
1067061da546Spatrick        '--debug',
1068061da546Spatrick        '-g',
1069061da546Spatrick        action='store_true',
1070061da546Spatrick        dest='debug',
1071061da546Spatrick        help='display verbose debug logging',
1072061da546Spatrick        default=False)
1073061da546Spatrick    option_parser.add_option(
1074061da546Spatrick        '--load-all',
1075061da546Spatrick        '-a',
1076061da546Spatrick        action='store_true',
1077061da546Spatrick        dest='load_all_images',
1078061da546Spatrick        help='load all executable images, not just the images found in the crashed stack frames',
1079061da546Spatrick        default=False)
1080061da546Spatrick    option_parser.add_option(
1081061da546Spatrick        '--images',
1082061da546Spatrick        action='store_true',
1083061da546Spatrick        dest='dump_image_list',
1084061da546Spatrick        help='show image list',
1085061da546Spatrick        default=False)
1086061da546Spatrick    option_parser.add_option(
1087061da546Spatrick        '--debug-delay',
1088061da546Spatrick        type='int',
1089061da546Spatrick        dest='debug_delay',
1090061da546Spatrick        metavar='NSEC',
1091061da546Spatrick        help='pause for NSEC seconds for debugger',
1092061da546Spatrick        default=0)
1093061da546Spatrick    option_parser.add_option(
1094061da546Spatrick        '--crashed-only',
1095061da546Spatrick        '-c',
1096061da546Spatrick        action='store_true',
1097061da546Spatrick        dest='crashed_only',
1098061da546Spatrick        help='only symbolicate the crashed thread',
1099061da546Spatrick        default=False)
1100061da546Spatrick    option_parser.add_option(
1101061da546Spatrick        '--disasm-depth',
1102061da546Spatrick        '-d',
1103061da546Spatrick        type='int',
1104061da546Spatrick        dest='disassemble_depth',
1105061da546Spatrick        help='set the depth in stack frames that should be disassembled (default is 1)',
1106061da546Spatrick        default=1)
1107061da546Spatrick    option_parser.add_option(
1108061da546Spatrick        '--disasm-all',
1109061da546Spatrick        '-D',
1110061da546Spatrick        action='store_true',
1111061da546Spatrick        dest='disassemble_all_threads',
1112061da546Spatrick        help='enabled disassembly of frames on all threads (not just the crashed thread)',
1113061da546Spatrick        default=False)
1114061da546Spatrick    option_parser.add_option(
1115061da546Spatrick        '--disasm-before',
1116061da546Spatrick        '-B',
1117061da546Spatrick        type='int',
1118061da546Spatrick        dest='disassemble_before',
1119061da546Spatrick        help='the number of instructions to disassemble before the frame PC',
1120061da546Spatrick        default=4)
1121061da546Spatrick    option_parser.add_option(
1122061da546Spatrick        '--disasm-after',
1123061da546Spatrick        '-A',
1124061da546Spatrick        type='int',
1125061da546Spatrick        dest='disassemble_after',
1126061da546Spatrick        help='the number of instructions to disassemble after the frame PC',
1127061da546Spatrick        default=4)
1128061da546Spatrick    option_parser.add_option(
1129061da546Spatrick        '--source-context',
1130061da546Spatrick        '-C',
1131061da546Spatrick        type='int',
1132061da546Spatrick        metavar='NLINES',
1133061da546Spatrick        dest='source_context',
1134061da546Spatrick        help='show NLINES source lines of source context (default = 4)',
1135061da546Spatrick        default=4)
1136061da546Spatrick    option_parser.add_option(
1137061da546Spatrick        '--source-frames',
1138061da546Spatrick        type='int',
1139061da546Spatrick        metavar='NFRAMES',
1140061da546Spatrick        dest='source_frames',
1141061da546Spatrick        help='show source for NFRAMES (default = 4)',
1142061da546Spatrick        default=4)
1143061da546Spatrick    option_parser.add_option(
1144061da546Spatrick        '--source-all',
1145061da546Spatrick        action='store_true',
1146061da546Spatrick        dest='source_all',
1147061da546Spatrick        help='show source for all threads, not just the crashed thread',
1148061da546Spatrick        default=False)
1149061da546Spatrick    if add_interactive_options:
1150061da546Spatrick        option_parser.add_option(
1151061da546Spatrick            '-i',
1152061da546Spatrick            '--interactive',
1153061da546Spatrick            action='store_true',
1154061da546Spatrick            help='parse all crash logs and enter interactive mode',
1155061da546Spatrick            default=False)
1156061da546Spatrick    return option_parser
1157061da546Spatrick
1158061da546Spatrick
1159*be691f3bSpatrickdef SymbolicateCrashLogs(debugger, command_args):
1160061da546Spatrick    description = '''Symbolicate one or more darwin crash log files to provide source file and line information,
1161061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash
1162061da546Spatrickfor the first frame of the crashed thread.
1163061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1164061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1165061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1166061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can
1167061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.'''
1168061da546Spatrick    option_parser = CreateSymbolicateCrashLogOptions(
1169061da546Spatrick        'crashlog', description, True)
1170061da546Spatrick    try:
1171061da546Spatrick        (options, args) = option_parser.parse_args(command_args)
1172061da546Spatrick    except:
1173061da546Spatrick        return
1174061da546Spatrick
1175061da546Spatrick    if options.debug:
1176061da546Spatrick        print('command_args = %s' % command_args)
1177061da546Spatrick        print('options', options)
1178061da546Spatrick        print('args', args)
1179061da546Spatrick
1180061da546Spatrick    if options.debug_delay > 0:
1181061da546Spatrick        print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1182061da546Spatrick        time.sleep(options.debug_delay)
1183061da546Spatrick    error = lldb.SBError()
1184061da546Spatrick
1185061da546Spatrick    if args:
1186061da546Spatrick        if options.interactive:
1187*be691f3bSpatrick            interactive_crashlogs(debugger, options, args)
1188061da546Spatrick        else:
1189061da546Spatrick            for crash_log_file in args:
1190*be691f3bSpatrick                crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose)
1191061da546Spatrick                SymbolicateCrashLog(crash_log, options)
1192061da546Spatrickif __name__ == '__main__':
1193061da546Spatrick    # Create a new debugger instance
1194*be691f3bSpatrick    debugger = lldb.SBDebugger.Create()
1195*be691f3bSpatrick    SymbolicateCrashLogs(debugger, sys.argv[1:])
1196*be691f3bSpatrick    lldb.SBDebugger.Destroy(debugger)
1197061da546Spatrickelif getattr(lldb, 'debugger', None):
1198061da546Spatrick    lldb.debugger.HandleCommand(
1199061da546Spatrick        'command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
1200061da546Spatrick    lldb.debugger.HandleCommand(
1201061da546Spatrick        'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
1202