]> git.saurik.com Git - apple/xnu.git/blobdiff - tools/lldbmacros/xnu.py
xnu-6153.141.1.tar.gz
[apple/xnu.git] / tools / lldbmacros / xnu.py
index 1806464fd72bbf38b64da88bf739cbb16312a764..6e58e3209985e6106a61d1455c9696031038de67 100755 (executable)
@@ -21,6 +21,8 @@ MODULE_NAME=__name__
 
 COMMON_HELP_STRING = """
     -h  Show the help string for the command.
+    -c [always|auto|never|0|1]
+                            Control the colorized output of certain commands
     -o <path/to/filename>   The output of this command execution will be saved to file. Parser information or errors will
                             not be sent to file though. eg /tmp/output.txt
     -s <filter_string>      The "filter_string" param is parsed to python regex expression and each line of output
@@ -45,11 +47,11 @@ def header(initial_value):
         return obj
     return _set_header
 
-# holds type declarations done by xnu. 
+# holds type declarations done by xnu.
 #DONOTTOUCHME: Exclusive use of lldb_type_summary only.
-lldb_summary_definitions = {} 
+lldb_summary_definitions = {}
 def lldb_type_summary(types_list):
-    """ A function decorator to register a summary for a type in lldb. 
+    """ A function decorator to register a summary for a type in lldb.
         params: types_list - [] an array of types that you wish to register a summary callback function. (ex. ['task *', 'task_t'])
         returns: Nothing. This is a decorator.
     """
@@ -60,13 +62,13 @@ def lldb_type_summary(types_list):
                 out_string += "\n" + obj.header +"\n"
             out_string += obj( core.value(lldbval) )
             return out_string
-        
+
         myglobals = globals()
         summary_function_name = "LLDBSummary" + obj.__name__
         myglobals[summary_function_name] = _internal_summary_function
         summary_function = myglobals[summary_function_name]
         summary_function.__doc__ = obj.__doc__
-        
+
         global lldb_summary_definitions
         for single_type in types_list:
             if config['showTypeSummary']:
@@ -74,19 +76,20 @@ def lldb_type_summary(types_list):
                     lldb.debugger.HandleCommand("type summary delete --category kernel \""+ single_type + "\"")
                 lldb.debugger.HandleCommand("type summary add \""+ single_type +"\" --category kernel --python-function " + MODULE_NAME + "." + summary_function_name)
             lldb_summary_definitions[single_type] = obj
-            
+
         return obj
     return _get_summary
 
-#global cache of documentation for lldb commands exported by this module 
+#global cache of documentation for lldb commands exported by this module
 #DONOTTOUCHME: Exclusive use of lldb_command only.
 lldb_command_documentation = {}
 
-def lldb_command(cmd_name, option_string = ''):
+def lldb_command(cmd_name, option_string = '', fancy=False):
     """ A function decorator to define a command with namd 'cmd_name' in the lldb scope to call python function.
         params: cmd_name - str : name of command to be set in lldb prompt.
-            option_string - str: getopt like option string. Only CAPITAL LETTER options allowed. 
+            option_string - str: getopt like option string. Only CAPITAL LETTER options allowed.
                                  see README on Customizing command options.
+            fancy - bool       : whether the command will receive an 'O' object to do fancy output (tables, indent, color)
     """
     if option_string != option_string.upper():
         raise RuntimeError("Cannot setup command with lowercase option args. %s" % option_string)
@@ -104,16 +107,18 @@ def lldb_command(cmd_name, option_string = ''):
             command_args = shlex.split(command)
             lldb.debugger.HandleCommand('type category disable kernel' )
             def_verbose_level = config['verbosity']
-            
+
             try:
                 stream.setOptions(command_args, option_string)
                 if stream.verbose_level != 0:
-                    config['verbosity'] +=  stream.verbose_level 
+                    config['verbosity'] +=  stream.verbose_level
                 with RedirectStdStreams(stdout=stream) :
+                    args = { 'cmd_args': stream.target_cmd_args }
                     if option_string:
-                        obj(cmd_args=stream.target_cmd_args, cmd_options=stream.target_cmd_options)
-                    else:
-                        obj(cmd_args=stream.target_cmd_args)
+                        args['cmd_options'] = stream.target_cmd_options
+                    if fancy:
+                        args['O'] = stream
+                    obj(**args)
             except KeyboardInterrupt:
                 print "Execution interrupted by user"
             except ArgumentError as arg_error:
@@ -133,7 +138,7 @@ However, it is recommended that you report the exception to lldb/kernel debuggin
 
             if config['showTypeSummary']:
                 lldb.debugger.HandleCommand('type category enable kernel' )
-            
+
             if stream.pluginRequired :
                 plugin = LoadXNUPlugin(stream.pluginName)
                 if plugin == None :
@@ -143,10 +148,10 @@ However, it is recommended that you report the exception to lldb/kernel debuggin
                 return_data = plugin.plugin_execute(cmd_name, result.GetOutput())
                 ProcessXNUPluginResult(return_data)
                 plugin.plugin_cleanup()
-            
+
             #restore the verbose level after command is complete
             config['verbosity'] = def_verbose_level
-            
+
             return
 
         myglobals = globals()
@@ -163,14 +168,24 @@ However, it is recommended that you report the exception to lldb/kernel debuggin
             lldb.debugger.HandleCommand("command script delete "+cmd_name)
         lldb_command_documentation[cmd_name] = (obj.__name__, obj.__doc__.lstrip(), option_string)
         lldb.debugger.HandleCommand("command script add -f " + MODULE_NAME + "." + command_function_name + " " + cmd_name)
