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