+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):