1061da546Spatrick#!/usr/bin/python
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 pprint  # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
38061da546Spatrickimport re
39061da546Spatrickimport shlex
40061da546Spatrickimport string
41061da546Spatrickimport subprocess
42061da546Spatrickimport sys
43061da546Spatrickimport time
44061da546Spatrickimport uuid
45061da546Spatrick
46061da546Spatrickdef read_plist(s):
47061da546Spatrick    if sys.version_info.major == 3:
48061da546Spatrick        return plistlib.loads(s)
49061da546Spatrick    else:
50061da546Spatrick        return plistlib.readPlistFromString(s)
51061da546Spatrick
52061da546Spatricktry:
53061da546Spatrick    # Just try for LLDB in case PYTHONPATH is already correctly setup
54061da546Spatrick    import lldb
55061da546Spatrickexcept ImportError:
56061da546Spatrick    lldb_python_dirs = list()
57061da546Spatrick    # lldb is not in the PYTHONPATH, try some defaults for the current platform
58061da546Spatrick    platform_system = platform.system()
59061da546Spatrick    if platform_system == 'Darwin':
60061da546Spatrick        # On Darwin, try the currently selected Xcode directory
61061da546Spatrick        xcode_dir = subprocess.check_output("xcode-select --print-path", shell=True).decode("utf-8")
62061da546Spatrick        if xcode_dir:
63061da546Spatrick            lldb_python_dirs.append(
64061da546Spatrick                os.path.realpath(
65061da546Spatrick                    xcode_dir +
66061da546Spatrick                    '/../SharedFrameworks/LLDB.framework/Resources/Python'))
67061da546Spatrick            lldb_python_dirs.append(
68061da546Spatrick                xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
69061da546Spatrick        lldb_python_dirs.append(
70061da546Spatrick            '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
71061da546Spatrick    success = False
72061da546Spatrick    for lldb_python_dir in lldb_python_dirs:
73061da546Spatrick        if os.path.exists(lldb_python_dir):
74061da546Spatrick            if not (sys.path.__contains__(lldb_python_dir)):
75061da546Spatrick                sys.path.append(lldb_python_dir)
76061da546Spatrick                try:
77061da546Spatrick                    import lldb
78061da546Spatrick                except ImportError:
79061da546Spatrick                    pass
80061da546Spatrick                else:
81061da546Spatrick                    print('imported lldb from: "%s"' % (lldb_python_dir))
82061da546Spatrick                    success = True
83061da546Spatrick                    break
84061da546Spatrick    if not success:
85061da546Spatrick        print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
86061da546Spatrick        sys.exit(1)
87061da546Spatrick
88061da546Spatrickfrom lldb.utils import symbolication
89061da546Spatrick
90061da546SpatrickPARSE_MODE_NORMAL = 0
91061da546SpatrickPARSE_MODE_THREAD = 1
92061da546SpatrickPARSE_MODE_IMAGES = 2
93061da546SpatrickPARSE_MODE_THREGS = 3
94061da546SpatrickPARSE_MODE_SYSTEM = 4
95061da546Spatrick
96061da546Spatrick
97061da546Spatrickclass CrashLog(symbolication.Symbolicator):
98061da546Spatrick    """Class that does parses darwin crash logs"""
99061da546Spatrick    parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]')
100061da546Spatrick    thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
101061da546Spatrick    thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
102061da546Spatrick    app_backtrace_regex = re.compile(
103061da546Spatrick        '^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
104061da546Spatrick    version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+'
105061da546Spatrick    frame_regex = re.compile(r'^([0-9]+)' r'\s'                # id
106061da546Spatrick                             r'+(.+?)'    r'\s+'               # img_name
107061da546Spatrick                             r'(' +version+ r')?'              # img_version
108061da546Spatrick                             r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr
109061da546Spatrick                             r' +(.*)'                         # offs
110061da546Spatrick                            )
111061da546Spatrick    null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)')
112061da546Spatrick    image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)'            # img_lo
113061da546Spatrick                                  r'\s+' '-' r'\s+'              #   -
114061da546Spatrick                                  r'(0x[0-9a-fA-F]+)'     r'\s+' # img_hi
115061da546Spatrick                                  r'[+]?(.+?)'            r'\s+' # img_name
116061da546Spatrick                                  r'(' +version+ ')?'            # img_version
117061da546Spatrick                                  r'(<([-0-9a-fA-F]+)>\s+)?'     # img_uuid
118061da546Spatrick                                  r'(/.*)'                       # img_path
119061da546Spatrick                                 )
120061da546Spatrick    empty_line_regex = re.compile('^$')
121061da546Spatrick
122061da546Spatrick    class Thread:
123061da546Spatrick        """Class that represents a thread in a darwin crash log"""
124061da546Spatrick
125061da546Spatrick        def __init__(self, index, app_specific_backtrace):
126061da546Spatrick            self.index = index
127061da546Spatrick            self.frames = list()
128061da546Spatrick            self.idents = list()
129061da546Spatrick            self.registers = dict()
130061da546Spatrick            self.reason = None
131061da546Spatrick            self.queue = None
132061da546Spatrick            self.app_specific_backtrace = app_specific_backtrace
133061da546Spatrick
134061da546Spatrick        def dump(self, prefix):
135061da546Spatrick            if self.app_specific_backtrace:
136061da546Spatrick                print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason))
137061da546Spatrick            else:
138061da546Spatrick                print("%sThread[%u] %s" % (prefix, self.index, self.reason))
139061da546Spatrick            if self.frames:
140061da546Spatrick                print("%s  Frames:" % (prefix))
141061da546Spatrick                for frame in self.frames:
142061da546Spatrick                    frame.dump(prefix + '    ')
143061da546Spatrick            if self.registers:
144061da546Spatrick                print("%s  Registers:" % (prefix))
145061da546Spatrick                for reg in self.registers.keys():
146061da546Spatrick                    print("%s    %-5s = %#16.16x" % (prefix, reg, self.registers[reg]))
147061da546Spatrick
148061da546Spatrick        def dump_symbolicated(self, crash_log, options):
149061da546Spatrick            this_thread_crashed = self.app_specific_backtrace
150061da546Spatrick            if not this_thread_crashed:
151061da546Spatrick                this_thread_crashed = self.did_crash()
152061da546Spatrick                if options.crashed_only and this_thread_crashed == False:
153061da546Spatrick                    return
154061da546Spatrick
155061da546Spatrick            print("%s" % self)
156061da546Spatrick            #prev_frame_index = -1
157061da546Spatrick            display_frame_idx = -1
158061da546Spatrick            for frame_idx, frame in enumerate(self.frames):
159061da546Spatrick                disassemble = (
160061da546Spatrick                    this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth
161061da546Spatrick                if frame_idx == 0:
162061da546Spatrick                    symbolicated_frame_addresses = crash_log.symbolicate(
163061da546Spatrick                        frame.pc & crash_log.addr_mask, options.verbose)
164061da546Spatrick                else:
165061da546Spatrick                    # Any frame above frame zero and we have to subtract one to
166061da546Spatrick                    # get the previous line entry
167061da546Spatrick                    symbolicated_frame_addresses = crash_log.symbolicate(
168061da546Spatrick                        (frame.pc & crash_log.addr_mask) - 1, options.verbose)
169061da546Spatrick
170061da546Spatrick                if symbolicated_frame_addresses:
171061da546Spatrick                    symbolicated_frame_address_idx = 0
172061da546Spatrick                    for symbolicated_frame_address in symbolicated_frame_addresses:
173061da546Spatrick                        display_frame_idx += 1
174061da546Spatrick                        print('[%3u] %s' % (frame_idx, symbolicated_frame_address))
175061da546Spatrick                        if (options.source_all or self.did_crash(
176061da546Spatrick                        )) and display_frame_idx < options.source_frames and options.source_context:
177061da546Spatrick                            source_context = options.source_context
178061da546Spatrick                            line_entry = symbolicated_frame_address.get_symbol_context().line_entry
179061da546Spatrick                            if line_entry.IsValid():
180061da546Spatrick                                strm = lldb.SBStream()
181061da546Spatrick                                if line_entry:
182061da546Spatrick                                    lldb.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
183061da546Spatrick                                        line_entry.file, line_entry.line, source_context, source_context, "->", strm)
184061da546Spatrick                                source_text = strm.GetData()
185061da546Spatrick                                if source_text:
186061da546Spatrick                                    # Indent the source a bit
187061da546Spatrick                                    indent_str = '    '
188061da546Spatrick                                    join_str = '\n' + indent_str
189061da546Spatrick                                    print('%s%s' % (indent_str, join_str.join(source_text.split('\n'))))
190061da546Spatrick                        if symbolicated_frame_address_idx == 0:
191061da546Spatrick                            if disassemble:
192061da546Spatrick                                instructions = symbolicated_frame_address.get_instructions()
193061da546Spatrick                                if instructions:
194061da546Spatrick                                    print()
195061da546Spatrick                                    symbolication.disassemble_instructions(
196061da546Spatrick                                        crash_log.get_target(),
197061da546Spatrick                                        instructions,
198061da546Spatrick                                        frame.pc,
199061da546Spatrick                                        options.disassemble_before,
200061da546Spatrick                                        options.disassemble_after,
201061da546Spatrick                                        frame.index > 0)
202061da546Spatrick                                    print()
203061da546Spatrick                        symbolicated_frame_address_idx += 1
204061da546Spatrick                else:
205061da546Spatrick                    print(frame)
206061da546Spatrick
207061da546Spatrick        def add_ident(self, ident):
208061da546Spatrick            if ident not in self.idents:
209061da546Spatrick                self.idents.append(ident)
210061da546Spatrick
211061da546Spatrick        def did_crash(self):
212061da546Spatrick            return self.reason is not None
213061da546Spatrick
214061da546Spatrick        def __str__(self):
215061da546Spatrick            if self.app_specific_backtrace:
216061da546Spatrick                s = "Application Specific Backtrace[%u]" % self.index
217061da546Spatrick            else:
218061da546Spatrick                s = "Thread[%u]" % self.index
219061da546Spatrick            if self.reason:
220061da546Spatrick                s += ' %s' % self.reason
221061da546Spatrick            return s
222061da546Spatrick
223061da546Spatrick    class Frame:
224061da546Spatrick        """Class that represents a stack frame in a thread in a darwin crash log"""
225061da546Spatrick
226061da546Spatrick        def __init__(self, index, pc, description):
227061da546Spatrick            self.pc = pc
228061da546Spatrick            self.description = description
229061da546Spatrick            self.index = index
230061da546Spatrick
231061da546Spatrick        def __str__(self):
232061da546Spatrick            if self.description:
233061da546Spatrick                return "[%3u] 0x%16.16x %s" % (
234061da546Spatrick                    self.index, self.pc, self.description)
235061da546Spatrick            else:
236061da546Spatrick                return "[%3u] 0x%16.16x" % (self.index, self.pc)
237061da546Spatrick
238061da546Spatrick        def dump(self, prefix):
239061da546Spatrick            print("%s%s" % (prefix, str(self)))
240061da546Spatrick
241061da546Spatrick    class DarwinImage(symbolication.Image):
242061da546Spatrick        """Class that represents a binary images in a darwin crash log"""
243061da546Spatrick        dsymForUUIDBinary = '/usr/local/bin/dsymForUUID'
244061da546Spatrick        if not os.path.exists(dsymForUUIDBinary):
245061da546Spatrick            try:
246061da546Spatrick                dsymForUUIDBinary = subprocess.check_output('which dsymForUUID',
247061da546Spatrick                                                            shell=True).decode("utf-8").rstrip('\n')
248061da546Spatrick            except:
249061da546Spatrick                dsymForUUIDBinary = ""
250061da546Spatrick
251061da546Spatrick        dwarfdump_uuid_regex = re.compile(
252061da546Spatrick            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
253061da546Spatrick
254061da546Spatrick        def __init__(
255061da546Spatrick                self,
256061da546Spatrick                text_addr_lo,
257061da546Spatrick                text_addr_hi,
258061da546Spatrick                identifier,
259061da546Spatrick                version,
260061da546Spatrick                uuid,
261061da546Spatrick                path,
262061da546Spatrick                verbose):
263061da546Spatrick            symbolication.Image.__init__(self, path, uuid)
264061da546Spatrick            self.add_section(
265061da546Spatrick                symbolication.Section(
266061da546Spatrick                    text_addr_lo,
267061da546Spatrick                    text_addr_hi,
268061da546Spatrick                    "__TEXT"))
269061da546Spatrick            self.identifier = identifier
270061da546Spatrick            self.version = version
271061da546Spatrick            self.verbose = verbose
272061da546Spatrick
273061da546Spatrick        def show_symbol_progress(self):
274061da546Spatrick            """
275061da546Spatrick            Hide progress output and errors from system frameworks as they are plentiful.
276061da546Spatrick            """
277061da546Spatrick            if self.verbose:
278061da546Spatrick                return True
279061da546Spatrick            return not (self.path.startswith("/System/Library/") or
280061da546Spatrick                        self.path.startswith("/usr/lib/"))
281061da546Spatrick
282061da546Spatrick
283061da546Spatrick        def find_matching_slice(self):
284061da546Spatrick            dwarfdump_cmd_output = subprocess.check_output(
285061da546Spatrick                'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8")
286061da546Spatrick            self_uuid = self.get_uuid()
287061da546Spatrick            for line in dwarfdump_cmd_output.splitlines():
288061da546Spatrick                match = self.dwarfdump_uuid_regex.search(line)
289061da546Spatrick                if match:
290061da546Spatrick                    dwarf_uuid_str = match.group(1)
291061da546Spatrick                    dwarf_uuid = uuid.UUID(dwarf_uuid_str)
292061da546Spatrick                    if self_uuid == dwarf_uuid:
293061da546Spatrick                        self.resolved_path = self.path
294061da546Spatrick                        self.arch = match.group(2)
295061da546Spatrick                        return True
296061da546Spatrick            if not self.resolved_path:
297061da546Spatrick                self.unavailable = True
298061da546Spatrick                if self.show_symbol_progress():
299061da546Spatrick                    print(("error\n    error: unable to locate '%s' with UUID %s"
300061da546Spatrick                           % (self.path, self.get_normalized_uuid_string())))
301061da546Spatrick                return False
302061da546Spatrick
303061da546Spatrick        def locate_module_and_debug_symbols(self):
304061da546Spatrick            # Don't load a module twice...
305061da546Spatrick            if self.resolved:
306061da546Spatrick                return True
307061da546Spatrick            # Mark this as resolved so we don't keep trying
308061da546Spatrick            self.resolved = True
309061da546Spatrick            uuid_str = self.get_normalized_uuid_string()
310061da546Spatrick            if self.show_symbol_progress():
311061da546Spatrick                print('Getting symbols for %s %s...' % (uuid_str, self.path), end=' ')
312061da546Spatrick            if os.path.exists(self.dsymForUUIDBinary):
313061da546Spatrick                dsym_for_uuid_command = '%s %s' % (
314061da546Spatrick                    self.dsymForUUIDBinary, uuid_str)
315061da546Spatrick                s = subprocess.check_output(dsym_for_uuid_command, shell=True)
316061da546Spatrick                if s:
317061da546Spatrick                    try:
318061da546Spatrick                        plist_root = read_plist(s)
319061da546Spatrick                    except:
320061da546Spatrick                        print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s))
321061da546Spatrick                        raise
322061da546Spatrick                    if plist_root:
323061da546Spatrick                        plist = plist_root[uuid_str]
324061da546Spatrick                        if plist:
325061da546Spatrick                            if 'DBGArchitecture' in plist:
326061da546Spatrick                                self.arch = plist['DBGArchitecture']
327061da546Spatrick                            if 'DBGDSYMPath' in plist:
328061da546Spatrick                                self.symfile = os.path.realpath(
329061da546Spatrick                                    plist['DBGDSYMPath'])
330061da546Spatrick                            if 'DBGSymbolRichExecutable' in plist:
331061da546Spatrick                                self.path = os.path.expanduser(
332061da546Spatrick                                    plist['DBGSymbolRichExecutable'])
333061da546Spatrick                                self.resolved_path = self.path
334061da546Spatrick            if not self.resolved_path and os.path.exists(self.path):
335061da546Spatrick                if not self.find_matching_slice():
336061da546Spatrick                    return False
337061da546Spatrick            if not self.resolved_path and not os.path.exists(self.path):
338061da546Spatrick                try:
339061da546Spatrick                    dsym = subprocess.check_output(
340061da546Spatrick                        ["/usr/bin/mdfind",
341061da546Spatrick                         "com_apple_xcode_dsym_uuids == %s"%uuid_str]).decode("utf-8")[:-1]
342061da546Spatrick                    if dsym and os.path.exists(dsym):
343061da546Spatrick                        print(('falling back to binary inside "%s"'%dsym))
344061da546Spatrick                        self.symfile = dsym
345061da546Spatrick                        dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
346061da546Spatrick                        for filename in os.listdir(dwarf_dir):
347061da546Spatrick                            self.path = os.path.join(dwarf_dir, filename)
348061da546Spatrick                            if not self.find_matching_slice():
349061da546Spatrick                                return False
350061da546Spatrick                            break
351061da546Spatrick                except:
352061da546Spatrick                    pass
353061da546Spatrick            if (self.resolved_path and os.path.exists(self.resolved_path)) or (
354061da546Spatrick                    self.path and os.path.exists(self.path)):
355061da546Spatrick                print('ok')
356061da546Spatrick                return True
357061da546Spatrick            else:
358061da546Spatrick                self.unavailable = True
359061da546Spatrick            return False
360061da546Spatrick
361061da546Spatrick    def __init__(self, path, verbose):
362061da546Spatrick        """CrashLog constructor that take a path to a darwin crash log file"""
363061da546Spatrick        symbolication.Symbolicator.__init__(self)
364061da546Spatrick        self.path = os.path.expanduser(path)
365061da546Spatrick        self.info_lines = list()
366061da546Spatrick        self.system_profile = list()
367061da546Spatrick        self.threads = list()
368061da546Spatrick        self.backtraces = list()  # For application specific backtraces
369061da546Spatrick        self.idents = list()  # A list of the required identifiers for doing all stack backtraces
370061da546Spatrick        self.crashed_thread_idx = -1
371061da546Spatrick        self.version = -1
372061da546Spatrick        self.error = None
373061da546Spatrick        self.target = None
374061da546Spatrick        self.verbose = verbose
375061da546Spatrick        # With possible initial component of ~ or ~user replaced by that user's
376061da546Spatrick        # home directory.
377061da546Spatrick        try:
378061da546Spatrick            f = open(self.path)
379061da546Spatrick        except IOError:
380061da546Spatrick            self.error = 'error: cannot open "%s"' % self.path
381061da546Spatrick            return
382061da546Spatrick
383061da546Spatrick        self.file_lines = f.read().splitlines()
384061da546Spatrick        parse_mode = PARSE_MODE_NORMAL
385061da546Spatrick        thread = None
386061da546Spatrick        app_specific_backtrace = False
387061da546Spatrick        for line in self.file_lines:
388061da546Spatrick            # print line
389061da546Spatrick            line_len = len(line)
390061da546Spatrick            if line_len == 0:
391061da546Spatrick                if thread:
392061da546Spatrick                    if parse_mode == PARSE_MODE_THREAD:
393061da546Spatrick                        if thread.index == self.crashed_thread_idx:
394061da546Spatrick                            thread.reason = ''
395061da546Spatrick                            if self.thread_exception:
396061da546Spatrick                                thread.reason += self.thread_exception
397061da546Spatrick                            if self.thread_exception_data:
398061da546Spatrick                                thread.reason += " (%s)" % self.thread_exception_data
399061da546Spatrick                        if app_specific_backtrace:
400061da546Spatrick                            self.backtraces.append(thread)
401061da546Spatrick                        else:
402061da546Spatrick                            self.threads.append(thread)
403061da546Spatrick                    thread = None
404061da546Spatrick                else:
405061da546Spatrick                    # only append an extra empty line if the previous line
406061da546Spatrick                    # in the info_lines wasn't empty
407061da546Spatrick                    if len(self.info_lines) > 0 and len(self.info_lines[-1]):
408061da546Spatrick                        self.info_lines.append(line)
409061da546Spatrick                parse_mode = PARSE_MODE_NORMAL
410061da546Spatrick                # print 'PARSE_MODE_NORMAL'
411061da546Spatrick            elif parse_mode == PARSE_MODE_NORMAL:
412061da546Spatrick                if line.startswith('Process:'):
413061da546Spatrick                    (self.process_name, pid_with_brackets) = line[
414061da546Spatrick                        8:].strip().split(' [')
415061da546Spatrick                    self.process_id = pid_with_brackets.strip('[]')
416061da546Spatrick                elif line.startswith('Path:'):
417061da546Spatrick                    self.process_path = line[5:].strip()
418061da546Spatrick                elif line.startswith('Identifier:'):
419061da546Spatrick                    self.process_identifier = line[11:].strip()
420061da546Spatrick                elif line.startswith('Version:'):
421061da546Spatrick                    version_string = line[8:].strip()
422061da546Spatrick                    matched_pair = re.search("(.+)\((.+)\)", version_string)
423061da546Spatrick                    if matched_pair:
424061da546Spatrick                        self.process_version = matched_pair.group(1)
425061da546Spatrick                        self.process_compatability_version = matched_pair.group(
426061da546Spatrick                            2)
427061da546Spatrick                    else:
428061da546Spatrick                        self.process = version_string
429061da546Spatrick                        self.process_compatability_version = version_string
430061da546Spatrick                elif self.parent_process_regex.search(line):
431061da546Spatrick                    parent_process_match = self.parent_process_regex.search(
432061da546Spatrick                        line)
433061da546Spatrick                    self.parent_process_name = parent_process_match.group(1)
434061da546Spatrick                    self.parent_process_id = parent_process_match.group(2)
435061da546Spatrick                elif line.startswith('Exception Type:'):
436061da546Spatrick                    self.thread_exception = line[15:].strip()
437061da546Spatrick                    continue
438061da546Spatrick                elif line.startswith('Exception Codes:'):
439061da546Spatrick                    self.thread_exception_data = line[16:].strip()
440061da546Spatrick                    continue
441061da546Spatrick                elif line.startswith('Exception Subtype:'): # iOS
442061da546Spatrick                    self.thread_exception_data = line[18:].strip()
443061da546Spatrick                    continue
444061da546Spatrick                elif line.startswith('Crashed Thread:'):
445061da546Spatrick                    self.crashed_thread_idx = int(line[15:].strip().split()[0])
446061da546Spatrick                    continue
447061da546Spatrick                elif line.startswith('Triggered by Thread:'): # iOS
448061da546Spatrick                    self.crashed_thread_idx = int(line[20:].strip().split()[0])
449061da546Spatrick                    continue
450061da546Spatrick                elif line.startswith('Report Version:'):
451061da546Spatrick                    self.version = int(line[15:].strip())
452061da546Spatrick                    continue
453061da546Spatrick                elif line.startswith('System Profile:'):
454061da546Spatrick                    parse_mode = PARSE_MODE_SYSTEM
455061da546Spatrick                    continue
456061da546Spatrick                elif (line.startswith('Interval Since Last Report:') or
457061da546Spatrick                      line.startswith('Crashes Since Last Report:') or
458061da546Spatrick                      line.startswith('Per-App Interval Since Last Report:') or
459061da546Spatrick                      line.startswith('Per-App Crashes Since Last Report:') or
460061da546Spatrick                      line.startswith('Sleep/Wake UUID:') or
461061da546Spatrick                      line.startswith('Anonymous UUID:')):
462061da546Spatrick                    # ignore these
463061da546Spatrick                    continue
464061da546Spatrick                elif line.startswith('Thread'):
465061da546Spatrick                    thread_state_match = self.thread_state_regex.search(line)
466061da546Spatrick                    if thread_state_match:
467061da546Spatrick                        app_specific_backtrace = False
468061da546Spatrick                        thread_state_match = self.thread_regex.search(line)
469061da546Spatrick                        thread_idx = int(thread_state_match.group(1))
470061da546Spatrick                        parse_mode = PARSE_MODE_THREGS
471061da546Spatrick                        thread = self.threads[thread_idx]
472061da546Spatrick                    else:
473061da546Spatrick                        thread_match = self.thread_regex.search(line)
474061da546Spatrick                        if thread_match:
475061da546Spatrick                            app_specific_backtrace = False
476061da546Spatrick                            parse_mode = PARSE_MODE_THREAD
477061da546Spatrick                            thread_idx = int(thread_match.group(1))
478061da546Spatrick                            thread = CrashLog.Thread(thread_idx, False)
479061da546Spatrick                    continue
480061da546Spatrick                elif line.startswith('Binary Images:'):
481061da546Spatrick                    parse_mode = PARSE_MODE_IMAGES
482061da546Spatrick                    continue
483061da546Spatrick                elif line.startswith('Application Specific Backtrace'):
484061da546Spatrick                    app_backtrace_match = self.app_backtrace_regex.search(line)
485061da546Spatrick                    if app_backtrace_match:
486061da546Spatrick                        parse_mode = PARSE_MODE_THREAD
487061da546Spatrick                        app_specific_backtrace = True
488061da546Spatrick                        idx = int(app_backtrace_match.group(1))
489061da546Spatrick                        thread = CrashLog.Thread(idx, True)
490061da546Spatrick                elif line.startswith('Last Exception Backtrace:'): # iOS
491061da546Spatrick                    parse_mode = PARSE_MODE_THREAD
492061da546Spatrick                    app_specific_backtrace = True
493061da546Spatrick                    idx = 1
494061da546Spatrick                    thread = CrashLog.Thread(idx, True)
495061da546Spatrick                self.info_lines.append(line.strip())
496061da546Spatrick            elif parse_mode == PARSE_MODE_THREAD:
497061da546Spatrick                if line.startswith('Thread'):
498061da546Spatrick                    continue
499061da546Spatrick                if self.null_frame_regex.search(line):
500061da546Spatrick                    print('warning: thread parser ignored null-frame: "%s"' % line)
501061da546Spatrick                    continue
502061da546Spatrick                frame_match = self.frame_regex.search(line)
503061da546Spatrick                if frame_match:
504061da546Spatrick                    (frame_id, frame_img_name, _, frame_img_version, _,
505061da546Spatrick                     frame_addr, frame_ofs) = frame_match.groups()
506061da546Spatrick                    ident = frame_img_name
507061da546Spatrick                    thread.add_ident(ident)
508061da546Spatrick                    if ident not in self.idents:
509061da546Spatrick                        self.idents.append(ident)
510061da546Spatrick                    thread.frames.append(CrashLog.Frame(int(frame_id), int(
511061da546Spatrick                        frame_addr, 0), frame_ofs))
512061da546Spatrick                else:
513061da546Spatrick                    print('error: frame regex failed for line: "%s"' % line)
514061da546Spatrick            elif parse_mode == PARSE_MODE_IMAGES:
515061da546Spatrick                image_match = self.image_regex_uuid.search(line)
516061da546Spatrick                if image_match:
517061da546Spatrick                    (img_lo, img_hi, img_name, _, img_version, _,
518061da546Spatrick                     _, img_uuid, img_path) = image_match.groups()
519061da546Spatrick                    image = CrashLog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
520061da546Spatrick                                                 img_name.strip(),
521061da546Spatrick                                                 img_version.strip()
522061da546Spatrick                                                 if img_version else "",
523061da546Spatrick                                                 uuid.UUID(img_uuid), img_path,
524061da546Spatrick                                                 self.verbose)
525061da546Spatrick                    self.images.append(image)
526061da546Spatrick                else:
527061da546Spatrick                    print("error: image regex failed for: %s" % line)
528061da546Spatrick
529061da546Spatrick            elif parse_mode == PARSE_MODE_THREGS:
530061da546Spatrick                stripped_line = line.strip()
531061da546Spatrick                # "r12: 0x00007fff6b5939c8  r13: 0x0000000007000006  r14: 0x0000000000002a03  r15: 0x0000000000000c00"
532061da546Spatrick                reg_values = re.findall(
533061da546Spatrick                    '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line)
534061da546Spatrick                for reg_value in reg_values:
535061da546Spatrick                    # print 'reg_value = "%s"' % reg_value
536061da546Spatrick                    (reg, value) = reg_value.split(': ')
537061da546Spatrick                    # print 'reg = "%s"' % reg
538061da546Spatrick                    # print 'value = "%s"' % value
539061da546Spatrick                    thread.registers[reg.strip()] = int(value, 0)
540061da546Spatrick            elif parse_mode == PARSE_MODE_SYSTEM:
541061da546Spatrick                self.system_profile.append(line)
542061da546Spatrick        f.close()
543061da546Spatrick
544061da546Spatrick    def dump(self):
545061da546Spatrick        print("Crash Log File: %s" % (self.path))
546061da546Spatrick        if self.backtraces:
547061da546Spatrick            print("\nApplication Specific Backtraces:")
548061da546Spatrick            for thread in self.backtraces:
549061da546Spatrick                thread.dump('  ')
550061da546Spatrick        print("\nThreads:")
551061da546Spatrick        for thread in self.threads:
552061da546Spatrick            thread.dump('  ')
553061da546Spatrick        print("\nImages:")
554061da546Spatrick        for image in self.images:
555061da546Spatrick            image.dump('  ')
556061da546Spatrick
557061da546Spatrick    def find_image_with_identifier(self, identifier):
558061da546Spatrick        for image in self.images:
559061da546Spatrick            if image.identifier == identifier:
560061da546Spatrick                return image
561061da546Spatrick        regex_text = '^.*\.%s$' % (re.escape(identifier))
562061da546Spatrick        regex = re.compile(regex_text)
563061da546Spatrick        for image in self.images:
564061da546Spatrick            if regex.match(image.identifier):
565061da546Spatrick                return image
566061da546Spatrick        return None
567061da546Spatrick
568061da546Spatrick    def create_target(self):
569061da546Spatrick        # print 'crashlog.create_target()...'
570061da546Spatrick        if self.target is None:
571061da546Spatrick            self.target = symbolication.Symbolicator.create_target(self)
572061da546Spatrick            if self.target:
573061da546Spatrick                return self.target
574061da546Spatrick            # We weren't able to open the main executable as, but we can still
575061da546Spatrick            # symbolicate
576061da546Spatrick            print('crashlog.create_target()...2')
577061da546Spatrick            if self.idents:
578061da546Spatrick                for ident in self.idents:
579061da546Spatrick                    image = self.find_image_with_identifier(ident)
580061da546Spatrick                    if image:
581061da546Spatrick                        self.target = image.create_target()
582061da546Spatrick                        if self.target:
583061da546Spatrick                            return self.target  # success
584061da546Spatrick            print('crashlog.create_target()...3')
585061da546Spatrick            for image in self.images:
586061da546Spatrick                self.target = image.create_target()
587061da546Spatrick                if self.target:
588061da546Spatrick                    return self.target  # success
589061da546Spatrick            print('crashlog.create_target()...4')
590061da546Spatrick            print('error: Unable to locate any executables from the crash log.')
591061da546Spatrick            print('       Try loading the executable into lldb before running crashlog')
592061da546Spatrick            print('       and/or make sure the .dSYM bundles can be found by Spotlight.')
593061da546Spatrick        return self.target
594061da546Spatrick
595061da546Spatrick    def get_target(self):
596061da546Spatrick        return self.target
597061da546Spatrick
598061da546Spatrick
599061da546Spatrickdef usage():
600061da546Spatrick    print("Usage: lldb-symbolicate.py [-n name] executable-image")
601061da546Spatrick    sys.exit(0)
602061da546Spatrick
603061da546Spatrick
604061da546Spatrickclass Interactive(cmd.Cmd):
605061da546Spatrick    '''Interactive prompt for analyzing one or more Darwin crash logs, type "help" to see a list of supported commands.'''
606061da546Spatrick    image_option_parser = None
607061da546Spatrick
608061da546Spatrick    def __init__(self, crash_logs):
609061da546Spatrick        cmd.Cmd.__init__(self)
610061da546Spatrick        self.use_rawinput = False
611061da546Spatrick        self.intro = 'Interactive crashlogs prompt, type "help" to see a list of supported commands.'
612061da546Spatrick        self.crash_logs = crash_logs
613061da546Spatrick        self.prompt = '% '
614061da546Spatrick
615061da546Spatrick    def default(self, line):
616061da546Spatrick        '''Catch all for unknown command, which will exit the interpreter.'''
617061da546Spatrick        print("uknown command: %s" % line)
618061da546Spatrick        return True
619061da546Spatrick
620061da546Spatrick    def do_q(self, line):
621061da546Spatrick        '''Quit command'''
622061da546Spatrick        return True
623061da546Spatrick
624061da546Spatrick    def do_quit(self, line):
625061da546Spatrick        '''Quit command'''
626061da546Spatrick        return True
627061da546Spatrick
628061da546Spatrick    def do_symbolicate(self, line):
629061da546Spatrick        description = '''Symbolicate one or more darwin crash log files by index to provide source file and line information,
630061da546Spatrick        inlined stack frames back to the concrete functions, and disassemble the location of the crash
631061da546Spatrick        for the first frame of the crashed thread.'''
632061da546Spatrick        option_parser = CreateSymbolicateCrashLogOptions(
633061da546Spatrick            'symbolicate', description, False)
634061da546Spatrick        command_args = shlex.split(line)
635061da546Spatrick        try:
636061da546Spatrick            (options, args) = option_parser.parse_args(command_args)
637061da546Spatrick        except:
638061da546Spatrick            return
639061da546Spatrick
640061da546Spatrick        if args:
641061da546Spatrick            # We have arguments, they must valid be crash log file indexes
642061da546Spatrick            for idx_str in args:
643061da546Spatrick                idx = int(idx_str)
644061da546Spatrick                if idx < len(self.crash_logs):
645061da546Spatrick                    SymbolicateCrashLog(self.crash_logs[idx], options)
646061da546Spatrick                else:
647061da546Spatrick                    print('error: crash log index %u is out of range' % (idx))
648061da546Spatrick        else:
649061da546Spatrick            # No arguments, symbolicate all crash logs using the options
650061da546Spatrick            # provided
651061da546Spatrick            for idx in range(len(self.crash_logs)):
652061da546Spatrick                SymbolicateCrashLog(self.crash_logs[idx], options)
653061da546Spatrick
654061da546Spatrick    def do_list(self, line=None):
655061da546Spatrick        '''Dump a list of all crash logs that are currently loaded.
656061da546Spatrick
657061da546Spatrick        USAGE: list'''
658061da546Spatrick        print('%u crash logs are loaded:' % len(self.crash_logs))
659061da546Spatrick        for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
660061da546Spatrick            print('[%u] = %s' % (crash_log_idx, crash_log.path))
661061da546Spatrick
662061da546Spatrick    def do_image(self, line):
663061da546Spatrick        '''Dump information about one or more binary images in the crash log given an image basename, or all images if no arguments are provided.'''
664061da546Spatrick        usage = "usage: %prog [options] <PATH> [PATH ...]"
665061da546Spatrick        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.'''
666061da546Spatrick        command_args = shlex.split(line)
667061da546Spatrick        if not self.image_option_parser:
668061da546Spatrick            self.image_option_parser = optparse.OptionParser(
669061da546Spatrick                description=description, prog='image', usage=usage)
670061da546Spatrick            self.image_option_parser.add_option(
671061da546Spatrick                '-a',
672061da546Spatrick                '--all',
673061da546Spatrick                action='store_true',
674061da546Spatrick                help='show all images',
675061da546Spatrick                default=False)
676061da546Spatrick        try:
677061da546Spatrick            (options, args) = self.image_option_parser.parse_args(command_args)
678061da546Spatrick        except:
679061da546Spatrick            return
680061da546Spatrick
681061da546Spatrick        if args:
682061da546Spatrick            for image_path in args:
683061da546Spatrick                fullpath_search = image_path[0] == '/'
684061da546Spatrick                for (crash_log_idx, crash_log) in enumerate(self.crash_logs):
685061da546Spatrick                    matches_found = 0
686061da546Spatrick                    for (image_idx, image) in enumerate(crash_log.images):
687061da546Spatrick                        if fullpath_search:
688061da546Spatrick                            if image.get_resolved_path() == image_path:
689061da546Spatrick                                matches_found += 1
690061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
691061da546Spatrick                        else:
692061da546Spatrick                            image_basename = image.get_resolved_path_basename()
693061da546Spatrick                            if image_basename == image_path:
694061da546Spatrick                                matches_found += 1
695061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
696061da546Spatrick                    if matches_found == 0:
697061da546Spatrick                        for (image_idx, image) in enumerate(crash_log.images):
698061da546Spatrick                            resolved_image_path = image.get_resolved_path()
699061da546Spatrick                            if resolved_image_path and string.find(
700061da546Spatrick                                    image.get_resolved_path(), image_path) >= 0:
701061da546Spatrick                                print('[%u] ' % (crash_log_idx), image)
702061da546Spatrick        else:
703061da546Spatrick            for crash_log in self.crash_logs:
704061da546Spatrick                for (image_idx, image) in enumerate(crash_log.images):
705061da546Spatrick                    print('[%u] %s' % (image_idx, image))
706061da546Spatrick        return False
707061da546Spatrick
708061da546Spatrick
709061da546Spatrickdef interactive_crashlogs(options, args):
710061da546Spatrick    crash_log_files = list()
711061da546Spatrick    for arg in args:
712061da546Spatrick        for resolved_path in glob.glob(arg):
713061da546Spatrick            crash_log_files.append(resolved_path)
714061da546Spatrick
715061da546Spatrick    crash_logs = list()
716061da546Spatrick    for crash_log_file in crash_log_files:
717061da546Spatrick        # print 'crash_log_file = "%s"' % crash_log_file
718061da546Spatrick        crash_log = CrashLog(crash_log_file, options.verbose)
719061da546Spatrick        if crash_log.error:
720061da546Spatrick            print(crash_log.error)
721061da546Spatrick            continue
722061da546Spatrick        if options.debug:
723061da546Spatrick            crash_log.dump()
724061da546Spatrick        if not crash_log.images:
725061da546Spatrick            print('error: no images in crash log "%s"' % (crash_log))
726061da546Spatrick            continue
727061da546Spatrick        else:
728061da546Spatrick            crash_logs.append(crash_log)
729061da546Spatrick
730061da546Spatrick    interpreter = Interactive(crash_logs)
731061da546Spatrick    # List all crash logs that were imported
732061da546Spatrick    interpreter.do_list()
733061da546Spatrick    interpreter.cmdloop()
734061da546Spatrick
735061da546Spatrick
736061da546Spatrickdef save_crashlog(debugger, command, exe_ctx, result, dict):
737061da546Spatrick    usage = "usage: %prog [options] <output-path>"
738061da546Spatrick    description = '''Export the state of current target into a crashlog file'''
739061da546Spatrick    parser = optparse.OptionParser(
740061da546Spatrick        description=description,
741061da546Spatrick        prog='save_crashlog',
742061da546Spatrick        usage=usage)
743061da546Spatrick    parser.add_option(
744061da546Spatrick        '-v',
745061da546Spatrick        '--verbose',
746061da546Spatrick        action='store_true',
747061da546Spatrick        dest='verbose',
748061da546Spatrick        help='display verbose debug info',
749061da546Spatrick        default=False)
750061da546Spatrick    try:
751061da546Spatrick        (options, args) = parser.parse_args(shlex.split(command))
752061da546Spatrick    except:
753061da546Spatrick        result.PutCString("error: invalid options")
754061da546Spatrick        return
755061da546Spatrick    if len(args) != 1:
756061da546Spatrick        result.PutCString(
757061da546Spatrick            "error: invalid arguments, a single output file is the only valid argument")
758061da546Spatrick        return
759061da546Spatrick    out_file = open(args[0], 'w')
760061da546Spatrick    if not out_file:
761061da546Spatrick        result.PutCString(
762061da546Spatrick            "error: failed to open file '%s' for writing...",
763061da546Spatrick            args[0])
764061da546Spatrick        return
765061da546Spatrick    target = exe_ctx.target
766061da546Spatrick    if target:
767061da546Spatrick        identifier = target.executable.basename
768061da546Spatrick        process = exe_ctx.process
769061da546Spatrick        if process:
770061da546Spatrick            pid = process.id
771061da546Spatrick            if pid != lldb.LLDB_INVALID_PROCESS_ID:
772061da546Spatrick                out_file.write(
773061da546Spatrick                    'Process:         %s [%u]\n' %
774061da546Spatrick                    (identifier, pid))
775061da546Spatrick        out_file.write('Path:            %s\n' % (target.executable.fullpath))
776061da546Spatrick        out_file.write('Identifier:      %s\n' % (identifier))
777061da546Spatrick        out_file.write('\nDate/Time:       %s\n' %
778061da546Spatrick                       (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
779061da546Spatrick        out_file.write(
780061da546Spatrick            'OS Version:      Mac OS X %s (%s)\n' %
781061da546Spatrick            (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8")))
782061da546Spatrick        out_file.write('Report Version:  9\n')
783061da546Spatrick        for thread_idx in range(process.num_threads):
784061da546Spatrick            thread = process.thread[thread_idx]
785061da546Spatrick            out_file.write('\nThread %u:\n' % (thread_idx))
786061da546Spatrick            for (frame_idx, frame) in enumerate(thread.frames):
787061da546Spatrick                frame_pc = frame.pc
788061da546Spatrick                frame_offset = 0
789061da546Spatrick                if frame.function:
790061da546Spatrick                    block = frame.GetFrameBlock()
791061da546Spatrick                    block_range = block.range[frame.addr]
792061da546Spatrick                    if block_range:
793061da546Spatrick                        block_start_addr = block_range[0]
794*dda28197Spatrick                        frame_offset = frame_pc - block_start_addr.GetLoadAddress(target)
795061da546Spatrick                    else:
796*dda28197Spatrick                        frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target)
797061da546Spatrick                elif frame.symbol:
798*dda28197Spatrick                    frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
799061da546Spatrick                out_file.write(
800061da546Spatrick                    '%-3u %-32s 0x%16.16x %s' %
801061da546Spatrick                    (frame_idx, frame.module.file.basename, frame_pc, frame.name))
802061da546Spatrick                if frame_offset > 0:
803061da546Spatrick                    out_file.write(' + %u' % (frame_offset))
804061da546Spatrick                line_entry = frame.line_entry
805061da546Spatrick                if line_entry:
806061da546Spatrick                    if options.verbose:
807061da546Spatrick                        # This will output the fullpath + line + column
808061da546Spatrick                        out_file.write(' %s' % (line_entry))
809061da546Spatrick                    else:
810061da546Spatrick                        out_file.write(
811061da546Spatrick                            ' %s:%u' %
812061da546Spatrick                            (line_entry.file.basename, line_entry.line))
813061da546Spatrick                        column = line_entry.column
814061da546Spatrick                        if column:
815061da546Spatrick                            out_file.write(':%u' % (column))
816061da546Spatrick                out_file.write('\n')
817061da546Spatrick
818061da546Spatrick        out_file.write('\nBinary Images:\n')
819061da546Spatrick        for module in target.modules:
820061da546Spatrick            text_segment = module.section['__TEXT']
821061da546Spatrick            if text_segment:
822061da546Spatrick                text_segment_load_addr = text_segment.GetLoadAddress(target)
823061da546Spatrick                if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
824061da546Spatrick                    text_segment_end_load_addr = text_segment_load_addr + text_segment.size
825061da546Spatrick                    identifier = module.file.basename
826061da546Spatrick                    module_version = '???'
827061da546Spatrick                    module_version_array = module.GetVersion()
828061da546Spatrick                    if module_version_array:
829061da546Spatrick                        module_version = '.'.join(
830061da546Spatrick                            map(str, module_version_array))
831061da546Spatrick                    out_file.write(
832061da546Spatrick                        '    0x%16.16x - 0x%16.16x  %s (%s - ???) <%s> %s\n' %
833061da546Spatrick                        (text_segment_load_addr,
834061da546Spatrick                         text_segment_end_load_addr,
835061da546Spatrick                         identifier,
836061da546Spatrick                         module_version,
837061da546Spatrick                         module.GetUUIDString(),
838061da546Spatrick                         module.file.fullpath))
839061da546Spatrick        out_file.close()
840061da546Spatrick    else:
841061da546Spatrick        result.PutCString("error: invalid target")
842061da546Spatrick
843061da546Spatrick
844061da546Spatrickdef Symbolicate(debugger, command, result, dict):
845061da546Spatrick    try:
846061da546Spatrick        SymbolicateCrashLogs(shlex.split(command))
847061da546Spatrick    except:
848061da546Spatrick        result.PutCString("error: python exception %s" % sys.exc_info()[0])
849061da546Spatrick
850061da546Spatrick
851061da546Spatrickdef SymbolicateCrashLog(crash_log, options):
852061da546Spatrick    if crash_log.error:
853061da546Spatrick        print(crash_log.error)
854061da546Spatrick        return
855061da546Spatrick    if options.debug:
856061da546Spatrick        crash_log.dump()
857061da546Spatrick    if not crash_log.images:
858061da546Spatrick        print('error: no images in crash log')
859061da546Spatrick        return
860061da546Spatrick
861061da546Spatrick    if options.dump_image_list:
862061da546Spatrick        print("Binary Images:")
863061da546Spatrick        for image in crash_log.images:
864061da546Spatrick            if options.verbose:
865061da546Spatrick                print(image.debug_dump())
866061da546Spatrick            else:
867061da546Spatrick                print(image)
868061da546Spatrick
869061da546Spatrick    target = crash_log.create_target()
870061da546Spatrick    if not target:
871061da546Spatrick        return
872061da546Spatrick    exe_module = target.GetModuleAtIndex(0)
873061da546Spatrick    images_to_load = list()
874061da546Spatrick    loaded_images = list()
875061da546Spatrick    if options.load_all_images:
876061da546Spatrick        # --load-all option was specified, load everything up
877061da546Spatrick        for image in crash_log.images:
878061da546Spatrick            images_to_load.append(image)
879061da546Spatrick    else:
880061da546Spatrick        # Only load the images found in stack frames for the crashed threads
881061da546Spatrick        if options.crashed_only:
882061da546Spatrick            for thread in crash_log.threads:
883061da546Spatrick                if thread.did_crash():
884061da546Spatrick                    for ident in thread.idents:
885061da546Spatrick                        images = crash_log.find_images_with_identifier(ident)
886061da546Spatrick                        if images:
887061da546Spatrick                            for image in images:
888061da546Spatrick                                images_to_load.append(image)
889061da546Spatrick                        else:
890061da546Spatrick                            print('error: can\'t find image for identifier "%s"' % ident)
891061da546Spatrick        else:
892061da546Spatrick            for ident in crash_log.idents:
893061da546Spatrick                images = crash_log.find_images_with_identifier(ident)
894061da546Spatrick                if images:
895061da546Spatrick                    for image in images:
896061da546Spatrick                        images_to_load.append(image)
897061da546Spatrick                else:
898061da546Spatrick                    print('error: can\'t find image for identifier "%s"' % ident)
899061da546Spatrick
900061da546Spatrick    for image in images_to_load:
901061da546Spatrick        if image not in loaded_images:
902061da546Spatrick            err = image.add_module(target)
903061da546Spatrick            if err:
904061da546Spatrick                print(err)
905061da546Spatrick            else:
906061da546Spatrick                # print 'loaded %s' % image
907061da546Spatrick                loaded_images.append(image)
908061da546Spatrick
909061da546Spatrick    if crash_log.backtraces:
910061da546Spatrick        for thread in crash_log.backtraces:
911061da546Spatrick            thread.dump_symbolicated(crash_log, options)
912061da546Spatrick            print()
913061da546Spatrick
914061da546Spatrick    for thread in crash_log.threads:
915061da546Spatrick        thread.dump_symbolicated(crash_log, options)
916061da546Spatrick        print()
917061da546Spatrick
918061da546Spatrick
919061da546Spatrickdef CreateSymbolicateCrashLogOptions(
920061da546Spatrick        command_name,
921061da546Spatrick        description,
922061da546Spatrick        add_interactive_options):
923061da546Spatrick    usage = "usage: %prog [options] <FILE> [FILE ...]"
924061da546Spatrick    option_parser = optparse.OptionParser(
925061da546Spatrick        description=description, prog='crashlog', usage=usage)
926061da546Spatrick    option_parser.add_option(
927061da546Spatrick        '--verbose',
928061da546Spatrick        '-v',
929061da546Spatrick        action='store_true',
930061da546Spatrick        dest='verbose',
931061da546Spatrick        help='display verbose debug info',
932061da546Spatrick        default=False)
933061da546Spatrick    option_parser.add_option(
934061da546Spatrick        '--debug',
935061da546Spatrick        '-g',
936061da546Spatrick        action='store_true',
937061da546Spatrick        dest='debug',
938061da546Spatrick        help='display verbose debug logging',
939061da546Spatrick        default=False)
940061da546Spatrick    option_parser.add_option(
941061da546Spatrick        '--load-all',
942061da546Spatrick        '-a',
943061da546Spatrick        action='store_true',
944061da546Spatrick        dest='load_all_images',
945061da546Spatrick        help='load all executable images, not just the images found in the crashed stack frames',
946061da546Spatrick        default=False)
947061da546Spatrick    option_parser.add_option(
948061da546Spatrick        '--images',
949061da546Spatrick        action='store_true',
950061da546Spatrick        dest='dump_image_list',
951061da546Spatrick        help='show image list',
952061da546Spatrick        default=False)
953061da546Spatrick    option_parser.add_option(
954061da546Spatrick        '--debug-delay',
955061da546Spatrick        type='int',
956061da546Spatrick        dest='debug_delay',
957061da546Spatrick        metavar='NSEC',
958061da546Spatrick        help='pause for NSEC seconds for debugger',
959061da546Spatrick        default=0)
960061da546Spatrick    option_parser.add_option(
961061da546Spatrick        '--crashed-only',
962061da546Spatrick        '-c',
963061da546Spatrick        action='store_true',
964061da546Spatrick        dest='crashed_only',
965061da546Spatrick        help='only symbolicate the crashed thread',
966061da546Spatrick        default=False)
967061da546Spatrick    option_parser.add_option(
968061da546Spatrick        '--disasm-depth',
969061da546Spatrick        '-d',
970061da546Spatrick        type='int',
971061da546Spatrick        dest='disassemble_depth',
972061da546Spatrick        help='set the depth in stack frames that should be disassembled (default is 1)',
973061da546Spatrick        default=1)
974061da546Spatrick    option_parser.add_option(
975061da546Spatrick        '--disasm-all',
976061da546Spatrick        '-D',
977061da546Spatrick        action='store_true',
978061da546Spatrick        dest='disassemble_all_threads',
979061da546Spatrick        help='enabled disassembly of frames on all threads (not just the crashed thread)',
980061da546Spatrick        default=False)
981061da546Spatrick    option_parser.add_option(
982061da546Spatrick        '--disasm-before',
983061da546Spatrick        '-B',
984061da546Spatrick        type='int',
985061da546Spatrick        dest='disassemble_before',
986061da546Spatrick        help='the number of instructions to disassemble before the frame PC',
987061da546Spatrick        default=4)
988061da546Spatrick    option_parser.add_option(
989061da546Spatrick        '--disasm-after',
990061da546Spatrick        '-A',
991061da546Spatrick        type='int',
992061da546Spatrick        dest='disassemble_after',
993061da546Spatrick        help='the number of instructions to disassemble after the frame PC',
994061da546Spatrick        default=4)
995061da546Spatrick    option_parser.add_option(
996061da546Spatrick        '--source-context',
997061da546Spatrick        '-C',
998061da546Spatrick        type='int',
999061da546Spatrick        metavar='NLINES',
1000061da546Spatrick        dest='source_context',
1001061da546Spatrick        help='show NLINES source lines of source context (default = 4)',
1002061da546Spatrick        default=4)
1003061da546Spatrick    option_parser.add_option(
1004061da546Spatrick        '--source-frames',
1005061da546Spatrick        type='int',
1006061da546Spatrick        metavar='NFRAMES',
1007061da546Spatrick        dest='source_frames',
1008061da546Spatrick        help='show source for NFRAMES (default = 4)',
1009061da546Spatrick        default=4)
1010061da546Spatrick    option_parser.add_option(
1011061da546Spatrick        '--source-all',
1012061da546Spatrick        action='store_true',
1013061da546Spatrick        dest='source_all',
1014061da546Spatrick        help='show source for all threads, not just the crashed thread',
1015061da546Spatrick        default=False)
1016061da546Spatrick    if add_interactive_options:
1017061da546Spatrick        option_parser.add_option(
1018061da546Spatrick            '-i',
1019061da546Spatrick            '--interactive',
1020061da546Spatrick            action='store_true',
1021061da546Spatrick            help='parse all crash logs and enter interactive mode',
1022061da546Spatrick            default=False)
1023061da546Spatrick    return option_parser
1024061da546Spatrick
1025061da546Spatrick
1026061da546Spatrickdef SymbolicateCrashLogs(command_args):
1027061da546Spatrick    description = '''Symbolicate one or more darwin crash log files to provide source file and line information,
1028061da546Spatrickinlined stack frames back to the concrete functions, and disassemble the location of the crash
1029061da546Spatrickfor the first frame of the crashed thread.
1030061da546SpatrickIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1031061da546Spatrickfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1032061da546Spatrickcreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1033061da546Spatrickyou to explore the program as if it were stopped at the locations described in the crash log and functions can
1034061da546Spatrickbe disassembled and lookups can be performed using the addresses found in the crash log.'''
1035061da546Spatrick    option_parser = CreateSymbolicateCrashLogOptions(
1036061da546Spatrick        'crashlog', description, True)
1037061da546Spatrick    try:
1038061da546Spatrick        (options, args) = option_parser.parse_args(command_args)
1039061da546Spatrick    except:
1040061da546Spatrick        return
1041061da546Spatrick
1042061da546Spatrick    if options.debug:
1043061da546Spatrick        print('command_args = %s' % command_args)
1044061da546Spatrick        print('options', options)
1045061da546Spatrick        print('args', args)
1046061da546Spatrick
1047061da546Spatrick    if options.debug_delay > 0:
1048061da546Spatrick        print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1049061da546Spatrick        time.sleep(options.debug_delay)
1050061da546Spatrick    error = lldb.SBError()
1051061da546Spatrick
1052061da546Spatrick    if args:
1053061da546Spatrick        if options.interactive:
1054061da546Spatrick            interactive_crashlogs(options, args)
1055061da546Spatrick        else:
1056061da546Spatrick            for crash_log_file in args:
1057061da546Spatrick                crash_log = CrashLog(crash_log_file, options.verbose)
1058061da546Spatrick                SymbolicateCrashLog(crash_log, options)
1059061da546Spatrickif __name__ == '__main__':
1060061da546Spatrick    # Create a new debugger instance
1061061da546Spatrick    lldb.debugger = lldb.SBDebugger.Create()
1062061da546Spatrick    SymbolicateCrashLogs(sys.argv[1:])
1063061da546Spatrick    lldb.SBDebugger.Destroy(lldb.debugger)
1064061da546Spatrickelif getattr(lldb, 'debugger', None):
1065061da546Spatrick    lldb.debugger.HandleCommand(
1066061da546Spatrick        'command script add -f lldb.macosx.crashlog.Symbolicate crashlog')
1067061da546Spatrick    lldb.debugger.HandleCommand(
1068061da546Spatrick        'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
1069061da546Spatrick    print('"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help')
1070