+
+        if fancy:
+            def wrapped_fun(cmd_args=None, cmd_options={}, O=None):
+                if O is None:
+                    stream = CommandOutput(cmd_name, fhandle=sys.stdout)
+                    with RedirectStdStreams(stdout=stream):
+                        return obj(cmd_args, cmd_options, stream)
+                else:
+                    return obj(cmd_args, cmd_options, O)
+            return wrapped_fun
         return obj
     return _cmd
 
 def lldb_alias(alias_name, cmd_line):
-    """ define an alias in the lldb command line. 
+    """ define an alias in the lldb command line.
         A programatic way of registering an alias. This basically does
         (lldb)command alias alias_name "cmd_line"
-        ex. 
+        ex.
         lldb_alias('readphys16', 'readphys 16')
     """
     alias_name = alias_name.strip()
@@ -194,7 +209,7 @@ def SetupLLDBTypeSummaries(reset=False):
     return
 
 def LoadXNUPlugin(name):
-    """ Try to load a plugin from the plugins directory. 
+    """ Try to load a plugin from the plugins directory.
     """
     retval = None
     name=name.strip()
@@ -208,7 +223,7 @@ def LoadXNUPlugin(name):
             print "Plugin is not correctly implemented. Please read documentation on implementing plugins"
     except:
         print "plugin not found :"+name
-         
+
     return retval
 
 def ProcessXNUPluginResult(result_data):
@@ -218,7 +233,7 @@ def ProcessXNUPluginResult(result_data):
     ret_status = result_data[0]
     ret_string = result_data[1]
     ret_commands = result_data[2]
-    
+
     if ret_status == False:
         print "Plugin failed: " + ret_string
         return
@@ -233,15 +248,15 @@ def ProcessXNUPluginResult(result_data):
 #DONOTTOUCHME: Exclusive use of xnudebug_test only
 lldb_command_tests = {}
 def xnudebug_test(test_name):
-    """ A function decoratore to register a test with the framework. Each test is supposed to be of format 
+    """ A function decoratore to register a test with the framework. Each test is supposed to be of format
         def Test<name>(kernel_target, config, lldb_obj, isConnected )
-        
+
         NOTE: The testname should start with "Test" else exception will be raised.
     """
     def _test(obj):
         global lldb_command_tests
         if obj.__name__.find("Test") != 0 :
-            print "Test name ", obj.__name__ , " should start with Test" 
+            print "Test name ", obj.__name__ , " should start with Test"
             raise ValueError
         lldb_command_tests[test_name] = (test_name, obj.__name__, obj, obj.__doc__)
         return obj
@@ -249,14 +264,14 @@ def xnudebug_test(test_name):
 
 
 # End Debugging specific utility functions
-# Kernel Debugging specific classes and accessor methods 
+# Kernel Debugging specific classes and accessor methods
 
 # global access object for target kernel
 
 def GetObjectAtIndexFromArray(array_base, index):
     """ Subscript indexing for arrays that are represented in C as pointers.
         for ex. int *arr = malloc(20*sizeof(int));
-        now to get 3rd int from 'arr' you'd do 
+        now to get 3rd int from 'arr' you'd do
         arr[2] in C
         GetObjectAtIndexFromArray(arr_val,2)
         params:
@@ -278,8 +293,8 @@ kern = None
 def GetLLDBThreadForKernelThread(thread_obj):
     """ Get a reference to lldb.SBThread representation for kernel thread.
         params:
-            thread_obj : core.cvalue - thread object of type thread_t 
-        returns 
+            thread_obj : core.cvalue - thread object of type thread_t
+        returns
             lldb.SBThread - lldb thread object for getting backtrace/registers etc.
     """
     tid = unsigned(thread_obj.thread_id)
@@ -369,10 +384,10 @@ def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""):
 
         if not function:
             # No debug info for 'function'.
-            out_string += prefix 
+            out_string += prefix
             if not is_continuation:
-                out_string += "{fp:#018x} ".format(fp = frame_p) 
-            
+                out_string += "{fp:#018x} ".format(fp = frame_p)
+
             symbol = frame.GetSymbol()
             if not symbol:
                 out_string += GetKextSymbolInfo(load_addr)
@@ -381,7 +396,7 @@ def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""):
                 start_addr = symbol.GetStartAddress().GetFileAddress()
                 symbol_name = symbol.GetName()
                 symbol_offset = file_addr - start_addr
-                out_string += "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr=load_addr, 
+                out_string += "{addr:#018x} {mod}`{symbol} + {offset:#x} \n".format(addr=load_addr,
                     mod=mod_name, symbol=symbol_name, offset=symbol_offset)
         else:
             # Debug info is available for 'function'.
@@ -391,15 +406,15 @@ def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""):
             func_name = '%s [inlined]' % func_name if frame.IsInlined() else func_name
             if is_continuation and frame.IsInlined():
                 debuglog("Skipping frame for thread {:#018x} since its inlined".format(thread_obj))
-                continue 
-            out_string += prefix 
+                continue
+            out_string += prefix
             if not is_continuation:
                 out_string += "{fp:#018x} ".format(fp=frame_p)
             out_string += "{addr:#018x} {func}{args} \n".format(addr=load_addr,
                                     func=func_name,
                                     file=file_name, line=line_num,
                                     args="(" + (str(frame.arguments).replace("\n", ", ") if len(frame.arguments) > 0 else "void") + ")")
-        iteration += 1 
+        iteration += 1
         if frame_p:
             last_frame_p = frame_p
 
