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