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