]> 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
index e2bdaf20ebc29eca34a2cddeeae3edfd4abd8ad9..0aa3355b11fce1c4b9ff33a233483e12e1cb861a 100755 (executable)
@@ -1,7 +1,10 @@
 from xnu import *
 from utils import *
 from kdp import *
+from core import caching
 import sys
+import lldb
+from collections import deque
 
 ######################################
 # Globals
@@ -22,6 +25,22 @@ def CastIOKitClass(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
 ######################################
@@ -34,6 +53,7 @@ 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() + ">"
@@ -91,6 +111,7 @@ def GetObjectTypeStr(obj):
         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()
@@ -128,6 +149,7 @@ def GetRegistryEntrySummary(entry):
     
     # 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, CastIOKitClass(entry, 'IORegistryEntry *').reserved.fRegistryEntryID, vtableAddr)
@@ -508,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
 ######################################
@@ -633,7 +833,498 @@ def FindRegistryObjectRecurse(entry, search_name):
                 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
     """
@@ -642,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
@@ -682,7 +1381,7 @@ def GetRegDictionary(osdict, prefix):
 def GetString(string):
     """ Returns the python string representation of a given OSString
     """
-    out_string = "\"{0:s}\"".format(CastIOKitClass(string, 'OSString *').string)
+    out_string = "{0:s}".format(CastIOKitClass(string, 'OSString *').string)
     return out_string
 
 def GetNumber(num):
@@ -723,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):
@@ -948,3 +1646,120 @@ def showinterruptstats(cmd_args=None):
     
     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