]> git.saurik.com Git - apple/xnu.git/blobdiff - tools/lldbmacros/ioreg.py
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tools / lldbmacros / ioreg.py
old mode 100644 (file)
new mode 100755 (executable)
index 930dcc1..0aa3355
@@ -1,12 +1,46 @@
 from xnu import *
 from utils import *
+from kdp import *
+from core import caching
 import sys
+import lldb
+from collections import deque
 
 ######################################
 # Globals
 ######################################
 plane = None
 
+#####################################
+# Utility functions.
+#####################################
+def CastIOKitClass(obj, target_type):
+    """ Type cast an object to another IOKIT CPP class.
+        params:
+            obj - core.value  object representing some C construct in lldb
+            target_type - str : ex 'OSString *'
+                        - lldb.SBType :
+    """
+    v = Cast(obj, target_type)
+    v.GetSBValue().SetPreferDynamicValue(lldb.eNoDynamicValues)
+    return v
+
+#####################################
+# Classes.
+#####################################
+class PreoslogHeader(object):
+    """
+    Represents preoslog buffer header. There's no symbol in the kernel for it.
+    """
+    valid_magic = "POSL"
+    def __init__(self):
+        self.magic = ""
+        self.offset = 0
+        self.size = 0
+        self.source = 0
+        self.wrapped = 0
+        self.data = None
+
 ######################################
 # Type Summaries
 ######################################
@@ -19,17 +53,19 @@ def GetObjectSummary(obj):
         return
 
     vt = dereference(Cast(obj, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
+    vt = kern.StripKernelPAC(vt)
     vtype = kern.SymbolicateFromAddress(vt)
+    if len(vtype):
+        vtype_str = " <" + vtype[0].GetName() + ">"
+    else:
+        vtype_str = ""
     if hasattr(obj, 'retainCount'):
         retCount = (obj.retainCount & 0xffff)
-        cntnrRetCount = (retCount >> 16)
-        out_string = "`object 0x{0: <16x}, vt 0x{1: <16x} <{2:s}>, retain count {3:d}, container retain {4:d}` ".format(obj, vt, vtype[0].GetName(), retCount, cntnrRetCount)
+        cntnrRetCount = (obj.retainCount >> 16)
+        out_string = "`object 0x{0: <16x}, vt 0x{1: <16x}{2:s}, retain count {3:d}, container retain {4:d}` ".format(obj, vt, vtype_str, retCount, cntnrRetCount)
     else:
-        if len(vtype):
-            out_string = "`object 0x{0: <16x}, vt 0x{1: <16x} <{2:s}>` ".format(obj, vt, vtype[0].GetName())
-        else:
-            out_string = "`object 0x{0: <16x}, vt 0x{1: <16x}` ".format(obj, vt)
-    
+        out_string = "`object 0x{0: <16x}, vt 0x{1: <16x}{2:s}` ".format(obj, vt, vtype_str)
+
     ztvAddr = kern.GetLoadAddressForSymbol('_ZTV8OSString')
     if vt == ztvAddr:
         out_string += GetString(obj)
@@ -52,21 +88,41 @@ def GetObjectSummary(obj):
     
     ztvAddr = kern.GetLoadAddressForSymbol('_ZTV7OSArray')
     if vt == ztvAddr:
-        out_string += "(" + GetArray(Cast(obj, 'OSArray *')) + ")"
+        out_string += "(" + GetArray(CastIOKitClass(obj, 'OSArray *')) + ")"
         return out_string
     
     ztvAddr = kern.GetLoadAddressForSymbol('_ZTV5OSSet')
     if vt == ztvAddr:
-        out_string += GetSet(Cast(obj, 'OSSet *'))
+        out_string += GetSet(CastIOKitClass(obj, 'OSSet *'))
         return out_string
     
     ztvAddr = kern.GetLoadAddressForSymbol('_ZTV12OSDictionary')
     if vt == ztvAddr:
-        out_string += GetDictionary(Cast(obj, 'OSDictionary *'))
+        out_string += GetDictionary(CastIOKitClass(obj, 'OSDictionary *'))
         return out_string
     
     return out_string
 
+
+def GetObjectTypeStr(obj):
+    """ Return the type of an OSObject's container class
+    """
+    if obj is None:
+        return None
+
+    vt = dereference(Cast(obj, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
+    vt = kern.StripKernelPAC(vt)
+    vtype = kern.SymbolicateFromAddress(vt)
+    if len(vtype):
+        return vtype[0].GetName()
+
+    # See if the value is in a kext with no symbols
+    for kval in IterateLinkedList(kern.globals.kmod, 'next'):
+        if vt >= unsigned(kval.address) and vt <= (unsigned(kval.address) + unsigned(kval.size)):
+            return "kmod:{:s}+{:#0x}".format(kval.name, vt - unsigned(kval.address))
+    return None
+
+
 @lldb_type_summary(['IORegistryEntry *'])
 @header("")
 def GetRegistryEntrySummary(entry):
@@ -85,24 +141,26 @@ def GetRegistryEntrySummary(entry):
         name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
     
     if name is not None:
-        out_string += "+-o {0:s}  ".format(GetString(Cast(name, 'OSString *')))
-    elif Cast(entry, 'IOService *').pwrMgt and Cast(entry, 'IOService *').pwrMgt.Name:
-        out_string += "+-o {0:s}  ".format(Cast(entry, 'IOService *').pwrMgt.Name)
+        out_string += "+-o {0:s}  ".format(GetString(CastIOKitClass(name, 'OSString *')))
+    elif CastIOKitClass(entry, 'IOService *').pwrMgt and CastIOKitClass(entry, 'IOService *').pwrMgt.Name:
+        out_string += "+-o {0:s}  ".format(CastIOKitClass(entry, 'IOService *').pwrMgt.Name)
     else:
         out_string += "+-o ??  "
     
     # I'm using uintptr_t for now to work around <rdar://problem/12749733> FindFirstType & Co. should allow you to make pointer types directly
     vtableAddr = dereference(Cast(entry, 'uintptr_t *')) - 2 * sizeof('uintptr_t *')
+    vtableAddr = kern.StripKernelPAC(vtableAddr)
     vtype = kern.SymbolicateFromAddress(vtableAddr)
     if vtype is None or len(vtype) < 1:
-        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x}".format(entry, entry.reserved.fRegistryEntryID, vtableAddr)
+        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x}".format(entry, CastIOKitClass(entry, 'IORegistryEntry *').reserved.fRegistryEntryID, vtableAddr)
     else:
-        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x} <{3:s}>".format(entry, entry.reserved.fRegistryEntryID, vtableAddr, vtype[0].GetName())
+        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x} <{3:s}>".format(entry, CastIOKitClass(entry, 'IORegistryEntry *').reserved.fRegistryEntryID,
+                                                                                           vtableAddr, vtype[0].GetName())
     
     ztvAddr = kern.GetLoadAddressForSymbol('_ZTV15IORegistryEntry')
     if vtableAddr != ztvAddr:
         out_string += ", "
-        state = Cast(entry, 'IOService *').__state[0]
+        state = CastIOKitClass(entry, 'IOService *').__state[0]
         # kIOServiceRegisteredState
         if 0 == state & 2:
             out_string += "!"
