1a5c3f10bSJonas Devlieghere#!/usr/bin/env python3
2aa9d02b1SGreg Clayton
3aa9d02b1SGreg Clayton#----------------------------------------------------------------------
4aa9d02b1SGreg Clayton# Be sure to add the python path that points to the LLDB shared library.
55c0f483eSGreg Clayton#
65c0f483eSGreg Clayton# To use this in the embedded python interpreter using "lldb":
75c0f483eSGreg Clayton#
85c0f483eSGreg Clayton#   cd /path/containing/crashlog.py
95c0f483eSGreg Clayton#   lldb
105c0f483eSGreg Clayton#   (lldb) script import crashlog
115c0f483eSGreg Clayton#   "crashlog" command installed, type "crashlog --help" for detailed help
125c0f483eSGreg Clayton#   (lldb) crashlog ~/Library/Logs/DiagnosticReports/a.crash
135c0f483eSGreg Clayton#
145c0f483eSGreg Clayton# The benefit of running the crashlog command inside lldb in the
155c0f483eSGreg Clayton# embedded python interpreter is when the command completes, there
165c0f483eSGreg Clayton# will be a target with all of the files loaded at the locations
175c0f483eSGreg Clayton# described in the crash log. Only the files that have stack frames
185c0f483eSGreg Clayton# in the backtrace will be loaded unless the "--load-all" option
195c0f483eSGreg Clayton# has been specified. This allows users to explore the program in the
205c0f483eSGreg Clayton# state it was in right at crash time.
215c0f483eSGreg Clayton#
22aa9d02b1SGreg Clayton# On MacOSX csh, tcsh:
235c0f483eSGreg Clayton#   ( setenv PYTHONPATH /path/to/LLDB.framework/Resources/Python ; ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash )
245c0f483eSGreg Clayton#
25aa9d02b1SGreg Clayton# On MacOSX sh, bash:
265c0f483eSGreg Clayton#   PYTHONPATH=/path/to/LLDB.framework/Resources/Python ./crashlog.py ~/Library/Logs/DiagnosticReports/a.crash
27aa9d02b1SGreg Clayton#----------------------------------------------------------------------
28aa9d02b1SGreg Clayton
29a8abb695SJonas Devlieghereimport concurrent.futures
308d097a6bSMed Ismail Bennaniimport contextlib
31f47b2c26SGreg Claytonimport datetime
328d097a6bSMed Ismail Bennaniimport json
33aa9d02b1SGreg Claytonimport optparse
34aa9d02b1SGreg Claytonimport os
35f47b2c26SGreg Claytonimport platform
36aa9d02b1SGreg Claytonimport plistlib
37aa9d02b1SGreg Claytonimport re
3853b43b07SGreg Claytonimport shlex
39c8f73d7bSGreg Claytonimport string
40a658ab9fSDavide Italianoimport subprocess
41aa9d02b1SGreg Claytonimport sys
42a8abb695SJonas Devlieghereimport threading
43aa9d02b1SGreg Claytonimport time
443d8d3db3SGreg Claytonimport uuid
45aa4d4531SGreg Clayton
46a8abb695SJonas Devlieghere
47a8abb695SJonas Devlieghereprint_lock = threading.RLock()
48a8abb695SJonas Devlieghere
49fb6d1c02SJonas Devliegheretry:
50fb6d1c02SJonas Devlieghere    # First try for LLDB in case PYTHONPATH is already correctly setup.
51fb6d1c02SJonas Devlieghere    import lldb
52fb6d1c02SJonas Devlieghereexcept ImportError:
53fb6d1c02SJonas Devlieghere    # Ask the command line driver for the path to the lldb module. Copy over
54fb6d1c02SJonas Devlieghere    # the environment so that SDKROOT is propagated to xcrun.
55fb6d1c02SJonas Devlieghere    command =  ['xcrun', 'lldb', '-P'] if platform.system() == 'Darwin' else ['lldb', '-P']
56fb6d1c02SJonas Devlieghere    # Extend the PYTHONPATH if the path exists and isn't already there.
57d52866e1SJonas Devlieghere    lldb_python_path = subprocess.check_output(command).decode("utf-8").strip()
58fb6d1c02SJonas Devlieghere    if os.path.exists(lldb_python_path) and not sys.path.__contains__(lldb_python_path):
59fb6d1c02SJonas Devlieghere        sys.path.append(lldb_python_path)
60fb6d1c02SJonas Devlieghere    # Try importing LLDB again.
61fb6d1c02SJonas Devlieghere    try:
62fb6d1c02SJonas Devlieghere        import lldb
63fb6d1c02SJonas Devlieghere    except ImportError:
64fb6d1c02SJonas Devlieghere        print("error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly")
65fb6d1c02SJonas Devlieghere        sys.exit(1)
66fb6d1c02SJonas Devlieghere
67fb6d1c02SJonas Devliegherefrom lldb.utils import symbolication
68fb6d1c02SJonas Devlieghere
698803124dSDavide Italianodef read_plist(s):
708803124dSDavide Italiano    if sys.version_info.major == 3:
718803124dSDavide Italiano        return plistlib.loads(s)
728803124dSDavide Italiano    else:
738803124dSDavide Italiano        return plistlib.readPlistFromString(s)
748803124dSDavide Italiano
75c8f73d7bSGreg Claytonclass CrashLog(symbolication.Symbolicator):
76814ad734SDavide Italiano    class Thread:
77aa9d02b1SGreg Clayton        """Class that represents a thread in a darwin crash log"""
78b9c1b51eSKate Stone
7948d157ddSGreg Clayton        def __init__(self, index, app_specific_backtrace):
80aa9d02b1SGreg Clayton            self.index = index
8112301d61SMed Ismail Bennani            self.id = index
82aa9d02b1SGreg Clayton            self.frames = list()
83563d0393SGreg Clayton            self.idents = list()
84aa9d02b1SGreg Clayton            self.registers = dict()
85aa9d02b1SGreg Clayton            self.reason = None
8612301d61SMed Ismail Bennani            self.name = None
87aa9d02b1SGreg Clayton            self.queue = None
88b913065bSJonas Devlieghere            self.crashed = False
8948d157ddSGreg Clayton            self.app_specific_backtrace = app_specific_backtrace
90aa9d02b1SGreg Clayton
91aa9d02b1SGreg Clayton        def dump(self, prefix):
9248d157ddSGreg Clayton            if self.app_specific_backtrace:
93a658ab9fSDavide Italiano                print("%Application Specific Backtrace[%u] %s" % (prefix, self.index, self.reason))
9448d157ddSGreg Clayton            else:
95a658ab9fSDavide Italiano                print("%sThread[%u] %s" % (prefix, self.index, self.reason))
96aa9d02b1SGreg Clayton            if self.frames:
97a658ab9fSDavide Italiano                print("%s  Frames:" % (prefix))
98aa9d02b1SGreg Clayton                for frame in self.frames:
99aa9d02b1SGreg Clayton                    frame.dump(prefix + '    ')
100aa9d02b1SGreg Clayton            if self.registers:
101a658ab9fSDavide Italiano                print("%s  Registers:" % (prefix))
102814ad734SDavide Italiano                for reg in self.registers.keys():
10391d3f739SJonas Devlieghere                    print("%s    %-8s = %#16.16x" % (prefix, reg, self.registers[reg]))
104aa9d02b1SGreg Clayton
10548d157ddSGreg Clayton        def dump_symbolicated(self, crash_log, options):
10648d157ddSGreg Clayton            this_thread_crashed = self.app_specific_backtrace
10748d157ddSGreg Clayton            if not this_thread_crashed:
10848d157ddSGreg Clayton                this_thread_crashed = self.did_crash()
10948d157ddSGreg Clayton                if options.crashed_only and this_thread_crashed == False:
11048d157ddSGreg Clayton                    return
11148d157ddSGreg Clayton
112a658ab9fSDavide Italiano            print("%s" % self)
11348d157ddSGreg Clayton            display_frame_idx = -1
11448d157ddSGreg Clayton            for frame_idx, frame in enumerate(self.frames):
115b9c1b51eSKate Stone                disassemble = (
116b9c1b51eSKate Stone                    this_thread_crashed or options.disassemble_all_threads) and frame_idx < options.disassemble_depth
1179defb3b4SJonas Devlieghere
1183e54ea0cSMed Ismail Bennani                # Except for the zeroth frame, we should subtract 1 from every
1193e54ea0cSMed Ismail Bennani                # frame pc to get the previous line entry.
1209defb3b4SJonas Devlieghere                pc = frame.pc & crash_log.addr_mask
1219defb3b4SJonas Devlieghere                pc = pc if frame_idx == 0 or pc == 0 else pc - 1
1229defb3b4SJonas Devlieghere                symbolicated_frame_addresses = crash_log.symbolicate(pc, options.verbose)
12348d157ddSGreg Clayton
12448d157ddSGreg Clayton                if symbolicated_frame_addresses:
12548d157ddSGreg Clayton                    symbolicated_frame_address_idx = 0
12648d157ddSGreg Clayton                    for symbolicated_frame_address in symbolicated_frame_addresses:
12748d157ddSGreg Clayton                        display_frame_idx += 1
128a658ab9fSDavide Italiano                        print('[%3u] %s' % (frame_idx, symbolicated_frame_address))
129b9c1b51eSKate Stone                        if (options.source_all or self.did_crash(
130b9c1b51eSKate Stone                        )) and display_frame_idx < options.source_frames and options.source_context:
13148d157ddSGreg Clayton                            source_context = options.source_context
13248d157ddSGreg Clayton                            line_entry = symbolicated_frame_address.get_symbol_context().line_entry
13348d157ddSGreg Clayton                            if line_entry.IsValid():
13448d157ddSGreg Clayton                                strm = lldb.SBStream()
13548d157ddSGreg Clayton                                if line_entry:
136c29c24beSJonas Devlieghere                                    crash_log.debugger.GetSourceManager().DisplaySourceLinesWithLineNumbers(
137b9c1b51eSKate Stone                                        line_entry.file, line_entry.line, source_context, source_context, "->", strm)
13848d157ddSGreg Clayton                                source_text = strm.GetData()
13948d157ddSGreg Clayton                                if source_text:
14048d157ddSGreg Clayton                                    # Indent the source a bit
14148d157ddSGreg Clayton                                    indent_str = '    '
14248d157ddSGreg Clayton                                    join_str = '\n' + indent_str
143a658ab9fSDavide Italiano                                    print('%s%s' % (indent_str, join_str.join(source_text.split('\n'))))
14448d157ddSGreg Clayton                        if symbolicated_frame_address_idx == 0:
14548d157ddSGreg Clayton                            if disassemble:
14648d157ddSGreg Clayton                                instructions = symbolicated_frame_address.get_instructions()
14748d157ddSGreg Clayton                                if instructions:
148a658ab9fSDavide Italiano                                    print()
149b9c1b51eSKate Stone                                    symbolication.disassemble_instructions(
150b9c1b51eSKate Stone                                        crash_log.get_target(),
15148d157ddSGreg Clayton                                        instructions,
15248d157ddSGreg Clayton                                        frame.pc,
15348d157ddSGreg Clayton                                        options.disassemble_before,
154b9c1b51eSKate Stone                                        options.disassemble_after,
155b9c1b51eSKate Stone                                        frame.index > 0)
156a658ab9fSDavide Italiano                                    print()
15748d157ddSGreg Clayton                        symbolicated_frame_address_idx += 1
15848d157ddSGreg Clayton                else:
159a658ab9fSDavide Italiano                    print(frame)
16091d3f739SJonas Devlieghere            if self.registers:
16191d3f739SJonas Devlieghere                print()
16291d3f739SJonas Devlieghere                for reg in self.registers.keys():
16391d3f739SJonas Devlieghere                    print("    %-8s = %#16.16x" % (reg, self.registers[reg]))
164b913065bSJonas Devlieghere            elif self.crashed:
165b913065bSJonas Devlieghere               print()
166b913065bSJonas Devlieghere               print("No thread state (register information) available")
16748d157ddSGreg Clayton
168563d0393SGreg Clayton        def add_ident(self, ident):
169b9c1b51eSKate Stone            if ident not in self.idents:
170563d0393SGreg Clayton                self.idents.append(ident)
171563d0393SGreg Clayton
172aa9d02b1SGreg Clayton        def did_crash(self):
173b9c1b51eSKate Stone            return self.reason is not None
174aa9d02b1SGreg Clayton
175aa9d02b1SGreg Clayton        def __str__(self):
17648d157ddSGreg Clayton            if self.app_specific_backtrace:
17748d157ddSGreg Clayton                s = "Application Specific Backtrace[%u]" % self.index
17848d157ddSGreg Clayton            else:
179aa9d02b1SGreg Clayton                s = "Thread[%u]" % self.index
180aa9d02b1SGreg Clayton            if self.reason:
181aa9d02b1SGreg Clayton                s += ' %s' % self.reason
182aa9d02b1SGreg Clayton            return s
183aa9d02b1SGreg Clayton
184814ad734SDavide Italiano    class Frame:
185aa9d02b1SGreg Clayton        """Class that represents a stack frame in a thread in a darwin crash log"""
186b9c1b51eSKate Stone
1873c2c4bb2SGreg Clayton        def __init__(self, index, pc, description):
188aa9d02b1SGreg Clayton            self.pc = pc
1893c2c4bb2SGreg Clayton            self.description = description
1903c2c4bb2SGreg Clayton            self.index = index
191aa9d02b1SGreg Clayton
192aa9d02b1SGreg Clayton        def __str__(self):
1933c2c4bb2SGreg Clayton            if self.description:
194b9c1b51eSKate Stone                return "[%3u] 0x%16.16x %s" % (
195b9c1b51eSKate Stone                    self.index, self.pc, self.description)
1963c2c4bb2SGreg Clayton            else:
1973c2c4bb2SGreg Clayton                return "[%3u] 0x%16.16x" % (self.index, self.pc)
1988e1fd43aSJohnny Chen
1998e1fd43aSJohnny Chen        def dump(self, prefix):
200a658ab9fSDavide Italiano            print("%s%s" % (prefix, str(self)))
201aa9d02b1SGreg Clayton
202c8f73d7bSGreg Clayton    class DarwinImage(symbolication.Image):
203aa9d02b1SGreg Clayton        """Class that represents a binary images in a darwin crash log"""
204e3b5eba1SDavide Italiano        dsymForUUIDBinary = '/usr/local/bin/dsymForUUID'
2053d8d3db3SGreg Clayton        if not os.path.exists(dsymForUUIDBinary):
206f4d2fa3fSAdrian Prantl            try:
207f4d2fa3fSAdrian Prantl                dsymForUUIDBinary = subprocess.check_output('which dsymForUUID',
208573ffd88SAdrian Prantl                                                            shell=True).decode("utf-8").rstrip('\n')
209f4d2fa3fSAdrian Prantl            except:
210f4d2fa3fSAdrian Prantl                dsymForUUIDBinary = ""
2113d8d3db3SGreg Clayton
212b9c1b51eSKate Stone        dwarfdump_uuid_regex = re.compile(
213b9c1b51eSKate Stone            'UUID: ([-0-9a-fA-F]+) \(([^\(]+)\) .*')
214a32bfbeeSGreg Clayton
215b9c1b51eSKate Stone        def __init__(
216b9c1b51eSKate Stone                self,
217b9c1b51eSKate Stone                text_addr_lo,
218b9c1b51eSKate Stone                text_addr_hi,
219b9c1b51eSKate Stone                identifier,
220b9c1b51eSKate Stone                version,
221b9c1b51eSKate Stone                uuid,
22238be2c65SAdrian Prantl                path,
22338be2c65SAdrian Prantl                verbose):
224b9c1b51eSKate Stone            symbolication.Image.__init__(self, path, uuid)
225b9c1b51eSKate Stone            self.add_section(
226b9c1b51eSKate Stone                symbolication.Section(
227b9c1b51eSKate Stone                    text_addr_lo,
228b9c1b51eSKate Stone                    text_addr_hi,
229b9c1b51eSKate Stone                    "__TEXT"))
2303c2c4bb2SGreg Clayton            self.identifier = identifier
231aa9d02b1SGreg Clayton            self.version = version
23238be2c65SAdrian Prantl            self.verbose = verbose
23338be2c65SAdrian Prantl
23438be2c65SAdrian Prantl        def show_symbol_progress(self):
23538be2c65SAdrian Prantl            """
23638be2c65SAdrian Prantl            Hide progress output and errors from system frameworks as they are plentiful.
23738be2c65SAdrian Prantl            """
23838be2c65SAdrian Prantl            if self.verbose:
23938be2c65SAdrian Prantl                return True
24038be2c65SAdrian Prantl            return not (self.path.startswith("/System/Library/") or
24138be2c65SAdrian Prantl                        self.path.startswith("/usr/lib/"))
24238be2c65SAdrian Prantl
243aa9d02b1SGreg Clayton
244fb5aa932SAdrian Prantl        def find_matching_slice(self):
245a658ab9fSDavide Italiano            dwarfdump_cmd_output = subprocess.check_output(
246192dd7dfSDavide Italiano                'dwarfdump --uuid "%s"' % self.path, shell=True).decode("utf-8")
247fb5aa932SAdrian Prantl            self_uuid = self.get_uuid()
248fb5aa932SAdrian Prantl            for line in dwarfdump_cmd_output.splitlines():
249fb5aa932SAdrian Prantl                match = self.dwarfdump_uuid_regex.search(line)
250fb5aa932SAdrian Prantl                if match:
251fb5aa932SAdrian Prantl                    dwarf_uuid_str = match.group(1)
252fb5aa932SAdrian Prantl                    dwarf_uuid = uuid.UUID(dwarf_uuid_str)
253fb5aa932SAdrian Prantl                    if self_uuid == dwarf_uuid:
254fb5aa932SAdrian Prantl                        self.resolved_path = self.path
255fb5aa932SAdrian Prantl                        self.arch = match.group(2)
256fb5aa932SAdrian Prantl                        return True
257fb5aa932SAdrian Prantl            if not self.resolved_path:
258fb5aa932SAdrian Prantl                self.unavailable = True
25938be2c65SAdrian Prantl                if self.show_symbol_progress():
260a658ab9fSDavide Italiano                    print(("error\n    error: unable to locate '%s' with UUID %s"
261a658ab9fSDavide Italiano                           % (self.path, self.get_normalized_uuid_string())))
262fb5aa932SAdrian Prantl                return False
263fb5aa932SAdrian Prantl
2643c2c4bb2SGreg Clayton        def locate_module_and_debug_symbols(self):
2653d8d3db3SGreg Clayton            # Don't load a module twice...
266f51a23fbSGreg Clayton            if self.resolved:
267f99295c3SGreg Clayton                return True
268f51a23fbSGreg Clayton            # Mark this as resolved so we don't keep trying
269f51a23fbSGreg Clayton            self.resolved = True
27060bb58f6SGreg Clayton            uuid_str = self.get_normalized_uuid_string()
27138be2c65SAdrian Prantl            if self.show_symbol_progress():
272a8abb695SJonas Devlieghere                with print_lock:
273a8abb695SJonas Devlieghere                    print('Getting symbols for %s %s...' % (uuid_str, self.path))
274a32bfbeeSGreg Clayton            if os.path.exists(self.dsymForUUIDBinary):
275b9c1b51eSKate Stone                dsym_for_uuid_command = '%s %s' % (
276b9c1b51eSKate Stone                    self.dsymForUUIDBinary, uuid_str)
27768946d10SDavide Italiano                s = subprocess.check_output(dsym_for_uuid_command, shell=True)
278aa9d02b1SGreg Clayton                if s:
2799f44d460SJim Ingham                    try:
2808803124dSDavide Italiano                        plist_root = read_plist(s)
2819f44d460SJim Ingham                    except:
282a8abb695SJonas Devlieghere                        with print_lock:
2833577da76SSerge Guelton                            print(("Got exception: ", sys.exc_info()[1], " handling dsymForUUID output: \n", s))
2849f44d460SJim Ingham                        raise
285aa9d02b1SGreg Clayton                    if plist_root:
28660bb58f6SGreg Clayton                        plist = plist_root[uuid_str]
2873d8d3db3SGreg Clayton                        if plist:
2883d8d3db3SGreg Clayton                            if 'DBGArchitecture' in plist:
2893d8d3db3SGreg Clayton                                self.arch = plist['DBGArchitecture']
2903d8d3db3SGreg Clayton                            if 'DBGDSYMPath' in plist:
291b9c1b51eSKate Stone                                self.symfile = os.path.realpath(
292b9c1b51eSKate Stone                                    plist['DBGDSYMPath'])
2933d8d3db3SGreg Clayton                            if 'DBGSymbolRichExecutable' in plist:
294b9c1b51eSKate Stone                                self.path = os.path.expanduser(
295b9c1b51eSKate Stone                                    plist['DBGSymbolRichExecutable'])
29686e70cb3SGreg Clayton                                self.resolved_path = self.path
2973d8d3db3SGreg Clayton            if not self.resolved_path and os.path.exists(self.path):
298fb5aa932SAdrian Prantl                if not self.find_matching_slice():
299f99295c3SGreg Clayton                    return False
300fb5aa932SAdrian Prantl            if not self.resolved_path and not os.path.exists(self.path):
301fb5aa932SAdrian Prantl                try:
3024da5a446SJonas Devlieghere                    mdfind_results = subprocess.check_output(
303fb5aa932SAdrian Prantl                        ["/usr/bin/mdfind",
3044da5a446SJonas Devlieghere                         "com_apple_xcode_dsym_uuids == %s" % uuid_str]).decode("utf-8").splitlines()
3054da5a446SJonas Devlieghere                    found_matching_slice = False
3064da5a446SJonas Devlieghere                    for dsym in mdfind_results:
307fb5aa932SAdrian Prantl                        dwarf_dir = os.path.join(dsym, 'Contents/Resources/DWARF')
3084da5a446SJonas Devlieghere                        if not os.path.exists(dwarf_dir):
3094da5a446SJonas Devlieghere                            # Not a dSYM bundle, probably an Xcode archive.
3104da5a446SJonas Devlieghere                            continue
311a8abb695SJonas Devlieghere                        with print_lock:
3124da5a446SJonas Devlieghere                            print('falling back to binary inside "%s"' % dsym)
3134da5a446SJonas Devlieghere                        self.symfile = dsym
314fb5aa932SAdrian Prantl                        for filename in os.listdir(dwarf_dir):
315fb5aa932SAdrian Prantl                           self.path = os.path.join(dwarf_dir, filename)
3164da5a446SJonas Devlieghere                           if self.find_matching_slice():
3174da5a446SJonas Devlieghere                              found_matching_slice = True
3184da5a446SJonas Devlieghere                              break
3194da5a446SJonas Devlieghere                        if found_matching_slice:
320fb5aa932SAdrian Prantl                           break
321fb5aa932SAdrian Prantl                except:
322fb5aa932SAdrian Prantl                    pass
323b9c1b51eSKate Stone            if (self.resolved_path and os.path.exists(self.resolved_path)) or (
324b9c1b51eSKate Stone                    self.path and os.path.exists(self.path)):
325a8abb695SJonas Devlieghere                with print_lock:
326a8abb695SJonas Devlieghere                    print('Resolved symbols for %s %s...' % (uuid_str, self.path))
327f99295c3SGreg Clayton                return True
328f51a23fbSGreg Clayton            else:
329f51a23fbSGreg Clayton                self.unavailable = True
330f99295c3SGreg Clayton            return False
331aa9d02b1SGreg Clayton
332c29c24beSJonas Devlieghere    def __init__(self, debugger, path, verbose):
333aa9d02b1SGreg Clayton        """CrashLog constructor that take a path to a darwin crash log file"""
334c29c24beSJonas Devlieghere        symbolication.Symbolicator.__init__(self, debugger)
335b9c1b51eSKate Stone        self.path = os.path.expanduser(path)
336aa9d02b1SGreg Clayton        self.info_lines = list()
337aa9d02b1SGreg Clayton        self.system_profile = list()
338aa9d02b1SGreg Clayton        self.threads = list()
33948d157ddSGreg Clayton        self.backtraces = list()  # For application specific backtraces
340aa9d02b1SGreg Clayton        self.idents = list()  # A list of the required identifiers for doing all stack backtraces
341b225c5f7SJonas Devlieghere        self.errors = list()
342aa9d02b1SGreg Clayton        self.crashed_thread_idx = -1
343aa9d02b1SGreg Clayton        self.version = -1
34448d157ddSGreg Clayton        self.target = None
34538be2c65SAdrian Prantl        self.verbose = verbose
346aa9d02b1SGreg Clayton
347aa9d02b1SGreg Clayton    def dump(self):
348a658ab9fSDavide Italiano        print("Crash Log File: %s" % (self.path))
34948d157ddSGreg Clayton        if self.backtraces:
350a658ab9fSDavide Italiano            print("\nApplication Specific Backtraces:")
35148d157ddSGreg Clayton            for thread in self.backtraces:
35248d157ddSGreg Clayton                thread.dump('  ')
353a658ab9fSDavide Italiano        print("\nThreads:")
354aa9d02b1SGreg Clayton        for thread in self.threads:
355aa9d02b1SGreg Clayton            thread.dump('  ')
356a658ab9fSDavide Italiano        print("\nImages:")
357aa9d02b1SGreg Clayton        for image in self.images:
358aa9d02b1SGreg Clayton            image.dump('  ')
359aa9d02b1SGreg Clayton
3600f29319eSJonas Devlieghere    def set_main_image(self, identifier):
3610f29319eSJonas Devlieghere        for i, image in enumerate(self.images):
3620f29319eSJonas Devlieghere            if image.identifier == identifier:
3630f29319eSJonas Devlieghere                self.images.insert(0, self.images.pop(i))
3640f29319eSJonas Devlieghere                break
3650f29319eSJonas Devlieghere
3663c2c4bb2SGreg Clayton    def find_image_with_identifier(self, identifier):
367aa9d02b1SGreg Clayton        for image in self.images:
3683c2c4bb2SGreg Clayton            if image.identifier == identifier:
369aa9d02b1SGreg Clayton                return image
3706c42aa77SGreg Clayton        regex_text = '^.*\.%s$' % (re.escape(identifier))
37148d157ddSGreg Clayton        regex = re.compile(regex_text)
37248d157ddSGreg Clayton        for image in self.images:
37348d157ddSGreg Clayton            if regex.match(image.identifier):
37448d157ddSGreg Clayton                return image
375aa9d02b1SGreg Clayton        return None
376aa9d02b1SGreg Clayton
37742a6eb71SGreg Clayton    def create_target(self):
37848d157ddSGreg Clayton        if self.target is None:
37948d157ddSGreg Clayton            self.target = symbolication.Symbolicator.create_target(self)
38048d157ddSGreg Clayton            if self.target:
38148d157ddSGreg Clayton                return self.target
382b9c1b51eSKate Stone            # We weren't able to open the main executable as, but we can still
383b9c1b51eSKate Stone            # symbolicate
384a658ab9fSDavide Italiano            print('crashlog.create_target()...2')
38542a6eb71SGreg Clayton            if self.idents:
38688685f28SSean Callanan                for ident in self.idents:
38742a6eb71SGreg Clayton                    image = self.find_image_with_identifier(ident)
38842a6eb71SGreg Clayton                    if image:
389c29c24beSJonas Devlieghere                        self.target = image.create_target(self.debugger)
39048d157ddSGreg Clayton                        if self.target:
39148d157ddSGreg Clayton                            return self.target  # success
392a658ab9fSDavide Italiano            print('crashlog.create_target()...3')
39342a6eb71SGreg Clayton            for image in self.images:
394c29c24beSJonas Devlieghere                self.target = image.create_target(self.debugger)
39548d157ddSGreg Clayton                if self.target:
39648d157ddSGreg Clayton                    return self.target  # success
397a658ab9fSDavide Italiano            print('crashlog.create_target()...4')
39838be2c65SAdrian Prantl            print('error: Unable to locate any executables from the crash log.')
39938be2c65SAdrian Prantl            print('       Try loading the executable into lldb before running crashlog')
40038be2c65SAdrian Prantl            print('       and/or make sure the .dSYM bundles can be found by Spotlight.')
40148d157ddSGreg Clayton        return self.target
40242a6eb71SGreg Clayton
40348d157ddSGreg Clayton    def get_target(self):
40448d157ddSGreg Clayton        return self.target
405aa9d02b1SGreg Clayton
406b9c1b51eSKate Stone
407c7cbf32fSJonas Devlieghereclass CrashLogFormatException(Exception):
408c7cbf32fSJonas Devlieghere    pass
409c7cbf32fSJonas Devlieghere
410c7cbf32fSJonas Devlieghere
4118639e2aaSJonas Devlieghereclass CrashLogParseException(Exception):
4128639e2aaSJonas Devlieghere    pass
4138639e2aaSJonas Devlieghere
4148639e2aaSJonas Devlieghere
415c7cbf32fSJonas Devlieghereclass CrashLogParser:
416c7cbf32fSJonas Devlieghere    def parse(self, debugger, path, verbose):
417c7cbf32fSJonas Devlieghere        try:
418c7cbf32fSJonas Devlieghere            return JSONCrashLogParser(debugger, path, verbose).parse()
419c7cbf32fSJonas Devlieghere        except CrashLogFormatException:
420c7cbf32fSJonas Devlieghere            return TextCrashLogParser(debugger, path, verbose).parse()
421c7cbf32fSJonas Devlieghere
422c7cbf32fSJonas Devlieghere
423c7cbf32fSJonas Devlieghereclass JSONCrashLogParser:
424c7cbf32fSJonas Devlieghere    def __init__(self, debugger, path, verbose):
425c7cbf32fSJonas Devlieghere        self.path = os.path.expanduser(path)
426c7cbf32fSJonas Devlieghere        self.verbose = verbose
427c7cbf32fSJonas Devlieghere        self.crashlog = CrashLog(debugger, self.path, self.verbose)
428c7cbf32fSJonas Devlieghere
429343662a0SJonas Devlieghere    def parse_json(self, buffer):
430343662a0SJonas Devlieghere        try:
431343662a0SJonas Devlieghere            return json.loads(buffer)
432343662a0SJonas Devlieghere        except:
433343662a0SJonas Devlieghere            # The first line can contain meta data. Try stripping it and try
434343662a0SJonas Devlieghere            # again.
435343662a0SJonas Devlieghere            head, _, tail = buffer.partition('\n')
436343662a0SJonas Devlieghere            return json.loads(tail)
437343662a0SJonas Devlieghere
438c7cbf32fSJonas Devlieghere    def parse(self):
439c7cbf32fSJonas Devlieghere        with open(self.path, 'r') as f:
440c7cbf32fSJonas Devlieghere            buffer = f.read()
441c7cbf32fSJonas Devlieghere
442730fca46SJonas Devlieghere        try:
443343662a0SJonas Devlieghere            self.data = self.parse_json(buffer)
444343662a0SJonas Devlieghere        except:
445c7cbf32fSJonas Devlieghere            raise CrashLogFormatException()
446c7cbf32fSJonas Devlieghere
4478639e2aaSJonas Devlieghere        try:
448c7cbf32fSJonas Devlieghere            self.parse_process_info(self.data)
449c7cbf32fSJonas Devlieghere            self.parse_images(self.data['usedImages'])
4500f29319eSJonas Devlieghere            self.parse_main_image(self.data)
451c7cbf32fSJonas Devlieghere            self.parse_threads(self.data['threads'])
452b225c5f7SJonas Devlieghere            self.parse_errors(self.data)
453c7cbf32fSJonas Devlieghere            thread = self.crashlog.threads[self.crashlog.crashed_thread_idx]
454a62cbd9aSJonas Devlieghere            reason = self.parse_crash_reason(self.data['exception'])
455a62cbd9aSJonas Devlieghere            if thread.reason:
456a62cbd9aSJonas Devlieghere                thread.reason = '{} {}'.format(thread.reason, reason)
457a62cbd9aSJonas Devlieghere            else:
458a62cbd9aSJonas Devlieghere                thread.reason = reason
4598639e2aaSJonas Devlieghere        except (KeyError, ValueError, TypeError) as e:
4608639e2aaSJonas Devlieghere            raise CrashLogParseException(
4618639e2aaSJonas Devlieghere                'Failed to parse JSON crashlog: {}: {}'.format(
4628639e2aaSJonas Devlieghere                    type(e).__name__, e))
463c7cbf32fSJonas Devlieghere
464c7cbf32fSJonas Devlieghere        return self.crashlog
465c7cbf32fSJonas Devlieghere
466c7cbf32fSJonas Devlieghere    def get_used_image(self, idx):
467c7cbf32fSJonas Devlieghere        return self.data['usedImages'][idx]
468c7cbf32fSJonas Devlieghere
469c7cbf32fSJonas Devlieghere    def parse_process_info(self, json_data):
470c7cbf32fSJonas Devlieghere        self.crashlog.process_id = json_data['pid']
471c7cbf32fSJonas Devlieghere        self.crashlog.process_identifier = json_data['procName']
472c7cbf32fSJonas Devlieghere        self.crashlog.process_path = json_data['procPath']
473c7cbf32fSJonas Devlieghere
474c7cbf32fSJonas Devlieghere    def parse_crash_reason(self, json_exception):
475c7cbf32fSJonas Devlieghere        exception_type = json_exception['type']
47621658b77SMed Ismail Bennani        exception_signal = " "
47721658b77SMed Ismail Bennani        if 'signal' in json_exception:
47821658b77SMed Ismail Bennani            exception_signal += "({})".format(json_exception['signal'])
47921658b77SMed Ismail Bennani
480c7cbf32fSJonas Devlieghere        if 'codes' in json_exception:
481c7cbf32fSJonas Devlieghere            exception_extra = " ({})".format(json_exception['codes'])
482c7cbf32fSJonas Devlieghere        elif 'subtype' in json_exception:
483c7cbf32fSJonas Devlieghere            exception_extra = " ({})".format(json_exception['subtype'])
484c7cbf32fSJonas Devlieghere        else:
485c7cbf32fSJonas Devlieghere            exception_extra = ""
48621658b77SMed Ismail Bennani        return "{}{}{}".format(exception_type, exception_signal,
487c7cbf32fSJonas Devlieghere                                  exception_extra)
488c7cbf32fSJonas Devlieghere
489c7cbf32fSJonas Devlieghere    def parse_images(self, json_images):
490c7cbf32fSJonas Devlieghere        idx = 0
491cc52ea30SJonas Devlieghere        for json_image in json_images:
492cc52ea30SJonas Devlieghere            img_uuid = uuid.UUID(json_image['uuid'])
493cc52ea30SJonas Devlieghere            low = int(json_image['base'])
494cc52ea30SJonas Devlieghere            high = int(0)
4952cbd3b04SJonas Devlieghere            name = json_image['name'] if 'name' in json_image else ''
4962cbd3b04SJonas Devlieghere            path = json_image['path'] if 'path' in json_image else ''
4972cbd3b04SJonas Devlieghere            version = ''
498c7cbf32fSJonas Devlieghere            darwin_image = self.crashlog.DarwinImage(low, high, name, version,
499c7cbf32fSJonas Devlieghere                                                     img_uuid, path,
500c7cbf32fSJonas Devlieghere                                                     self.verbose)
501c7cbf32fSJonas Devlieghere            self.crashlog.images.append(darwin_image)
502c7cbf32fSJonas Devlieghere            idx += 1
503c7cbf32fSJonas Devlieghere
5040f29319eSJonas Devlieghere    def parse_main_image(self, json_data):
5050f29319eSJonas Devlieghere        if 'procName' in json_data:
5060f29319eSJonas Devlieghere            proc_name = json_data['procName']
5070f29319eSJonas Devlieghere            self.crashlog.set_main_image(proc_name)
5080f29319eSJonas Devlieghere
509c7cbf32fSJonas Devlieghere    def parse_frames(self, thread, json_frames):
510c7cbf32fSJonas Devlieghere        idx = 0
511c7cbf32fSJonas Devlieghere        for json_frame in json_frames:
512cc52ea30SJonas Devlieghere            image_id = int(json_frame['imageIndex'])
51305cdd294SJonas Devlieghere            json_image = self.get_used_image(image_id)
51405cdd294SJonas Devlieghere            ident = json_image['name'] if 'name' in json_image else ''
515c7cbf32fSJonas Devlieghere            thread.add_ident(ident)
516c7cbf32fSJonas Devlieghere            if ident not in self.crashlog.idents:
517c7cbf32fSJonas Devlieghere                self.crashlog.idents.append(ident)
518c7cbf32fSJonas Devlieghere
519cc52ea30SJonas Devlieghere            frame_offset = int(json_frame['imageOffset'])
520cc52ea30SJonas Devlieghere            image_addr = self.get_used_image(image_id)['base']
521c7cbf32fSJonas Devlieghere            pc = image_addr + frame_offset
522c7cbf32fSJonas Devlieghere            thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
5232be791e3SJason Molenda
5242be791e3SJason Molenda            # on arm64 systems, if it jump through a null function pointer,
5252be791e3SJason Molenda            # we end up at address 0 and the crash reporter unwinder
5262be791e3SJason Molenda            # misses the frame that actually faulted.
5272be791e3SJason Molenda            # But $lr can tell us where the last BL/BLR instruction used
5282be791e3SJason Molenda            # was at, so insert that address as the caller stack frame.
5292be791e3SJason Molenda            if idx == 0 and pc == 0 and "lr" in thread.registers:
5302be791e3SJason Molenda                pc = thread.registers["lr"]
5312be791e3SJason Molenda                for image in self.data['usedImages']:
5322be791e3SJason Molenda                    text_lo = image['base']
5332be791e3SJason Molenda                    text_hi = text_lo + image['size']
5342be791e3SJason Molenda                    if text_lo <= pc < text_hi:
5352be791e3SJason Molenda                      idx += 1
5362be791e3SJason Molenda                      frame_offset = pc - text_lo
5372be791e3SJason Molenda                      thread.frames.append(self.crashlog.Frame(idx, pc, frame_offset))
5382be791e3SJason Molenda                      break
5392be791e3SJason Molenda
540c7cbf32fSJonas Devlieghere            idx += 1
541c7cbf32fSJonas Devlieghere
542c7cbf32fSJonas Devlieghere    def parse_threads(self, json_threads):
543c7cbf32fSJonas Devlieghere        idx = 0
544c7cbf32fSJonas Devlieghere        for json_thread in json_threads:
545c7cbf32fSJonas Devlieghere            thread = self.crashlog.Thread(idx, False)
546a62cbd9aSJonas Devlieghere            if 'name' in json_thread:
54712301d61SMed Ismail Bennani                thread.name = json_thread['name']
548a62cbd9aSJonas Devlieghere                thread.reason = json_thread['name']
54912301d61SMed Ismail Bennani            if 'id' in json_thread:
55012301d61SMed Ismail Bennani                thread.id = int(json_thread['id'])
551c7cbf32fSJonas Devlieghere            if json_thread.get('triggered', False):
552c7cbf32fSJonas Devlieghere                self.crashlog.crashed_thread_idx = idx
553b913065bSJonas Devlieghere                thread.crashed = True
554b913065bSJonas Devlieghere                if 'threadState' in json_thread:
55591d3f739SJonas Devlieghere                    thread.registers = self.parse_thread_registers(
556cc52ea30SJonas Devlieghere                        json_thread['threadState'])
55712301d61SMed Ismail Bennani            if 'queue' in json_thread:
558c7cbf32fSJonas Devlieghere                thread.queue = json_thread.get('queue')
559c7cbf32fSJonas Devlieghere            self.parse_frames(thread, json_thread.get('frames', []))
560c7cbf32fSJonas Devlieghere            self.crashlog.threads.append(thread)
561c7cbf32fSJonas Devlieghere            idx += 1
562c7cbf32fSJonas Devlieghere
5639a9bf12cSMed Ismail Bennani    def parse_thread_registers(self, json_thread_state, prefix=None):
564c7cbf32fSJonas Devlieghere        registers = dict()
56591d3f739SJonas Devlieghere        for key, state in json_thread_state.items():
566e1cad130SJonas Devlieghere            if key == "rosetta":
567e1cad130SJonas Devlieghere                registers.update(self.parse_thread_registers(state))
568e1cad130SJonas Devlieghere                continue
5699a9bf12cSMed Ismail Bennani            if key == "x":
5709a9bf12cSMed Ismail Bennani                gpr_dict = { str(idx) : reg for idx,reg in enumerate(state) }
5719a9bf12cSMed Ismail Bennani                registers.update(self.parse_thread_registers(gpr_dict, key))
5729a9bf12cSMed Ismail Bennani                continue
57391d3f739SJonas Devlieghere            try:
57491d3f739SJonas Devlieghere                value = int(state['value'])
5752be791e3SJason Molenda                registers["{}{}".format(prefix or '',key)] = value
576e1cad130SJonas Devlieghere            except (KeyError, ValueError, TypeError):
57791d3f739SJonas Devlieghere                pass
578c7cbf32fSJonas Devlieghere        return registers
579c7cbf32fSJonas Devlieghere
580b225c5f7SJonas Devlieghere    def parse_errors(self, json_data):
581b225c5f7SJonas Devlieghere       if 'reportNotes' in json_data:
582b225c5f7SJonas Devlieghere          self.crashlog.errors = json_data['reportNotes']
583b225c5f7SJonas Devlieghere
584c7cbf32fSJonas Devlieghere
5854b846820SJonas Devlieghereclass CrashLogParseMode:
5864b846820SJonas Devlieghere    NORMAL = 0
5874b846820SJonas Devlieghere    THREAD = 1
5884b846820SJonas Devlieghere    IMAGES = 2
5894b846820SJonas Devlieghere    THREGS = 3
5904b846820SJonas Devlieghere    SYSTEM = 4
5914b846820SJonas Devlieghere    INSTRS = 5
5924b846820SJonas Devlieghere
5934b846820SJonas Devlieghere
594c7cbf32fSJonas Devlieghereclass TextCrashLogParser:
5954b846820SJonas Devlieghere    parent_process_regex = re.compile('^Parent Process:\s*(.*)\[(\d+)\]')
5964b846820SJonas Devlieghere    thread_state_regex = re.compile('^Thread ([0-9]+) crashed with')
5974b846820SJonas Devlieghere    thread_instrs_regex = re.compile('^Thread ([0-9]+) instruction stream')
5984b846820SJonas Devlieghere    thread_regex = re.compile('^Thread ([0-9]+)([^:]*):(.*)')
5994b846820SJonas Devlieghere    app_backtrace_regex = re.compile('^Application Specific Backtrace ([0-9]+)([^:]*):(.*)')
6004b846820SJonas Devlieghere    version = r'(\(.+\)|(arm|x86_)[0-9a-z]+)\s+'
6014b846820SJonas Devlieghere    frame_regex = re.compile(r'^([0-9]+)' r'\s'                # id
6024b846820SJonas Devlieghere                             r'+(.+?)'    r'\s+'               # img_name
6034b846820SJonas Devlieghere                             r'(' +version+ r')?'              # img_version
6044b846820SJonas Devlieghere                             r'(0x[0-9a-fA-F]{7}[0-9a-fA-F]+)' # addr
6054b846820SJonas Devlieghere                             r' +(.*)'                         # offs
6064b846820SJonas Devlieghere                            )
6074b846820SJonas Devlieghere    null_frame_regex = re.compile(r'^([0-9]+)\s+\?\?\?\s+(0{7}0+) +(.*)')
6084b846820SJonas Devlieghere    image_regex_uuid = re.compile(r'(0x[0-9a-fA-F]+)'            # img_lo
6094b846820SJonas Devlieghere                                  r'\s+' '-' r'\s+'              #   -
6104b846820SJonas Devlieghere                                  r'(0x[0-9a-fA-F]+)'     r'\s+' # img_hi
6114b846820SJonas Devlieghere                                  r'[+]?(.+?)'            r'\s+' # img_name
6124b846820SJonas Devlieghere                                  r'(' +version+ ')?'            # img_version
6134b846820SJonas Devlieghere                                  r'(<([-0-9a-fA-F]+)>\s+)?'     # img_uuid
6144b846820SJonas Devlieghere                                  r'(/.*)'                       # img_path
6154b846820SJonas Devlieghere                                 )
6164b846820SJonas Devlieghere
6174b846820SJonas Devlieghere
618c29c24beSJonas Devlieghere    def __init__(self, debugger, path, verbose):
6194b846820SJonas Devlieghere        self.path = os.path.expanduser(path)
6204b846820SJonas Devlieghere        self.verbose = verbose
6214b846820SJonas Devlieghere        self.thread = None
6224b846820SJonas Devlieghere        self.app_specific_backtrace = False
623c29c24beSJonas Devlieghere        self.crashlog = CrashLog(debugger, self.path, self.verbose)
62416dd6934SJonas Devlieghere        self.parse_mode = CrashLogParseMode.NORMAL
62516dd6934SJonas Devlieghere        self.parsers = {
62616dd6934SJonas Devlieghere            CrashLogParseMode.NORMAL : self.parse_normal,
62716dd6934SJonas Devlieghere            CrashLogParseMode.THREAD : self.parse_thread,
62816dd6934SJonas Devlieghere            CrashLogParseMode.IMAGES : self.parse_images,
62916dd6934SJonas Devlieghere            CrashLogParseMode.THREGS : self.parse_thread_registers,
63016dd6934SJonas Devlieghere            CrashLogParseMode.SYSTEM : self.parse_system,
63116dd6934SJonas Devlieghere            CrashLogParseMode.INSTRS : self.parse_instructions,
63216dd6934SJonas Devlieghere        }
6334b846820SJonas Devlieghere
6344b846820SJonas Devlieghere    def parse(self):
6354b846820SJonas Devlieghere        with open(self.path,'r') as f:
6364b846820SJonas Devlieghere            lines = f.read().splitlines()
6374b846820SJonas Devlieghere
6384b846820SJonas Devlieghere        for line in lines:
6394b846820SJonas Devlieghere            line_len = len(line)
6404b846820SJonas Devlieghere            if line_len == 0:
6414b846820SJonas Devlieghere                if self.thread:
6424b846820SJonas Devlieghere                    if self.parse_mode == CrashLogParseMode.THREAD:
6434b846820SJonas Devlieghere                        if self.thread.index == self.crashlog.crashed_thread_idx:
6444b846820SJonas Devlieghere                            self.thread.reason = ''
6454b846820SJonas Devlieghere                            if self.crashlog.thread_exception:
6464b846820SJonas Devlieghere                                self.thread.reason += self.crashlog.thread_exception
6474b846820SJonas Devlieghere                            if self.crashlog.thread_exception_data:
6484b846820SJonas Devlieghere                                self.thread.reason += " (%s)" % self.crashlog.thread_exception_data
6494b846820SJonas Devlieghere                        if self.app_specific_backtrace:
6504b846820SJonas Devlieghere                            self.crashlog.backtraces.append(self.thread)
6514b846820SJonas Devlieghere                        else:
6524b846820SJonas Devlieghere                            self.crashlog.threads.append(self.thread)
6534b846820SJonas Devlieghere                    self.thread = None
6544b846820SJonas Devlieghere                else:
6554b846820SJonas Devlieghere                    # only append an extra empty line if the previous line
6564b846820SJonas Devlieghere                    # in the info_lines wasn't empty
6574b846820SJonas Devlieghere                    if len(self.crashlog.info_lines) > 0 and len(self.crashlog.info_lines[-1]):
6584b846820SJonas Devlieghere                        self.crashlog.info_lines.append(line)
6594b846820SJonas Devlieghere                self.parse_mode = CrashLogParseMode.NORMAL
66016dd6934SJonas Devlieghere            else:
66116dd6934SJonas Devlieghere                self.parsers[self.parse_mode](line)
66216dd6934SJonas Devlieghere
66316dd6934SJonas Devlieghere        return self.crashlog
66416dd6934SJonas Devlieghere
66516dd6934SJonas Devlieghere
66616dd6934SJonas Devlieghere    def parse_normal(self, line):
6674b846820SJonas Devlieghere        if line.startswith('Process:'):
6684b846820SJonas Devlieghere            (self.crashlog.process_name, pid_with_brackets) = line[
6694b846820SJonas Devlieghere                8:].strip().split(' [')
6704b846820SJonas Devlieghere            self.crashlog.process_id = pid_with_brackets.strip('[]')
6714b846820SJonas Devlieghere        elif line.startswith('Path:'):
6724b846820SJonas Devlieghere            self.crashlog.process_path = line[5:].strip()
6734b846820SJonas Devlieghere        elif line.startswith('Identifier:'):
6744b846820SJonas Devlieghere            self.crashlog.process_identifier = line[11:].strip()
6754b846820SJonas Devlieghere        elif line.startswith('Version:'):
6764b846820SJonas Devlieghere            version_string = line[8:].strip()
6774b846820SJonas Devlieghere            matched_pair = re.search("(.+)\((.+)\)", version_string)
6784b846820SJonas Devlieghere            if matched_pair:
6794b846820SJonas Devlieghere                self.crashlog.process_version = matched_pair.group(1)
6804b846820SJonas Devlieghere                self.crashlog.process_compatability_version = matched_pair.group(
6814b846820SJonas Devlieghere                    2)
6824b846820SJonas Devlieghere            else:
6834b846820SJonas Devlieghere                self.crashlog.process = version_string
6844b846820SJonas Devlieghere                self.crashlog.process_compatability_version = version_string
6854b846820SJonas Devlieghere        elif self.parent_process_regex.search(line):
6864b846820SJonas Devlieghere            parent_process_match = self.parent_process_regex.search(
6874b846820SJonas Devlieghere                line)
6884b846820SJonas Devlieghere            self.crashlog.parent_process_name = parent_process_match.group(1)
6894b846820SJonas Devlieghere            self.crashlog.parent_process_id = parent_process_match.group(2)
6904b846820SJonas Devlieghere        elif line.startswith('Exception Type:'):
6914b846820SJonas Devlieghere            self.crashlog.thread_exception = line[15:].strip()
69216dd6934SJonas Devlieghere            return
6934b846820SJonas Devlieghere        elif line.startswith('Exception Codes:'):
6944b846820SJonas Devlieghere            self.crashlog.thread_exception_data = line[16:].strip()
69516dd6934SJonas Devlieghere            return
6964b846820SJonas Devlieghere        elif line.startswith('Exception Subtype:'): # iOS
6974b846820SJonas Devlieghere            self.crashlog.thread_exception_data = line[18:].strip()
69816dd6934SJonas Devlieghere            return
6994b846820SJonas Devlieghere        elif line.startswith('Crashed Thread:'):
7004b846820SJonas Devlieghere            self.crashlog.crashed_thread_idx = int(line[15:].strip().split()[0])
70116dd6934SJonas Devlieghere            return
7024b846820SJonas Devlieghere        elif line.startswith('Triggered by Thread:'): # iOS
7034b846820SJonas Devlieghere            self.crashlog.crashed_thread_idx = int(line[20:].strip().split()[0])
70416dd6934SJonas Devlieghere            return
7054b846820SJonas Devlieghere        elif line.startswith('Report Version:'):
7064b846820SJonas Devlieghere            self.crashlog.version = int(line[15:].strip())
70716dd6934SJonas Devlieghere            return
7084b846820SJonas Devlieghere        elif line.startswith('System Profile:'):
7094b846820SJonas Devlieghere            self.parse_mode = CrashLogParseMode.SYSTEM
71016dd6934SJonas Devlieghere            return
7114b846820SJonas Devlieghere        elif (line.startswith('Interval Since Last Report:') or
7124b846820SJonas Devlieghere                line.startswith('Crashes Since Last Report:') or
7134b846820SJonas Devlieghere                line.startswith('Per-App Interval Since Last Report:') or
7144b846820SJonas Devlieghere                line.startswith('Per-App Crashes Since Last Report:') or
7154b846820SJonas Devlieghere                line.startswith('Sleep/Wake UUID:') or
7164b846820SJonas Devlieghere                line.startswith('Anonymous UUID:')):
7174b846820SJonas Devlieghere            # ignore these
71816dd6934SJonas Devlieghere            return
7194b846820SJonas Devlieghere        elif line.startswith('Thread'):
7204b846820SJonas Devlieghere            thread_state_match = self.thread_state_regex.search(line)
7214b846820SJonas Devlieghere            if thread_state_match:
7224b846820SJonas Devlieghere                self.app_specific_backtrace = False
7234b846820SJonas Devlieghere                thread_state_match = self.thread_regex.search(line)
7244b846820SJonas Devlieghere                thread_idx = int(thread_state_match.group(1))
7254b846820SJonas Devlieghere                self.parse_mode = CrashLogParseMode.THREGS
7264b846820SJonas Devlieghere                self.thread = self.crashlog.threads[thread_idx]
72716dd6934SJonas Devlieghere                return
7284b846820SJonas Devlieghere            thread_insts_match  = self.thread_instrs_regex.search(line)
7294b846820SJonas Devlieghere            if thread_insts_match:
7304b846820SJonas Devlieghere                self.parse_mode = CrashLogParseMode.INSTRS
73116dd6934SJonas Devlieghere                return
7324b846820SJonas Devlieghere            thread_match = self.thread_regex.search(line)
7334b846820SJonas Devlieghere            if thread_match:
7344b846820SJonas Devlieghere                self.app_specific_backtrace = False
7354b846820SJonas Devlieghere                self.parse_mode = CrashLogParseMode.THREAD
7364b846820SJonas Devlieghere                thread_idx = int(thread_match.group(1))
7374b846820SJonas Devlieghere                self.thread = self.crashlog.Thread(thread_idx, False)
73816dd6934SJonas Devlieghere                return
73916dd6934SJonas Devlieghere            return
7404b846820SJonas Devlieghere        elif line.startswith('Binary Images:'):
7414b846820SJonas Devlieghere            self.parse_mode = CrashLogParseMode.IMAGES
74216dd6934SJonas Devlieghere            return
7434b846820SJonas Devlieghere        elif line.startswith('Application Specific Backtrace'):
7444b846820SJonas Devlieghere            app_backtrace_match = self.app_backtrace_regex.search(line)
7454b846820SJonas Devlieghere            if app_backtrace_match:
7464b846820SJonas Devlieghere                self.parse_mode = CrashLogParseMode.THREAD
7474b846820SJonas Devlieghere                self.app_specific_backtrace = True
7484b846820SJonas Devlieghere                idx = int(app_backtrace_match.group(1))
7494b846820SJonas Devlieghere                self.thread = self.crashlog.Thread(idx, True)
7504b846820SJonas Devlieghere        elif line.startswith('Last Exception Backtrace:'): # iOS
7514b846820SJonas Devlieghere            self.parse_mode = CrashLogParseMode.THREAD
7524b846820SJonas Devlieghere            self.app_specific_backtrace = True
7534b846820SJonas Devlieghere            idx = 1
7544b846820SJonas Devlieghere            self.thread = self.crashlog.Thread(idx, True)
7554b846820SJonas Devlieghere        self.crashlog.info_lines.append(line.strip())
75616dd6934SJonas Devlieghere
75716dd6934SJonas Devlieghere    def parse_thread(self, line):
7584b846820SJonas Devlieghere        if line.startswith('Thread'):
75916dd6934SJonas Devlieghere            return
7604b846820SJonas Devlieghere        if self.null_frame_regex.search(line):
7614b846820SJonas Devlieghere            print('warning: thread parser ignored null-frame: "%s"' % line)
76216dd6934SJonas Devlieghere            return
7634b846820SJonas Devlieghere        frame_match = self.frame_regex.search(line)
7644b846820SJonas Devlieghere        if frame_match:
7654b846820SJonas Devlieghere            (frame_id, frame_img_name, _, frame_img_version, _,
7664b846820SJonas Devlieghere                frame_addr, frame_ofs) = frame_match.groups()
7674b846820SJonas Devlieghere            ident = frame_img_name
7684b846820SJonas Devlieghere            self.thread.add_ident(ident)
7694b846820SJonas Devlieghere            if ident not in self.crashlog.idents:
7704b846820SJonas Devlieghere                self.crashlog.idents.append(ident)
7714b846820SJonas Devlieghere            self.thread.frames.append(self.crashlog.Frame(int(frame_id), int(
7724b846820SJonas Devlieghere                frame_addr, 0), frame_ofs))
7734b846820SJonas Devlieghere        else:
7744b846820SJonas Devlieghere            print('error: frame regex failed for line: "%s"' % line)
77516dd6934SJonas Devlieghere
77616dd6934SJonas Devlieghere    def parse_images(self, line):
7774b846820SJonas Devlieghere        image_match = self.image_regex_uuid.search(line)
7784b846820SJonas Devlieghere        if image_match:
7794b846820SJonas Devlieghere            (img_lo, img_hi, img_name, _, img_version, _,
7804b846820SJonas Devlieghere                _, img_uuid, img_path) = image_match.groups()
7814b846820SJonas Devlieghere            image = self.crashlog.DarwinImage(int(img_lo, 0), int(img_hi, 0),
7824b846820SJonas Devlieghere                                            img_name.strip(),
7834b846820SJonas Devlieghere                                            img_version.strip()
7844b846820SJonas Devlieghere                                            if img_version else "",
7854b846820SJonas Devlieghere                                            uuid.UUID(img_uuid), img_path,
7864b846820SJonas Devlieghere                                            self.verbose)
7874b846820SJonas Devlieghere            self.crashlog.images.append(image)
7884b846820SJonas Devlieghere        else:
7894b846820SJonas Devlieghere            print("error: image regex failed for: %s" % line)
7904b846820SJonas Devlieghere
79116dd6934SJonas Devlieghere
79216dd6934SJonas Devlieghere    def parse_thread_registers(self, line):
7934b846820SJonas Devlieghere        stripped_line = line.strip()
7944b846820SJonas Devlieghere        # "r12: 0x00007fff6b5939c8  r13: 0x0000000007000006  r14: 0x0000000000002a03  r15: 0x0000000000000c00"
7954b846820SJonas Devlieghere        reg_values = re.findall(
7964b846820SJonas Devlieghere            '([a-zA-Z0-9]+: 0[Xx][0-9a-fA-F]+) *', stripped_line)
7974b846820SJonas Devlieghere        for reg_value in reg_values:
7984b846820SJonas Devlieghere            (reg, value) = reg_value.split(': ')
7994b846820SJonas Devlieghere            self.thread.registers[reg.strip()] = int(value, 0)
80016dd6934SJonas Devlieghere
80116dd6934SJonas Devlieghere    def parse_system(self, line):
8024b846820SJonas Devlieghere        self.crashlog.system_profile.append(line)
80316dd6934SJonas Devlieghere
80416dd6934SJonas Devlieghere    def parse_instructions(self, line):
8054b846820SJonas Devlieghere        pass
8064b846820SJonas Devlieghere
8074b846820SJonas Devlieghere
808aa9d02b1SGreg Claytondef usage():
809a658ab9fSDavide Italiano    print("Usage: lldb-symbolicate.py [-n name] executable-image")
810aa9d02b1SGreg Clayton    sys.exit(0)
811aa9d02b1SGreg Clayton
812b9c1b51eSKate Stone
8138bebdff4SJim Inghamdef save_crashlog(debugger, command, exe_ctx, result, dict):
814f47b2c26SGreg Clayton    usage = "usage: %prog [options] <output-path>"
815f47b2c26SGreg Clayton    description = '''Export the state of current target into a crashlog file'''
816b9c1b51eSKate Stone    parser = optparse.OptionParser(
817b9c1b51eSKate Stone        description=description,
818b9c1b51eSKate Stone        prog='save_crashlog',
819b9c1b51eSKate Stone        usage=usage)
820b9c1b51eSKate Stone    parser.add_option(
821b9c1b51eSKate Stone        '-v',
822b9c1b51eSKate Stone        '--verbose',
823b9c1b51eSKate Stone        action='store_true',
824b9c1b51eSKate Stone        dest='verbose',
825b9c1b51eSKate Stone        help='display verbose debug info',
826b9c1b51eSKate Stone        default=False)
827f47b2c26SGreg Clayton    try:
828f47b2c26SGreg Clayton        (options, args) = parser.parse_args(shlex.split(command))
829f47b2c26SGreg Clayton    except:
830b9c1b51eSKate Stone        result.PutCString("error: invalid options")
831f47b2c26SGreg Clayton        return
832f47b2c26SGreg Clayton    if len(args) != 1:
833b9c1b51eSKate Stone        result.PutCString(
834b9c1b51eSKate Stone            "error: invalid arguments, a single output file is the only valid argument")
835f47b2c26SGreg Clayton        return
836f47b2c26SGreg Clayton    out_file = open(args[0], 'w')
837f47b2c26SGreg Clayton    if not out_file:
838b9c1b51eSKate Stone        result.PutCString(
839b9c1b51eSKate Stone            "error: failed to open file '%s' for writing...",
840b9c1b51eSKate Stone            args[0])
841f47b2c26SGreg Clayton        return
8428bebdff4SJim Ingham    target = exe_ctx.target
843c917a381SJim Ingham    if target:
844c917a381SJim Ingham        identifier = target.executable.basename
8458bebdff4SJim Ingham        process = exe_ctx.process
8468bebdff4SJim Ingham        if process:
8478bebdff4SJim Ingham            pid = process.id
848f47b2c26SGreg Clayton            if pid != lldb.LLDB_INVALID_PROCESS_ID:
849b9c1b51eSKate Stone                out_file.write(
850b9c1b51eSKate Stone                    'Process:         %s [%u]\n' %
851b9c1b51eSKate Stone                    (identifier, pid))
852c917a381SJim Ingham        out_file.write('Path:            %s\n' % (target.executable.fullpath))
853f47b2c26SGreg Clayton        out_file.write('Identifier:      %s\n' % (identifier))
854b9c1b51eSKate Stone        out_file.write('\nDate/Time:       %s\n' %
855b9c1b51eSKate Stone                       (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
856b9c1b51eSKate Stone        out_file.write(
857b9c1b51eSKate Stone            'OS Version:      Mac OS X %s (%s)\n' %
858573ffd88SAdrian Prantl            (platform.mac_ver()[0], subprocess.check_output('sysctl -n kern.osversion', shell=True).decode("utf-8")))
859f47b2c26SGreg Clayton        out_file.write('Report Version:  9\n')
8608bebdff4SJim Ingham        for thread_idx in range(process.num_threads):
8618bebdff4SJim Ingham            thread = process.thread[thread_idx]
862f47b2c26SGreg Clayton            out_file.write('\nThread %u:\n' % (thread_idx))
863f47b2c26SGreg Clayton            for (frame_idx, frame) in enumerate(thread.frames):
864f47b2c26SGreg Clayton                frame_pc = frame.pc
865f47b2c26SGreg Clayton                frame_offset = 0
866f47b2c26SGreg Clayton                if frame.function:
867f47b2c26SGreg Clayton                    block = frame.GetFrameBlock()
868f47b2c26SGreg Clayton                    block_range = block.range[frame.addr]
869f47b2c26SGreg Clayton                    if block_range:
870f47b2c26SGreg Clayton                        block_start_addr = block_range[0]
871723a1caaSJim Ingham                        frame_offset = frame_pc - block_start_addr.GetLoadAddress(target)
872f47b2c26SGreg Clayton                    else:
873723a1caaSJim Ingham                        frame_offset = frame_pc - frame.function.addr.GetLoadAddress(target)
874f47b2c26SGreg Clayton                elif frame.symbol:
875723a1caaSJim Ingham                    frame_offset = frame_pc - frame.symbol.addr.GetLoadAddress(target)
876b9c1b51eSKate Stone                out_file.write(
877b9c1b51eSKate Stone                    '%-3u %-32s 0x%16.16x %s' %
878b9c1b51eSKate Stone                    (frame_idx, frame.module.file.basename, frame_pc, frame.name))
879f47b2c26SGreg Clayton                if frame_offset > 0:
880f47b2c26SGreg Clayton                    out_file.write(' + %u' % (frame_offset))
881f47b2c26SGreg Clayton                line_entry = frame.line_entry
882f47b2c26SGreg Clayton                if line_entry:
883f47b2c26SGreg Clayton                    if options.verbose:
884f47b2c26SGreg Clayton                        # This will output the fullpath + line + column
885f47b2c26SGreg Clayton                        out_file.write(' %s' % (line_entry))
886f47b2c26SGreg Clayton                    else:
887b9c1b51eSKate Stone                        out_file.write(
888b9c1b51eSKate Stone                            ' %s:%u' %
889b9c1b51eSKate Stone                            (line_entry.file.basename, line_entry.line))
890f47b2c26SGreg Clayton                        column = line_entry.column
891f47b2c26SGreg Clayton                        if column:
892f47b2c26SGreg Clayton                            out_file.write(':%u' % (column))
893f47b2c26SGreg Clayton                out_file.write('\n')
894f47b2c26SGreg Clayton
895f47b2c26SGreg Clayton        out_file.write('\nBinary Images:\n')
896c917a381SJim Ingham        for module in target.modules:
897f47b2c26SGreg Clayton            text_segment = module.section['__TEXT']
898f47b2c26SGreg Clayton            if text_segment:
899c917a381SJim Ingham                text_segment_load_addr = text_segment.GetLoadAddress(target)
900f47b2c26SGreg Clayton                if text_segment_load_addr != lldb.LLDB_INVALID_ADDRESS:
901f47b2c26SGreg Clayton                    text_segment_end_load_addr = text_segment_load_addr + text_segment.size
902f47b2c26SGreg Clayton                    identifier = module.file.basename
903f47b2c26SGreg Clayton                    module_version = '???'
904f47b2c26SGreg Clayton                    module_version_array = module.GetVersion()
905f47b2c26SGreg Clayton                    if module_version_array:
906b9c1b51eSKate Stone                        module_version = '.'.join(
907b9c1b51eSKate Stone                            map(str, module_version_array))
908b9c1b51eSKate Stone                    out_file.write(
909b9c1b51eSKate Stone                        '    0x%16.16x - 0x%16.16x  %s (%s - ???) <%s> %s\n' %
910b9c1b51eSKate Stone                        (text_segment_load_addr,
911b9c1b51eSKate Stone                         text_segment_end_load_addr,
912b9c1b51eSKate Stone                         identifier,
913b9c1b51eSKate Stone                         module_version,
914b9c1b51eSKate Stone                         module.GetUUIDString(),
915b9c1b51eSKate Stone                         module.file.fullpath))
916f47b2c26SGreg Clayton        out_file.close()
917f47b2c26SGreg Clayton    else:
918b9c1b51eSKate Stone        result.PutCString("error: invalid target")
919f47b2c26SGreg Clayton
920f47b2c26SGreg Clayton
921cb5ea132SDave Leeclass Symbolicate:
922cb5ea132SDave Lee    def __init__(self, debugger, internal_dict):
923cb5ea132SDave Lee        pass
924cb5ea132SDave Lee
925cb5ea132SDave Lee    def __call__(self, debugger, command, exe_ctx, result):
926c29c24beSJonas Devlieghere        SymbolicateCrashLogs(debugger, shlex.split(command))
927aa9d02b1SGreg Clayton
928cb5ea132SDave Lee    def get_short_help(self):
929cb5ea132SDave Lee        return "Symbolicate one or more darwin crash log files."
930cb5ea132SDave Lee
931cb5ea132SDave Lee    def get_long_help(self):
932cb5ea132SDave Lee        option_parser = CrashLogOptionParser()
933cb5ea132SDave Lee        return option_parser.format_help()
934cb5ea132SDave Lee
935b9c1b51eSKate Stone
936eb749096SGreg Claytondef SymbolicateCrashLog(crash_log, options):
937a7fb1dcdSGreg Clayton    if options.debug:
938aa9d02b1SGreg Clayton        crash_log.dump()
9395c0f483eSGreg Clayton    if not crash_log.images:
940a658ab9fSDavide Italiano        print('error: no images in crash log')
9415c0f483eSGreg Clayton        return
9425c0f483eSGreg Clayton
9431f746071SGreg Clayton    if options.dump_image_list:
944a658ab9fSDavide Italiano        print("Binary Images:")
9451f746071SGreg Clayton        for image in crash_log.images:
9461f746071SGreg Clayton            if options.verbose:
947a658ab9fSDavide Italiano                print(image.debug_dump())
9481f746071SGreg Clayton            else:
949a658ab9fSDavide Italiano                print(image)
9501f746071SGreg Clayton
9513c2c4bb2SGreg Clayton    target = crash_log.create_target()
9523c2c4bb2SGreg Clayton    if not target:
9535c0f483eSGreg Clayton        return
9543c2c4bb2SGreg Clayton    exe_module = target.GetModuleAtIndex(0)
9555c0f483eSGreg Clayton    images_to_load = list()
9563c2c4bb2SGreg Clayton    loaded_images = list()
9575c0f483eSGreg Clayton    if options.load_all_images:
9585c0f483eSGreg Clayton        # --load-all option was specified, load everything up
9595c0f483eSGreg Clayton        for image in crash_log.images:
9605c0f483eSGreg Clayton            images_to_load.append(image)
9615c0f483eSGreg Clayton    else:
9625c0f483eSGreg Clayton        # Only load the images found in stack frames for the crashed threads
963563d0393SGreg Clayton        if options.crashed_only:
964563d0393SGreg Clayton            for thread in crash_log.threads:
965563d0393SGreg Clayton                if thread.did_crash():
966563d0393SGreg Clayton                    for ident in thread.idents:
967563d0393SGreg Clayton                        images = crash_log.find_images_with_identifier(ident)
968563d0393SGreg Clayton                        if images:
969563d0393SGreg Clayton                            for image in images:
970563d0393SGreg Clayton                                images_to_load.append(image)
971563d0393SGreg Clayton                        else:
972a658ab9fSDavide Italiano                            print('error: can\'t find image for identifier "%s"' % ident)
973563d0393SGreg Clayton        else:
974aa9d02b1SGreg Clayton            for ident in crash_log.idents:
9753c2c4bb2SGreg Clayton                images = crash_log.find_images_with_identifier(ident)
9763c2c4bb2SGreg Clayton                if images:
9773c2c4bb2SGreg Clayton                    for image in images:
9785c0f483eSGreg Clayton                        images_to_load.append(image)
9795c0f483eSGreg Clayton                else:
980a658ab9fSDavide Italiano                    print('error: can\'t find image for identifier "%s"' % ident)
9815c0f483eSGreg Clayton
982a8abb695SJonas Devlieghere    futures = []
983a8abb695SJonas Devlieghere    with concurrent.futures.ThreadPoolExecutor() as executor:
984a8abb695SJonas Devlieghere        def add_module(image, target):
985a8abb695SJonas Devlieghere            return image, image.add_module(target)
986a8abb695SJonas Devlieghere
9875c0f483eSGreg Clayton        for image in images_to_load:
988a8abb695SJonas Devlieghere            futures.append(executor.submit(add_module, image=image, target=target))
989a8abb695SJonas Devlieghere
990a8abb695SJonas Devlieghere        for future in concurrent.futures.as_completed(futures):
991a8abb695SJonas Devlieghere            image, err = future.result()
992aa9d02b1SGreg Clayton            if err:
993a658ab9fSDavide Italiano                print(err)
994aa9d02b1SGreg Clayton            else:
9953c2c4bb2SGreg Clayton                loaded_images.append(image)
996aa9d02b1SGreg Clayton
99748d157ddSGreg Clayton    if crash_log.backtraces:
99848d157ddSGreg Clayton        for thread in crash_log.backtraces:
99948d157ddSGreg Clayton            thread.dump_symbolicated(crash_log, options)
1000a658ab9fSDavide Italiano            print()
1001f67002ddSGreg Clayton
100248d157ddSGreg Clayton    for thread in crash_log.threads:
100348d157ddSGreg Clayton        thread.dump_symbolicated(crash_log, options)
1004a658ab9fSDavide Italiano        print()
100548d157ddSGreg Clayton
1006b225c5f7SJonas Devlieghere    if crash_log.errors:
1007b225c5f7SJonas Devlieghere        print("Errors:")
1008b225c5f7SJonas Devlieghere        for error in crash_log.errors:
1009b225c5f7SJonas Devlieghere            print(error)
1010b225c5f7SJonas Devlieghere
10110a65112cSMed Ismail Bennanidef load_crashlog_in_scripted_process(debugger, crash_log_file, options):
10127c54ffdcSMed Ismail Bennani    result = lldb.SBCommandReturnObject()
10137c54ffdcSMed Ismail Bennani
10147c54ffdcSMed Ismail Bennani    crashlog_path = os.path.expanduser(crash_log_file)
10157c54ffdcSMed Ismail Bennani    if not os.path.exists(crashlog_path):
10167c54ffdcSMed Ismail Bennani        result.PutCString("error: crashlog file %s does not exist" % crashlog_path)
10177c54ffdcSMed Ismail Bennani
10187c54ffdcSMed Ismail Bennani    crashlog = CrashLogParser().parse(debugger, crashlog_path, False)
10197c54ffdcSMed Ismail Bennani
1020badb6e27SMed Ismail Bennani    if debugger.GetNumTargets() > 0:
1021badb6e27SMed Ismail Bennani        target = debugger.GetTargetAtIndex(0)
1022badb6e27SMed Ismail Bennani    else:
10237c54ffdcSMed Ismail Bennani        target = crashlog.create_target()
10247c54ffdcSMed Ismail Bennani    if not target:
10257c54ffdcSMed Ismail Bennani        result.PutCString("error: couldn't create target")
10267c54ffdcSMed Ismail Bennani        return
10277c54ffdcSMed Ismail Bennani
10287c54ffdcSMed Ismail Bennani    ci = debugger.GetCommandInterpreter()
10297c54ffdcSMed Ismail Bennani    if not ci:
10307c54ffdcSMed Ismail Bennani        result.PutCString("error: couldn't get command interpreter")
10317c54ffdcSMed Ismail Bennani        return
10327c54ffdcSMed Ismail Bennani
10337c54ffdcSMed Ismail Bennani    res = lldb.SBCommandReturnObject()
10347c54ffdcSMed Ismail Bennani    ci.HandleCommand('script from lldb.macosx import crashlog_scripted_process', res)
10357c54ffdcSMed Ismail Bennani    if not res.Succeeded():
10367c54ffdcSMed Ismail Bennani        result.PutCString("error: couldn't import crashlog scripted process module")
10377c54ffdcSMed Ismail Bennani        return
10387c54ffdcSMed Ismail Bennani
10397c54ffdcSMed Ismail Bennani    structured_data = lldb.SBStructuredData()
10400a65112cSMed Ismail Bennani    structured_data.SetFromJSON(json.dumps({ "crashlog_path" : crashlog_path,
10410a65112cSMed Ismail Bennani                                             "load_all_images": options.load_all_images }))
10427c54ffdcSMed Ismail Bennani    launch_info = lldb.SBLaunchInfo(None)
10437c54ffdcSMed Ismail Bennani    launch_info.SetProcessPluginName("ScriptedProcess")
10447c54ffdcSMed Ismail Bennani    launch_info.SetScriptedProcessClassName("crashlog_scripted_process.CrashLogScriptedProcess")
10457c54ffdcSMed Ismail Bennani    launch_info.SetScriptedProcessDictionary(structured_data)
10467c54ffdcSMed Ismail Bennani    error = lldb.SBError()
10477c54ffdcSMed Ismail Bennani    process = target.Launch(launch_info, error)
1048aa9d02b1SGreg Clayton
10498d097a6bSMed Ismail Bennani    if not process or error.Fail():
10508d097a6bSMed Ismail Bennani        return
10518d097a6bSMed Ismail Bennani
10528d097a6bSMed Ismail Bennani    @contextlib.contextmanager
10538d097a6bSMed Ismail Bennani    def synchronous(debugger):
10548d097a6bSMed Ismail Bennani        async_state = debugger.GetAsync()
10558d097a6bSMed Ismail Bennani        debugger.SetAsync(False)
10568d097a6bSMed Ismail Bennani        try:
10578d097a6bSMed Ismail Bennani            yield
10588d097a6bSMed Ismail Bennani        finally:
10598d097a6bSMed Ismail Bennani            debugger.SetAsync(async_state)
10608d097a6bSMed Ismail Bennani
10618d097a6bSMed Ismail Bennani    with synchronous(debugger):
10628d097a6bSMed Ismail Bennani        run_options = lldb.SBCommandInterpreterRunOptions()
10638d097a6bSMed Ismail Bennani        run_options.SetStopOnError(True)
10648d097a6bSMed Ismail Bennani        run_options.SetStopOnCrash(True)
10658d097a6bSMed Ismail Bennani        run_options.SetEchoCommands(True)
10668d097a6bSMed Ismail Bennani
10678d097a6bSMed Ismail Bennani        commands_stream = lldb.SBStream()
10688d097a6bSMed Ismail Bennani        commands_stream.Print("process status\n")
10698d097a6bSMed Ismail Bennani        commands_stream.Print("thread backtrace\n")
10708d097a6bSMed Ismail Bennani        error = debugger.SetInputString(commands_stream.GetData())
10718d097a6bSMed Ismail Bennani        if error.Success():
10728d097a6bSMed Ismail Bennani            debugger.RunCommandInterpreter(True, False, run_options, 0, False, True)
10738d097a6bSMed Ismail Bennani
1074b9c1b51eSKate Stonedef CreateSymbolicateCrashLogOptions(
1075b9c1b51eSKate Stone        command_name,
1076b9c1b51eSKate Stone        description,
1077b9c1b51eSKate Stone        add_interactive_options):
1078eb749096SGreg Clayton    usage = "usage: %prog [options] <FILE> [FILE ...]"
1079b9c1b51eSKate Stone    option_parser = optparse.OptionParser(
1080b9c1b51eSKate Stone        description=description, prog='crashlog', usage=usage)
1081b9c1b51eSKate Stone    option_parser.add_option(
1082b9c1b51eSKate Stone        '--verbose',
1083b9c1b51eSKate Stone        '-v',
1084b9c1b51eSKate Stone        action='store_true',
1085b9c1b51eSKate Stone        dest='verbose',
1086b9c1b51eSKate Stone        help='display verbose debug info',
1087b9c1b51eSKate Stone        default=False)
1088b9c1b51eSKate Stone    option_parser.add_option(
1089b9c1b51eSKate Stone        '--debug',
1090b9c1b51eSKate Stone        '-g',
1091b9c1b51eSKate Stone        action='store_true',
1092b9c1b51eSKate Stone        dest='debug',
1093b9c1b51eSKate Stone        help='display verbose debug logging',
1094b9c1b51eSKate Stone        default=False)
1095b9c1b51eSKate Stone    option_parser.add_option(
1096b9c1b51eSKate Stone        '--load-all',
1097b9c1b51eSKate Stone        '-a',
1098b9c1b51eSKate Stone        action='store_true',
1099b9c1b51eSKate Stone        dest='load_all_images',
11000a65112cSMed Ismail Bennani        help='load all executable images, not just the images found in the '
11010a65112cSMed Ismail Bennani        'crashed stack frames, loads stackframes for all the threads in '
11020a65112cSMed Ismail Bennani        'interactive mode.',
1103b9c1b51eSKate Stone        default=False)
1104b9c1b51eSKate Stone    option_parser.add_option(
1105b9c1b51eSKate Stone        '--images',
1106b9c1b51eSKate Stone        action='store_true',
1107b9c1b51eSKate Stone        dest='dump_image_list',
1108b9c1b51eSKate Stone        help='show image list',
1109b9c1b51eSKate Stone        default=False)
1110b9c1b51eSKate Stone    option_parser.add_option(
1111b9c1b51eSKate Stone        '--debug-delay',
1112b9c1b51eSKate Stone        type='int',
1113b9c1b51eSKate Stone        dest='debug_delay',
1114b9c1b51eSKate Stone        metavar='NSEC',
1115b9c1b51eSKate Stone        help='pause for NSEC seconds for debugger',
1116b9c1b51eSKate Stone        default=0)
1117b9c1b51eSKate Stone    option_parser.add_option(
1118b9c1b51eSKate Stone        '--crashed-only',
1119b9c1b51eSKate Stone        '-c',
1120b9c1b51eSKate Stone        action='store_true',
1121b9c1b51eSKate Stone        dest='crashed_only',
1122b9c1b51eSKate Stone        help='only symbolicate the crashed thread',
1123b9c1b51eSKate Stone        default=False)
1124b9c1b51eSKate Stone    option_parser.add_option(
1125b9c1b51eSKate Stone        '--disasm-depth',
1126b9c1b51eSKate Stone        '-d',
1127b9c1b51eSKate Stone        type='int',
1128b9c1b51eSKate Stone        dest='disassemble_depth',
1129b9c1b51eSKate Stone        help='set the depth in stack frames that should be disassembled (default is 1)',
1130b9c1b51eSKate Stone        default=1)
1131b9c1b51eSKate Stone    option_parser.add_option(
1132b9c1b51eSKate Stone        '--disasm-all',
1133b9c1b51eSKate Stone        '-D',
1134b9c1b51eSKate Stone        action='store_true',
1135b9c1b51eSKate Stone        dest='disassemble_all_threads',
1136b9c1b51eSKate Stone        help='enabled disassembly of frames on all threads (not just the crashed thread)',
1137b9c1b51eSKate Stone        default=False)
1138b9c1b51eSKate Stone    option_parser.add_option(
1139b9c1b51eSKate Stone        '--disasm-before',
1140b9c1b51eSKate Stone        '-B',
1141b9c1b51eSKate Stone        type='int',
1142b9c1b51eSKate Stone        dest='disassemble_before',
1143b9c1b51eSKate Stone        help='the number of instructions to disassemble before the frame PC',
1144b9c1b51eSKate Stone        default=4)
1145b9c1b51eSKate Stone    option_parser.add_option(
1146b9c1b51eSKate Stone        '--disasm-after',
1147b9c1b51eSKate Stone        '-A',
1148b9c1b51eSKate Stone        type='int',
1149b9c1b51eSKate Stone        dest='disassemble_after',
1150b9c1b51eSKate Stone        help='the number of instructions to disassemble after the frame PC',
1151b9c1b51eSKate Stone        default=4)
1152b9c1b51eSKate Stone    option_parser.add_option(
1153b9c1b51eSKate Stone        '--source-context',
1154b9c1b51eSKate Stone        '-C',
1155b9c1b51eSKate Stone        type='int',
1156b9c1b51eSKate Stone        metavar='NLINES',
1157b9c1b51eSKate Stone        dest='source_context',
1158b9c1b51eSKate Stone        help='show NLINES source lines of source context (default = 4)',
1159b9c1b51eSKate Stone        default=4)
1160b9c1b51eSKate Stone    option_parser.add_option(
1161b9c1b51eSKate Stone        '--source-frames',
1162b9c1b51eSKate Stone        type='int',
1163b9c1b51eSKate Stone        metavar='NFRAMES',
1164b9c1b51eSKate Stone        dest='source_frames',
1165b9c1b51eSKate Stone        help='show source for NFRAMES (default = 4)',
1166b9c1b51eSKate Stone        default=4)
1167b9c1b51eSKate Stone    option_parser.add_option(
1168b9c1b51eSKate Stone        '--source-all',
1169b9c1b51eSKate Stone        action='store_true',
1170b9c1b51eSKate Stone        dest='source_all',
1171b9c1b51eSKate Stone        help='show source for all threads, not just the crashed thread',
1172b9c1b51eSKate Stone        default=False)
1173eb749096SGreg Clayton    if add_interactive_options:
1174b9c1b51eSKate Stone        option_parser.add_option(
1175b9c1b51eSKate Stone            '-i',
1176b9c1b51eSKate Stone            '--interactive',
1177b9c1b51eSKate Stone            action='store_true',
11787c54ffdcSMed Ismail Bennani            help='parse a crash log and load it in a ScriptedProcess',
1179b9c1b51eSKate Stone            default=False)
11807c54ffdcSMed Ismail Bennani        option_parser.add_option(
11817c54ffdcSMed Ismail Bennani            '-b',
11827c54ffdcSMed Ismail Bennani            '--batch',
11837c54ffdcSMed Ismail Bennani            action='store_true',
11847c54ffdcSMed Ismail Bennani            help='dump symbolicated stackframes without creating a debug session',
11857c54ffdcSMed Ismail Bennani            default=True)
1186eb749096SGreg Clayton    return option_parser
1187eb749096SGreg Clayton
1188b9c1b51eSKate Stone
1189cb5ea132SDave Leedef CrashLogOptionParser():
1190eb749096SGreg Clayton    description = '''Symbolicate one or more darwin crash log files to provide source file and line information,
1191eb749096SGreg Claytoninlined stack frames back to the concrete functions, and disassemble the location of the crash
1192eb749096SGreg Claytonfor the first frame of the crashed thread.
1193eb749096SGreg ClaytonIf this script is imported into the LLDB command interpreter, a "crashlog" command will be added to the interpreter
1194eb749096SGreg Claytonfor use at the LLDB command line. After a crash log has been parsed and symbolicated, a target will have been
1195eb749096SGreg Claytoncreated that has all of the shared libraries loaded at the load addresses found in the crash log file. This allows
1196eb749096SGreg Claytonyou to explore the program as if it were stopped at the locations described in the crash log and functions can
1197eb749096SGreg Claytonbe disassembled and lookups can be performed using the addresses found in the crash log.'''
1198cb5ea132SDave Lee    return CreateSymbolicateCrashLogOptions('crashlog', description, True)
1199cb5ea132SDave Lee
1200cb5ea132SDave Leedef SymbolicateCrashLogs(debugger, command_args):
1201cb5ea132SDave Lee    option_parser = CrashLogOptionParser()
1202*5a9fa21cSMed Ismail Bennani
1203*5a9fa21cSMed Ismail Bennani    if not len(command_args):
1204*5a9fa21cSMed Ismail Bennani        option_parser.print_help()
1205*5a9fa21cSMed Ismail Bennani        return
1206*5a9fa21cSMed Ismail Bennani
1207eb749096SGreg Clayton    try:
1208eb749096SGreg Clayton        (options, args) = option_parser.parse_args(command_args)
1209eb749096SGreg Clayton    except:
1210eb749096SGreg Clayton        return
1211eb749096SGreg Clayton
1212a7fb1dcdSGreg Clayton    if options.debug:
1213a658ab9fSDavide Italiano        print('command_args = %s' % command_args)
1214a658ab9fSDavide Italiano        print('options', options)
1215a658ab9fSDavide Italiano        print('args', args)
1216eb749096SGreg Clayton
1217eb749096SGreg Clayton    if options.debug_delay > 0:
1218a658ab9fSDavide Italiano        print("Waiting %u seconds for debugger to attach..." % options.debug_delay)
1219eb749096SGreg Clayton        time.sleep(options.debug_delay)
1220eb749096SGreg Clayton    error = lldb.SBError()
1221eb749096SGreg Clayton
12227c54ffdcSMed Ismail Bennani    def should_run_in_interactive_mode(options, ci):
1223eb749096SGreg Clayton        if options.interactive:
12247c54ffdcSMed Ismail Bennani            return True
12257c54ffdcSMed Ismail Bennani        elif options.batch:
12267c54ffdcSMed Ismail Bennani            return False
12277c54ffdcSMed Ismail Bennani        # elif ci and ci.IsInteractive():
12287c54ffdcSMed Ismail Bennani        #     return True
1229eb749096SGreg Clayton        else:
12307c54ffdcSMed Ismail Bennani            return False
12317c54ffdcSMed Ismail Bennani
12327c54ffdcSMed Ismail Bennani    ci = debugger.GetCommandInterpreter()
12337c54ffdcSMed Ismail Bennani
12347c54ffdcSMed Ismail Bennani    if args:
1235eb749096SGreg Clayton        for crash_log_file in args:
12367c54ffdcSMed Ismail Bennani            if should_run_in_interactive_mode(options, ci):
12370a65112cSMed Ismail Bennani                load_crashlog_in_scripted_process(debugger, crash_log_file,
12380a65112cSMed Ismail Bennani                                                  options)
12397c54ffdcSMed Ismail Bennani            else:
1240c7cbf32fSJonas Devlieghere                crash_log = CrashLogParser().parse(debugger, crash_log_file, options.verbose)
1241eb749096SGreg Clayton                SymbolicateCrashLog(crash_log, options)
1242cb5ea132SDave Lee
1243aa9d02b1SGreg Claytonif __name__ == '__main__':
124442a6eb71SGreg Clayton    # Create a new debugger instance
1245c29c24beSJonas Devlieghere    debugger = lldb.SBDebugger.Create()
1246c29c24beSJonas Devlieghere    SymbolicateCrashLogs(debugger, sys.argv[1:])
1247c29c24beSJonas Devlieghere    lldb.SBDebugger.Destroy(debugger)
1248cb5ea132SDave Lee
1249cb5ea132SDave Leedef __lldb_init_module(debugger, internal_dict):
1250cb5ea132SDave Lee    debugger.HandleCommand(
1251cb5ea132SDave Lee        'command script add -c lldb.macosx.crashlog.Symbolicate crashlog')
1252cb5ea132SDave Lee    debugger.HandleCommand(
1253b9c1b51eSKate Stone        'command script add -f lldb.macosx.crashlog.save_crashlog save_crashlog')
1254f8d889a7SDave Lee    print('"crashlog" and "save_crashlog" commands have been installed, use '
1255f8d889a7SDave Lee          'the "--help" options on these commands for detailed help.')
1256