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