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.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                if self.fetch_symboled_executable_and_dsym ():
232                    resolved_path = self.get_resolved_path();
233                    path_spec = lldb.SBFileSpec (resolved_path)
234                    #print 'target.AddModule (path="%s", arch="%s", uuid=%s)' % (resolved_path, self.arch, self.uuid)
235                    self.module = lldb.target.AddModule (resolved_path, self.arch, self.uuid)
236                    if self.module:
237                        err = self.load_module()
238                        if err:
239                            print err;
240                        else:
241                            return None
242                    else:
243                        return 'error: unable to get module for (%s) "%s"' % (self.arch, resolved_path)
244            else:
245                return 'error: invalid target'
246
247    def __init__(self, path):
248        """CrashLog constructor that take a path to a darwin crash log file"""
249        self.path = os.path.expanduser(path);
250        self.info_lines = list()
251        self.system_profile = list()
252        self.threads = list()
253        self.images = list()
254        self.idents = list() # A list of the required identifiers for doing all stack backtraces
255        self.crashed_thread_idx = -1
256        self.version = -1
257        self.error = None
258        # With possible initial component of ~ or ~user replaced by that user's home directory.
259        try:
260            f = open(self.path)
261        except IOError:
262            self.error = 'error: cannot open "%s"' % self.path
263            return
264
265        self.file_lines = f.read().splitlines()
266        parse_mode = PARSE_MODE_NORMAL
267        thread = None
268        for line in self.file_lines:
269            # print line
270            line_len = len(line)
271            if line_len == 0:
272                if thread:
273                    if parse_mode == PARSE_MODE_THREAD:
274                        if thread.index == self.crashed_thread_idx:
275                            thread.reason = ''
276                            if self.thread_exception:
277                                thread.reason += self.thread_exception
278                            if self.thread_exception_data:
279                                thread.reason += " (%s)" % self.thread_exception_data
280                        self.threads.append(thread)
281                    thread = None
282                else:
283                    # only append an extra empty line if the previous line
284                    # in the info_lines wasn't empty
285                    if len(self.info_lines) > 0 and len(self.info_lines[-1]):
286                        self.info_lines.append(line)
287                parse_mode = PARSE_MODE_NORMAL
288                # print 'PARSE_MODE_NORMAL'
289            elif parse_mode == PARSE_MODE_NORMAL:
290                if line.startswith ('Process:'):
291                    (self.process_name, pid_with_brackets) = line[8:].strip().split()
292                    self.process_id = pid_with_brackets.strip('[]')
293                elif line.startswith ('Path:'):
294                    self.process_path = line[5:].strip()
295                elif line.startswith ('Identifier:'):
296                    self.process_identifier = line[11:].strip()
297                elif line.startswith ('Version:'):
298                    (self.process_version, compatability_version) = line[8:].strip().split()
299                    self.process_compatability_version = compatability_version.strip('()')
300                elif line.startswith ('Parent Process:'):
301                    (self.parent_process_name, pid_with_brackets) = line[15:].strip().split()
302                    self.parent_process_id = pid_with_brackets.strip('[]')
303                elif line.startswith ('Exception Type:'):
304                    self.thread_exception = line[15:].strip()
305                    continue
306                elif line.startswith ('Exception Codes:'):
307                    self.thread_exception_data = line[16:].strip()
308                    continue
309                elif line.startswith ('Crashed Thread:'):
310                    self.crashed_thread_idx = int(line[15:].strip().split()[0])
311                    continue
312                elif line.startswith ('Report Version:'):
313                    self.version = int(line[15:].strip())
314                    continue
315                elif line.startswith ('System Profile:'):
316                    parse_mode = PARSE_MODE_SYSTEM
317                    continue
318                elif (line.startswith ('Interval Since Last Report:') or
319                      line.startswith ('Crashes Since Last Report:') or
320                      line.startswith ('Per-App Interval Since Last Report:') or
321                      line.startswith ('Per-App Crashes Since Last Report:') or
322                      line.startswith ('Sleep/Wake UUID:') or
323                      line.startswith ('Anonymous UUID:')):
324                    # ignore these
325                    continue
326                elif line.startswith ('Thread'):
327                    thread_state_match = self.thread_state_regex.search (line)
328                    if thread_state_match:
329                        thread_state_match = self.thread_regex.search (line)
330                        thread_idx = int(thread_state_match.group(1))
331                        parse_mode = PARSE_MODE_THREGS
332                        thread = self.threads[thread_idx]
333                    else:
334                        thread_match = self.thread_regex.search (line)
335                        if thread_match:
336                            # print 'PARSE_MODE_THREAD'
337                            parse_mode = PARSE_MODE_THREAD
338                            thread_idx = int(thread_match.group(1))
339                            thread = CrashLog.Thread(thread_idx)
340                    continue
341                elif line.startswith ('Binary Images:'):
342                    parse_mode = PARSE_MODE_IMAGES
343                    continue
344                self.info_lines.append(line.strip())
345            elif parse_mode == PARSE_MODE_THREAD:
346                frame_match = self.frame_regex.search(line)
347                if frame_match:
348                    ident = frame_match.group(2)
349                    if not ident in self.idents:
350                        self.idents.append(ident)
351                    thread.frames.append (CrashLog.Frame(int(frame_match.group(1)), int(frame_match.group(3), 0), frame_match.group(4)))
352                else:
353                    print 'error: frame regex failed for line: "%s"' % line
354            elif parse_mode == PARSE_MODE_IMAGES:
355                image_match = self.image_regex_uuid.search (line)
356                if image_match:
357                    image = CrashLog.Image (int(image_match.group(1),0),
358                                            int(image_match.group(2),0),
359                                            image_match.group(3).strip(),
360                                            image_match.group(4).strip(),
361                                            image_match.group(5),
362                                            image_match.group(6))
363                    self.images.append (image)
364                else:
365                    image_match = self.image_regex_no_uuid.search (line)
366                    if image_match:
367                        image = CrashLog.Image (int(image_match.group(1),0),
368                                                int(image_match.group(2),0),
369                                                image_match.group(3).strip(),
370                                                image_match.group(4).strip(),
371                                                None,
372                                                image_match.group(5))
373                        self.images.append (image)
374                    else:
375                        print "error: image regex failed for: %s" % line
376
377            elif parse_mode == PARSE_MODE_THREGS:
378                stripped_line = line.strip()
379                reg_values = stripped_line.split('  ')
380                for reg_value in reg_values:
381                    (reg, value) = reg_value.split(': ')
382                    thread.registers[reg.strip()] = int(value, 0)
383            elif parse_mode == PARSE_MODE_SYSTEM:
384                self.system_profile.append(line)
385        f.close()
386
387    def dump(self):
388        print "Crash Log File: %s" % (self.path)
389        print "\nThreads:"
390        for thread in self.threads:
391            thread.dump('  ')
392        print "\nImages:"
393        for image in self.images:
394            image.dump('  ')
395
396    def find_image_with_identifier(self, ident):
397        for image in self.images:
398            if image.ident == ident:
399                return image
400        return None
401
402    def create_target(self):
403        if not self.images:
404            return 'error: no images in crash log'
405        exe_path = self.images[0].get_resolved_path()
406        err = self.images[0].create_target ()
407        if not err:
408            return None # success
409        # We weren't able to open the main executable as, but we can still symbolicate
410        if self.idents:
411            for ident in self.idents:
412                image = self.find_image_with_identifier (ident)
413                if image:
414                    err = image.create_target ()
415                    if not err:
416                        return None # success
417        for image in self.images:
418            err = image.create_target ()
419            if not err:
420                return None # success
421        return 'error: unable to locate any executables from the crash log'
422
423def disassemble_instructions (instructions, pc, options, non_zeroeth_frame):
424    lines = list()
425    pc_index = -1
426    comment_column = 50
427    for inst_idx, inst in enumerate(instructions):
428        inst_pc = inst.GetAddress().GetLoadAddress(lldb.target);
429        if pc == inst_pc:
430            pc_index = inst_idx
431        mnemonic = inst.GetMnemonic (lldb.target)
432        operands =  inst.GetOperands (lldb.target)
433        comment =  inst.GetComment (lldb.target)
434        #data = inst.GetData (lldb.target)
435        lines.append ("%#16.16x: %8s %s" % (inst_pc, mnemonic, operands))
436        if comment:
437            line_len = len(lines[-1])
438            if line_len < comment_column:
439                lines[-1] += ' ' * (comment_column - line_len)
440                lines[-1] += "; %s" % comment
441
442    if pc_index >= 0:
443        # If we are disassembling the non-zeroeth frame, we need to backup the PC by 1
444        if non_zeroeth_frame and pc_index > 0:
445            pc_index = pc_index - 1
446        if options.disassemble_before == -1:
447            start_idx = 0
448        else:
449            start_idx = pc_index - options.disassemble_before
450        if start_idx < 0:
451            start_idx = 0
452        if options.disassemble_before == -1:
453            end_idx = inst_idx
454        else:
455            end_idx = pc_index + options.disassemble_after
456        if end_idx > inst_idx:
457            end_idx = inst_idx
458        for i in range(start_idx, end_idx+1):
459            if i == pc_index:
460                print ' -> ', lines[i]
461            else:
462                print '    ', lines[i]
463
464def print_module_section_data (section):
465    print section
466    section_data = section.GetSectionData()
467    if section_data:
468        ostream = lldb.SBStream()
469        section_data.GetDescription (ostream, section.GetFileAddress())
470        print ostream.GetData()
471
472def print_module_section (section, depth):
473    print section
474
475    if depth > 0:
476        num_sub_sections = section.GetNumSubSections()
477        for sect_idx in range(num_sub_sections):
478            print_module_section (section.GetSubSectionAtIndex(sect_idx), depth - 1)
479
480def print_module_sections (module, depth):
481    for sect in module.section_iter():
482        print_module_section (sect, depth)
483
484def print_module_symbols (module):
485    for sym in module:
486        print sym
487
488def usage():
489    print "Usage: lldb-symbolicate.py [-n name] executable-image"
490    sys.exit(0)
491
492def Symbolicate(debugger, command, result, dict):
493    try:
494        SymbolicateCrashLog (shlex.split(command))
495    except:
496        result.PutCString ("error: python exception %s" % sys.exc_info()[0])
497
498def SymbolicateCrashLog(command_args):
499    usage = "usage: %prog [options] <FILE> [FILE ...]"
500    description='''Symbolicate one or more darwin crash log files to provide source file and line information,
501inlined stack frames back to the concrete functions, and disassemble the location of the crash
502for the first frame of the crashed thread.
503If this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
504for use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
505created that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
506you to explore the program as if it were stopped at the locations described in the crash log and functions can
507be disassembled and lookups can be performed using the addresses found in the crash log.'''
508    parser = optparse.OptionParser(description=description, prog='crashlog.py',usage=usage)
509    parser.add_option('--platform', type='string', metavar='platform', dest='platform', help='specify one platform by name')
510    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False)
511    parser.add_option('--no-images', action='store_false', dest='show_images', help='don\'t show images in stack frames', default=True)
512    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)
513    parser.add_option('--image-list', action='store_true', dest='dump_image_list', help='show image list', default=False)
514    parser.add_option('-g', '--debug-delay', type='int', dest='debug_delay', metavar='NSEC', help='pause for NSEC seconds for debugger', default=0)
515    parser.add_option('-c', '--crashed-only', action='store_true', dest='crashed_only', help='only symbolicate the crashed thread', default=False)
516    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)
517    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)
518    parser.add_option('-B', '--disasm-before', type='int', dest='disassemble_before', help='the number of instructions to disassemble before the frame PC', default=4)
519    parser.add_option('-A', '--disasm-after', type='int', dest='disassemble_after', help='the number of instructions to disassemble after the frame PC', default=4)
520    loaded_addresses = False
521    try:
522        (options, args) = parser.parse_args(command_args)
523    except:
524        return
525
526    if options.verbose:
527        print 'command_args = %s' % command_args
528        print 'options', options
529        print 'args', args
530
531    if options.debug_delay > 0:
532        print "Waiting %u seconds for debugger to attach..." % options.debug_delay
533        time.sleep(options.debug_delay)
534    error = lldb.SBError()
535    if args:
536        for crash_log_file in args:
537            crash_log = CrashLog(crash_log_file)
538            if crash_log.error:
539                print crash_log.error
540                return
541            if options.verbose:
542                crash_log.dump()
543            if not crash_log.images:
544                print 'error: no images in crash log'
545                return
546
547            err = crash_log.create_target ()
548            if err:
549                print err
550                return
551
552            exe_module = lldb.target.GetModuleAtIndex(0)
553            images_to_load = list()
554            loaded_image_paths = list()
555            if options.load_all_images:
556                # --load-all option was specified, load everything up
557                for image in crash_log.images:
558                    images_to_load.append(image)
559            else:
560                # Only load the images found in stack frames for the crashed threads
561                for ident in crash_log.idents:
562                    image = crash_log.find_image_with_identifier (ident)
563                    if image:
564                        images_to_load.append(image)
565                    else:
566                        print 'error: can\'t find image for identifier "%s"' % ident
567
568            for image in images_to_load:
569                if image.path in loaded_image_paths:
570                    print "warning: skipping %s loaded at %#16.16x duplicate entry (probably commpage)" % (image.path, image.text_addr_lo)
571                else:
572                    err = image.add_target_module ()
573                    if err:
574                        print err
575                    else:
576                        loaded_image_paths.append(image.path)
577
578            for line in crash_log.info_lines:
579                print line
580
581            # Reconstruct inlined frames for all threads for anything that has debug info
582            for thread in crash_log.threads:
583                if options.crashed_only and thread.did_crash() == False:
584                    continue
585                # start a new frame list that we will fixup for each thread
586                new_thread_frames = list()
587                # Iterate through all concrete frames for a thread and resolve
588                # any parent frames of inlined functions
589                for frame_idx, frame in enumerate(thread.frames):
590                    # Resolve the frame's pc into a section + offset address 'pc_addr'
591                    pc_addr = lldb.target.ResolveLoadAddress (frame.pc)
592                    # Check to see if we were able to resolve the address
593                    if pc_addr:
594                        # We were able to resolve the frame's PC into a section offset
595                        # address.
596
597                        # Resolve the frame's PC value into a symbol context. A symbol
598                        # context can resolve a module, compile unit, function, block,
599                        # line table entry and/or symbol. If the frame has a block, then
600                        # we can look for inlined frames, which are represented by blocks
601                        # that have inlined information in them
602                        frame.sym_ctx = lldb.target.ResolveSymbolContextForAddress (pc_addr, lldb.eSymbolContextEverything);
603
604                        # dump if the verbose option was specified
605                        if options.verbose:
606                            print "frame.pc = %#16.16x (file_addr = %#16.16x)" % (frame.pc, pc_addr.GetFileAddress())
607                            print "frame.pc_addr = ", pc_addr
608                            print "frame.sym_ctx = "
609                            print frame.sym_ctx
610                            print
611
612                        # Append the frame we already had from the crash log to the new
613                        # frames list
614                        new_thread_frames.append(frame)
615
616                        new_frame = CrashLog.Frame (frame.index, -1, None)
617
618                        # Try and use the current frame's symbol context to calculate a
619                        # parent frame for an inlined function. If the curent frame is
620                        # inlined, it will return a valid symbol context for the parent
621                        # frame of the current inlined function
622                        parent_pc_addr = lldb.SBAddress()
623                        new_frame.sym_ctx = frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
624
625                        # See if we were able to reconstruct anything?
626                        while new_frame.sym_ctx:
627                            # We have a parent frame of an inlined frame, create a new frame
628                            # Convert the section + offset 'parent_pc_addr' to a load address
629                            new_frame.pc = parent_pc_addr.GetLoadAddress(lldb.target)
630                            # push the new frame onto the new frame stack
631                            new_thread_frames.append (new_frame)
632                            # dump if the verbose option was specified
633                            if options.verbose:
634                                print "new_frame.pc = %#16.16x (%s)" % (new_frame.pc, parent_pc_addr)
635                                print "new_frame.sym_ctx = "
636                                print new_frame.sym_ctx
637                                print
638                            # Create another new frame in case we have multiple inlined frames
639                            prev_new_frame = new_frame
640                            new_frame = CrashLog.Frame (frame.index, -1, None)
641                            # Swap the addresses so we can try another inlined lookup
642                            pc_addr = parent_pc_addr;
643                            new_frame.sym_ctx = prev_new_frame.sym_ctx.GetParentOfInlinedScope (pc_addr, parent_pc_addr)
644                # Replace our thread frames with our new list that includes parent
645                # frames for inlined functions
646                thread.frames = new_thread_frames
647            # Now iterate through all threads and display our richer stack backtraces
648            for thread in crash_log.threads:
649                this_thread_crashed = thread.did_crash()
650                if options.crashed_only and this_thread_crashed == False:
651                    continue
652                print "%s" % thread
653                prev_frame_index = -1
654                for frame_idx, frame in enumerate(thread.frames):
655                    details = '          %s' % frame.details
656                    module = frame.sym_ctx.GetModule()
657                    instructions = None
658                    if module:
659                        module_basename = module.GetFileSpec().GetFilename();
660                        function_start_load_addr = -1
661                        function_name = None
662                        function = frame.sym_ctx.GetFunction()
663                        block = frame.sym_ctx.GetBlock()
664                        line_entry = frame.sym_ctx.GetLineEntry()
665                        symbol = frame.sym_ctx.GetSymbol()
666                        inlined_block = block.GetContainingInlinedBlock();
667                        disassemble = (this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth;
668                        if inlined_block:
669                            function_name = inlined_block.GetInlinedName();
670                            block_range_idx = inlined_block.GetRangeIndexForBlockAddress (lldb.target.ResolveLoadAddress (frame.pc))
671                            if block_range_idx < lldb.UINT32_MAX:
672                                block_range_start_addr = inlined_block.GetRangeStartAddress (block_range_idx)
673                                function_start_load_addr = block_range_start_addr.GetLoadAddress (lldb.target)
674                            else:
675                                function_start_load_addr = frame.pc
676                            if disassemble:
677                                instructions = function.GetInstructions(lldb.target)
678                        elif function:
679                            function_name = function.GetName()
680                            function_start_load_addr = function.GetStartAddress().GetLoadAddress (lldb.target)
681                            if disassemble:
682                                instructions = function.GetInstructions(lldb.target)
683                        elif symbol:
684                            function_name = symbol.GetName()
685                            function_start_load_addr = symbol.GetStartAddress().GetLoadAddress (lldb.target)
686                            if disassemble:
687                                instructions = symbol.GetInstructions(lldb.target)
688
689                        if function_name:
690                            # Print the function or symbol name and annotate if it was inlined
691                            inline_suffix = ''
692                            if inlined_block:
693                                inline_suffix = '[inlined] '
694                            else:
695                                inline_suffix = '          '
696                            if options.show_images:
697                                details = "%s%s`%s" % (inline_suffix, module_basename, function_name)
698                            else:
699                                details = "%s" % (function_name)
700                            # Dump the offset from the current function or symbol if it is non zero
701                            function_offset = frame.pc - function_start_load_addr
702                            if function_offset > 0:
703                                details += " + %u" % (function_offset)
704                            elif function_offset < 0:
705                                defaults += " %i (invalid negative offset, file a bug) " % function_offset
706                            # Print out any line information if any is available
707                            if line_entry.GetFileSpec():
708                                details += ' at %s' % line_entry.GetFileSpec().GetFilename()
709                                details += ':%u' % line_entry.GetLine ()
710                                column = line_entry.GetColumn()
711                                if column > 0:
712                                    details += ':%u' % column
713
714
715                    # Only print out the concrete frame index if it changes.
716                    # if prev_frame_index != frame.index:
717                    #     print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
718                    # else:
719                    #     print "     %#16.16x %s" % (frame.pc, details)
720                    print "[%2u] %#16.16x %s" % (frame.index, frame.pc, details)
721                    prev_frame_index = frame.index
722                    if instructions:
723                        print
724                        disassemble_instructions (instructions, frame.pc, options, frame.index > 0)
725                        print
726
727                print
728
729            if options.dump_image_list:
730                print "Binary Images:"
731                for image in crash_log.images:
732                    print image
733
734
735if __name__ == '__main__':
736    # Create a new debugger instance
737    lldb.debugger = lldb.SBDebugger.Create()
738    SymbolicateCrashLog (sys.argv)
739elif lldb.debugger:
740    lldb.debugger.HandleCommand('command script add -f crashlog.Symbolicate crashlog')
741    print '"crashlog" command installed, type "crashlog --help" for detailed help'
742
743