@@ -114,11 +172,9 @@ def GetRegistryEntrySummary(entry):
         #kIOServiceInactiveState
         if 0 != state & 1:
             out_string += "in"
-        busyCount = (Cast(entry, 'IOService *').__state[1] & 0xff)
-        retCount = (Cast(entry, 'IOService *').retainCount & 0xffff)
+        busyCount = (CastIOKitClass(entry, 'IOService *').__state[1] & 0xff)
+        retCount = (CastIOKitClass(entry, 'IOService *').retainCount & 0xffff)
         out_string += "active, busy {0}, retain count {1}>".format(busyCount, retCount)
-    #else:
-    #    out_string += "\n"
     return out_string
 
 ######################################
@@ -133,7 +189,7 @@ def ShowAllClasses(cmd_args=None):
     count = unsigned(kern.globals.sAllClassesDict.count)
     
     while idx < count:
-        meta = Cast(kern.globals.sAllClassesDict.dictionary[idx].value, 'OSMetaClass *')
+        meta = CastIOKitClass(kern.globals.sAllClassesDict.dictionary[idx].value, 'OSMetaClass *')
         idx += 1
         print GetMetaClass(meta)
 
@@ -148,6 +204,45 @@ def ShowObject(cmd_args=None):
     obj = kern.GetValueFromAddress(cmd_args[0], 'OSObject *')
     print GetObjectSummary(obj)
 
+#Macro: dumpobject
+@lldb_command('dumpobject')
+def DumpObject(cmd_args=None):
+    """ Dumps object information if it is a valid object confirmed by showobject
+        Usage: dumpobject <address of object to be dumped> [class/struct type of object]
+    """
+    if not cmd_args:
+        print "No arguments passed"
+        print DumpObject.__doc__
+        return False
+
+    if len(cmd_args) == 1:
+        try:
+            object_info = lldb_run_command("showobject {:s}".format(cmd_args[0]))
+        except:
+            print "Error!! showobject failed due to invalid value"
+            print DumpObject.__doc__
+            return False
+
+        srch = re.search(r'<vtable for ([A-Za-z].*)>', object_info)
+        if not srch:
+            print "Error!! Couldn't find object in registry, input type manually as 2nd argument"
+            print DumpObject.__doc__
+            return False
+
+        object_type = srch.group(1)
+    else:
+        type_lookup = lldb_run_command("image lookup -t {:s}".format(cmd_args[1]))
+        if type_lookup.find(cmd_args[1])!= -1:
+            object_type = cmd_args[1]
+        else:
+            print "Error!! Input type {:s} isn't available in image lookup".format(cmd_args[1])
+            return False
+
+    print "******** Object Dump for value \'{:s}\' with type \"{:s}\" ********".format(cmd_args[0], object_type)
+    print lldb_run_command("p/x *({:s}*){:s}".format(object_type, cmd_args[0]))
+
+#EndMacro: dumpobject
+
 @lldb_command('setregistryplane')
 def SetRegistryPlane(cmd_args=None):
     """ Set the plane to be used for the IOKit registry macros
@@ -262,7 +357,7 @@ def ReadIOPort8(cmd_args=None):
     ReadIOPortInt(portAddr, 1, lcpu)
 
 @lldb_command('readioport16')
-def ReadIOPort8(cmd_args=None):
+def ReadIOPort16(cmd_args=None):
     """ Read value stored in the specified IO port. The CPU can be optionally
         specified as well.
         Prints 0xBAD10AD in case of a bad read
@@ -282,7 +377,7 @@ def ReadIOPort8(cmd_args=None):
     ReadIOPortInt(portAddr, 2, lcpu)
 
 @lldb_command('readioport32')
-def ReadIOPort8(cmd_args=None):
+def ReadIOPort32(cmd_args=None):
     """ Read value stored in the specified IO port. The CPU can be optionally
         specified as well.
         Prints 0xBAD10AD in case of a bad read
@@ -324,7 +419,7 @@ def WriteIOPort8(cmd_args=None):
     WriteIOPortInt(portAddr, 1, value, lcpu)
 
 @lldb_command('writeioport16')
-def WriteIOPort8(cmd_args=None):
+def WriteIOPort16(cmd_args=None):
     """ Write the value to the specified IO port. The size of the value is
         determined by the name of the command. The CPU used can be optionally
         specified as well.
@@ -346,7 +441,7 @@ def WriteIOPort8(cmd_args=None):
     WriteIOPortInt(portAddr, 2, value, lcpu)
 
 @lldb_command('writeioport32')
