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