xref: /xnu-12377.1.9/tools/lldbmacros/xnu.py (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
1import sys, os, re, time, getopt, shlex, inspect, xnudefines
2import lldb
3import uuid
4import base64
5import json
6from importlib import reload
7from importlib.util import find_spec
8from functools import wraps
9from ctypes import c_ulonglong as uint64_t
10from ctypes import c_void_p as voidptr_t
11import core
12from core import caching
13from core.standard import *
14from core.configuration import *
15from core.kernelcore import *
16from utils import *
17from core.lazytarget import *
18
19MODULE_NAME=__name__
20
21""" Kernel Debugging macros for lldb.
22    Please make sure you read the README COMPLETELY BEFORE reading anything below.
23    It is very critical that you read coding guidelines in Section E in README file.
24"""
25
26COMMON_HELP_STRING = """
27    -h  Show the help string for the command.
28    -c [always|auto|never|0|1]
29                            Control the colorized output of certain commands
30    -o <path/to/filename>   The output of this command execution will be saved to file. Parser information or errors will
31                            not be sent to file though. eg /tmp/output.txt
32    -s <filter_string>      The "filter_string" param is parsed to python regex expression and each line of output
33                            will be printed/saved only if it matches the expression.
34    -v [-v...]  Each additional -v will increase the verbosity of the command.
35    -p <plugin_name>        Send the output of the command to plugin. Please see README for usage of plugins.
36"""
37# End Utility functions
38# Debugging specific utility functions
39
40#decorators. Not to be called directly.
41
42def static_var(var_name, initial_value):
43    def _set_var(obj):
44        setattr(obj, var_name, initial_value)
45        return obj
46    return _set_var
47
48def header(initial_value):
49    def _set_header(obj):
50        setattr(obj, 'header', initial_value)
51        return obj
52    return _set_header
53
54def md_header(fmt, args):
55    def _set_md_header(obj):
56        header = "|" + "|".join(fmt.split(" ")).format(*args) + "|"
57
58        colhead = map(lambda fmt, col: "-"*len(fmt.format(col)), fmt.split(" "), args)
59        sub_header = "|" + "|".join(colhead) + "|"
60        setattr(obj, 'markdown', "\n".join([header, sub_header]))
61        return obj
62    return _set_md_header
63
64# holds type declarations done by xnu.
65#DONOTTOUCHME: Exclusive use of lldb_type_summary only.
66lldb_summary_definitions = {}
67def lldb_type_summary(types_list):
68    """ A function decorator to register a summary for a type in lldb.
69        params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t'])
70        returns: Nothing. This is a decorator.
71    """
72    def _get_summary(obj):
73        summary_function_name = "LLDBSummary" + obj.__name__
74
75        def _internal_summary_function(lldbval, internal_dict):
76            args, _, _, _ = inspect.getargspec(obj)
77            if 'O' in args:
78                stream = CommandOutput(summary_function_name, fhandle=sys.stdout)
79                with RedirectStdStreams(stdout=stream), caching.ImplicitContext(lldbval):
80                    return '\n' + obj.header + '\n' + obj(core.value(lldbval), O=stream)
81
82            out_string = ""
83            if internal_dict != None and len(obj.header) > 0 :
84                out_string += "\n" + obj.header +"\n"
85            with caching.ImplicitContext(lldbval):
86                out_string += obj(core.value(lldbval))
87            return out_string
88
89        myglobals = globals()
90        myglobals[summary_function_name] = _internal_summary_function
91        summary_function = myglobals[summary_function_name]
92        summary_function.__doc__ = obj.__doc__
93
94        global lldb_summary_definitions
95        for single_type in types_list:
96            if config['showTypeSummary']:
97                if single_type in lldb_summary_definitions:
98                    lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"")
99                lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name)
100            lldb_summary_definitions[single_type] = obj
101
102        return obj
103    return _get_summary
104
105#
106# Exception handling from commands
107#
108
109_LLDB_WARNING = (
110    "*********************  LLDB found an exception  *********************\n"
111    "{lldb_version}\n\n"
112    "  There has been an uncaught exception.\n"
113    "  It could be because the debugger was disconnected.\n"
114    "\n"
115    "  In order to debug the macro being run, run the macro again\n"
116    "  with the `--debug` flag to get richer information, for example:\n"
117    "\n"
118    "      (lldb) showtask --debug 0x1234\n"
119    "\n"
120    "  In order to file a bug report instead, run the macro again\n"
121    "  with the `--radar` flag which will produce a tarball to\n"
122    "  attach to your report, for example:\n"
123    "\n"
124    "      (lldb) showtask --radar 0x1234\n"
125    "********************************************************************\n"
126)
127
128def _format_exc(exc, vt):
129    import traceback, textwrap
130
131    out_str = ""
132
133    w = textwrap.TextWrapper(width=100, placeholder="...", max_lines=3)
134    tb = traceback.TracebackException.from_exception(exc, limit=None, lookup_lines=True, capture_locals=True)
135
136    for frame in tb.stack:
137        out_str += (
138            f"File \"{vt.DarkBlue}{frame.filename}\"{vt.Magenta}@{frame.lineno}{vt.Reset} "
139            f"in {vt.Bold}{vt.DarkCyan}{frame.name}{vt.Reset}\n"
140        )
141        out_str += "  Locals:\n"
142        for name, value in frame.locals.items():
143            variable = f"    {vt.Bold}{vt.DarkGreen}{name}{vt.Reset} = "
144            first = True
145            for wline in w.wrap(str(value)):
146                if first:
147                    out_str += variable + f"{vt.Oblique}{wline}\n"
148                    first = False
149                else:
150                    out_str += " " * (len(name) + 7) + wline + "\n"
151                out_str += vt.EndOblique
152
153        out_str += "  " + "-" * 100 + "\n"
154        try:
155            src = open(frame.filename, "r")
156        except IOError:
157            out_str += "    < Sources not available >\n"
158        else:
159            with src:
160                lines = src.readlines()
161
162                startline = frame.lineno - 3 if frame.lineno > 2 else 0
163                endline = min(frame.lineno + 2, len(lines))
164                for lineno in range(startline, endline):
165
166                    if lineno + 1 == frame.lineno:
167                        fmt = vt.Bold + vt.Default
168                        marker = '>'
169                    else:
170                        fmt = vt.Default
171                        marker = ' '
172
173                    out_str += f"{fmt}  {marker} {lineno + 1:5}  {lines[lineno].rstrip()}{vt.Reset}\n"
174
175        out_str += "  " + "-" * 100 + "\n"
176        out_str += "\n"
177
178    return out_str
179
180_RADAR_URL = "rdar://new/problem?title=LLDB%20macro%20failed%3A%20{}&attachments={}"
181
182def diagnostic_report(result, exc, stream, cmd_name, debug_opts, lldb_log_fname=None):
183    """ Collect diagnostic report for radar submission.
184
185        @param exc (Exception type)
186            Exception being reported.
187
188        @param stream (OutputObject)
189            Command's output stream to support formattting.
190
191        @param cmd_name (str)
192            Name of command being executed.
193
194        @param debug_opts ([str])
195            List of active debugging options (--debug, --radar, --pdb)
196
197        @param lldb_log_fname (str)
198            LLDB log file name to collect (optional)
199    """
200
201    # Print prologue common to all exceptions handling modes.
202    print(stream.VT.DarkRed + _LLDB_WARNING.format(lldb_version=lldb.SBDebugger.GetVersionString()))
203    print(stream.VT.Bold + stream.VT.DarkGreen + type(exc).__name__ +
204          stream.VT.Default + ": {}".format(str(exc)) + stream.VT.Reset)
205    print()
206
207    if not debug_opts:
208        result.SetError(f"{type(exc).__name__}: {exc}")
209        raise exc
210
211    #
212    # Display enhanced diagnostics when requested.
213    #
214    if "--debug" in debug_opts:
215        # Format exception for terminal
216        print(_format_exc(exc, stream.VT))
217
218        print("version:")
219        print(lldb.SBDebugger.GetVersionString())
220        print()
221
222    #
223    # Construct tar bundle for radar attachement
224    #
225    if "--radar" in debug_opts:
226        import tarfile, urllib.parse
227        print("Creating radar bundle ...")
228
229        itime = int(time.time())
230        tar_fname = "/tmp/debug.{:d}.tar".format(itime)
231
232        with tarfile.open(tar_fname, "w") as tar:
233            # Collect LLDB log. It can't be unlinked here because it is still used
234            # for the whole duration of xnudebug debug enable.
235            if lldb_log_fname is not None:
236                print("  Adding {}".format(lldb_log_fname))
237                tar.add(lldb_log_fname, "radar/lldb.log")
238                os.unlink(lldb_log_fname)
239
240            # Collect traceback
241            tb_fname = "/tmp/tb.{:d}.log".format(itime)
242            print("  Adding {}".format(tb_fname))
243            with open(tb_fname,"w") as f:
244                f.write(f"{type(exc).__name__}: {str(exc)}\n\n")
245                f.write(_format_exc(exc, NOVT()))
246                f.write("version:\n")
247                f.write(f"{lldb.SBDebugger.GetVersionString()}\n")
248                f.write("loaded images:\n")
249                f.write(lldb_run_command("image list") + "\n")
250                f.write("lldb settings:\n")
251                f.write(lldb_run_command("settings show") + "\n")
252            tar.add(tb_fname, "radar/traceback.log")
253            os.unlink(tb_fname)
254
255        # Radar submission
256        print()
257        print(stream.VT.DarkRed + "Please attach {} to your radar or open the URL below to create one:".format(tar_fname) + stream.VT.Reset)
258        print()
259        print("  " + _RADAR_URL.format(urllib.parse.quote(cmd_name),urllib.parse.quote(tar_fname)))
260        print()
261        print("Don't forget to mention where the coredump came from (e.g. original Radar ID), or attach it :)")
262        print()
263
264    # Enter pdb when requested.
265    if "--pdb" in debug_opts:
266        print("Starting debugger ...")
267        import pdb
268        pdb.post_mortem(exc.__traceback__)
269
270    return False
271
272#global cache of documentation for lldb commands exported by this module
273#DONOTTOUCHME: Exclusive use of lldb_command only.
274lldb_command_documentation = {}
275
276_DEBUG_OPTS = { "--debug", "--radar", "--pdb" }
277
278def lldb_command(cmd_name, option_string = '', fancy=False):
279    """ A function decorator to define a command with name 'cmd_name' in the lldb scope to call python function.
280        params: cmd_name - str : name of command to be set in lldb prompt.
281            option_string - str: getopt like option string. Only CAPITAL LETTER options allowed.
282                                 see README on Customizing command options.
283            fancy - bool       : whether the command will receive an 'O' object to do fancy output (tables, indent, color)
284    """
285    if option_string != option_string.upper():
286        raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string)
287
288    def _cmd(obj):
289        def _internal_command_function(debugger, command, exe_ctx, result, internal_dict):
290            global config, lldb_run_command_state
291            stream = CommandOutput(cmd_name, result)
292            # need to avoid printing on stdout if called from lldb_run_command.
293            if 'active' in lldb_run_command_state and lldb_run_command_state['active']:
294                debuglog('Running %s from lldb_run_command' % command)
295            else:
296                result.SetImmediateOutputFile(sys.__stdout__)
297
298            command_args = shlex.split(command)
299            lldb.debugger.HandleCommand('type category disable kernel')
300            def_verbose_level = config['verbosity']
301
302            # Filter out debugging arguments and enable logging
303            debug_opts = [opt for opt in command_args if opt in _DEBUG_OPTS]
304            command_args = [opt for opt in command_args if opt not in _DEBUG_OPTS]
305            lldb_log_filename = None
306
307            if "--radar" in debug_opts:
308                lldb_log_filename = "/tmp/lldb.{:d}.log".format(int(time.time()))
309                lldb_run_command("log enable --file {:s} lldb api".format(lldb_log_filename))
310                lldb_run_command("log enable --file {:s} gdb-remote packets".format(lldb_log_filename))
311                lldb_run_command("log enable --file {:s} kdp-remote packets".format(lldb_log_filename))
312
313            try:
314                stream.setOptions(command_args, option_string)
315                if stream.verbose_level != 0:
316                    config['verbosity'] +=  stream.verbose_level
317                with RedirectStdStreams(stdout=stream), caching.ImplicitContext(exe_ctx):
318                    args = { 'cmd_args': stream.target_cmd_args }
319                    if option_string:
320                        args['cmd_options'] = stream.target_cmd_options
321                    if fancy:
322                        args['O'] = stream
323                    obj(**args)
324            except KeyboardInterrupt:
325                print("Execution interrupted by user")
326            except (ArgumentError, NotImplementedError) as arg_error:
327                if str(arg_error) != HELP_ARGUMENT_EXCEPTION_SENTINEL:
328                    formatted_err = f"{type(arg_error).__name__}: {arg_error}"
329                    print(formatted_err)
330                    result.SetError(formatted_err)
331                print("{0:s}:\n        {1:s}".format(cmd_name, obj.__doc__.strip()))
332                return False
333            except Exception as exc:
334                if "--radar" in debug_opts: lldb_run_command("log disable")
335                return diagnostic_report(result, exc, stream, cmd_name, debug_opts, lldb_log_filename)
336
337            if config['showTypeSummary']:
338                lldb.debugger.HandleCommand('type category enable kernel' )
339
340            if stream.pluginRequired :
341                plugin = LoadXNUPlugin(stream.pluginName)
342                if plugin == None :
343                    print("Could not load plugins."+stream.pluginName)
344                    return
345                plugin.plugin_init(kern, config, lldb, kern.IsDebuggerConnected())
346                return_data = plugin.plugin_execute(cmd_name, result.GetOutput())
347                ProcessXNUPluginResult(return_data)
348                plugin.plugin_cleanup()
349
350            #restore the verbose level after command is complete
351            config['verbosity'] = def_verbose_level
352
353            return
354
355        myglobals = globals()
356        command_function_name = obj.__name__+"Command"
357        myglobals[command_function_name] =  _internal_command_function
358        myglobals[f"XnuCommandSentinel{cmd_name}"] = None
359        command_function = myglobals[command_function_name]
360        if not obj.__doc__:
361            print("ERROR: Cannot register command({:s}) without documentation".format(cmd_name))
362            return obj
363        obj.__doc__ += "\n" + COMMON_HELP_STRING
364        command_function.__doc__ = obj.__doc__
365        global lldb_command_documentation
366        lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string)
367
368        script_add_command = f"command script add -o -f {MODULE_NAME}.{command_function_name} {cmd_name}"
369        lldb.debugger.HandleCommand(script_add_command)
370
371        setattr(obj, 'fancy', fancy)
372        if fancy:
373            @wraps(obj)
374            def wrapped_fun(cmd_args=None, cmd_options={}, O=None):
375                if O is None:
376                    stream = CommandOutput(cmd_name, fhandle=sys.stdout)
377                    with RedirectStdStreams(stdout=stream):
378                        return obj(cmd_args, cmd_options, O=stream)
379                else:
380                    return obj(cmd_args, cmd_options, O)
381            return wrapped_fun
382        return obj
383    return _cmd
384
385
386def lldb_alias(alias_name, cmd_line):
387    """ define an alias in the lldb command line.
388        A programatic way of registering an alias. This basically does
389        (lldb)command alias alias_name "cmd_line"
390        ex.
391        lldb_alias('readphys16', 'readphys 16')
392    """
393    alias_name = alias_name.strip()
394    cmd_line = cmd_line.strip()
395    lldb.debugger.HandleCommand("command alias " + alias_name + " "+ cmd_line)
396
397def SetupLLDBTypeSummaries(reset=False):
398    global lldb_summary_definitions, MODULE_NAME
399    if reset:
400            lldb.debugger.HandleCommand("type category delete  kernel ")
401    for single_type in list(lldb_summary_definitions.keys()):
402        summary_function = lldb_summary_definitions[single_type]
403        lldb_cmd = "type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + ".LLDBSummary" + summary_function.__name__
404        debuglog(lldb_cmd)
405        lldb.debugger.HandleCommand(lldb_cmd)
406    if config['showTypeSummary']:
407            lldb.debugger.HandleCommand("type category enable  kernel")
408    else:
409            lldb.debugger.HandleCommand("type category disable kernel")
410
411    return
412
413def LoadXNUPlugin(name):
414    """ Try to load a plugin from the plugins directory.
415    """
416    retval = None
417    name=name.strip()
418    try:
419        module_obj = __import__('plugins.'+name, globals(), locals(), [], -1)
420        module_obj = module_obj.__dict__[name]
421        defs = dir(module_obj)
422        if 'plugin_init' in defs and 'plugin_execute' in defs and 'plugin_cleanup' in defs:
423            retval = module_obj
424        else:
425            print("Plugin is not correctly implemented. Please read documentation on implementing plugins")
426    except:
427        print("plugin not found :"+name)
428
429    return retval
430
431def ProcessXNUPluginResult(result_data):
432    """ Look at the returned data from plugin and see if anymore actions are required or not
433        params: result_data - list of format (status, out_string, more_commands)
434    """
435    ret_status = result_data[0]
436    ret_string = result_data[1]
437    ret_commands = result_data[2]
438
439    if not ret_status:
440        print("Plugin failed: " + ret_string)
441        return
442    print(ret_string)
443    if len(ret_commands) >= 0:
444        for cmd in ret_commands:
445            print("Running command on behalf of plugin:" + cmd)
446            lldb.debugger.HandleCommand(cmd)
447    return
448
449# holds tests registered with xnu.
450#DONOTTOUCHME: Exclusive use of xnudebug_test only
451lldb_command_tests = {}
452def xnudebug_test(test_name):
453    """ A function decoratore to register a test with the framework. Each test is supposed to be of format
454        def Test<name>(kernel_target, config, lldb_obj, isConnected )
455
456        NOTE: The testname should start with "Test" else exception will be raised.
457    """
458    def _test(obj):
459        global lldb_command_tests
460        if obj.__name__.find("Test") != 0 :
461            print("Test name ", obj.__name__ , " should start with Test")
462            raise ValueError
463        lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__)
464        return obj
465    return _test
466
467
468# End Debugging specific utility functions
469# Kernel Debugging specific classes and accessor methods
470
471# global access object for target kernel
472
473def GetObjectAtIndexFromArray(array_base: value, index: int):
474    """ Subscript indexing for arrays that are represented in C as pointers.
475        for ex. int *arr = malloc(20*sizeof(int));
476        now to get 3rd int from 'arr' you'd do
477        arr[2] in C
478        GetObjectAtIndexFromArray(arr_val,2)
479        params:
480            array_base : core.value - representing a pointer type (ex. base of type 'ipc_entry *')
481            index : int - 0 based index into the array
482        returns:
483            core.value : core.value of the same type as array_base_val but pointing to index'th element
484    """
485    array_base_val = array_base.GetSBValue()
486    base_address = array_base_val.GetValueAsAddress()
487    size = array_base_val.GetType().GetPointeeType().GetByteSize()
488    obj_address = base_address + (index * size)
489    obj = kern.CreateValueFromAddress(obj_address, array_base_val.GetType().GetPointeeType().name)
490    return addressof(obj)
491
492
493kern: KernelTarget = None
494
495def GetLLDBThreadForKernelThread(thread_obj):
496    """ Get a reference to lldb.SBThread representation for kernel thread.
497        params:
498            thread_obj : core.cvalue - thread object of type thread_t
499        returns
500            lldb.SBThread - lldb thread object for getting backtrace/registers etc.
501    """
502    tid = unsigned(thread_obj.thread_id)
503    lldb_process = LazyTarget.GetProcess()
504    sbthread = lldb_process.GetThreadByID(tid)
505    if not sbthread.IsValid():
506        # in case lldb doesnt know about this thread, create one
507        if hasattr(lldb_process, "CreateOSPluginThread"):
508            debuglog("creating os plugin thread on the fly for {0:d} 0x{1:x}".format(tid, thread_obj))
509            lldb_process.CreateOSPluginThread(tid, unsigned(thread_obj))
510        else:
511            raise RuntimeError("LLDB process does not support CreateOSPluginThread.")
512        sbthread = lldb_process.GetThreadByID(tid)
513
514    if not sbthread.IsValid():
515        raise RuntimeError("Unable to find lldb thread for tid={0:d} thread = {1:#018x} (#16049947: have you put 'settings set target.load-script-from-symbol-file true' in your .lldbinit?)".format(tid, thread_obj))
516
517    return sbthread
518
519def GetKextSymbolInfo(load_addr):
520    """ Get a string descriptiong load_addr <kextname> + offset
521        params:
522            load_addr - int address value of pc in backtrace.
523        returns: str - kext name + offset string. If no cached data available, warning message is returned.
524    """
525    symbol_name = "None"
526    symbol_offset = load_addr
527    kmod_val = kern.globals.kmod
528    if not kern.arch.startswith('arm64'):
529        for kval in IterateLinkedList(kmod_val, 'next'):
530            if load_addr >= unsigned(kval.address) and \
531                load_addr <= (unsigned(kval.address) + unsigned(kval.size)):
532                symbol_name = kval.name
533                symbol_offset = load_addr - unsigned(kval.address)
534                break
535        return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset)
536
537    # only for arm64 we do lookup for split kexts.
538    if not GetAllKextSummaries.cached():
539        if str(GetConnectionProtocol()) != "core":
540            return "{:#018x} ~ kext info not available. please run 'showallkexts' once ~ \n".format(load_addr)
541
542    for kval in GetAllKextSummaries():
543        text_seg = text_segment(kval.segments)
544        if load_addr >= text_seg.vmaddr and \
545            load_addr <= (text_seg.vmaddr + text_seg.vmsize):
546            symbol_name = kval.name
547            symbol_offset = load_addr - text_seg.vmaddr
548            break
549    return "{:#018x} {:s} + {:#x} \n".format(load_addr, symbol_name, symbol_offset)
550
551def GetThreadBackTrace(thread_obj: value, verbosity = vHUMAN, prefix = ""):
552    """ Get a string to display back trace for a thread.
553        params:
554            thread_obj - core.cvalue : a thread object of type thread_t.
555            verbosity - int : either of vHUMAN, vSCRIPT or vDETAIL to describe the verbosity of output
556            prefix - str : a string prefix added before the line for each frame.
557            isContinuation - bool : is thread a continuation?
558        returns:
559            str - a multi line string showing each frame in backtrace.
560    """
561    kernel_stack = unsigned(thread_obj.kernel_stack)
562    is_continuation = not bool(kernel_stack)
563    thread_val = GetLLDBThreadForKernelThread(thread_obj)
564    out_string = ""
565    reserved_stack = unsigned(thread_obj.reserved_stack)
566    if not is_continuation:
567        if kernel_stack and reserved_stack:
568            out_string += f"{prefix}reserved_stack = {reserved_stack:#018x}\n"
569        out_string += f"{prefix}kernel_stack = {kernel_stack:#018x}\n"
570    else:
571        out_string += prefix + "continuation ="
572    iteration = 0
573    last_frame_p = 0
574    for frame in thread_val.frames:
575        addr = frame.GetPCAddress()
576        load_addr = addr.GetLoadAddress(LazyTarget.GetTarget())
577        function = frame.GetFunction()
578        frame_p = frame.GetFP()
579        mod_name = frame.GetModule().GetFileSpec().GetFilename()
580
581        if iteration == 0 and not is_continuation:
582            out_string += f"{prefix}stacktop = {frame_p:#018x}\n"
583
584        if not function:
585            # No debug info for 'function'.
586            out_string += prefix
587            if not is_continuation:
588                out_string += f"{frame_p:#018x} "
589
590            symbol = frame.GetSymbol()
591            if not symbol:
592                out_string += GetKextSymbolInfo(load_addr)
593            else:
594                file_addr = addr.GetFileAddress()
595                start_addr = symbol.GetStartAddress().GetFileAddress()
596                symbol_name = symbol.GetName()
597                symbol_offset = file_addr - start_addr
598                out_string += f"{load_addr:#018x} {mod_name}`{symbol_name} + {symbol_offset:#x} \n"
599        else:
600            # Debug info is available for 'function'.
601            inlined_suffix= " [inlined]" if frame.IsInlined() else ''
602            func_name = f"{frame.GetFunctionName()}{inlined_suffix}"
603            # file_name = frame.GetLineEntry().GetFileSpec().GetFilename()
604            # line_num = frame.GetLineEntry().GetLine()
605            if is_continuation and frame.IsInlined():
606                debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj))
607                continue
608            out_string += prefix
609            if not is_continuation:
610                out_string += f"{frame_p:#018x} "
611
612            if len(frame.arguments) > 0:
613                func_args = str(frame.arguments).replace('\n', ', ')
614                out_string += f"{load_addr:#018x} {func_name}({func_args}) \n"
615            else:
616                out_string += f"{load_addr:#018x} {func_name}(void) \n"
617
618        iteration += 1
619        if frame_p:
620            last_frame_p = frame_p
621
622    if not is_continuation and last_frame_p:
623        out_string += prefix + "stackbottom = {:#018x}".format(last_frame_p)
624    out_string = out_string.replace("variable not available","")
625    return out_string
626
627def GetSourceInformationForAddress(addr):
628    """ convert and address to function +offset information.
629        params: addr - int address in the binary to be symbolicated
630        returns: string of format "0xaddress: function + offset"
631    """
632    try:
633        return str(kern.SymbolicateFromAddress(addr, fullSymbol=True)[0])
634    except:
635        return '{0:<#x} <unknown: use `addkextaddr {0:#x}` to resolve>'.format(addr)
636
637def GetFrameLocalVariable(variable_name, frame_no=0):
638    """ Find a local variable by name
639        params:
640          variable_name: str - name of variable to search for
641        returns:
642          core.value - if the variable is found.
643          None   - if not found or not Valid
644    """
645    retval = None
646    sbval = None
647    lldb_SBThread = LazyTarget.GetProcess().GetSelectedThread()
648    frame = lldb_SBThread.GetSelectedFrame()
649    if frame_no :
650      frame = lldb_SBThread.GetFrameAtIndex(frame_no)
651    if frame :
652      sbval = frame.FindVariable(variable_name)
653    if sbval and sbval.IsValid():
654      retval = core.cvalue.value(sbval)
655    return retval
656
657# Begin Macros for kernel debugging
658
659@lldb_command('kgmhelp')
660def KernelDebugCommandsHelp(cmd_args=None):
661    """ Show a list of registered commands for kenel debugging.
662    """
663    global lldb_command_documentation
664    print("List of commands provided by " + MODULE_NAME + " for kernel debugging.")
665    cmds = list(lldb_command_documentation.keys())
666    cmds.sort()
667    for cmd in cmds:
668        if isinstance(lldb_command_documentation[cmd][-1], str):
669            print(" {0: <20s} - {1}".format(cmd , lldb_command_documentation[cmd][1].split("\n")[0].strip()))
670        else:
671            print(" {0: <20s} - {1}".format(cmd , "No help string found."))
672    print('Each of the functions listed here accept the following common options. ')
673    print(COMMON_HELP_STRING)
674    print('Additionally, each command implementation may have more options. "(lldb) help <command> " will show these options.')
675    return None
676
677
678@lldb_command('showraw')
679def ShowRawCommand(cmd_args=None):
680    """ A command to disable the kernel summaries and show data as seen by the system.
681        This is useful when trying to read every field of a struct as compared to brief summary
682
683        Usage: showraw foo_command foo_arg1 foo_arg2 ...
684    """
685    if cmd_args is None or len(cmd_args) == 0:
686        raise ArgumentError("'showraw' requires a command to run")
687
688    command = " ".join(cmd_args)
689    lldb.debugger.HandleCommand('type category disable kernel' )
690    lldb.debugger.HandleCommand(command)
691    lldb.debugger.HandleCommand('type category enable kernel' )
692
693
694@lldb_command('xnudebug')
695def XnuDebugCommand(cmd_args=None):
696    """  command interface for operating on the xnu macros. Allowed commands are as follows
697        reload:
698            Reload a submodule from the xnu/tools/lldb directory. Do not include the ".py" suffix in modulename.
699            usage: xnudebug reload <modulename> (eg. memory, process, stats etc)
700        flushcache:
701            remove any cached data held in static or dynamic data cache.
702            usage: xnudebug flushcache
703        test:
704            Start running registered test with <name> from various modules.
705            usage: xnudebug test <name> (eg. test_memstats)
706        testall:
707            Go through all registered tests and run them
708        debug:
709            Toggle state of debug configuration flag.
710        profile:
711            Profile an lldb command and write its profile info to a file.
712            usage: xnudebug profile <path_to_profile> <cmd...>
713
714            e.g. `xnudebug profile /tmp/showallstacks_profile.prof showallstacks
715        coverage:
716            Collect coverage for an lldb command and save it to a file.
717            usage: xnudebug coverage <path_to_coverage_file> <cmd ...>
718
719            e.g. `xnudebug coverage /tmp/showallstacks_coverage.cov showallstacks`
720            An HTML report can then be generated via `coverage html --data-file=<path>`
721    """
722    global config
723    command_args = cmd_args
724    if len(command_args) == 0:
725        raise ArgumentError("No command specified.")
726    supported_subcommands = ['debug', 'reload', 'test', 'testall', 'flushcache', 'profile', 'coverage']
727    subcommand = GetLongestMatchOption(command_args[0], supported_subcommands, True)
728
729    if len(subcommand) == 0:
730        raise ArgumentError("Subcommand (%s) is not a valid command. " % str(command_args[0]))
731
732    subcommand = subcommand[0].lower()
733    if subcommand == 'debug':
734        if command_args[-1].lower().find('dis') >=0 and config['debug']:
735            config['debug'] = False
736            print("Disabled debug logging.")
737        elif command_args[-1].lower().find('dis') < 0 and not config['debug']:
738            config['debug'] = True
739            EnableLLDBAPILogging()  # provided by utils.py
740            print("Enabled debug logging. \nPlease run 'xnudebug debug disable' to disable it again. ")
741
742    if subcommand == 'flushcache':
743        print("Current size of cache: {}".format(caching.GetSizeOfCache()))
744        caching.ClearAllCache()
745
746    if subcommand == 'reload':
747        module_name = command_args[-1]
748        if module_name in sys.modules:
749            reload(sys.modules[module_name])
750            print(module_name + " is reloaded from " + sys.modules[module_name].__file__)
751        else:
752            print("Unable to locate module named ", module_name)
753
754    if subcommand == 'testall':
755        for test_name in list(lldb_command_tests.keys()):
756            print("[BEGIN]", test_name)
757            res = lldb_command_tests[test_name][2](kern, config, lldb, True)
758            if res:
759                print("[PASSED] {:s}".format(test_name))
760            else:
761                print("[FAILED] {:s}".format(test_name))
762
763    if subcommand == 'test':
764        test_name = command_args[-1]
765        if test_name in lldb_command_tests:
766            test = lldb_command_tests[test_name]
767            print("Running test {:s}".format(test[0]))
768            if test[2](kern, config, lldb, True) :
769                print("[PASSED] {:s}".format(test[0]))
770            else:
771                print("[FAILED] {:s}".format(test[0]))
772            return ""
773        else:
774            print("No such test registered with name: {:s}".format(test_name))
775            print("XNUDEBUG Available tests are:")
776            for i in list(lldb_command_tests.keys()):
777                print(i)
778        return None
779
780    if subcommand == 'profile':
781        save_path = command_args[1]
782
783        import cProfile, pstats, io
784
785        pr = cProfile.Profile()
786        pr.enable()
787
788        lldb.debugger.HandleCommand(" ".join(command_args[2:]))
789
790        pr.disable()
791        pr.dump_stats(save_path)
792
793        print("")
794        print("=" * 80)
795        print("")
796
797        s = io.StringIO()
798        ps = pstats.Stats(pr, stream=s)
799        ps.strip_dirs()
800        ps.sort_stats('cumulative')
801        ps.print_stats(30)
802        print(s.getvalue().rstrip())
803        print("")
804
805        print(f"Profile info saved to \"{save_path}\"")
806
807    if subcommand == 'coverage':
808        coverage_module = find_spec('coverage')
809        if not coverage_module:
810            print("Missing 'coverage' module. Please install it for the interpreter currently running.`")
811            return
812
813        save_path = command_args[1]
814
815        import coverage
816        cov = coverage.Coverage(data_file=save_path)
817        cov.start()
818
819        lldb.debugger.HandleCommand(" ".join(command_args[2:]))
820
821        cov.stop()
822        cov.save()
823
824        print(cov.report())
825        print(f"Coverage info saved to: \"{save_path}\"")
826
827
828    return False
829
830
831
832@lldb_command('showversion')
833def ShowVersion(cmd_args=None):
834    """ Read the kernel version string from a fixed address in low
835        memory. Useful if you don't know which kernel is on the other end,
836        and need to find the appropriate symbols. Beware that if you've
837        loaded a symbol file, but aren't connected to a remote target,
838        the version string from the symbol file will be displayed instead.
839        This macro expects to be connected to the remote kernel to function
840        correctly.
841
842    """
843    print(kern.version)
844
845def ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options):
846    """ Process the panic stackshot from the panic header, saving it to a file if it is valid
847        params: panic_stackshot_addr : start address of the panic stackshot binary data
848                panic_stackshot_len : length of the stackshot binary data
849        returns: nothing
850    """
851    if not panic_stackshot_addr:
852        print("No panic stackshot available (invalid addr)")
853        return
854
855    if not panic_stackshot_len:
856        print("No panic stackshot available (zero length)")
857        return;
858
859    if "-D" in cmd_options:
860        dir_ = cmd_options["-D"]
861        if os.path.exists(dir_):
862            if not os.access(dir_, os.W_OK):
863                print("Write access to {} denied".format(dir_))
864                return
865        else:
866            try:
867                os.makedirs(dir_)
868            except OSError as e:
869                print("An error occurred {} while creating a folder : {}".format(e, dir_))
870                return
871    else:
872        dir_ = "/tmp"
873
874    id = str(uuid.uuid4())[:8]
875    ss_binfile = os.path.join(dir_, "panic_%s.bin" % id)
876    ss_ipsfile = os.path.join(dir_, "panic_%s.ips" % id)
877
878    if not SaveDataToFile(panic_stackshot_addr, panic_stackshot_len, ss_binfile, None):
879        print("Failed to save stackshot binary data to file")
880        return
881
882    from kcdata import decode_kcdata_file
883    try:
884        with open(ss_binfile, "rb") as binfile:
885            decode_kcdata_file(binfile, ss_ipsfile)
886        print("Saved ips stackshot file as %s" % ss_ipsfile)
887    except Exception as e:
888        print("Failed to decode the stackshot: %s" % str(e))
889
890def ParseEmbeddedPanicLog(panic_header, cmd_options={}):
891    panic_buf = Cast(panic_header, 'char *')
892    panic_log_magic = unsigned(panic_header.eph_magic)
893    panic_log_begin_offset = unsigned(panic_header.eph_panic_log_offset)
894    panic_log_len = unsigned(panic_header.eph_panic_log_len)
895    other_log_begin_offset = unsigned(panic_header.eph_other_log_offset)
896    other_log_len = unsigned(panic_header.eph_other_log_len)
897    expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC
898    panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.eph_stackshot_offset)
899    panic_stackshot_len = unsigned(panic_header.eph_stackshot_len)
900    panic_header_flags = unsigned(panic_header.eph_panic_flags)
901
902    warn_str = ""
903    out_str = ""
904
905    if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
906        warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
907                    expected_panic_magic)
908
909    if warn_str:
910        print("\n %s" % warn_str)
911        if panic_log_begin_offset == 0:
912            return
913
914    if "-S" in cmd_options:
915        if panic_header_flags & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG:
916            ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options)
917        else:
918            print("No panic stackshot available")
919    elif "-D" in cmd_options:
920        print("-D option must be specified along with the -S option")
921        return
922
923    panic_log_curindex = 0
924    while panic_log_curindex < panic_log_len:
925        p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)])
926        out_str += p_char
927        panic_log_curindex += 1
928
929    if other_log_begin_offset != 0:
930        other_log_curindex = 0
931        while other_log_curindex < other_log_len:
932            p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)])
933            out_str += p_char
934            other_log_curindex += 1
935
936    print(out_str)
937    return
938
939def ParseMacOSPanicLog(panic_header, cmd_options={}):
940    panic_buf = Cast(panic_header, 'char *')
941    panic_log_magic = unsigned(panic_header.mph_magic)
942    panic_log_begin_offset = unsigned(panic_header.mph_panic_log_offset)
943    panic_log_len = unsigned(panic_header.mph_panic_log_len)
944    other_log_begin_offset = unsigned(panic_header.mph_other_log_offset)
945    other_log_len = unsigned(panic_header.mph_other_log_len)
946    cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(panic_header))
947    if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)):
948        other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset
949    expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC
950
951    # use the global if it's available (on an x86 corefile), otherwise refer to the header
952    if hasattr(kern.globals, "panic_stackshot_buf"):
953        panic_stackshot_addr = unsigned(kern.globals.panic_stackshot_buf)
954        panic_stackshot_len = unsigned(kern.globals.panic_stackshot_len)
955    else:
956        panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.mph_stackshot_offset)
957        panic_stackshot_len = unsigned(panic_header.mph_stackshot_len)
958
959    panic_header_flags = unsigned(panic_header.mph_panic_flags)
960
961    warn_str = ""
962    out_str = ""
963
964    if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
965        warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
966                    expected_panic_magic)
967
968    if warn_str:
969        print("\n %s" % warn_str)
970        if panic_log_begin_offset == 0:
971            return
972
973    if "-S" in cmd_options:
974        if panic_header_flags & xnudefines.MACOS_PANIC_STACKSHOT_SUCCEEDED_FLAG:
975            ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len, cmd_options)
976        else:
977            print("No panic stackshot available")
978    elif "-D" in cmd_options:
979        print("-D option must be specified along with the -S option")
980        return
981
982    panic_log_curindex = 0
983    while panic_log_curindex < panic_log_len:
984        p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)])
985        out_str += p_char
986        panic_log_curindex += 1
987
988    if other_log_begin_offset != 0:
989        other_log_curindex = 0
990        while other_log_curindex < other_log_len:
991            p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)])
992            out_str += p_char
993            other_log_curindex += 1
994
995    print(out_str)
996    return
997
998def ParseAURRPanicLog(panic_header, cmd_options={}):
999    reset_cause = {
1000        0x0: "OTHER",
1001        0x1: "CATERR",
1002        0x2: "SWD_TIMEOUT",
1003        0x3: "GLOBAL RESET",
1004        0x4: "STRAIGHT TO S5",
1005    }
1006
1007    expected_panic_magic = xnudefines.AURR_PANIC_MAGIC
1008
1009    panic_buf = Cast(panic_header, 'char *')
1010
1011    try:
1012        # This line will blow up if there's not type info for this struct (older kernel)
1013        # We fall back to manual parsing below
1014        aurr_panic_header = Cast(panic_header, 'struct efi_aurr_panic_header *')
1015        panic_log_magic = unsigned(aurr_panic_header.efi_aurr_magic)
1016        panic_log_version = unsigned(aurr_panic_header.efi_aurr_version)
1017        panic_log_reset_cause = unsigned(aurr_panic_header.efi_aurr_reset_cause)
1018        panic_log_reset_log_offset = unsigned(aurr_panic_header.efi_aurr_reset_log_offset)
1019        panic_log_reset_log_len = unsigned(aurr_panic_header.efi_aurr_reset_log_len)
1020    except Exception as e:
1021        print("*** Warning: kernel symbol file has no type information for 'struct efi_aurr_panic_header'...")
1022        print("*** Warning: trying to manually parse...")
1023        aurr_panic_header = Cast(panic_header, "uint32_t *")
1024        panic_log_magic = unsigned(aurr_panic_header[0])
1025        # panic_log_crc = unsigned(aurr_panic_header[1])
1026        panic_log_version = unsigned(aurr_panic_header[2])
1027        panic_log_reset_cause = unsigned(aurr_panic_header[3])
1028        panic_log_reset_log_offset = unsigned(aurr_panic_header[4])
1029        panic_log_reset_log_len = unsigned(aurr_panic_header[5])
1030
1031    if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
1032        print("BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
1033                    expected_panic_magic))
1034        return
1035
1036    print("AURR Panic Version: %d" % (panic_log_version))
1037
1038    # When it comes time to extend this in the future, please follow the
1039    # construct used below in ShowPanicLog()
1040    if panic_log_version in (xnudefines.AURR_PANIC_VERSION, xnudefines.AURR_CRASHLOG_PANIC_VERSION):
1041        # AURR Report Version 1 (AURR/MacEFI) or 2 (Crashlog)
1042        # see macefifirmware/Vendor/Apple/EfiPkg/AppleDebugSupport/Library/Debugger.h
1043        print("Reset Cause: 0x%x (%s)" % (panic_log_reset_cause, reset_cause.get(panic_log_reset_cause, "UNKNOWN")))
1044
1045        # Adjust panic log string length (cap to maximum supported values)
1046        if panic_log_version == xnudefines.AURR_PANIC_VERSION:
1047            max_string_len = panic_log_reset_log_len
1048        elif panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION:
1049            max_string_len = xnudefines.CRASHLOG_PANIC_STRING_LEN
1050
1051        panic_str_offset = 0
1052        out_str = ""
1053
1054        while panic_str_offset < max_string_len:
1055            p_char = str(panic_buf[panic_log_reset_log_offset + panic_str_offset])
1056            out_str += p_char
1057            panic_str_offset += 1
1058
1059        print(out_str)
1060
1061        # Save Crashlog Binary Data (if available)
1062        if "-S" in cmd_options and panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION:
1063            crashlog_binary_offset = panic_log_reset_log_offset + xnudefines.CRASHLOG_PANIC_STRING_LEN
1064            crashlog_binary_size = (panic_log_reset_log_len > xnudefines.CRASHLOG_PANIC_STRING_LEN) and (panic_log_reset_log_len - xnudefines.CRASHLOG_PANIC_STRING_LEN) or 0
1065
1066            if 0 == crashlog_binary_size:
1067                print("No crashlog data found...")
1068                return
1069
1070            # Save to file
1071            ts = int(time.time())
1072            ss_binfile = "/tmp/crashlog_%d.bin" % ts
1073
1074            if not SaveDataToFile(panic_buf + crashlog_binary_offset, crashlog_binary_size, ss_binfile, None):
1075                print("Failed to save crashlog binary data to file")
1076                return
1077    else:
1078        return ParseUnknownPanicLog(panic_header, cmd_options)
1079
1080    return
1081
1082def ParseUnknownPanicLog(panic_header, cmd_options={}):
1083    magic_ptr = Cast(panic_header, 'uint32_t *')
1084    panic_log_magic = dereference(magic_ptr)
1085    print("Unrecognized panic header format. Magic: 0x%x..." % unsigned(panic_log_magic))
1086    print("Panic region starts at 0x%08x" % int(panic_header))
1087    print("Hint: To dump this panic header in order to try manually parsing it, use this command:")
1088    print(" (lldb) memory read -fx -s4 -c64 0x%08x" % int(panic_header))
1089    print(" ^ that will dump the first 256 bytes of the panic region")
1090    ## TBD: Hexdump some bits here to allow folks to poke at the region manually?
1091    return
1092
1093
1094@lldb_command('paniclog', 'SMD:')
1095def ShowPanicLog(cmd_args=None, cmd_options={}):
1096    """ Display the paniclog information
1097        usage: (lldb) paniclog
1098        options:
1099            -v : increase verbosity
1100            -S : parse stackshot data (if panic stackshot available)
1101            -D : Takes a folder name for stackshot. This must be specified along with the -S option.
1102            -M : parse macOS panic area (print panic string (if available), and/or capture crashlog info)
1103            -E : Takes a file name and redirects the ext paniclog output to the file
1104    """
1105
1106    if "-M" in cmd_options:
1107        if not hasattr(kern.globals, "mac_panic_header"):
1108            print("macOS panic data requested but unavailable on this device")
1109            return
1110        panic_header = kern.globals.mac_panic_header
1111        # DEBUG HACK FOR TESTING
1112        #panic_header = kern.GetValueFromAddress(0xfffffff054098000, "uint32_t *")
1113    else:
1114        panic_header = kern.globals.panic_info
1115
1116    if hasattr(panic_header, "eph_magic"):
1117        panic_log_magic = unsigned(panic_header.eph_magic)
1118    elif hasattr(panic_header, "mph_magic"):
1119        panic_log_magic = unsigned(panic_header.mph_magic)
1120    else:
1121        print("*** Warning: unsure of panic header format, trying anyway")
1122        magic_ptr = Cast(panic_header, 'uint32_t *')
1123        panic_log_magic = int(dereference(magic_ptr))
1124
1125    if panic_log_magic == 0:
1126        # No panic here..
1127        return
1128
1129    panic_parsers = {
1130        int(xnudefines.AURR_PANIC_MAGIC)     : ParseAURRPanicLog,
1131        int(xnudefines.MACOS_PANIC_MAGIC)    : ParseMacOSPanicLog,
1132        int(xnudefines.EMBEDDED_PANIC_MAGIC) : ParseEmbeddedPanicLog,
1133    }
1134
1135    # Find the right parser (fall back to unknown parser above)
1136    parser = panic_parsers.get(panic_log_magic, ParseUnknownPanicLog)
1137
1138    # execute it
1139    return parser(panic_header, cmd_options)
1140
1141@lldb_command('extpaniclog', 'F:')
1142def ProcessExtensiblePaniclog(cmd_args=None, cmd_options={}):
1143    """ Write the extensible paniclog information to a file
1144        usage: (lldb) paniclog
1145        options:
1146            -F : Output file name
1147    """
1148
1149    if not "-F" in cmd_options:
1150        print("Output file name is needed: Use -F")
1151        return
1152
1153    panic_header = kern.globals.panic_info
1154    process = LazyTarget().GetProcess()
1155    error = lldb.SBError()
1156    EXT_PANICLOG_MAX_SIZE = 32 # 32 is the max size of the string in Data ID
1157
1158    ext_paniclog_len = unsigned(panic_header.eph_ext_paniclog_len)
1159    if ext_paniclog_len == 0:
1160        print("Cannot find extensible paniclog")
1161        return
1162
1163    ext_paniclog_addr = unsigned(panic_header) + unsigned(panic_header.eph_ext_paniclog_offset)
1164
1165    ext_paniclog_bytes = process.chkReadMemory(ext_paniclog_addr, ext_paniclog_len);
1166
1167    idx = 0;
1168    ext_paniclog_ver_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')]
1169    ext_paniclog_ver = int.from_bytes(ext_paniclog_ver_bytes, 'little')
1170
1171    idx += sizeof('uint32_t')
1172    no_of_logs_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')]
1173    no_of_logs = int.from_bytes(no_of_logs_bytes, 'little')
1174
1175    idx += sizeof('uint32_t')
1176
1177    ext_paniclog = dict()
1178
1179    logs_processed = 0
1180    for _ in range(no_of_logs):
1181        uuid_bytes = ext_paniclog_bytes[idx:idx+sizeof('uuid_t')]
1182        ext_uuid = str(uuid.UUID(bytes=uuid_bytes))
1183
1184        idx += sizeof('uuid_t')
1185        flags_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')]
1186        flags = int.from_bytes(flags_bytes, 'little')
1187
1188        idx += sizeof('ext_paniclog_flags_t')
1189        data_id_bytes = ext_paniclog_bytes[idx:idx + EXT_PANICLOG_MAX_SIZE].split(b'\0')[0]
1190        data_id = data_id_bytes.decode('utf-8')
1191        data_id_len = len(data_id_bytes)
1192
1193        idx += data_id_len + 1
1194        data_len_bytes = ext_paniclog_bytes[idx:idx+sizeof('uint32_t')]
1195        data_len = int.from_bytes(data_len_bytes, 'little')
1196
1197        idx += sizeof('uint32_t')
1198        data_bytes = ext_paniclog_bytes[idx:idx+data_len]
1199        data = base64.b64encode(data_bytes).decode('ascii')
1200
1201        idx += data_len
1202
1203        temp_dict = dict(Data_Id=data_id, Data=data)
1204
1205        ext_paniclog.setdefault(ext_uuid, []).append(temp_dict)
1206
1207        logs_processed += 1
1208
1209    if logs_processed < no_of_logs:
1210        print("** Warning: Extensible paniclog might be corrupted **")
1211
1212    with open(cmd_options['-F'], 'w') as out_file:
1213        out_file.write(json.dumps(ext_paniclog))
1214        print("Wrote extensible paniclog to %s" % cmd_options['-F'])
1215
1216    return
1217
1218@lldb_command('showbootargs')
1219def ShowBootArgs(cmd_args=None):
1220    """ Display boot arguments passed to the target kernel
1221    """
1222    bootargs = Cast(kern.GetGlobalVariable('PE_state').bootArgs, 'boot_args *')
1223    bootargs_cmd = bootargs.CommandLine
1224    print(str(bootargs_cmd))
1225
1226# The initialization code to add your commands
1227_xnu_framework_init = False
1228def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict):
1229    global kern, lldb_command_documentation, config, _xnu_framework_init
1230    if _xnu_framework_init:
1231        return
1232    _xnu_framework_init = True
1233    debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char *\[[0-9]*\]"')
1234    debugger.HandleCommand('type format add --format hex -C yes uintptr_t')
1235    debugger.HandleCommand('type format add --format hex -C yes cpumap_t')
1236    kern = KernelTarget(debugger)
1237    if not hasattr(lldb.SBValue, 'GetValueAsAddress'):
1238        warn_str = "WARNING: lldb version is too old. Some commands may break. Please update to latest lldb."
1239        if os.isatty(sys.__stdout__.fileno()):
1240            warn_str = VT.DarkRed + warn_str + VT.Default
1241        print(warn_str)
1242    print("xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries.")
1243    # print(f"xnu debugger ID: {debugger.GetID()}")
1244
1245__lldb_init_module(lldb.debugger, None)
1246
1247@lldb_command("showlldbtypesummaries")
1248def ShowLLDBTypeSummaries(cmd_args=[]):
1249    """ Enable/Disable kernel type summaries. Default is disabled.
1250        Usage: showlldbtypesummaries [enable|disable]
1251        default is enable
1252    """
1253    global config
1254    action = "enable"
1255    trailer_msg = ''
1256    if len(cmd_args) > 0 and cmd_args[0].lower().find('disable') >=0:
1257        action = "disable"
1258        config['showTypeSummary'] = False
1259        trailer_msg = "Please run 'showlldbtypesummaries enable' to enable the summary feature."
1260    else:
1261        config['showTypeSummary'] = True
1262        SetupLLDBTypeSummaries(True)
1263        trailer_msg = "Please run 'showlldbtypesummaries disable' to disable the summary feature."
1264    lldb_run_command("type category "+ action +" kernel")
1265    print("Successfully "+action+"d the kernel type summaries. %s" % trailer_msg)
1266
1267@lldb_command('walkqueue_head', 'S')
1268def WalkQueueHead(cmd_args=[], cmd_options={}):
1269    """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h
1270        Option: -S - suppress summary output.
1271        Usage: (lldb) walkqueue_head  <queue_entry *> <struct type> <fieldname>
1272        ex:    (lldb) walkqueue_head  0x7fffff80 "thread *" "task_threads"
1273
1274    """
1275    global lldb_summary_definitions
1276    if cmd_args is None or len(cmd_args) == 0:
1277        raise ArgumentError("invalid arguments")
1278    if len(cmd_args) != 3:
1279        raise ArgumentError("insufficient arguments")
1280    queue_head = kern.GetValueFromAddress(cmd_args[0], 'struct queue_entry *')
1281    el_type = cmd_args[1]
1282    field_name = cmd_args[2]
1283    showsummary = False
1284    if el_type in lldb_summary_definitions:
1285        showsummary = True
1286    if '-S' in cmd_options:
1287        showsummary = False
1288
1289    for i in IterateQueue(queue_head, el_type, field_name):
1290        if showsummary:
1291            print(lldb_summary_definitions[el_type](i))
1292        else:
1293            print("{0: <#020x}".format(i))
1294
1295
1296
1297@lldb_command('walklist_entry', 'SE')
1298def WalkList(cmd_args=[], cmd_options={}):
1299    """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h
1300        params:
1301            object addr  - value : address of object
1302            element_type - str   : Type of the next element
1303            field_name   - str   : Name of the field in next element's structure
1304
1305        Options: -S - suppress summary output.
1306                 -E - Iterate using SLIST_ENTRYs
1307
1308        Usage: (lldb) walklist_entry  <obj with list_entry *> <struct type> <fieldname>
1309        ex:    (lldb) walklist_entry  0x7fffff80 "struct proc *" "p_sibling"
1310
1311    """
1312    global lldb_summary_definitions
1313    if cmd_args is None or len(cmd_args) == 0:
1314        raise ArgumentError("invalid arguments")
1315    if len(cmd_args) != 3:
1316        raise ArgumentError("insufficient arguments")
1317    el_type = cmd_args[1]
1318    queue_head = kern.GetValueFromAddress(cmd_args[0], el_type)
1319    field_name = cmd_args[2]
1320    showsummary = False
1321    if el_type in lldb_summary_definitions:
1322        showsummary = True
1323    if '-S' in cmd_options:
1324        showsummary = False
1325    if '-E' in cmd_options:
1326        prefix = 's'
1327    else:
1328        prefix = ''
1329    elt = queue_head
1330    while unsigned(elt) != 0:
1331        i = elt
1332        elt = elt.__getattr__(field_name).__getattr__(prefix + 'le_next')
1333        if showsummary:
1334            print(lldb_summary_definitions[el_type](i))
1335        else:
1336            print("{0: <#020x}".format(i))
1337
1338def trace_parse_Copt(Copt):
1339    """Parses the -C option argument and returns a list of CPUs
1340    """
1341    cpusOpt = Copt
1342    cpuList = cpusOpt.split(",")
1343    chosen_cpus = []
1344    for cpu_num_string in cpuList:
1345        try:
1346            if '-' in cpu_num_string:
1347                parts = cpu_num_string.split('-')
1348                if len(parts) != 2 or not (parts[0].isdigit() and parts[1].isdigit()):
1349                    raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string)
1350                firstRange = int(parts[0])
1351                lastRange = int(parts[1])
1352                if firstRange >= kern.globals.real_ncpus or lastRange >= kern.globals.real_ncpus:
1353                    raise ValueError()
1354                if lastRange < firstRange:
1355                    raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string)
1356                for cpu_num in range(firstRange, lastRange + 1):
1357                    if cpu_num not in chosen_cpus:
1358                        chosen_cpus.append(cpu_num)
1359            else:
1360                chosen_cpu = int(cpu_num_string)
1361                if chosen_cpu < 0 or chosen_cpu >= kern.globals.real_ncpus:
1362                    raise ValueError()
1363                if chosen_cpu not in chosen_cpus:
1364                    chosen_cpus.append(chosen_cpu)
1365        except ValueError:
1366            raise ArgumentError("Invalid CPU number specified.  Valid range is 0..%d" % (kern.globals.real_ncpus - 1))
1367
1368    return chosen_cpus
1369
1370
1371IDX_CPU = 0
1372IDX_RINGPOS = 1
1373IDX_RINGENTRY = 2
1374def Trace_cmd(cmd_args=[], cmd_options={}, headerString=lambda:"", entryString=lambda x:"", ring='', entries_per_cpu=0, max_backtraces=0):
1375    """Generic trace dumper helper function
1376    """
1377
1378    if '-S' in cmd_options:
1379        field_arg = cmd_options['-S']
1380        try:
1381            getattr(kern.PERCPU_GET(ring, 0)[0], field_arg)
1382            sort_key_field_name = field_arg
1383        except AttributeError:
1384            raise ArgumentError("Invalid sort key field name `%s'" % field_arg)
1385    else:
1386            sort_key_field_name = 'start_time_abs'
1387
1388    if '-C' in cmd_options:
1389        chosen_cpus = trace_parse_Copt(cmd_options['-C'])
1390    else:
1391        chosen_cpus = [x for x in range(kern.globals.real_ncpus)]
1392
1393    try:
1394        limit_output_count = int(cmd_options['-N'])
1395    except ValueError:
1396        raise ArgumentError("Invalid output count `%s'" % cmd_options['-N']);
1397    except KeyError:
1398        limit_output_count = None
1399
1400    reverse_sort = '-R' in cmd_options
1401    backtraces = '-B' in cmd_options
1402
1403    # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected,
1404    # the original ring index, and the iotrace entry.
1405    entries = []
1406    for x in chosen_cpus:
1407        ring_slice = [(x, y, kern.PERCPU_GET(ring, x)[y]) for y in range(entries_per_cpu)]
1408        entries.extend(ring_slice)
1409
1410    total_entries = len(entries)
1411
1412    entries.sort(key=lambda x: getattr(x[IDX_RINGENTRY], sort_key_field_name), reverse=reverse_sort)
1413
1414    if limit_output_count is not None and limit_output_count > total_entries:
1415        print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count, total_entries));
1416        limit_output_count = total_entries
1417
1418    if len(chosen_cpus) < kern.globals.real_ncpus:
1419        print("NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus) > 1 else "", str(chosen_cpus)))
1420
1421    if limit_output_count is not None and limit_output_count < total_entries:
1422        entries_to_display = limit_output_count
1423        print("NOTE: Limiting to the %s" % ("first entry" if entries_to_display == 1 else ("first %d entries" % entries_to_display)))
1424    else:
1425        entries_to_display = total_entries
1426
1427    print(headerString())
1428
1429    for x in range(entries_to_display):
1430        print(entryString(entries[x]))
1431
1432        if backtraces:
1433            for btidx in range(max_backtraces):
1434                nextbt = entries[x][IDX_RINGENTRY].backtrace[btidx]
1435                if nextbt == 0:
1436                    break
1437                print("\t" + GetSourceInformationForAddress(nextbt))
1438
1439
1440@lldb_command('iotrace', 'C:N:S:RB')
1441def IOTrace_cmd(cmd_args=[], cmd_options={}):
1442    """ Prints the iotrace ring buffers for all CPUs by default.
1443        Arguments:
1444          -B                              : Print backtraces for each ring entry
1445          -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
1446                                            single CPU number or a range separated by a dash (e.g. "0-3"))
1447          -N <count>                      : Limit output to the first <count> entries (across all chosen CPUs)
1448          -R                              : Display results in reverse-sorted order (oldest first; default is newest-first)
1449          -S <sort_key_field_name>        : Sort output by specified iotrace_entry_t field name (instead of by timestamp)
1450    """
1451    MAX_IOTRACE_BACKTRACES = 16
1452
1453    if not hasattr(kern.globals, 'iotrace_entries_per_cpu'):
1454        print("Sorry, iotrace is not supported.")
1455        return
1456
1457    if kern.globals.iotrace_entries_per_cpu == 0:
1458        print("Sorry, iotrace is disabled.")
1459        return
1460
1461    hdrString = lambda : "%-19s %-8s %-10s %-20s SZ  %-18s %-17s DATA" % (
1462        "START TIME",
1463        "DURATION",
1464        "CPU#[RIDX]",
1465        "      TYPE",
1466        "   VIRT ADDR",
1467        "   PHYS ADDR")
1468
1469    entryString = lambda x : "%-20u(%6u) %6s[%02d] %-20s %-2d 0x%016x 0x%016x 0x%x" % (
1470        x[IDX_RINGENTRY].start_time_abs,
1471        x[IDX_RINGENTRY].duration,
1472        "CPU%d" % x[IDX_CPU],
1473        x[IDX_RINGPOS],
1474        str(x[IDX_RINGENTRY].iotype).split("=")[1].strip(),
1475        x[IDX_RINGENTRY].size,
1476        x[IDX_RINGENTRY].vaddr,
1477        x[IDX_RINGENTRY].paddr,
1478        x[IDX_RINGENTRY].val)
1479
1480    Trace_cmd(cmd_args, cmd_options, hdrString, entryString, 'iotrace_ring',
1481        kern.globals.iotrace_entries_per_cpu, MAX_IOTRACE_BACKTRACES)
1482
1483
1484@lldb_command('ttrace', 'C:N:S:RB')
1485def TrapTrace_cmd(cmd_args=[], cmd_options={}):
1486    """ Prints the iotrace ring buffers for all CPUs by default.
1487        Arguments:
1488          -B                              : Print backtraces for each ring entry
1489          -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
1490                                            single CPU number or a range separated by a dash (e.g. "0-3"))
1491          -N <count>                      : Limit output to the first <count> entries (across all chosen CPUs)
1492          -R                              : Display results in reverse-sorted order (oldest first; default is newest-first)
1493          -S <sort_key_field_name>        : Sort output by specified traptrace_entry_t field name (instead of by timestamp)
1494    """
1495    MAX_TRAPTRACE_BACKTRACES = 8
1496
1497    if kern.arch != "x86_64":
1498        print("Sorry, ttrace is an x86-only command.")
1499        return
1500
1501    hdrString = lambda : "%-30s CPU#[RIDX] VECT INTERRUPTED_THREAD PREMLV INTRLV INTERRUPTED_PC" % (
1502        "START TIME   (DURATION [ns])")
1503    entryString = lambda x : "%-20u(%6s) %8s[%02d] 0x%02x 0x%016x %6d %6d %s" % (
1504        x[IDX_RINGENTRY].start_time_abs,
1505        str(x[IDX_RINGENTRY].duration) if hex(x[IDX_RINGENTRY].duration) != "0xffffffffffffffff" else 'inprog',
1506        "CPU%d" % x[IDX_CPU],
1507        x[IDX_RINGPOS],
1508        int(x[IDX_RINGENTRY].vector),
1509        x[IDX_RINGENTRY].curthread,
1510        x[IDX_RINGENTRY].curpl,
1511        x[IDX_RINGENTRY].curil,
1512        GetSourceInformationForAddress(x[IDX_RINGENTRY].interrupted_pc))
1513
1514    Trace_cmd(cmd_args, cmd_options, hdrString, entryString, 'traptrace_ring',
1515        kern.globals.traptrace_entries_per_cpu, MAX_TRAPTRACE_BACKTRACES)
1516
1517# Yields an iterator over all the sysctls from the provided root.
1518# Can optionally filter by the given prefix
1519def IterateSysctls(root_oid, prefix="", depth = 0, parent = ""):
1520    headp = root_oid
1521    for pp in IterateListEntry(headp, 'oid_link', 's'):
1522        node_str = ""
1523        if prefix != "":
1524            node_str = str(pp.oid_name)
1525            if parent != "":
1526                node_str = parent + "." + node_str
1527                if node_str.startswith(prefix):
1528                    yield pp, depth, parent
1529        else:
1530            yield pp, depth, parent
1531        type = pp.oid_kind & 0xf
1532        if type == 1 and pp.oid_arg1 != 0:
1533            if node_str == "":
1534                next_parent = str(pp.oid_name)
1535                if parent != "":
1536                    next_parent = parent + "." + next_parent
1537            else:
1538                next_parent = node_str
1539            # Only recurse if the next parent starts with our allowed prefix.
1540            # Note that it's OK if the parent string is too short (because the prefix might be for a deeper node).
1541            prefix_len = min(len(prefix), len(next_parent))
1542            if next_parent[:prefix_len] == prefix[:prefix_len]:
1543                for x in IterateSysctls(Cast(pp.oid_arg1, "struct sysctl_oid_list *"), prefix, depth + 1, next_parent):
1544                    yield x
1545
1546@lldb_command('showsysctls', 'P:')
1547def ShowSysctls(cmd_args=[], cmd_options={}):
1548    """ Walks the list of sysctl data structures, printing out each during traversal.
1549        Arguments:
1550          -P <string> : Limit output to sysctls starting with the specified prefix.
1551    """
1552    if '-P' in cmd_options:
1553        _ShowSysctl_prefix = cmd_options['-P']
1554        allowed_prefixes = _ShowSysctl_prefix.split('.')
1555        if allowed_prefixes:
1556            for x in range(1, len(allowed_prefixes)):
1557                allowed_prefixes[x] = allowed_prefixes[x - 1] + "." + allowed_prefixes[x]
1558    else:
1559        _ShowSysctl_prefix = ''
1560        allowed_prefixes = []
1561
1562    for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children, _ShowSysctl_prefix):
1563        if parentstr == "":
1564            parentstr = "<none>"
1565        headp = sysctl
1566        st = (" " * depth * 2) + str(sysctl.GetSBValue().Dereference()).replace("\n", "\n" + (" " * depth * 2))
1567        print('parent = "%s"' % parentstr, st[st.find("{"):])
1568
1569@lldb_command('showexperiments', 'F')
1570def ShowExperiments(cmd_args=[], cmd_options={}):
1571    """ Shows any active kernel experiments being run on the device via trial.
1572        Arguments:
1573        -F: Scan for changed experiment values even if no trial identifiers have been set.
1574    """
1575    treatment_id = str(kern.globals.trial_treatment_id)
1576    experiment_id = str(kern.globals.trial_experiment_id)
1577    deployment_id = kern.globals.trial_deployment_id._GetValueAsSigned()
1578    if treatment_id == "" and experiment_id == "" and deployment_id == -1:
1579        print("Device is not enrolled in any kernel experiments.")
1580        if not '-F' in cmd_options:
1581            return
1582    else:
1583        print("""Device is enrolled in a kernel experiment:
1584    treatment_id: %s
1585    experiment_id: %s
1586    deployment_id: %d""" % (treatment_id, experiment_id, deployment_id))
1587
1588    print("Scanning sysctl tree for modified factors...")
1589
1590    kExperimentFactorFlag = 0x00100000
1591
1592    formats = {
1593            "IU": gettype("unsigned int *"),
1594            "I": gettype("int *"),
1595            "LU": gettype("unsigned long *"),
1596            "L": gettype("long *"),
1597            "QU": gettype("uint64_t *"),
1598            "Q": gettype("int64_t *")
1599    }
1600
1601    for sysctl, depth, parentstr in IterateSysctls(kern.globals.sysctl__children):
1602        if sysctl.oid_kind & kExperimentFactorFlag:
1603            spec = cast(sysctl.oid_arg1, "struct experiment_spec *")
1604            # Skip if arg2 isn't set to 1 (indicates an experiment factor created without an experiment_spec).
1605            if sysctl.oid_arg2 == 1:
1606                if spec.modified == 1:
1607                    fmt = str(sysctl.oid_fmt)
1608                    ptr = spec.ptr
1609                    t = formats.get(fmt, None)
1610                    if t:
1611                        value = cast(ptr, t)
1612                    else:
1613                        # Unknown type
1614                        continue
1615                    name = str(parentstr) + "." + str(sysctl.oid_name)
1616                    print("%s = %d (Default value is %d)" % (name, dereference(value), spec.original_value))
1617
1618
1619from memory import *
1620from process import *
1621from ipc import *
1622from pmap import *
1623from ioreg import *
1624from mbufs import *
1625from net import *
1626from skywalk import *
1627from kext import *
1628from kdp import *
1629from userspace import *
1630from pci import *
1631from scheduler import *
1632from recount import *
1633from misc import *
1634from apic import *
1635from structanalyze import *
1636from ipcimportancedetail import *
1637from bank import *
1638from turnstile import *
1639from kasan import *
1640from waitq import *
1641from usertaskgdbserver import *
1642from ktrace import *
1643from microstackshot import *
1644from xnutriage import *
1645from kmtriage import *
1646from kevent import *
1647from workqueue import *
1648from ulock import *
1649from ntstat import *
1650from zonetriage import *
1651from sysreg import *
1652from counter import *
1653from refgrp import *
1654from workload import *
1655from log import showLogStream, show_log_stream_info
1656from nvram import *
1657from exclaves import *
1658from memorystatus import *
1659from vm_pageout import *
1660from taskinfo import *
1661