-def WriteIOPort8(cmd_args=None):
+def WriteIOPort32(cmd_args=None):
     """ Write the value to the specified IO port. The size of the value is
         determined by the name of the command. The CPU used can be optionally
         specified as well.
@@ -435,6 +530,184 @@ def ShowIOServicePM(cmd_args=None):
     
     print out_string
 
+@lldb_type_summary(['IOPMWorkQueue *'])
+@header("")
+def GetIOPMWorkQueueSummary(wq):
+    out_str = ""
+    ioservicepm_header = "{:<20s}{:<4s}{:<4s}{:<4s}{:<4s}\n"
+    iopmrequest_indent = "    "
+    iopmrequest_header = iopmrequest_indent + "{:<20s}{:<6s}{:<20s}{:<20s}{:<12s}{:<12s}{:<20s}{:<20s}{:<20s}\n"
+
+    for next in IterateQueue(wq.fWorkQueue, 'IOServicePM *', 'WorkChain'):
+        out_str += ioservicepm_header.format("IOService", "ps", "ms", "wr", "name")
+        out_str += "0x{:<16x}  {:<2d}  {:<2d}  {:<2d}  {:<s}\n".format(
+            next.Owner, next.CurrentPowerState, next.MachineState, next.WaitReason, next.Name)
+        out_str += iopmrequest_header.format("IOPMRequest", "type", "next_req", "root_req", "work_wait", "free_wait", "arg0", "arg1", "arg2")
+        for request in IterateQueue(next.RequestHead, 'IOPMRequest *', 'fCommandChain'):
+            out_str += iopmrequest_indent
+            out_str += "0x{:<16x}  0x{:<2x}  0x{:<16x}  0x{:<16x}".format(
+                request, request.fRequestType, request.fRequestNext, request.fRequestRoot)
+            out_str += "  0x{:<8x}  0x{:<8x}".format(
+                request.fWorkWaitCount, request.fFreeWaitCount)
+            out_str += "  0x{:<16x}  0x{:<16x}  0x{:<16x}\n".format(
+                request.fArg0, request.fArg1, request.fArg2)
+    return out_str
+
+@lldb_command('showiopmqueues')
+def ShowIOPMQueues(cmd_args=None):
+    """ Show IOKit power management queues and IOPMRequest objects.
+    """
+    print "IOPMWorkQueue 0x{:<16x} ({:<d} IOServicePM)\n".format(
+        kern.globals.gIOPMWorkQueue, kern.globals.gIOPMWorkQueue.fQueueLength)
+    print GetIOPMWorkQueueSummary(kern.globals.gIOPMWorkQueue)
+
+@lldb_type_summary(['IOService *'])
+@header("")
+def GetIOPMInterest(service):
+    iopm = CastIOKitClass(service.pwrMgt, 'IOServicePM *')
+    if unsigned(iopm) == 0:
+        print("error: no IOServicePM")
+        return
+
+    list = CastIOKitClass(iopm.InterestedDrivers, 'IOPMinformeeList *')
+    out_str = "IOServicePM 0x{:<16x} ({:<d} interest, {:<d} pending ack)\n".format(
+        iopm, list.length, iopm.HeadNotePendingAcks)
+    if list.length == 0:
+        return
+
+    out_str += "    {:<20s}{:<8s}{:<10s}{:<20s}{:<20s}{:<20s}{:<s}\n".format(
+        "informee", "active", "ticks", "notifyTime", "service", "regId", "name")
+    next = CastIOKitClass(list.firstItem, 'IOPMinformee *')
+    while unsigned(next) != 0:
+        driver = CastIOKitClass(next.whatObject, 'IOService *')
+        name = GetRegistryEntryName(driver)
+        reg_id = CastIOKitClass(driver, 'IORegistryEntry *').reserved.fRegistryEntryID;
+        out_str += "    0x{:<16x}  {:<6s}  {:<8d}  0x{:<16x}  0x{:<16x}  0x{:<16x}  {:<s}\n".format(
+            next, "Yes" if next.active != 0 else "No" , next.timer, next.startTime, next.whatObject, reg_id, name)
+        next = CastIOKitClass(next.nextInList, 'IOPMinformee *')
+    return out_str
+
+@lldb_command('showiopminterest')
+def ShowIOPMInterest(cmd_args=None):
+    """ Show the interested drivers for an IOService.
+        syntax: (lldb) showiopminterest <IOService>
+    """
+    if not cmd_args:
+        print "Please specify the address of the IOService"
+        print ShowIOPMInterest.__doc__
+        return
+
+    obj = kern.GetValueFromAddress(cmd_args[0], 'IOService *')
+    print GetIOPMInterest(obj)
+
+@lldb_command("showinterruptvectors")
+def ShowInterruptVectorInfo(cmd_args=None):
+    """
+    Shows interrupt vectors.
+    """
+
+    # Constants
+    kInterruptTriggerModeMask  = 0x01
+    kInterruptTriggerModeEdge  = 0x00
+    kInterruptTriggerModeLevel = kInterruptTriggerModeMask
+    kInterruptPolarityMask     = 0x02
+    kInterruptPolarityHigh     = 0x00
+    kInterruptPolarityLow      = kInterruptPolarityMask
+    kInterruptShareableMask    = 0x04
+    kInterruptNotShareable     = 0x00
+    kInterruptIsShareable      = kInterruptShareableMask
+    kIOInterruptTypePCIMessaged = 0x00010000
+
+    # Get all interrupt controllers
+    interrupt_controllers = list(SearchInterruptControllerDrivers())
+
+    print("Interrupt controllers: ")
+    for ic in interrupt_controllers:
+        print("  {}".format(ic))
+    print("")
+
+    # Iterate over all entries in the registry
+    for entry in GetMatchingEntries(lambda _: True):
+        # Get the name of the entry
+        entry_name = GetRegistryEntryName(entry)
+
+        # Get the location of the entry
+        entry_location = GetRegistryEntryLocationInPlane(entry, kern.globals.gIOServicePlane)
+        if entry_location is None:
+            entry_location = ""
+        else:
+            entry_location = "@" + entry_location
+
+        # Get the interrupt properties
+        (msi_mode, vectorDataList, vectorContList) = GetRegistryEntryInterruptProperties(entry)
+        should_print = False
+        out_str = ""
+        for (vector_data, vector_cont) in zip(vectorDataList, vectorContList):
+            # vector_cont is the name of the interrupt controller. Find the matching controller from
+            # the list of controllers obtained earlier
+            matching_ics = filter(lambda ic: ic.name == vector_cont, interrupt_controllers)
+
+            if len(matching_ics) > 0:
+                should_print = True
+                # Take the first match
+                matchingIC = matching_ics[0]
+
+                # Use the vector_data to determine the vector and any flags
+                data_ptr = vector_data.data
+                data_length = vector_data.length
+
+                # Dereference vector_data as a uint32_t * and add the base vector number
+                gsi = unsigned(dereference(Cast(data_ptr, 'uint32_t *')))
+                gsi += matchingIC.base_vector_number
+
+                # If data_length is >= 8 then vector_data contains interrupt flags
+                if data_length >= 8:
+                    # Add sizeof(uint32_t) to data_ptr to get the flags pointer
+                    flags_ptr = kern.GetValueFromAddress(unsigned(data_ptr) + sizeof("uint32_t"))
+                    flags = unsigned(dereference(Cast(flags_ptr, 'uint32_t *')))
+                    out_str += "  +----- [Interrupt Controller {ic}] vector {gsi}, {trigger_level}, {active}, {shareable}{messaged}\n" \
+                            .format(ic=matchingIC.name, gsi=hex(gsi), 
+                                    trigger_level="level trigger" if flags & kInterruptTriggerModeLevel else "edge trigger",
+                                    active="active low" if flags & kInterruptPolarityLow else "active high",
+                                    shareable="shareable" if flags & kInterruptIsShareable else "exclusive",
+                                    messaged=", messaged" if flags & kIOInterruptTypePCIMessaged else "")
+                else:
+                    out_str += "  +----- [Interrupt Controller {ic}] vector {gsi}\n".format(ic=matchingIC.name, gsi=hex(gsi))
+        if should_print:
+            print("[ {entry_name}{entry_location} ]{msi_mode}\n{out_str}" \
+                .format(entry_name=entry_name,
+                        entry_location=entry_location,
+                        msi_mode=" - MSIs enabled" if msi_mode else "",
+                        out_str=out_str))
+
+@lldb_command("showiokitclasshierarchy")
+def ShowIOKitClassHierarchy(cmd_args=None):
+    """
+    Show class hierarchy for a IOKit class
+    """
+    if not cmd_args:
+        print("Usage: showiokitclasshierarchy <IOKit class name>")
+        return
+
+    class_name = cmd_args[0]
+    metaclasses = GetMetaClasses()
+    if class_name not in metaclasses:
+        print("Class {} does not exist".format(class_name))
+        return
+    metaclass = metaclasses[class_name]
+
+    # loop over superclasses
+    hierarchy = []
+    current_metaclass = metaclass
+    while current_metaclass is not None:
+        hierarchy.insert(0, current_metaclass)
+        current_metaclass = current_metaclass.superclass()
+
+    for (index, mc) in enumerate(hierarchy):
+        indent = ("    " * index) + "+---"
+        print("{}[ {} ] {}".format(indent, str(mc.className()), str(mc.data())))
+
+
 ######################################
 #  Helper routines
 ######################################
@@ -462,13 +735,13 @@ def ShowRegistryEntryRecurse(entry, prefix, printProps):
     childArray = LookupKeyInOSDict(registryTable, childKey)
     if childArray is not None:
         idx = 0
-        ca = Cast(childArray, 'OSArray *')
+        ca = CastIOKitClass(childArray, 'OSArray *')
         count = unsigned(ca.count)
         while idx < count:
             if plen != 0 and plen != 1 and (plen & (plen - 1)) == 0:
-                ShowRegistryEntryRecurse(Cast(ca.array[idx], 'IORegistryEntry *'), prefix + "| ", printProps)
+                ShowRegistryEntryRecurse(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'), prefix + "| ", printProps)
             else:
-                ShowRegistryEntryRecurse(Cast(ca.array[idx], 'IORegistryEntry *'), prefix + "  ", printProps)
+                ShowRegistryEntryRecurse(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'), prefix + "  ", printProps)
             idx += 1
 
 def FindRegistryEntryRecurse(entry, search_name, stopAfterFirst):
@@ -490,12 +763,12 @@ def FindRegistryEntryRecurse(entry, search_name, stopAfterFirst):
         name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
     
     if name is not None:
-        if str(Cast(name, 'OSString *').string) == search_name:
+        if str(CastIOKitClass(name, 'OSString *').string) == search_name:
             print GetRegistryEntrySummary(entry)
             if stopAfterFirst is True:
                 return True
-    elif Cast(entry, 'IOService *').pwrMgt and Cast(entry, 'IOService *').pwrMgt.Name:
-        name = Cast(entry, 'IOService *').pwrMgt.Name
+    elif CastIOKitClass(entry, 'IOService *').pwrMgt and CastIOKitClass(entry, 'IOService *').pwrMgt.Name:
+        name = CastIOKitClass(entry, 'IOService *').pwrMgt.Name
         if str(name) == search_name:
             print GetRegistryEntrySummary(entry)
             if stopAfterFirst is True:
@@ -509,10 +782,10 @@ def FindRegistryEntryRecurse(entry, search_name, stopAfterFirst):
     childArray = LookupKeyInOSDict(registryTable, childKey)
     if childArray is not None:
         idx = 0
-        ca = Cast(childArray, 'OSArray *')
+        ca = CastIOKitClass(childArray, 'OSArray *')
         count = unsigned(ca.count)
         while idx < count:
-            if FindRegistryEntryRecurse(Cast(ca.array[idx], 'IORegistryEntry *'), search_name, stopAfterFirst) is True:
+            if FindRegistryEntryRecurse(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'), search_name, stopAfterFirst) is True:
                 return True
             idx += 1
     return False
@@ -537,10 +810,10 @@ def FindRegistryObjectRecurse(entry, search_name):
         name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
     
     if name is not None:
-        if str(Cast(name, 'OSString *').string) == search_name:
+        if str(CastIOKitClass(name, 'OSString *').string) == search_name:
             return entry
-    elif Cast(entry, 'IOService *').pwrMgt and Cast(entry, 'IOService *').pwrMgt.Name:
-        name = Cast(entry, 'IOService *').pwrMgt.Name
+    elif CastIOKitClass(entry, 'IOService *').pwrMgt and CastIOKitClass(entry, 'IOService *').pwrMgt.Name:
+        name = CastIOKitClass(entry, 'IOService *').pwrMgt.Name
         if str(name) == search_name:
             return entry
     
@@ -551,16 +824,507 @@ def FindRegistryObjectRecurse(entry, search_name):
         childKey = plane.keys[1]
     childArray = LookupKeyInOSDict(registryTable, childKey)
     if childArray is not None:
-        ca = Cast(childArray, 'OSArray *')
+        ca = CastIOKitClass(childArray, 'OSArray *')
         for idx in range(ca.count):
-            registry_object = FindRegistryObjectRecurse(Cast(ca.array[idx], 'IORegistryEntry *'), search_name)
+            registry_object = FindRegistryObjectRecurse(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'), search_name)
             if not registry_object or int(registry_object) == int(0):
                 continue
             else:
                 return registry_object
     return None
 
-def LookupKeyInOSDict(osdict, key):
+def CompareStringToOSSymbol(string, os_sym):
+    """
+    Lexicographically compare python string to OSSymbol
+    Params:
+    string - python string
+    os_sym - OSSymbol
+
+    Returns:
+    0 if string == os_sym
+    1 if string > os_sym
+    -1 if string < os_sym
+    """
+    os_sym_str = GetString(os_sym)
+    if string > os_sym_str:
+        return 1
+    elif string < os_sym_str:
+        return -1
+    else:
+        return 0
+
+class IOKitMetaClass(object):
+    """
+    A class that represents a IOKit metaclass. This is used to represent the
+    IOKit inheritance hierarchy.
+    """
+
+    def __init__(self, meta):
+        """
+        Initialize a IOKitMetaClass object.
+
+        Args:
+            meta (core.cvalue.value): A LLDB value representing a
+                OSMetaClass *.
+        """
+        self._meta = meta
+        self._superclass = None
+
+    def data(self):
+        return self._meta
+
+    def setSuperclass(self, superclass):
+        """
+        Set the superclass for this metaclass.
+
+        Args:
+            superclass (core.cvalue.value): A LLDB value representing a
+                OSMetaClass *.
+        """
+        self._superclass = superclass
+
+    def superclass(self):
+        """
+        Get the superclass for this metaclass (set by the setSuperclass method).
+
+        Returns:
+            core.cvalue.value: A LLDB value representing a OSMetaClass *.
+        """
+        return self._superclass
+
+    def className(self):
+        """
+        Get the name of the class this metaclass represents.
+
+        Returns:
+            str: The class name
+        """
+        return self._meta.className.string
+
+    def inheritsFrom(self, other):
+        """
+        Check if the class represented by this metaclass inherits from a class
+        represented by another metaclass.
+
+        Args:
+            other (IOKitMetaClass): The other metaclass
+
+        Returns:
+            bool: Returns True if this class inherits from the other class and
+                False otherwise.
+        """
+        current = self
+        while current is not None:
+            if current == other:
+                return True
+            else:
+                current = current.superclass()
+
+
+def GetRegistryEntryClassName(entry):
+    """
+    Get the class name of a registry entry.
+
+    Args:
+        entry (core.cvalue.value): A LLDB value representing a
+            IORegistryEntry *.
+
+    Returns:
+        str: The class name of the entry or None if a class name could not be
+            found.
+    """
+    # Check using IOClass key
+    result = LookupKeyInOSDict(entry.fPropertyTable, kern.globals.gIOClassKey)
+    if result is not None:
+        return GetString(result).replace("\"", "")
+    else:
+        # Use the vtable of the entry to determine the concrete type
+        vt = dereference(Cast(entry, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
+        vt = kern.StripKernelPAC(vt)
+        vtype = kern.SymbolicateFromAddress(vt)
+        if len(vtype) > 0:
+            vtableName = vtype[0].GetName()
+            return vtableName[11:] # strip off "vtable for "
+        else:
+            return None
+
+
+def GetRegistryEntryName(entry):
+    """
+    Get the name of a registry entry.
+
+    Args:
+        entry (core.cvalue.value): A LLDB value representing a
+            IORegistryEntry *.
+
+    Returns:
+        str: The name of the entry or None if a name could not be found.
+    """
+    name = None
+
+    # First check the IOService plane nameKey
+    result = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOServicePlane.nameKey)
+    if result is not None:
+        name = GetString(result)
+
+    # Check the global IOName key
+    if name is None:
+        result = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIONameKey)
+        if result is not None:
+            name = GetString(result)
+
+    # Check the IOClass key
+    if name is None:
+        result = LookupKeyInOSDict(entry.fPropertyTable, kern.globals.gIOClassKey)
+        if result is not None:
+            name = GetString(result)
+
+    # Remove extra quotes        
+    if name is not None:
+        return name.replace("\"", "")
+    else:
+        return GetRegistryEntryClassName(entry)
+
+
+def GetRegistryEntryLocationInPlane(entry, plane):
+    """
+    Get the registry entry location in a IOKit plane.
+
+    Args:
+        entry (core.cvalue.value): A LLDB value representing a
+            IORegistryEntry *.
+        plane: An IOKit plane such as kern.globals.gIOServicePlane.
+
+    Returns:
+        str: The location of the entry or None if a location could not be
+            found.
+    """
+    # Check the plane's pathLocationKey
+    sym = LookupKeyInOSDict(entry.fRegistryTable, plane.pathLocationKey)
+
+    # Check the global IOLocation key
+    if sym is None:
+        sym = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOLocationKey)
+    if sym is not None:
+        return GetString(sym).replace("\"", "")
+    else:
+        return None
+
+
+def GetMetaClasses():
+    """
+    Enumerate all IOKit metaclasses. Uses dynamic caching.
+
+    Returns:
+        Dict[str, IOKitMetaClass]: A dictionary mapping each metaclass name to
+            a IOKitMetaClass object representing the metaclass.
+    """
+    METACLASS_CACHE_KEY = "iokit_metaclasses"
+    cached_data = caching.GetDynamicCacheData(METACLASS_CACHE_KEY)
+
+    # If we have cached data, return immediately
+    if cached_data is not None:
+        return cached_data
+
+    # This method takes a while, so it prints a progress indicator
+    print("Enumerating IOKit metaclasses: ")
+    
+    # Iterate over all classes present in sAllClassesDict
+    idx = 0
+    count = unsigned(kern.globals.sAllClassesDict.count)
+    metaclasses_by_address = {}
+    while idx < count:
+        # Print progress after every 10 items
+        if idx % 10 == 0:
+            print("  {} metaclass structures parsed...".format(idx))
+        
+        # Address of metaclass
+        address = kern.globals.sAllClassesDict.dictionary[idx].value
+
+        # Create IOKitMetaClass and store in dict
+        metaclasses_by_address[int(address)] = IOKitMetaClass(CastIOKitClass(kern.globals.sAllClassesDict.dictionary[idx].value, 'OSMetaClass *'))
+        idx += 1
+    
+    print("  Enumerated {} metaclasses.".format(count))
+
+    # At this point, each metaclass is independent of each other. We don't have superclass links set up yet.
+
+    for (address, metaclass) in metaclasses_by_address.items():
+        # Get the address of the superclass using the superClassLink in IOMetaClass
+        superclass_address = int(metaclass.data().superClassLink)
+
+        # Skip null superclass
+        if superclass_address == 0:
+            continue
+
+        # Find the superclass object in the dict
+        if superclass_address in metaclasses_by_address:
+            metaclass.setSuperclass(metaclasses_by_address[superclass_address])
+        else:
+            print("warning: could not find superclass for {}".format(str(metaclass.data())))
+    
+    # This method returns a dictionary mapping each class name to the associated metaclass object
+    metaclasses_by_name = {}
+    for (_, metaclass) in metaclasses_by_address.items():
+        metaclasses_by_name[str(metaclass.className())] = metaclass
+
+    # Save the result in the cache
+    caching.SaveDynamicCacheData(METACLASS_CACHE_KEY, metaclasses_by_name)
+
+    return metaclasses_by_name
+
+
+def GetMatchingEntries(matcher):
+    """
+    Iterate over the IOKit registry and find entries that match specific
+        criteria.
+
+    Args:
+        matcher (function): A matching function that returns True for a match
+            and False otherwise.
+
+    Yields:
+        core.cvalue.value: LLDB values that represent IORegistryEntry * for
+            each registry entry found.
+    """
+
+    # Perform a BFS over the IOKit registry tree
+    bfs_queue = deque()
+    bfs_queue.append(kern.globals.gRegistryRoot)
+    while len(bfs_queue) > 0:
+        # Dequeue an entry
+        entry = bfs_queue.popleft()
+
+        # Check if entry matches
+        if matcher(entry):
+            yield entry
+
+        # Find children of this entry and enqueue them
+        child_array = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOServicePlane.keys[1])
+        if child_array is not None:
+            idx = 0
+            ca = CastIOKitClass(child_array, 'OSArray *')
+            count = unsigned(ca.count)
+            while idx < count:
+                bfs_queue.append(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'))
+                idx += 1
+
+
+def FindMatchingServices(matching_name):
+    """
+    Finds registry entries that match the given string. Works similarly to:
+
+    io_iterator_t iter;
+    IOServiceGetMatchingServices(..., IOServiceMatching(matching_name), &iter);
+    while (( io_object_t next = IOIteratorNext(iter))) { ... }
+
+    Args:
+        matching_name (str): The class name to search for.
+
+    Yields:
+        core.cvalue.value: LLDB values that represent IORegistryEntry * for
+            each registry entry found.
+    """
+
+    # Check if the argument is valid
+    metaclasses = GetMetaClasses()
+    if matching_name not in metaclasses:
+        return
+    matching_metaclass = metaclasses[matching_name]
+
+    # An entry matches if it inherits from matching_metaclass
+    def matcher(entry):
+        # Get the class name of the entry and the associated metaclass
+        entry_name = GetRegistryEntryClassName(entry)
+        if entry_name in metaclasses:
+            entry_metaclass = metaclasses[entry_name]
+            return entry_metaclass.inheritsFrom(matching_metaclass)
+        else:
+            return False
+    
+    # Search for entries
+    for entry in GetMatchingEntries(matcher):
+        yield entry
+
+
+def GetRegistryEntryParent(entry, iokit_plane=None):
+    """
+    Gets the parent entry of a registry entry.
+
+    Args:
+        entry (core.cvalue.value): A LLDB value representing a
+            IORegistryEntry *.
+        iokit_plane (core.cvalue.value, optional): A LLDB value representing a
+            IORegistryPlane *. By default, this method uses the IOService
+            plane.
+
+    Returns:
+        core.cvalue.value: A LLDB value representing a IORegistryEntry* that
+            is the parent entry of the entry argument in the specified plane.
+            Returns None if no entry could be found.
+    """
+    kParentSetIndex = 0
+    parent_key = None
+    if iokit_plane is None:
+        parent_key = kern.globals.gIOServicePlane.keys[kParentSetIndex]
+    else:
+        parent_key = plane.keys[kParentSetIndex]
+    parent_array = LookupKeyInOSDict(entry.fRegistryTable, parent_key)
+    parent_entry = None
+    if parent_array is not None:
+        idx = 0
+        ca = CastIOKitClass(parent_array, 'OSArray *')
+        count = unsigned(ca.count)
+        if count > 0:
+            parent_entry = CastIOKitClass(ca.array[0], 'IORegistryEntry *')
+    return parent_entry
+
+
+def GetRegistryEntryInterruptProperties(entry):
+    """
+    Get the interrupt properties of a registry entry.
+
+    Args:
+        entry (core.cvalue.value): A LLDB value representing a IORegistryEntry *.
+
+    Returns:
+        (bool, List[core.cvalue.value], List[str]): A tuple with the following
+            fields:
+                - First field (bool): Whether this entry has a non-null
+                    IOPCIMSIMode.
+                - Second field (List[core.cvalue.value]): A list of LLDB values
+                    representing OSData *. The OSData* pointer points to
+                    interrupt vector data.
+                - Third field (List[str]): A list of strings representing the
+                    interrupt controller names from the
+                    IOInterruptControllers property.
+    """
+    INTERRUPT_SPECIFIERS_PROPERTY = "IOInterruptSpecifiers"
+    INTERRUPT_CONTROLLERS_PROPERTY = "IOInterruptControllers"
+    MSI_MODE_PROPERTY = "IOPCIMSIMode"
+
+    # Check IOInterruptSpecifiers
+    interrupt_specifiers = LookupKeyInPropTable(entry.fPropertyTable, INTERRUPT_SPECIFIERS_PROPERTY)
+    if interrupt_specifiers is not None:
+        interrupt_specifiers = CastIOKitClass(interrupt_specifiers, 'OSArray *')
+    
+    # Check IOInterruptControllers
+    interrupt_controllers = LookupKeyInPropTable(entry.fPropertyTable, INTERRUPT_CONTROLLERS_PROPERTY)
+    if interrupt_controllers is not None:
+        interrupt_controllers = CastIOKitClass(interrupt_controllers, 'OSArray *')
+
+    # Check MSI mode
+    msi_mode = LookupKeyInPropTable(entry.fPropertyTable, MSI_MODE_PROPERTY)
+
+    result_vector_data = []
+    result_vector_cont = []
+    if interrupt_specifiers is not None and interrupt_controllers is not None:
+        interrupt_specifiers_array_count = unsigned(interrupt_specifiers.count)
+        interrupt_controllers_array_count = unsigned(interrupt_controllers.count)
+        # The array lengths should be the same
+        if interrupt_specifiers_array_count == interrupt_controllers_array_count and interrupt_specifiers_array_count > 0:
+            idx = 0
+            while idx < interrupt_specifiers_array_count:
+                # IOInterruptSpecifiers is an array of OSData *
+                vector_data = CastIOKitClass(interrupt_specifiers.array[idx], "OSData *")
+
+                # IOInterruptControllers is an array of OSString *
+                vector_cont = GetString(interrupt_controllers.array[idx])
+
+                result_vector_data.append(vector_data)
+                result_vector_cont.append(vector_cont)
+                idx += 1
+    
+    return (msi_mode is not None, result_vector_data, result_vector_cont)
+
+
+class InterruptControllerDevice(object):
+    """Represents a IOInterruptController"""
+
+    def __init__(self, device, driver, base_vector_number, name):
+        """
+        Initialize a InterruptControllerDevice.
+
+        Args:
+            device (core.cvalue.value): The device object.
+            driver (core.cvalue.value): The driver object.
+            base_vector_number (int): The base interrupt vector.
+            name (str): The name of this interrupt controller.
+
+        Note:
+            Use the factory method makeInterruptControllerDevice to validate
+            properties.
+        """
+        self.device = device
+        self.driver = driver
+        self.name = name
+        self.base_vector_number = base_vector_number
+
+
+    def __str__(self):
+        """
+        String representation of this InterruptControllerDevice.
+        """
+        return " Name {}, base vector = {}, device = {}, driver = {}".format(
+            self.name, hex(self.base_vector_number), str(self.device), str(self.driver))
+
+    @staticmethod
+    def makeInterruptControllerDevice(device, driver):
+        """
+        Factory method to create a InterruptControllerDevice.
+
+        Args:
+            device (core.cvalue.value): The device object.
+            driver (core.cvalue.value): The driver object.
+
+        Returns:
+            InterruptControllerDevice: Returns an instance of
+                InterruptControllerDevice or None if the arguments do not have
+                the required properties.
+        """
+        BASE_VECTOR_PROPERTY = "Base Vector Number"
+        INTERRUPT_CONTROLLER_NAME_PROPERTY = "InterruptControllerName"
+        base_vector = LookupKeyInPropTable(device.fPropertyTable, BASE_VECTOR_PROPERTY)
+        if base_vector is None:
+            base_vector = LookupKeyInPropTable(driver.fPropertyTable, BASE_VECTOR_PROPERTY)
+        device_name = LookupKeyInPropTable(device.fPropertyTable, INTERRUPT_CONTROLLER_NAME_PROPERTY)
+        if device_name is None:
+            device_name = LookupKeyInPropTable(driver.fPropertyTable, INTERRUPT_CONTROLLER_NAME_PROPERTY)
+
+        if device_name is not None:
+            # Some interrupt controllers do not have a base vector number. Assume it is 0.
+            base_vector_number = 0
+            if base_vector is not None:
+                base_vector_number = unsigned(GetNumber(base_vector))
+            device_name = GetString(device_name)
+            # Construct object and return
+            return InterruptControllerDevice(device, driver, base_vector_number, device_name)
+        else:
+            # error case
+            return None
+
+
+def SearchInterruptControllerDrivers():
+    """
+    Search the IOKit registry for entries that match IOInterruptController.
+
+    Yields:
+        core.cvalue.value: A LLDB value representing a IORegistryEntry * that
+        inherits from IOInterruptController.
+    """
+    for entry in FindMatchingServices("IOInterruptController"):
+        # Get parent
+        parent = GetRegistryEntryParent(entry)
+
+        # Make the interrupt controller object
+        ic = InterruptControllerDevice.makeInterruptControllerDevice(parent, entry)
+
+        # Yield object
+        if ic is not None:
+            yield ic
+
+
+def LookupKeyInOSDict(osdict, key, comparer = None):
     """ Returns the value corresponding to a given key in a OSDictionary
         Returns None if the key was not found
     """
@@ -569,8 +1333,16 @@ def LookupKeyInOSDict(osdict, key):
     count = unsigned(osdict.count)
     result = None
     idx = 0
+
+    if not comparer:
+        # When comparer is specified, "key" argument can be of any type as "comparer" knows how to compare "key" to a key from "osdict".
+        # When comparer is not specified, key is of cpp_obj type.
+        key = getOSPtr(key)
     while idx < count and result is None:
-        if key == osdict.dictionary[idx].key:
+        if comparer is not None:
+            if comparer(key, osdict.dictionary[idx].key) == 0:
+                result = osdict.dictionary[idx].value
+        elif key == osdict.dictionary[idx].key:
             result = osdict.dictionary[idx].value
         idx += 1
     return result
@@ -609,11 +1381,11 @@ def GetRegDictionary(osdict, prefix):
 def GetString(string):
     """ Returns the python string representation of a given OSString
     """
-    out_string = "\"{0:s}\"".format(Cast(string, 'OSString *').string)
+    out_string = "{0:s}".format(CastIOKitClass(string, 'OSString *').string)
     return out_string
 
 def GetNumber(num):
-    out_string = "{0:d}".format(Cast(num, 'OSNumber *').value)
+    out_string = "{0:d}".format(CastIOKitClass(num, 'OSNumber *').value)
     return out_string
 
 def GetBoolean(b):
@@ -650,25 +1422,24 @@ def GetArray(arr):
 def GetDictionary(d):
     """ Returns a string containing info about a given OSDictionary
     """
-    out_string = "{"
+    if d is None:
+        return ""
+    out_string = "{\n"
     idx = 0
     count = unsigned(d.count)
 
     while idx < count:
-        obj = d.dictionary[idx].key
-        out_string += GetObjectSummary(obj) + "="
-        obj = d.dictionary[idx].value
+        key = d.dictionary[idx].key
+        value = d.dictionary[idx].value
+        out_string += "    \"{}\" = {}\n".format(GetString(key), GetObjectSummary(value))
         idx += 1
-        out_string += GetObjectSummary(obj)
-        if idx < count:
-            out_string += ","
     out_string += "}"
     return out_string
 
 def GetSet(se):
     """ Returns a string containing info about a given OSSet
     """
-    out_string += "[" + GetArray(se.members) + "]"
+    out_string = "[" + GetArray(se.members) + "]"
     return out_string
 
 def ReadIOPortInt(addr, numbytes, lcpu):
@@ -707,16 +1478,14 @@ def ReadIOPortInt(addr, numbytes, lcpu):
         result_pkt = Cast(addressof(kern.globals.manual_pkt.data), 'kdp_readioport_reply_t *')
         
         if(result_pkt.error == 0):
-            print "This macro is incomplete till <rdar://problem/12868059> is fixed"
-            # FIXME: Uncomment me when <rdar://problem/12868059> is fixed
-            #if numbytes == 1:
-            #    result = dereference(Cast(result_pkt.data, 'uint8_t *'))
-            #elif numbytes == 2:
-            #    result = dereference(Cast(result_pkt.data, 'uint16_t *'))
-            #elif numbytes == 4:
-            #    result = dereference(cast(result_pkt.data, 'uint32_t *'))
-    
-    print "0x{0: <4x}: 0x{1: <1x}".format(addr, result)
+            if numbytes == 1:
+                result = dereference(Cast(addressof(result_pkt.data), 'uint8_t *'))
+            elif numbytes == 2:
+                result = dereference(Cast(addressof(result_pkt.data), 'uint16_t *'))
+            elif numbytes == 4:
+                result = dereference(Cast(addressof(result_pkt.data), 'uint32_t *'))
+
+    print "{0: <#6x}: {1:#0{2}x}".format(addr, result, (numbytes*2)+2)
 
 def WriteIOPortInt(addr, numbytes, value, lcpu):
     """ Writes 'value' into ioport specified by 'addr'. Prints errors if it encounters any
