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