]> git.saurik.com Git - apple/xnu.git/blobdiff - tools/lldbmacros/ioreg.py
xnu-6153.101.6.tar.gz
[apple/xnu.git] / tools / lldbmacros / ioreg.py
index 74d2e3baadc06432218c8d654e90250ad5b44b88..19fd2879945b7d6fc976acf1c9f3ff6c709b4fe4 100755 (executable)
@@ -1,6 +1,9 @@
 from xnu import *
 from utils import *
+from kdp import *
+from core import caching
 import sys
+from collections import deque
 
 ######################################
 # Globals
@@ -33,6 +36,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() + ">"
@@ -90,6 +94,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()
@@ -127,6 +132,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)
@@ -507,6 +513,117 @@ def ShowIOServicePM(cmd_args=None):
     
     print out_string
 
+
+@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
 ######################################
@@ -632,6 +749,478 @@ def FindRegistryObjectRecurse(entry, search_name):
                 return registry_object
     return None
 
+
+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):
     """ Returns the value corresponding to a given key in a OSDictionary
         Returns None if the key was not found