@@ -730,12 +1499,12 @@ def WriteIOPortInt(addr, numbytes, value, lcpu):
     len_address = unsigned(addressof(kern.globals.manual_pkt.len))
     data_address = unsigned(addressof(kern.globals.manual_pkt.data))
     if not WriteInt32ToMemoryAddress(0, input_address):
-        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
+        print "error writing {0: #x} to port {1: <#6x}: failed to write 0 to input_address".format(value, addr)
         return
     
     kdp_pkt_size = GetType('kdp_writeioport_req_t').GetByteSize()
     if not WriteInt32ToMemoryAddress(kdp_pkt_size, len_address):
-        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
+        print "error writing {0: #x} to port {1: <#6x}: failed to write kdp_pkt_size".format(value, addr)
         return
     
     kgm_pkt = kern.GetValueFromAddress(data_address, 'kdp_writeioport_req_t *')
@@ -747,27 +1516,250 @@ def WriteIOPortInt(addr, numbytes, value, lcpu):
         WriteInt32ToMemoryAddress(numbytes, int(addressof(kgm_pkt.nbytes))) and
         WriteInt16ToMemoryAddress(lcpu, int(addressof(kgm_pkt.lcpu)))
         ):
-        print "This macro is incomplete till <rdar://problem/12868059> is fixed"
-        # FIXME: Uncomment me when <rdar://problem/12868059> is fixed
-        #if numbytes == 1:
-        #    if not WriteInt8ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
-        #        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
-        #elif numbytes == 2:
-        #    if not WriteInt16ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
-        #        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
-        #elif numbytes == 4:
-        #    if not WriteInt32ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
-        #        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
-        
+        if numbytes == 1:
+            if not WriteInt8ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
+                print "error writing {0: #x} to port {1: <#6x}: failed to write 8 bit data".format(value, addr)
+                return
+        elif numbytes == 2:
+            if not WriteInt16ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
+                print "error writing {0: #x} to port {1: <#6x}: failed to write 16 bit data".format(value, addr)
+                return
+        elif numbytes == 4:
+            if not WriteInt32ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
+                print "error writing {0: #x} to port {1: <#6x}: failed to write 32 bit data".format(value, addr)
+                return
         if not WriteInt32ToMemoryAddress(1, input_address):