@@ -409,9 +424,9 @@ def GetThreadBackTrace(thread_obj, verbosity = vHUMAN, prefix = ""):
     return out_string
 
 def GetSourceInformationForAddress(addr):
-    """ convert and address to function +offset information. 
+    """ convert and address to function +offset information.
         params: addr - int address in the binary to be symbolicated
-        returns: string of format "0xaddress: function + offset" 
+        returns: string of format "0xaddress: function + offset"
     """
     symbols = kern.SymbolicateFromAddress(addr)
     format_string = "{0:#018x} <{1:s} + {2:#0x}>"
@@ -429,7 +444,7 @@ def GetFrameLocalVariable(variable_name, frame_no=0):
     """ Find a local variable by name
         params:
           variable_name: str - name of variable to search for
-        returns: 
+        returns:
           core.value - if the variable is found.
           None   - if not found or not Valid
     """
@@ -466,16 +481,16 @@ def KernelDebugCommandsHelp(cmd_args=None):
     return None
 
 
-@lldb_command('showraw')    
+@lldb_command('showraw')
 def ShowRawCommand(cmd_args=None):
-    """ A command to disable the kernel summaries and show data as seen by the system. 
+    """ A command to disable the kernel summaries and show data as seen by the system.
         This is useful when trying to read every field of a struct as compared to brief summary
     """
     command = " ".join(cmd_args)
     lldb.debugger.HandleCommand('type category disable kernel' )
     lldb.debugger.HandleCommand( command )
     lldb.debugger.HandleCommand('type category enable kernel' )
+
 
 @lldb_command('xnudebug')
 def XnuDebugCommand(cmd_args=None):
@@ -537,18 +552,18 @@ def XnuDebugCommand(cmd_args=None):
         if test_name in lldb_command_tests:
             test = lldb_command_tests[test_name]
             print "Running test {:s}".format(test[0])
-            if test[2](kern, config, lldb, True) : 
+            if test[2](kern, config, lldb, True) :
                 print "[PASSED] {:s}".format(test[0])
             else:
                 print "[FAILED] {:s}".format(test[0])
-            return ""    
+            return ""
         else:
             print "No such test registered with name: {:s}".format(test_name)
             print "XNUDEBUG Available tests are:"
             for i in lldb_command_tests.keys():
                 print i
         return None
-    
+
     return False
 
 @lldb_command('showversion')
@@ -564,83 +579,68 @@ def ShowVersion(cmd_args=None):
     """
     print kern.version
 
-
-@lldb_command('paniclog', 'S')
-def ShowPanicLog(cmd_args=None, cmd_options={}):
-    """ Display the paniclog information
-        usage: (lldb) paniclog
-        options:
-            -v : increase verbosity
-            -S : parse stackshot data (if panic stackshot available)
+def ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len):
+    """ Process the panic stackshot from the panic header, saving it to a file if it is valid
+        params: panic_stackshot_addr : start address of the panic stackshot binary data
+                panic_stackshot_len : length of the stackshot binary data
+        returns: nothing
     """
+    if not panic_stackshot_addr:
+        print "No panic stackshot available (invalid addr)"
+        return
 
-    if "-S" in cmd_options:
-        if hasattr(kern.globals, "kc_panic_data"):
-            stackshot_saved = False
-            # TODO: Update logic to handle "in-memory" panic stackshot on Gibraltar platforms
-            #       once we drop support for the on disk one there.
-            if kern.arch == 'x86_64':
-                if kern.globals.panic_stackshot_len != 0:
-                    stackshot_saved = True
-                else:
-                    print "No panic stackshot available"
-            else:
-                if unsigned(kern.globals.panic_info.eph_panic_flags) & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG:
-                    stackshot_saved = True
-                else:
-                    print "No panic stackshot available"
-            if stackshot_saved:
-                kc_data = unsigned(addressof(kern.globals.kc_panic_data))
-                ts = int(time.time())
-                ss_binfile = "/tmp/panic_%d.bin" % ts
-                ss_ipsfile = "/tmp/stacks_%d.ips" % ts
-                print "savekcdata  0x%x -O %s" % (kc_data, ss_binfile)
-                SaveKCDataToFile(["0x%x" % kc_data], {"-O":ss_binfile})
-                self_path = str(__file__)
-                base_dir_name = self_path[:self_path.rfind("/")]
-                print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)
-                (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile))
-                if c == 0:
-                    print "Saved ips stackshot file as %s" % ss_ipsfile
-                else:
-                    print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se)
-        else:
-            print "kc_panic_data is unavailable for this kernel config."
+    if not panic_stackshot_len:
+        print "No panic stackshot available (zero length)"
+        return;
 
-    out_str = ""
-    warn_str = ""
+    ts = int(time.time())
+    ss_binfile = "/tmp/panic_%d.bin" % ts
+    ss_ipsfile = "/tmp/stacks_%d.ips" % ts
 
-    if kern.arch == 'x86_64':
-        panic_buf = Cast(kern.globals.panic_info, 'char *')
-        panic_log_magic = unsigned(kern.globals.panic_info.mph_magic)
-        panic_log_begin_offset = unsigned(kern.globals.panic_info.mph_panic_log_offset)
-        panic_log_len = unsigned(kern.globals.panic_info.mph_panic_log_len)
-        other_log_begin_offset = unsigned(kern.globals.panic_info.mph_other_log_offset)
-        other_log_len = unsigned(kern.globals.panic_info.mph_other_log_len)
-        cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(kern.globals.panic_info))
-        if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)):
-            other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset
-        expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC
+    if not SaveDataToFile(panic_stackshot_addr, panic_stackshot_len, ss_binfile, None):
+        print "Failed to save stackshot binary data to file"
+        return
+
+    self_path = str(__file__)
+    base_dir_name = self_path[:self_path.rfind("/")]
+    print "python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile)
+    (c,so,se) = RunShellCommand("python %s/kcdata.py %s -s %s" % (base_dir_name, ss_binfile, ss_ipsfile))
+    if c == 0:
+        print "Saved ips stackshot file as %s" % ss_ipsfile
+        return
     else:
