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