-            print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
+            print "error writing {0: #x} to port {1: <#6x}: failed to write to input_address".format(value, addr)
             return
 
         result_pkt = Cast(addressof(kern.globals.manual_pkt.data), 'kdp_writeioport_reply_t *')
         
         # Done with the write
         if(result_pkt.error == 0):
-            print "Writing 0x {0: x} to port {1: <4x} was successful".format(value, addr)
+            print "Writing {0: #x} to port {1: <#6x} was successful".format(value, addr)
     else:
-        print "error writing 0x{0: x} to port 0x{1: <4x}".format(value, addr)
+        print "error writing {0: #x} to port {1: <#6x}".format(value, addr)
+
+@lldb_command('showinterruptcounts')
+def showinterruptcounts(cmd_args=None):
+    """ Shows event source based interrupt counts by nub name and interrupt index.
+        Does not cover interrupts that are not event source based.  Will report 0
+        if interrupt accounting is disabled.
+    """
+
+    header_format = "{0: <20s} {1: >5s} {2: >20s}"
+    content_format = "{0: <20s} {1: >5d} {2: >20d}"
+
+    print header_format.format("Name", "Index", "Count")
+    
+    for i in kern.interrupt_stats:
+        owner = CastIOKitClass(i.owner, 'IOInterruptEventSource *')
+        nub = CastIOKitClass(owner.provider, 'IORegistryEntry *') 
+        name = None
+
+        # To uniquely identify an interrupt, we need the nub name and the index.  The index
+        # is stored with the stats object, but we need to retrieve the name.
+
+        registryTable = nub.fRegistryTable
+        propertyTable = nub.fPropertyTable
+    
+        name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
+        if name is None:
+            name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
+        if name is None:
+            name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
+
+        if name is None:
+            nub_name = "Unknown"
+        else:
+            nub_name = GetString(CastIOKitClass(name, 'OSString *'))
+
+        # We now have everything we need; spew the requested data.
+
+        interrupt_index = i.interruptIndex
+        first_level_count = i.interruptStatistics[0]
+
+        print content_format.format(nub_name, interrupt_index, first_level_count)
+    
+    return True
+
+@lldb_command('showinterruptstats')
+def showinterruptstats(cmd_args=None):
+    """ Shows event source based interrupt statistics by nub name and interrupt index.
+        Does not cover interrupts that are not event source based.  Will report 0
+        if interrupt accounting is disabled, or if specific statistics are disabled.
+        Time is reported in ticks of mach_absolute_time.  Statistics are:
+        
+        Interrupt Count: Number of times the interrupt context handler was run
+        Interrupt Time: Total time spent in the interrupt context handler (if any)
+        Workloop Count: Number of times the kernel context handler was run
+        Workloop CPU Time: Total CPU time spent running the kernel context handler
+        Workloop Time: Total time spent running the kernel context handler
+    """
+
+    header_format = "{0: <20s} {1: >5s} {2: >20s} {3: >20s} {4: >20s} {5: >20s} {6: >20s} {7: >20s} {8: >20s} {9: >20s}"
+    content_format = "{0: <20s} {1: >5d} {2: >20d} {3: >20d} {4: >20d} {5: >20d} {6: >20d} {7: >20d} {8: >20d} {9: >#20x}"
+
+    print header_format.format("Name", "Index", "Interrupt Count", "Interrupt Time", "Avg Interrupt Time", "Workloop Count", "Workloop CPU Time", "Workloop Time", "Avg Workloop Time", "Owner")
+    
+    for i in kern.interrupt_stats:
+        owner = CastIOKitClass(i.owner, 'IOInterruptEventSource *')
+        nub = CastIOKitClass(owner.provider, 'IORegistryEntry *') 
+        name = None
+
+        # To uniquely identify an interrupt, we need the nub name and the index.  The index
+        # is stored with the stats object, but we need to retrieve the name.
+
+        registryTable = nub.fRegistryTable
+        propertyTable = nub.fPropertyTable
+    
+        name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
+        if name is None:
+            name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
+        if name is None:
+            name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
+
+        if name is None:
+            nub_name = "Unknown"
+        else:
+            nub_name = GetString(CastIOKitClass(name, 'OSString *'))
+
+        # We now have everything we need; spew the requested data.
+
+        interrupt_index = i.interruptIndex
+        first_level_count = i.interruptStatistics[0]
+        second_level_count = i.interruptStatistics[1]
+        first_level_time = i.interruptStatistics[2]
+        second_level_cpu_time = i.interruptStatistics[3]
+        second_level_system_time = i.interruptStatistics[4]
+
+        avg_first_level_time = 0
+        if first_level_count != 0:
+            avg_first_level_time = first_level_time / first_level_count
+
+        avg_second_level_time = 0
+        if second_level_count != 0:
+            avg_second_level_time = second_level_system_time / second_level_count
+
+        print content_format.format(nub_name, interrupt_index, first_level_count, first_level_time, avg_first_level_time,
+            second_level_count, second_level_cpu_time, second_level_system_time, avg_second_level_time, owner)
+    
+    return True
+
+def GetRegistryPlane(plane_name):
+    """
+    Given plane_name, returns IORegistryPlane * object or None if there's no such registry plane
+    """
+    return LookupKeyInOSDict(kern.globals.gIORegistryPlanes, plane_name, CompareStringToOSSymbol)
+
+def DecodePreoslogSource(source):
+    """
+    Given preoslog source, return a matching string representation
+    """
+    source_to_str = {0 : "iboot"}
+    if source in source_to_str:
+        return source_to_str[source]
+    return "UNKNOWN"
+
+def GetPreoslogHeader():
+    """
+    Scan IODeviceTree for preoslog and return a python representation of it
+    """
+    edt_plane = GetRegistryPlane("IODeviceTree")
+    if edt_plane is None:
+        print "Couldn't obtain a pointer to IODeviceTree"
+        return None
+
+    # Registry API functions operate on "plane" global variable
+    global plane
+    prev_plane = plane
+    plane = edt_plane
+    chosen = FindRegistryObjectRecurse(kern.globals.gRegistryRoot, "chosen")
+    if chosen is None:
+        print "Couldn't obtain /chosen IORegistryEntry"
+        return None
+
+    memory_map = FindRegistryObjectRecurse(chosen, "memory-map")
+    if memory_map is None:
+        print "Couldn't obtain memory-map from /chosen"
+        return None
+
+    plane = prev_plane
+
+    mm_preoslog = LookupKeyInOSDict(memory_map.fPropertyTable, "preoslog", CompareStringToOSSymbol)
+    if mm_preoslog is None:
+        print "Couldn't find preoslog entry in memory-map"
+        return None
+
+    if mm_preoslog.length != 16:
+        print "preoslog entry in memory-map is malformed, expected len is 16, given len is {}".format(preoslog.length)
+        return None
+
+    data = cast(mm_preoslog.data, "dtptr_t *")
+    preoslog_paddr = unsigned(data[0])
+    preoslog_vaddr = kern.PhysToKernelVirt(preoslog_paddr)
+    preoslog_size = unsigned(data[1])
+
+    preoslog_header = PreoslogHeader()
+
+    # This structure defnition doesn't exist in xnu
+    """
+    typedef struct  __attribute__((packed)) {
+        char magic[4];
+        uint32_t size;
+        uint32_t offset;
+        uint8_t source;
+        uint8_t wrapped;
+        char data[];
+    } preoslog_header_t; 
+    """
+    preoslog_header_ptr = kern.GetValueFromAddress(preoslog_vaddr, "uint8_t *")
+    preoslog_header.magic = preoslog_header_ptr[0:4]
+    preoslog_header.source = DecodePreoslogSource(unsigned(preoslog_header_ptr[12]))
+    preoslog_header.wrapped = unsigned(preoslog_header_ptr[13])
+    preoslog_header_ptr = kern.GetValueFromAddress(preoslog_vaddr, "uint32_t *")
+    preoslog_header.size = unsigned(preoslog_header_ptr[1])
+    preoslog_header.offset = unsigned(preoslog_header_ptr[2])
+
+    for i in xrange(len(preoslog_header.valid_magic)):
+        c = chr(unsigned(preoslog_header.magic[i]))
+        if c != preoslog_header.valid_magic[i]:
+            string = "Error: magic doesn't match, expected {:.4s}, given {:.4s}"
+            print string.format(preoslog_header.valid_magic, preoslog_header.magic)
+            return None
+
+    if preoslog_header.size != preoslog_size:
+        string = "Error: size mismatch preoslog_header.size ({}) != preoslog_size ({})"
+        print string.format(preoslog_header.size, preoslog_size)
+        return None
+
+    preoslog_data_ptr = kern.GetValueFromAddress(preoslog_vaddr + 14, "char *")
+    preoslog_header.data = preoslog_data_ptr.sbvalue.GetPointeeData(0, preoslog_size)
+    return preoslog_header
+
+@lldb_command("showpreoslog")
+def showpreoslog(cmd_args=None):
+    """ Display preoslog buffer """
+
+    preoslog = GetPreoslogHeader()
+    if preoslog is None:
+        print "Error: couldn't obtain preoslog header"
+        return False
+
+    header = "".join([
+        "----preoslog log header-----\n",
+        "size - {} bytes\n",
+        "write offset - {:#x}\n",
+        "wrapped - {}\n",
+        "source - {}\n",
+        "----preoslog log start------"
+        ])
+
+    print header.format(preoslog.size, preoslog.offset, preoslog.wrapped, preoslog.source)
 
+    err = lldb.SBError()
+    if preoslog.wrapped > 0:
+        print preoslog.data.GetString(err, preoslog.offset + 1)
+    print preoslog.data.GetString(err, 0)
+    print "-----preoslog log end-------"
+    return True