-        panic_buf = Cast(kern.globals.panic_info, 'char *')
-        panic_log_magic = unsigned(kern.globals.panic_info.eph_magic)
-        panic_log_begin_offset = unsigned(kern.globals.panic_info.eph_panic_log_offset)
-        panic_log_len = unsigned(kern.globals.panic_info.eph_panic_log_len)
-        other_log_begin_offset = unsigned(kern.globals.panic_info.eph_other_log_offset)
-        other_log_len = unsigned(kern.globals.panic_info.eph_other_log_len)
-        expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC
-
-    if panic_log_begin_offset == 0:
+        print "Failed to run command: exit code: %d, SO: %s SE: %s" % (c, so, se)
         return
 
+def ParseEmbeddedPanicLog(panic_header, cmd_options={}):
+    panic_buf = Cast(panic_header, 'char *')
+    panic_log_magic = unsigned(panic_header.eph_magic)
+    panic_log_begin_offset = unsigned(panic_header.eph_panic_log_offset)
+    panic_log_len = unsigned(panic_header.eph_panic_log_len)
+    other_log_begin_offset = unsigned(panic_header.eph_other_log_offset)
+    other_log_len = unsigned(panic_header.eph_other_log_len)
+    expected_panic_magic = xnudefines.EMBEDDED_PANIC_MAGIC
+    panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.eph_stackshot_offset)
+    panic_stackshot_len = unsigned(panic_header.eph_stackshot_len)
+    panic_header_flags = unsigned(panic_header.eph_panic_flags)
+
+    warn_str = ""
+    out_str = ""
+
     if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
-        warn_str += "BAD MAGIC! Found 0x%x expected 0x%x".format(panic_log_magic,
+        warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
                     expected_panic_magic)
 
-    if panic_log_begin_offset == 0:
-        if warn_str:
-            print "\n %s" % warn_str
-        return
+    if warn_str:
+        print "\n %s" % warn_str
+        if panic_log_begin_offset == 0:
+            return
+
+    if "-S" in cmd_options:
+        if panic_header_flags & xnudefines.EMBEDDED_PANIC_STACKSHOT_SUCCEEDED_FLAG:
+            ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len)
+        else:
+            print "No panic stackshot available"
 
     panic_log_curindex = 0
     while panic_log_curindex < panic_log_len:
@@ -656,12 +656,198 @@ def ShowPanicLog(cmd_args=None, cmd_options={}):
             other_log_curindex += 1
 
     print out_str
+    return
+
+def ParseMacOSPanicLog(panic_header, cmd_options={}):
+    panic_buf = Cast(panic_header, 'char *')
+    panic_log_magic = unsigned(panic_header.mph_magic)
+    panic_log_begin_offset = unsigned(panic_header.mph_panic_log_offset)
+    panic_log_len = unsigned(panic_header.mph_panic_log_len)
+    other_log_begin_offset = unsigned(panic_header.mph_other_log_offset)
+    other_log_len = unsigned(panic_header.mph_other_log_len)
+    cur_debug_buf_ptr_offset = (unsigned(kern.globals.debug_buf_ptr) - unsigned(panic_header))
+    if other_log_begin_offset != 0 and (other_log_len == 0 or other_log_len < (cur_debug_buf_ptr_offset - other_log_begin_offset)):
+        other_log_len = cur_debug_buf_ptr_offset - other_log_begin_offset
+    expected_panic_magic = xnudefines.MACOS_PANIC_MAGIC
+    panic_stackshot_addr = unsigned(panic_header) + unsigned(panic_header.mph_stackshot_offset)
+    panic_stackshot_len = unsigned(panic_header.mph_stackshot_len)
+    panic_header_flags = unsigned(panic_header.mph_panic_flags)
+
+    warn_str = ""
+    out_str = ""
+
+    if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
+        warn_str += "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
+                    expected_panic_magic)
 
     if warn_str:
         print "\n %s" % warn_str
+        if panic_log_begin_offset == 0:
+            return
+
+    if "-S" in cmd_options:
+        if panic_header_flags & xnudefines.MACOS_PANIC_STACKSHOT_SUCCEEDED_FLAG:
+            ProcessPanicStackshot(panic_stackshot_addr, panic_stackshot_len)
+        else:
+            print "No panic stackshot available"
+
+    panic_log_curindex = 0
+    while panic_log_curindex < panic_log_len:
+        p_char = str(panic_buf[(panic_log_begin_offset + panic_log_curindex)])
+        out_str += p_char
+        panic_log_curindex += 1
+
+    if other_log_begin_offset != 0:
+        other_log_curindex = 0
+        while other_log_curindex < other_log_len:
+            p_char = str(panic_buf[(other_log_begin_offset + other_log_curindex)])
+            out_str += p_char
+            other_log_curindex += 1
+
+    print out_str
+    return
+
+def ParseAURRPanicLog(panic_header, cmd_options={}):
+    reset_cause = {
+        0x0: "OTHER",
+        0x1: "CATERR",
+        0x2: "SWD_TIMEOUT",
+        0x3: "GLOBAL RESET",
+        0x4: "STRAIGHT TO S5",
+    }
+
+    expected_panic_magic = xnudefines.AURR_PANIC_MAGIC
 
