1#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# Be sure to add the python path that points to the LLDB shared library.
5#
6# To use this in the embedded python interpreter using "lldb":
7#
8#   cd /path/containing/crashlog.py
9#   lldb
10#   (lldb) script import crashlog
11#   "crashlog" command installed, type "crashlog --help" for detailed help
12#   (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
13#
14# The benefit of running the crashlog command inside lldb in the
15# embedded python interpreter is when the command completes, there
16# will be a target with all of the files loaded at the locations
17# described in the crash log. Only the files that have stack frames
18# in the backtrace will be loaded unless the "--load-all" option
19# has been specified. This allows users to explore the program in the
20# state it was in right at crash time.
21#
22# On MacOSX csh, tcsh:
23#   ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
24#
25# On MacOSX sh, bash:
26#   PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
27#----------------------------------------------------------------------
28
29import lldb
30import commands
31import optparse
32import os
33import plistlib
34#import pprint # pp = pprint.PrettyPrinter(indent=4); pp.pprint(command_args)
35import re
36import shlex
37import sys
38import time
39import uuid
40
41
42PARSE_MODE_NORMAL = 0
43PARSE_MODE_THREAD = 1
44PARSE_MODE_IMAGES = 2
45PARSE_MODE_THREGS = 3
46PARSE_MODE_SYSTEM = 4
47
48class CrashLog:
49    """Class that does parses darwin crash logs"""
50    thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
51    thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
52    frame_regex = re.compile('^([0-9]+) +([^ ]+) *\t(0x[0-9a-fA-F]+) +(.*)')
53    image_regex_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^<]+)<([-0-9a-fA-F]+)> (.*)');
54    image_regex_no_uuid = re.compile('(0x[0-9a-fA-F]+)[- ]+(0x[0-9a-fA-F]+) +[+]?([^ ]+) +([^/]+)/(.*)');
55    empty_line_regex = re.compile('^$')
56
57    class Thread:
58        """Class that represents a thread in a darwin crash log"""
59        def __init__(self, index):
60            self.index = index
61            self.frames = list()
62            self.registers = dict()
63            self.reason = None
64            self.queue = None
65
66        def dump(self, prefix):
67            print "%sThread[%u] %s" % (prefix, self.index, self.reason)
68            if self.frames:
69                print "%s  Frames:" % (prefix)
70                for frame in self.frames:
71                    frame.dump(prefix + '    ')
72            if self.registers:
73                print "%s  Registers:" % (prefix)
74                for reg in self.registers.keys():
75                    print "%s    %-5s = %#16.16x" % (prefix, reg, self.registers[reg])
76
77        def did_crash(self):
78            return self.reason != None
79
80        def __str__(self):
81            s = "Thread[%u]" % self.index
82            if self.reason:
83                s += ' %s' % self.reason
84            return s
85
86
87    class Frame:
88        """Class that represents a stack frame in a thread in a darwin crash log"""
89        def __init__(self, index, pc, details):
90            self.index = index
91            self.pc = pc
92            self.sym_ctx = None
93            self.details = details
94
95        def __str__(self):
96            return "[%2u] %#16.16x %s" % (self.index, self.pc, self.details)
97
98        def dump(self, prefix):
99            print "%s%s" % (prefix, self)
100
101
102    class Image:
103        """Class that represents a binary images in a darwin crash log"""
104        dsymForUUIDBinary = os.path.expanduser('~rc/bin/dsymForUUID')
105        if not os.path.exists(dsymForUUIDBinary):
106            dsymForUUIDBinary = commands.getoutput('which dsymForUUID')
107
108        dwarfdump_uuid_regex = re.compile('UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
109
110        def __init__(self, text_addr_lo, text_addr_hi, ident, version, uuid, path):
111            self.text_addr_lo = text_addr_lo
112            self.text_addr_hi = text_addr_hi
113            self.ident = ident
114            self.version = version
115            self.arch = None
116            self.uuid = uuid
117            self.path = path
118            self.resolved_path = None
119            self.dsym = None
120            self.module = None
121
122        def dump(self, prefix):
123            print "%s%s" % (prefix, self)
124
125        def __str__(self):
126            return "%#16.16x %s %s" % (self.text_addr_lo, self.uuid, self.get_resolved_path())
127
128        def get_resolved_path(self):
129            if self.resolved_path:
130                return self.resolved_path
131            elif self.path:
132                return self.path
133            return None
134
135        def get_resolved_path_basename(self):
136            path = self.get_resolved_path()
137            if path:
138                return os.path.basename(path)
139            return None
140
141        def dsym_basename(self):
142            if self.dsym:
143                return os.path.basename(self.dsym)
144            return None
145
146        def fetch_symboled_executable_and_dsym(self):
147            if self.resolved_path:
148                # Don't load a module twice...
149                return 0
150            print 'Locating %s %s...' % (self.uuid, self.path),
151            if os.path.exists(self.dsymForUUIDBinary):
152                dsym_for_uuid_command = '%s %s' % (self.dsymForUUIDBinary, self.uuid)
153                s = commands.getoutput(dsym_for_uuid_command)
154                if s:
155                    plist_root = plistlib.readPlistFromString (s)
156                    if plist_root:
157                        plist = plist_root[self.uuid]
158                        if plist:
159                            if 'DBGArchitecture' in plist:
160                                self.arch = plist['DBGArchitecture']
161                            if 'DBGDSYMPath' in plist:
162                                self.dsym = os.path.realpath(plist['DBGDSYMPath'])
163                            if 'DBGSymbolRichExecutable' in plist:
164                                self.resolved_path = os.path.expanduser (plist['DBGSymbolRichExecutable'])
165            if not self.resolved_path and os.path.exists(self.path):
166                dwarfdump_cmd_output = commands.getoutput('dwarfdump --uuid "%s"' % self.path)
167                self_uuid = uuid.UUID(self.uuid)
168                for line in dwarfdump_cmd_output.splitlines():
169                    match = self.dwarfdump_uuid_regex.search (line)
170                    if match:
171                        dwarf_uuid_str = match.group(1)
172                        dwarf_uuid = uuid.UUID(dwarf_uuid_str)
173                        if self_uuid == dwarf_uuid:
174                            self.resolved_path = self.path
175                            self.arch = match.group(2)
176                            break;
177                if not self.resolved_path:
178                    print "error: file %s '%s' doesn't match the UUID in the installed file" % (self.uuid, self.path)
179                    return 0
180            if (self.resolved_path and os.path.exists(self.resolved_path)) or (self.path and os.path.exists(self.path)):
181                print 'ok'
182                if self.path != self.resolved_path:
183                    print '  exe = "%s"' % self.resolved_path
184                if self.dsym:
185                    print ' dsym = "%s"' % self.dsym
186                return 1
187            else:
188                return 0
189
190        def load_module(self):
191            if not lldb.target:
192                return 'error: no target'
193            if self.module:
194                text_section = self.module.FindSection ("__TEXT")
195                if text_section:
196                    error = lldb.target.SetSectionLoadAddress (text_section, self.text_addr_lo)
197                    if error.Success():
198                        #print 'Success: loaded %s.__TEXT = 0x%x' % (self.get_resolved_path_basename(), self.text_addr_lo)
199                        return None
200                    else:
201                        return 'error: %s' % error.GetCString()
202                else:
203                    return 'error: unable to find "__TEXT" section in "%s"' % self.get_resolved_path()
204            else:
205                return 'error: invalid module'
206
207        def create_target(self):
208            if self.fetch_symboled_executable_and_dsym ():
209                resolved_path = self.get_resolved_path();
210                path_spec = lldb.SBFileSpec (resolved_path)
211                #result.PutCString ('plist[%s] = %s' % (uuid, self.plist))
212                error = lldb.SBError()
213                lldb.target = lldb.debugger.CreateTarget (resolved_path, self.arch, None, False, error);
214                if lldb.target:
215                    self.module = lldb.target.FindModule (path_spec)
216                    if self.module:
217                        err = self.load_module()
218                        if err:
219                            print err
220                        else:
221                            return None
222                    else:
223                        return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
224                else:
225                    return 'error: unable to create target for (%s) "%s"' % (self.arch, resolved_path)
226            else:
227                return 'error: unable to locate main executable (%s) "%s"' % (self.arch, self.path)
228
229        def add_target_module(self):
230            if lldb.target:
231                # Check for the module by UUID first in case it has been already loaded in LLDB
232                self.module = lldb.target.AddModule (None, None, str(self.uuid))
233                if not self.module:
234                    if self.fetch_symboled_executable_and_dsym ():
235                        resolved_path = self.get_resolved_path();
236                        path_spec = lldb.SBFileSpec (resolved_path)
237                        #print 'target.AddModule (path="%s", arch="%s", uuid=%s)' % (resolved_path, self.arch, self.uuid)
238                        self.module = lldb.target.AddModule (resolved_path, self.arch, self.uuid)
239                if self.module:
240                    err = self.load_module()
241                    if err:
242                        print err;
243                    else:
244                        return None
245                else:
246                    return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
247            else:
248                return 'error: invalid target'
249
250    def __init__(self, path):
251        """CrashLog constructor that take a path to a darwin crash log file"""
252        self.path = os.path.expanduser(path);
253        self.info_lines = list()
254        self.system_profile = list()
255        self.threads = list()
256        self.images = list()
257        self.idents = list() # A list of the required identifiers for doing all stack backtraces
258        self.crashed_thread_idx = -1
259        self.version = -1
260        self.error = None
261        # With possible initial component of ~ or ~user replaced by that user's home directory.
262        try:
263            f = open(self.path)
264        except IOError:
265            self.error = 'error: cannot open "%s"' % self.path
266            return
267
268        self.file_lines = f.read().splitlines()
269        parse_mode = PARSE_MODE_NORMAL
270        thread = None
271        for line in self.file_lines:
272            # print line
273            line_len = len(line)
274            if line_len == 0:
275                if thread:
276                    if parse_mode == PARSE_MODE_THREAD:
277                        if thread.index == self.crashed_thread_idx:
278                            thread.reason = ''
279                            if self.thread_exception:
280                                thread.reason += self.thread_exception
281                            if self.thread_exception_data:
282                                thread.reason += " (%s)" % self.thread_exception_data
283                        self.threads.append(thread)
284                    thread = None
285                else:
286                    # only append an extra empty line if the previous line
287                    # in the info_lines wasn't empty
288                    if len(self.info_lines) > 0 and len(self.info_lines[-1]):
289                        self.info_lines.append(line)
290                parse_mode = PARSE_MODE_NORMAL
291                # print 'PARSE_MODE_NORMAL'
292            elif parse_mode == PARSE_MODE_NORMAL:
293                if line.startswith ('Process:'):
294                    (self.process_name, pid_with_brackets) = line[8:].strip().split()
295                    self.process_id = pid_with_brackets.strip('[]')
296                elif line.startswith ('Path:'):
297                    self.process_path = line[5:].strip()
298                elif line.startswith ('Identifier:'):
299                    self.process_identifier = line[11:].strip()
300                elif line.startswith ('Version:'):
301                    (self.process_version, compatability_version) = line[8:].strip().split()
302                    self.process_compatability_version = compatability_version.strip('()')
303                elif line.startswith ('Parent Process:'):
304                    (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
305                    self.parent_process_id = pid_with_brackets.strip('[]')
306                elif line.startswith ('Exception Type:'):
307                    self.thread_exception = line[15:].strip()
308                    continue
309                elif line.startswith ('Exception Codes:'):
310                    self.thread_exception_data = line[16:].strip()
311                    continue
312                elif line.startswith ('Crashed Thread:'):
313                    self.crashed_thread_idx = int(line[15:].strip().split()[0])
314                    continue
315                elif line.startswith ('Report Version:'):
316                    self.version = int(line[15:].strip())
317                    continue
318                elif line.startswith ('System Profile:'):
319                    parse_mode = PARSE_MODE_SYSTEM
320                    continue
321                elif (line.startswith ('Interval Since Last Report:') or
322                      line.startswith ('Crashes Since Last Report:') or
323                      line.startswith ('Per-App Interval Since Last Report:') or
324                      line.startswith ('Per-App Crashes Since Last Report:') or
325                      line.startswith ('Sleep/Wake UUID:') or
326                      line.startswith ('Anonymous UUID:')):
327                    # ignore these
328                    continue
329                elif line.startswith ('Thread'):
330                    thread_state_match = self.thread_state_regex.search (line)
331                    if thread_state_match:
332                        thread_state_match = self.thread_regex.search (line)
333                        thread_idx = int(thread_state_match.group(1))
334                        parse_mode = PARSE_MODE_THREGS
335                        thread = self.threads[thread_idx]
336                    else:
337                        thread_match = self.thread_regex.search (line)
338                        if thread_match:
339                            # print 'PARSE_MODE_THREAD'
340                            parse_mode = PARSE_MODE_THREAD
341                            thread_idx = int(thread_match.group(1))
342                            thread = CrashLog.Thread(thread_idx)
343                    continue
344                elif line.startswith ('Binary Images:'):
345                    parse_mode = PARSE_MODE_IMAGES
346                    continue
347                self.info_lines.append(line.strip())
348            elif parse_mode == PARSE_MODE_THREAD:
349                frame_match = self.frame_regex.search(line)
350                if frame_match:
351                    ident = frame_match.group(2)
352                    if not ident in self.idents:
353                        self.idents.append(ident)
354                    thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
355                else:
356                    print 'error: frame regex failed for line: "%s"' % line
357            elif parse_mode == PARSE_MODE_IMAGES:
358                image_match = self.image_regex_uuid.search (line)
359                if image_match:
360                    image = CrashLog.Image (int(image_match.group(1),0),
361                                            int(image_match.group(2),0),
362                                            image_match.group(3).strip(),
363                                            image_match.group(4).strip(),
364                                            image_match.group(5),
365                                            image_match.group(6))
366                    self.images.append (image)
367                else:
368                    image_match = self.image_regex_no_uuid.search (line)
369                    if image_match:
370                        image = CrashLog.Image (int(image_match.group(1),0),
371                                                int(image_match.group(2),0),
372                                                image_match.group(3).strip(),
373                                                image_match.group(4).strip(),
374                                                None,
375                                                image_match.group(5))
376                        self.images.append (image)
377                    else:
378                        print "error: image regex failed for: %s" % line
379
380            elif parse_mode == PARSE_MODE_THREGS:
381                stripped_line = line.strip()
382                reg_values = stripped_line.split('  ')
383                for reg_value in reg_values:
384                    (reg, value) = reg_value.split(': ')
385                    thread.registers[reg.strip()] = int(value, 0)
386            elif parse_mode == PARSE_MODE_SYSTEM:
387                self.system_profile.append(line)
388        f.close()
389
390    def dump(self):
391        print "Crash Log File: %s" % (self.path)
392        print "\nThreads:"
393        for thread in self.threads:
394            thread.dump('  ')
395        print "\nImages:"
396        for image in self.images:
397            image.dump('  ')
398
399    def find_image_with_identifier(self, ident):
400        for image in self.images:
401            if image.ident == ident:
402                return image
403        return None
404
405    def create_target(self):
406        if not self.images:
407            return 'error: no images in crash log'
408        exe_path = self.images[0].get_resolved_path()
409        err = self.images[0].create_target ()
410        if not err:
411            return None # success
412        # We weren't able to open the main executable as, but we can still symbolicate
413        if self.idents:
414            for ident in self.idents:
415                image = self.find_image_with_identifier (ident)
416                if image:
417                    err = image.create_target ()
418                    if not err:
419                        return None # success
420        for image in self.images:
421            err = image.create_target ()
422            if not err:
423                return None # success
424        return 'error: unable to locate any executables from the crash log'
425
426def disassemble_instructions (instructions, pc, options, non_zeroeth_frame):
427    lines = list()
428    pc_index = -1
429    comment_column = 50
430    for inst_idx, inst in enumerate(instructions):
431        inst_pc = inst.GetAddress().GetLoadAddress(lldb.target);
432        if pc == inst_pc:
433            pc_index = inst_idx
434        mnemonic = inst.GetMnemonic (lldb.target)
435        operands =  inst.GetOperands (lldb.target)
436        comment =  inst.GetComment (lldb.target)
437        #data = inst.GetData (lldb.target)
438        lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands))
439        if comment:
440            line_len = len(lines[-1])
441            if line_len < comment_column:
442                lines[-1] += ' ' * (comment_column - line_len)
443                lines[-1] += "; %s" % comment
444
445    if pc_index >= 0:
446        # If we are disassembling the non-zeroeth frame, we need to backup the PC by 1
447        if non_zeroeth_frame and pc_index > 0:
448            pc_index = pc_index - 1
449        if options.disassemble_before == -1:
450            start_idx = 0
451        else:
452            start_idx = pc_index - options.disassemble_before
453        if start_idx < 0:
454            start_idx = 0
455        if options.disassemble_before == -1:
456            end_idx = inst_idx
457        else:
458            end_idx = pc_index + options.disassemble_after
459        if end_idx > inst_idx:
460            end_idx = inst_idx
461        for i in range(start_idx, end_idx+1):
462            if i == pc_index:
463                print ' -> ', lines[i]
464            else:
465                print '    ', lines[i]
466
467def print_module_section_data (section):
468    print section
469    section_data = section.GetSectionData()
470    if section_data:
471        ostream = lldb.SBStream()
472        section_data.GetDescription (ostream, section.GetFileAddress())
473        print ostream.GetData()
474
475def print_module_section (section, depth):
476    print section
477
478    if depth > 0:
479        num_sub_sections = section.GetNumSubSections()
480        for sect_idx in range(num_sub_sections):
481            print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1)
482
483def print_module_sections (module, depth):
484    for sect in module.section_iter():
485        print_module_section (sect, depth)
486
487def print_module_symbols (module):
488    for sym in module:
489        print sym
490
491def usage():
492    print "Usage: lldb-symbolicate.py [-n name] executable-image"
493    sys.exit(0)
494
495def Symbolicate(debugger, command, result, dict):
496    try:
497        SymbolicateCrashLog (shlex.split(command))
498    except:
499        result.PutCString ("error: python exception %s" % sys.exc_info()[0])
500
501def SymbolicateCrashLog(command_args):
502    usage = "usage: %prog [options] <FILE> [FILE ...]"
503    description='''Symbolicate one or more darwin crash log files to provide source file and line information,
504inlined stack frames back to the concrete functions, and disassemble the location of the crash
505for the first frame of the crashed thread.
506If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
507for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
508created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
509you to explore the program as if it were stopped at the locations described in the crash log and functions can
510be disassembled and lookups can be performed using the addresses found in the crash log.'''
511    parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage)
512    parser.add_option('--platform', type='string', metavar='platform', dest='platform', help='specify one platform by name')
513    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
514    parser.add_option('--no-images', action='store_false', dest='show_images', help='don\'t show images in stack frames', default=True)
515    parser.add_option('-a', '--load-all', action='store_true', dest='load_all_images', help='load all executable images, not just the images found in the crashed stack frames', default=False)
516    parser.add_option('--image-list', action='store_true', dest='dump_image_list', help='show image list', default=False)
517    parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
518    parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
519    parser.add_option('-d', '--disasm-depth', type='int', dest='disassemble_depth', help='set the depth in stack frames that should be disassembled (default is 1)', default=1)
520    parser.add_option('-D', '--disasm-all', action='store_true', dest='disassemble_all_threads', help='enabled disassembly of frames on all threads (not just the crashed thread)', default=False)
521    parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
522    parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
523    loaded_addresses = False
524    try:
525        (options, args) = parser.parse_args(command_args)
526    except:
527        return
528
529    if options.verbose:
530        print 'command_args = %s' % command_args
531        print 'options', options
532        print 'args', args
533
534    if options.debug_delay > 0:
535        print "Waiting %u seconds for debugger to attach..." % options.debug_delay
536        time.sleep(options.debug_delay)
537    error = lldb.SBError()
538    if args:
539        for crash_log_file in args:
540            crash_log = CrashLog(crash_log_file)
541            if crash_log.error:
542                print crash_log.error
543                return
544            if options.verbose:
545                crash_log.dump()
546            if not crash_log.images:
547                print 'error: no images in crash log'
548                return
549
550            err = crash_log.create_target ()
551            if err:
552                print err
553                return
554
555            exe_module = lldb.target.GetModuleAtIndex(0)
556            images_to_load = list()
557            loaded_image_paths = list()
558            if options.load_all_images:
559                # --load-all option was specified, load everything up
560                for image in crash_log.images:
561                    images_to_load.append(image)
562            else:
563                # Only load the images found in stack frames for the crashed threads
564                for ident in crash_log.idents:
565                    image = crash_log.find_image_with_identifier (ident)
566                    if image:
567                        images_to_load.append(image)
568                    else:
569                        print 'error: can\'t find image for identifier "%s"' % ident
570
571            for image in images_to_load:
572                if image.path in loaded_image_paths:
573                    print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
574                else:
575                    err = image.add_target_module ()
576                    if err:
577                        print err
578                    else:
579                        loaded_image_paths.append(image.path)
580
581            for line in crash_log.info_lines:
582                print line
583
584            # Reconstruct inlined frames for all threads for anything that has debug info
585            for thread in crash_log.threads:
586                if options.crashed_only and thread.did_crash() == False:
587                    continue
588                # start a new frame list that we will fixup for each thread
589                new_thread_frames = list()
590                # Iterate through all concrete frames for a thread and resolve
591                # any parent frames of inlined functions
592                for frame_idx, frame in enumerate(thread.frames):
593                    # Resolve the frame's pc into a section + offset address 'pc_addr'
594                    pc_addr = lldb.target.ResolveLoadAddress (frame.pc)
595                    # Check to see if we were able to resolve the address
596                    if pc_addr:
597                        # We were able to resolve the frame's PC into a section offset
598                        # address.
599
600                        # Resolve the frame's PC value into a symbol context. A symbol
601                        # context can resolve a module, compile unit, function, block,
602                        # line table entry and/or symbol. If the frame has a block, then
603                        # we can look for inlined frames, which are represented by blocks
604                        # that have inlined information in them
605                        frame.sym_ctx = lldb.target.ResolveSymbolContextForAddress (pc_addr, lldb.eSymbolContextEverything);
606
607                        # dump if the verbose option was specified
608                        if options.verbose:
609                            print "frame.pc = %#16.16x (file_addr = %#16.16x)" % (frame.pc, pc_addr.GetFileAddress())
610                            print "frame.pc_addr = ", pc_addr
611                            print "frame.sym_ctx = "
612                            print frame.sym_ctx
613                            print
614
615                        # Append the frame we already had from the crash log to the new
616                        # frames list
617                        new_thread_frames.append(frame)
618
619                        new_frame = CrashLog.Frame (frame.index, -1, None)
620
621                        # Try and use the current frame's symbol context to calculate a
622                        # parent frame for an inlined function. If the curent frame is
623                        # inlined, it will return a valid symbol context for the parent
624                        # frame of the current inlined function
625                        parent_pc_addr = lldb.SBAddress()
626                        new_frame.sym_ctx = frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
627
628                        # See if we were able to reconstruct anything?
629                        while new_frame.sym_ctx:
630                            # We have a parent frame of an inlined frame, create a new frame
631                            # Convert the section + offset 'parent_pc_addr' to a load address
632                            new_frame.pc = parent_pc_addr.GetLoadAddress(lldb.target)
633                            # push the new frame onto the new frame stack
634                            new_thread_frames.append (new_frame)
635                            # dump if the verbose option was specified
636                            if options.verbose:
637                                print "new_frame.pc = %#16.16x (%s)" % (new_frame.pc, parent_pc_addr)
638                                print "new_frame.sym_ctx = "
639                                print new_frame.sym_ctx
640                                print
641                            # Create another new frame in case we have multiple inlined frames
642                            prev_new_frame = new_frame
643                            new_frame = CrashLog.Frame (frame.index, -1, None)
644                            # Swap the addresses so we can try another inlined lookup
645                            pc_addr = parent_pc_addr;
646                            new_frame.sym_ctx = prev_new_frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
647                # Replace our thread frames with our new list that includes parent
648                # frames for inlined functions
649                thread.frames = new_thread_frames
650            # Now iterate through all threads and display our richer stack backtraces
651            for thread in crash_log.threads:
652                this_thread_crashed = thread.did_crash()
653                if options.crashed_only and this_thread_crashed == False:
654                    continue
655                print "%s" % thread
656                prev_frame_index = -1
657                for frame_idx, frame in enumerate(thread.frames):
658                    details = '          %s' % frame.details
659                    module = frame.sym_ctx.GetModule()
660                    instructions = None
661                    if module:
662                        module_basename = module.GetFileSpec().GetFilename();
663                        function_start_load_addr = -1
664                        function_name = None
665                        function = frame.sym_ctx.GetFunction()
666                        block = frame.sym_ctx.GetBlock()
667                        line_entry = frame.sym_ctx.GetLineEntry()
668                        symbol = frame.sym_ctx.GetSymbol()
669                        inlined_block = block.GetContainingInlinedBlock();
670                        disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
671                        if inlined_block:
672                            function_name = inlined_block.GetInlinedName();
673                            block_range_idx = inlined_block.GetRangeIndexForBlockAddress (lldb.target.ResolveLoadAddress (frame.pc))
674                            if block_range_idx < lldb.UINT32_MAX:
675                                block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx)
676                                function_start_load_addr = block_range_start_addr.GetLoadAddress (lldb.target)
677                            else:
678                                function_start_load_addr = frame.pc
679                            if disassemble:
680                                instructions = function.GetInstructions(lldb.target)
681                        elif function:
682                            function_name = function.GetName()
683                            function_start_load_addr = function.GetStartAddress().GetLoadAddress (lldb.target)
684                            if disassemble:
685                                instructions = function.GetInstructions(lldb.target)
686                        elif symbol:
687                            function_name = symbol.GetName()
688                            function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (lldb.target)
689                            if disassemble:
690                                instructions = symbol.GetInstructions(lldb.target)
691
692                        if function_name:
693                            # Print the function or symbol name and annotate if it was inlined
694                            inline_suffix = ''
695                            if inlined_block:
696                                inline_suffix = '[inlined] '
697                            else:
698                                inline_suffix = '          '
699                            if options.show_images:
700                                details = "%s%s`%s" % (inline_suffix, module_basename, function_name)
701                            else:
702                                details = "%s" % (function_name)
703                            # Dump the offset from the current function or symbol if it is non zero
704                            function_offset = frame.pc - function_start_load_addr
705                            if function_offset > 0:
706                                details += " + %u" % (function_offset)
707                            elif function_offset < 0:
708                                defaults += " %i (invalid negative offset, file a bug) " % function_offset
709                            # Print out any line information if any is available
710                            if line_entry.GetFileSpec():
711                                details += ' at %s' % line_entry.GetFileSpec().GetFilename()
712                                details += ':%u' % line_entry.GetLine ()
713                                column = line_entry.GetColumn()
714                                if column > 0:
715                                    details += ':%u' % column
716
717
718                    # Only print out the concrete frame index if it changes.
719                    # if prev_frame_index != frame.index:
720                    #     print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
721                    # else:
722                    #     print "     %#16.16x %s" % (frame.pc, details)
723                    print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
724                    prev_frame_index = frame.index
725                    if instructions:
726                        print
727                        disassemble_instructions (instructions, frame.pc, options, frame.index > 0)
728                        print
729
730                print
731
732            if options.dump_image_list:
733                print "Binary Images:"
734                for image in crash_log.images:
735                    print image
736
737
738if __name__ == '__main__':
739    # Create a new debugger instance
740    lldb.debugger = lldb.SBDebugger.Create()
741    SymbolicateCrashLog (sys.argv)
742elif lldb.debugger:
743    lldb.debugger.HandleCommand('command script add -f crashlog.Symbolicate crashlog')
744    print '"crashlog" command installed, type "crashlog --help" for detailed help'
745
746