+    panic_buf = Cast(panic_header, 'char *')
+
+    try:
+        # This line will blow up if there's not type info for this struct (older kernel)
+        # We fall back to manual parsing below
+        aurr_panic_header = Cast(panic_header, 'struct efi_aurr_panic_header *')
+        panic_log_magic = unsigned(aurr_panic_header.efi_aurr_magic)
+        panic_log_version = unsigned(aurr_panic_header.efi_aurr_version)
+        panic_log_reset_cause = unsigned(aurr_panic_header.efi_aurr_reset_cause)
+        panic_log_reset_log_offset = unsigned(aurr_panic_header.efi_aurr_reset_log_offset)
+        panic_log_reset_log_len = unsigned(aurr_panic_header.efi_aurr_reset_log_len)
+    except Exception as e:
+        print "*** Warning: kernel symbol file has no type information for 'struct efi_aurr_panic_header'..."
+        print "*** Warning: trying to manually parse..."
+        aurr_panic_header = Cast(panic_header, "uint32_t *")
+        panic_log_magic = unsigned(aurr_panic_header[0])
+        # panic_log_crc = unsigned(aurr_panic_header[1])
+        panic_log_version = unsigned(aurr_panic_header[2])
+        panic_log_reset_cause = unsigned(aurr_panic_header[3])
+        panic_log_reset_log_offset = unsigned(aurr_panic_header[4])
+        panic_log_reset_log_len = unsigned(aurr_panic_header[5])
+
+    if panic_log_magic != 0 and panic_log_magic != expected_panic_magic:
+        print "BAD MAGIC! Found 0x%x expected 0x%x" % (panic_log_magic,
+                    expected_panic_magic)
+        return
+
+    print "AURR Panic Version: %d" % (panic_log_version)
+
+    # When it comes time to extend this in the future, please follow the
+    # construct used below in ShowPanicLog()
+    if panic_log_version in (xnudefines.AURR_PANIC_VERSION, xnudefines.AURR_CRASHLOG_PANIC_VERSION):
+        # AURR Report Version 1 (AURR/MacEFI) or 2 (Crashlog)
+        # see macefifirmware/Vendor/Apple/EfiPkg/AppleDebugSupport/Library/Debugger.h
+        print "Reset Cause: 0x%x (%s)" % (panic_log_reset_cause, reset_cause.get(panic_log_reset_cause, "UNKNOWN"))
+
+        # Adjust panic log string length (cap to maximum supported values)
+        if panic_log_version == xnudefines.AURR_PANIC_VERSION:
+            max_string_len = panic_log_reset_log_len and min(panic_log_reset_log_len, xnudefines.AURR_PANIC_STRING_LEN) or 0
+        elif panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION:
+            max_string_len = xnudefines.CRASHLOG_PANIC_STRING_LEN
+
+        panic_str_offset = 0
+        out_str = ""
+
+        while panic_str_offset < max_string_len:
+            p_char = str(panic_buf[panic_log_reset_log_offset + panic_str_offset])
+            out_str += p_char
+            panic_str_offset += 1
+
+        print out_str
+
+        # Save Crashlog Binary Data (if available)
+        if "-S" in cmd_options and panic_log_version == xnudefines.AURR_CRASHLOG_PANIC_VERSION:
+            crashlog_binary_offset = panic_log_reset_log_offset + xnudefines.CRASHLOG_PANIC_STRING_LEN
+            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
+
+            if 0 == crashlog_binary_size:
+                print "No crashlog data found..."
+                return
+
+            # Save to file
+            ts = int(time.time())
+            ss_binfile = "/tmp/crashlog_%d.bin" % ts
+
+            if not SaveDataToFile(panic_buf + crashlog_binary_offset, crashlog_binary_size, ss_binfile, None):
+                print "Failed to save crashlog binary data to file"
+                return
+    else:
+        return ParseUnknownPanicLog(panic_header, cmd_options)
+
+    return
+
+def ParseUnknownPanicLog(panic_header, cmd_options={}):
+    magic_ptr = Cast(panic_header, 'uint32_t *')
+    panic_log_magic = dereference(magic_ptr)
+    print "Unrecognized panic header format. Magic: 0x%x..." % unsigned(panic_log_magic)
+    print "Panic region starts at 0x%08x" % int(panic_header)
+    print "Hint: To dump this panic header in order to try manually parsing it, use this command:"
+    print " (lldb) memory read -fx -s4 -c64 0x%08x" % int(panic_header)
+    print " ^ that will dump the first 256 bytes of the panic region"
+    ## TBD: Hexdump some bits here to allow folks to poke at the region manually?
     return
 
+
+@lldb_command('paniclog', 'SM')
+def ShowPanicLog(cmd_args=None, cmd_options={}):
+    """ Display the paniclog information
+        usage: (lldb) paniclog
+        options:
+            -v : increase verbosity
+            -S : parse stackshot data (if panic stackshot available)
+            -M : parse macOS panic area (print panic string (if available), and/or capture crashlog info)
+    """
+
+    if "-M" in cmd_options:
+        if not hasattr(kern.globals, "mac_panic_header"):
+            print "macOS panic data requested but unavailable on this device"
+            return
+        panic_header = kern.globals.mac_panic_header
+        # DEBUG HACK FOR TESTING
+        #panic_header = kern.GetValueFromAddress(0xfffffff054098000, "uint32_t *")
+    else:
+        panic_header = kern.globals.panic_info
+
+    if hasattr(panic_header, "eph_magic"):
+        panic_log_magic = unsigned(panic_header.eph_magic)
+    elif hasattr(panic_header, "mph_magic"):
+        panic_log_magic = unsigned(panic_header.mph_magic)
+    else:
+        print "*** Warning: unsure of panic header format, trying anyway"
+        magic_ptr = Cast(panic_header, 'uint32_t *')
+        panic_log_magic = int(dereference(magic_ptr))
+
+    if panic_log_magic == 0:
+        # No panic here..
+        return
+
+    panic_parsers = {
+        int(xnudefines.AURR_PANIC_MAGIC)     : ParseAURRPanicLog,
+        int(xnudefines.MACOS_PANIC_MAGIC)    : ParseMacOSPanicLog,
+        int(xnudefines.EMBEDDED_PANIC_MAGIC) : ParseEmbeddedPanicLog,
+    }
+
+    # Find the right parser (fall back to unknown parser above)
+    parser = panic_parsers.get(panic_log_magic, ParseUnknownPanicLog)
+
+    # execute it
+    return parser(panic_header, cmd_options)
+
 @lldb_command('showbootargs')
 def ShowBootArgs(cmd_args=None):
     """ Display boot arguments passed to the target kernel
@@ -672,7 +858,7 @@ def ShowBootArgs(cmd_args=None):
 
 @static_var("last_process_uniq_id", 1)
 def GetDebuggerStopIDValue():
-    """ Create a unique session identifier. 
+    """ Create a unique session identifier.
         returns:
             int - a unique number identified by processid and stopid.
     """
@@ -689,7 +875,7 @@ def GetDebuggerStopIDValue():
         GetDebuggerStopIDValue.last_process_uniq_id +=1
         proc_uniq_id = GetDebuggerStopIDValue.last_process_uniq_id + 1
 
-    stop_id_str = "{:d}:{:d}".format(proc_uniq_id, stop_id)        
+    stop_id_str = "{:d}:{:d}".format(proc_uniq_id, stop_id)
     return hash(stop_id_str)
 
 # The initialization code to add your commands
@@ -703,6 +889,11 @@ def __lldb_init_module(debugger, internal_dict):
     debugger.HandleCommand('type summary add --regex --summary-string "${var%s}" -C yes -p -v "char \[[0-9]*\]"')
     debugger.HandleCommand('type format add --format hex -C yes uintptr_t')
     kern = KernelTarget(debugger)
+    if not hasattr(lldb.SBValue, 'GetValueAsAddress'):
+        warn_str = "WARNING: lldb version is too old. Some commands may break. Please update to latest lldb."
+        if os.isatty(sys.__stdout__.fileno()):
+            warn_str = VT.DarkRed + warn_str + VT.Default
+        print warn_str
     print "xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries."
 
 __lldb_init_module(lldb.debugger, None)
@@ -729,11 +920,11 @@ def ShowLLDBTypeSummaries(cmd_args=[]):
 
 @lldb_command('walkqueue_head', 'S')
 def WalkQueueHead(cmd_args=[], cmd_options={}):
-    """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h 
+    """ walk a queue_head_t and list all members in it. Note this is for queue_head_t. refer to osfmk/kern/queue.h
         Option: -S - suppress summary output.
         Usage: (lldb) walkqueue_head  <queue_entry *> <struct type> <fieldname>
         ex:    (lldb) walkqueue_head  0x7fffff80 "thread *" "task_threads"
-        
+
     """
     global lldb_summary_definitions
     if not cmd_args:
@@ -754,10 +945,10 @@ def WalkQueueHead(cmd_args=[], cmd_options={}):
             print lldb_summary_definitions[el_type](i)
         else:
             print "{0: <#020x}".format(i)
-    
 
 
-@lldb_command('walklist_entry', 'S')
+
+@lldb_command('walklist_entry', 'SE')
 def WalkList(cmd_args=[], cmd_options={}):
     """ iterate over a list as defined with LIST_ENTRY in bsd/sys/queue.h
         params:
@@ -765,10 +956,12 @@ def WalkList(cmd_args=[], cmd_options={}):
             element_type - str   : Type of the next element
             field_name   - str   : Name of the field in next element's structure
 
-        Option: -S - suppress summary output.
+        Options: -S - suppress summary output.
+                 -E - Iterate using SLIST_ENTRYs
+
         Usage: (lldb) walklist_entry  <obj with list_entry *> <struct type> <fieldname>
         ex:    (lldb) walklist_entry  0x7fffff80 "struct proc *" "p_sibling"
-        
+
     """
     global lldb_summary_definitions
     if not cmd_args:
@@ -778,30 +971,245 @@ def WalkList(cmd_args=[], cmd_options={}):
     el_type = cmd_args[1]
     queue_head = kern.GetValueFromAddress(cmd_args[0], el_type)
     field_name = cmd_args[2]
-
     showsummary = False
     if el_type in lldb_summary_definitions:
         showsummary = True
     if '-S' in cmd_options:
         showsummary = False
+    if '-E' in cmd_options:
+        prefix = 's'
+    else:
+        prefix = ''
     elt = queue_head
     while unsigned(elt) != 0:
         i = elt
-        elt = elt.__getattr__(field_name).le_next
+        elt = elt.__getattr__(field_name).__getattr__(prefix + 'le_next')
         if showsummary:
             print lldb_summary_definitions[el_type](i)
         else:
             print "{0: <#020x}".format(i)
 
+def trace_parse_Copt(Copt):
+    """Parses the -C option argument and returns a list of CPUs
+    """
+    cpusOpt = Copt
+    cpuList = cpusOpt.split(",")
+    chosen_cpus = []
+    for cpu_num_string in cpuList:
+        try:
+            if '-' in cpu_num_string:
+                parts = cpu_num_string.split('-')
+                if len(parts) != 2 or not (parts[0].isdigit() and parts[1].isdigit()):
+                    raise ArgumentError("Invalid cpu specification: %s" % cpu_num_string)
+                firstRange = int(parts[0])
+                lastRange = int(parts[1])
+                if firstRange >= kern.globals.real_ncpus or lastRange >= kern.globals.real_ncpus:
+                    raise ValueError()
+                if lastRange < firstRange:
+                    raise ArgumentError("Invalid CPU range specified: `%s'" % cpu_num_string)
+                for cpu_num in range(firstRange, lastRange + 1):
+                    if cpu_num not in chosen_cpus:
+                        chosen_cpus.append(cpu_num)
+            else:
+                chosen_cpu = int(cpu_num_string)
+                if chosen_cpu < 0 or chosen_cpu >= kern.globals.real_ncpus:
+                    raise ValueError()
+                if chosen_cpu not in chosen_cpus:
+                    chosen_cpus.append(chosen_cpu)
+        except ValueError:
+            raise ArgumentError("Invalid CPU number specified.  Valid range is 0..%d" % (kern.globals.real_ncpus - 1))
+
+    return chosen_cpus
+
+
+IDX_CPU = 0
+IDX_RINGPOS = 1
+IDX_RINGENTRY = 2
+def Trace_cmd(cmd_args=[], cmd_options={}, headerString=lambda:"", entryString=lambda x:"", ring=[], entries_per_cpu=0, max_backtraces=0):
+    """Generic trace dumper helper function
+    """
+
+    if '-S' in cmd_options:
+        field_arg = cmd_options['-S']
+        try:
+            getattr(ring[0][0], field_arg)
+            sort_key_field_name = field_arg
+        except AttributeError:
+            raise ArgumentError("Invalid sort key field name `%s'" % field_arg)
+    else:
+            sort_key_field_name = 'start_time_abs'
+
+    if '-C' in cmd_options:
+        chosen_cpus = trace_parse_Copt(cmd_options['-C'])
+    else:
+        chosen_cpus = [x for x in range(kern.globals.real_ncpus)]
+
+    try:
+        limit_output_count = int(cmd_options['-N'])
+    except ValueError:
+        raise ArgumentError("Invalid output count `%s'" % cmd_options['-N']);
+    except KeyError:
+        limit_output_count = None
+        
+    reverse_sort = '-R' in cmd_options
+    backtraces = '-B' in cmd_options
+
+    # entries will be a list of 3-tuples, each holding the CPU on which the iotrace entry was collected,
+    # the original ring index, and the iotrace entry. 
+    entries = []
+    for x in chosen_cpus:
+        ring_slice = [(x, y, ring[x][y]) for y in range(entries_per_cpu)]
+        entries.extend(ring_slice)
+
+    total_entries = len(entries)
+
+    entries.sort(key=lambda x: getattr(x[IDX_RINGENTRY], sort_key_field_name), reverse=reverse_sort)
+
+    if limit_output_count is not None and limit_output_count > total_entries:
+        print ("NOTE: Output count `%d' is too large; showing all %d entries" % (limit_output_count, total_entries));
+        limit_output_count = total_entries
+
+    if len(chosen_cpus) < kern.globals.real_ncpus:
+        print "NOTE: Limiting to entries from cpu%s %s" % ("s" if len(chosen_cpus) > 1 else "", str(chosen_cpus))
+
+    if limit_output_count is not None and limit_output_count < total_entries:
+        entries_to_display = limit_output_count
+        print "NOTE: Limiting to the %s" % ("first entry" if entries_to_display == 1 else ("first %d entries" % entries_to_display))
+    else:
+        entries_to_display = total_entries
+
+    print headerString()
+
+    for x in xrange(entries_to_display):
+        print entryString(entries[x])
+
+        if backtraces:
+            for btidx in range(max_backtraces):
+                nextbt = entries[x][IDX_RINGENTRY].backtrace[btidx]
+                if nextbt == 0:
+                    break
+                print "\t" + GetSourceInformationForAddress(nextbt)
+
+
+@lldb_command('iotrace', 'C:N:S:RB')
+def IOTrace_cmd(cmd_args=[], cmd_options={}):
+    """ Prints the iotrace ring buffers for all CPUs by default.
+        Arguments:
+          -B                              : Print backtraces for each ring entry
+          -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
+                                            single CPU number or a range separated by a dash (e.g. "0-3"))
+          -N <count>                      : Limit output to the first <count> entries (across all chosen CPUs)
+          -R                              : Display results in reverse-sorted order (oldest first; default is newest-first)
+          -S <sort_key_field_name>        : Sort output by specified iotrace_entry_t field name (instead of by timestamp)
+    """
+    MAX_IOTRACE_BACKTRACES = 16
+
+    if kern.arch != "x86_64":
+        print "Sorry, iotrace is an x86-only command."
+        return
+
+    hdrString = lambda : "%-19s %-8s %-10s %-20s SZ  %-18s %-17s DATA" % (
+        "START TIME",
+        "DURATION",
+        "CPU#[RIDX]",
+        "      TYPE",
+        "   VIRT ADDR",
+        "   PHYS ADDR")
+
+    entryString = lambda x : "%-20u(%6u) %6s[%02d] %-20s %-2d 0x%016x 0x%016x 0x%x" % (
+        x[IDX_RINGENTRY].start_time_abs,
+        x[IDX_RINGENTRY].duration,
+        "CPU%d" % x[IDX_CPU],
+        x[IDX_RINGPOS],
+        str(x[IDX_RINGENTRY].iotype).split("=")[1].strip(),
+        x[IDX_RINGENTRY].size,
+        x[IDX_RINGENTRY].vaddr,
+        x[IDX_RINGENTRY].paddr,
+        x[IDX_RINGENTRY].val)
+
+    Trace_cmd(cmd_args, cmd_options, hdrString, entryString, kern.globals.iotrace_ring, kern.globals.iotrace_entries_per_cpu, MAX_IOTRACE_BACKTRACES)
+
+
+@lldb_command('ttrace', 'C:N:S:RB')
+def TrapTrace_cmd(cmd_args=[], cmd_options={}):
+    """ Prints the iotrace ring buffers for all CPUs by default.
+        Arguments:
+          -B                              : Print backtraces for each ring entry
+          -C <cpuSpec#>[,...,<cpuSpec#N>] : Limit trace entries to those generated by the specified CPUs (each cpuSpec can be a
+                                            single CPU number or a range separated by a dash (e.g. "0-3"))
+          -N <count>                      : Limit output to the first <count> entries (across all chosen CPUs)
+          -R                              : Display results in reverse-sorted order (oldest first; default is newest-first)
+          -S <sort_key_field_name>        : Sort output by specified traptrace_entry_t field name (instead of by timestamp)
+    """
+    MAX_TRAPTRACE_BACKTRACES = 8
+
+    if kern.arch != "x86_64":
+        print "Sorry, ttrace is an x86-only command."
+        return
+
+    hdrString = lambda : "%-30s CPU#[RIDX] VECT INTERRUPTED_THREAD PREMLV INTRLV INTERRUPTED_PC" % (
+        "START TIME   (DURATION [ns])")
+    entryString = lambda x : "%-20u(%6s) %8s[%02d] 0x%02x 0x%016x %6d %6d %s" % (
+        x[IDX_RINGENTRY].start_time_abs,
+        str(x[IDX_RINGENTRY].duration) if hex(x[IDX_RINGENTRY].duration) != "0xffffffffffffffff" else 'inprog',
+        "CPU%d" % x[IDX_CPU],
+        x[IDX_RINGPOS],
+        int(x[IDX_RINGENTRY].vector),
+        x[IDX_RINGENTRY].curthread,
+        x[IDX_RINGENTRY].curpl,
+        x[IDX_RINGENTRY].curil,
+        GetSourceInformationForAddress(x[IDX_RINGENTRY].interrupted_pc))
+
+    Trace_cmd(cmd_args, cmd_options, hdrString, entryString, kern.globals.traptrace_ring,
+        kern.globals.traptrace_entries_per_cpu, MAX_TRAPTRACE_BACKTRACES)
+                
+
+@lldb_command('showsysctls', 'P:')
+def ShowSysctls(cmd_args=[], cmd_options={}):
+    """ Walks the list of sysctl data structures, printing out each during traversal.
+        Arguments:
+          -P <string> : Limit output to sysctls starting with the specified prefix.
+    """
+    if '-P' in cmd_options:
+        _ShowSysctl_prefix = cmd_options['-P']
+        allowed_prefixes = _ShowSysctl_prefix.split('.')
+        if allowed_prefixes:
+            for x in xrange(1, len(allowed_prefixes)):
+                allowed_prefixes[x] = allowed_prefixes[x - 1] + "." + allowed_prefixes[x]
+    else:
+        _ShowSysctl_prefix = ''
+        allowed_prefixes = []
+    def IterateSysctls(oid, parent_str, i):
+        headp = oid
+        parentstr = "<none>" if parent_str is None else parent_str
+        for pp in IterateListEntry(headp, 'struct sysctl_oid *', 'oid_link', 's'):
+            type = pp.oid_kind & 0xf
+            next_parent = str(pp.oid_name)
+            if parent_str is not None:
+                next_parent = parent_str + "." + next_parent
+            st = (" " * i) + str(pp.GetSBValue().Dereference()).replace("\n", "\n" + (" " * i))
+            if type == 1 and pp.oid_arg1 != 0:
+                # Check allowed_prefixes to see if we can recurse from root to the allowed prefix.
+                # To recurse further, we need to check only the the next parent starts with the user-specified
+                # prefix
+                if next_parent not in allowed_prefixes and next_parent.startswith(_ShowSysctl_prefix) is False:
+                    continue
+                print 'parent = "%s"' % parentstr, st[st.find("{"):]
+                IterateSysctls(Cast(pp.oid_arg1, "struct sysctl_oid_list *"), next_parent, i + 2)
+            elif _ShowSysctl_prefix == '' or next_parent.startswith(_ShowSysctl_prefix):
+                print ('parent = "%s"' % parentstr), st[st.find("{"):]
+    IterateSysctls(kern.globals.sysctl__children, None, 0)
+
 
 
 from memory import *
 from process import *
-from ipc import * 
+from ipc import *
 from pmap import *
 from ioreg import *
 from mbufs import *
 from net import *
+from skywalk import *
 from kdp import *
 from userspace import *
 from pci import *
@@ -812,6 +1220,7 @@ from atm import *
 from structanalyze import *
 from ipcimportancedetail import *
 from bank import *
+from turnstile import *
 from kasan import *
 from kauth import *
 from waitq import *
@@ -820,6 +1229,8 @@ from ktrace import *
 from pgtrace import *
 from xnutriage import *
 from kevent import *
+from workqueue import *
+from ulock import *
 from ntstat import *
 from zonetriage import *
-
+from sysreg import *