xref: /xnu-12377.61.12/tools/lldbmacros/ioreg.py (revision 4d495c6e23c53686cf65f45067f79024cf5dcee8) !
1from xnu import *
2from utils import *
3from kdp import *
4from core import caching
5from core.pointer import NativePointer
6import sys
7import lldb
8import os
9import sys
10from collections import deque
11
12######################################
13# Globals
14######################################
15plane = None
16
17#####################################
18# Utility functions.
19#####################################
20def CastIOKitClass(obj, target_type):
21    """ Type cast an object to another IOKIT CPP class.
22        params:
23            obj - core.value  object representing some C construct in lldb
24            target_type - str : ex 'OSString *'
25                        - lldb.SBType :
26    """
27    v = obj.GetSBValue()
28    # We need to do that so that LLDB doesn't try to "helpfully"
29    # Guess which instance type it is...
30    v.SetPreferDynamicValue(lldb.eNoDynamicValues)
31    if isinstance(target_type, str):
32        target_type = gettype(target_type)
33    return value(v.Cast(target_type))
34
35#####################################
36# Classes.
37#####################################
38class PreoslogHeader(object):
39    """
40    Represents preoslog buffer header. There's no symbol in the kernel for it.
41    """
42    valid_magic = "POSL"
43    def __init__(self):
44        self.magic = ""
45        self.offset = 0
46        self.size = 0
47        self.source = 0
48        self.wrapped = 0
49        self.data = None
50
51
52class IOKitSmartPointer(NativePointer):
53    """ IOKit's smart pointer
54
55        Every smart pointer inherits from libkern::intrusive_shared_ptr.
56        The real pointer is wrapped behind ptr_ member.
57    """
58
59    @classmethod
60    def match(cls, sbvalue):
61
62        # Smart pointers in IOKit are OSSharedPtr and OSTaggedSharedPtr
63        name = sbvalue.GetType().GetCanonicalType().GetName()
64        if name.startswith(("OSSharedPtr", "OSTaggedSharedPtr")):
65            return cls()
66
67        return None
68
69    def GetPointerSBValue(self, sbvalue):
70        sbv = sbvalue.GetChildMemberWithName('ptr_')
71        return super().GetPointerSBValue(sbv)
72
73
74######################################
75# Type Summaries
76######################################
77@lldb_type_summary(['OSObject *'])
78@header("")
79def GetObjectSummary(obj):
80    """ Show info about an OSObject - its vtable ptr and retain count, & more info for simple container classes.
81    """
82    if obj is None:
83        return
84
85    vt = dereference(Cast(obj, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
86    vt = kern.StripKernelPAC(vt)
87    vtype = kern.SymbolicateFromAddress(vt)
88    if len(vtype):
89        vtype_str = " <" + vtype[0].GetName() + ">"
90    else:
91        vtype_str = ""
92    if (retainCount := get_field(obj, 'retainCount')) is not None:
93        retCount = (retainCount & 0xffff)
94        cntnrRetCount = (retainCount >> 16)
95        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)
96    else:
97        out_string = "`object 0x{0: <16x}, vt 0x{1: <16x}{2:s}` ".format(obj, vt, vtype_str)
98
99    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV8OSString')
100    if vt == ztvAddr:
101        out_string += GetString(obj)
102        return out_string
103
104    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV8OSSymbol')
105    if vt == ztvAddr:
106        out_string += GetString(obj)
107        return out_string
108
109    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV8OSNumber')
110    if vt == ztvAddr:
111        out_string += GetNumber(obj)
112        return out_string
113
114    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV9OSBoolean')
115    if vt == ztvAddr:
116        out_string += GetBoolean(obj)
117        return out_string
118
119    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV7OSArray')
120    if vt == ztvAddr:
121        out_string += "(" + GetArray(CastIOKitClass(obj, 'OSArray *')) + ")"
122        return out_string
123
124    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV5OSSet')
125    if vt == ztvAddr:
126        out_string += GetSet(CastIOKitClass(obj, 'OSSet *'))
127        return out_string
128
129    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV12OSDictionary')
130    if vt == ztvAddr:
131        out_string += GetDictionary(CastIOKitClass(obj, 'OSDictionary *'))
132        return out_string
133
134    return out_string
135
136
137def GetObjectTypeStr(obj):
138    """ Return the type of an OSObject's container class
139    """
140    if obj is None:
141        return None
142
143    vt = dereference(Cast(obj, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
144    vt = kern.StripKernelPAC(vt)
145    vtype = kern.SymbolicateFromAddress(vt)
146    if len(vtype):
147        return vtype[0].GetName()
148
149    # See if the value is in a kext with no symbols
150    for kval in IterateLinkedList(kern.globals.kmod, 'next'):
151        if vt >= unsigned(kval.address) and vt <= (unsigned(kval.address) + unsigned(kval.size)):
152            return "kmod:{:s}+{:#0x}".format(kval.name, vt - unsigned(kval.address))
153    return None
154
155
156@lldb_type_summary(['IORegistryEntry *'])
157@header("")
158def GetRegistryEntrySummary(entry):
159    """ returns a string containing summary information about an IORegistry
160        object including it's registry id , vtable ptr and retain count
161    """
162    name = None
163    out_string = ""
164    registryTable = entry.fRegistryTable
165    propertyTable = entry.fPropertyTable
166
167    name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
168    if name is None:
169        name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
170    if name is None:
171        name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
172
173    if name is not None:
174        out_string += "+-o {0:s}  ".format(GetString(CastIOKitClass(name, 'OSString *')))
175    elif (pwrMgt := CastIOKitClass(entry, 'IOService *').pwrMgt) and (service_name := pwrMgt.Name):
176        out_string += "+-o {0:s}  ".format(service_name)
177    else:
178        out_string += "+-o ??  "
179
180    # I'm using uintptr_t for now to work around <rdar://problem/12749733> FindFirstType & Co. should allow you to make pointer types directly
181    vtableAddr = dereference(Cast(entry, 'uintptr_t *')) - 2 * sizeof('uintptr_t *')
182    vtableAddr = kern.StripKernelPAC(vtableAddr)
183    vtype = kern.SymbolicateFromAddress(vtableAddr)
184    if vtype is None or len(vtype) < 1:
185        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x}".format(entry, CastIOKitClass(entry, 'IORegistryEntry *').reserved.fRegistryEntryID, vtableAddr)
186    else:
187        out_string += "<object 0x{0: <16x}, id 0x{1:x}, vtable 0x{2: <16x} <{3:s}>".format(entry, CastIOKitClass(entry, 'IORegistryEntry *').reserved.fRegistryEntryID,
188                                                                                           vtableAddr, vtype[0].GetName())
189
190    ztvAddr = kern.GetLoadAddressForSymbol('_ZTV15IORegistryEntry')
191    if vtableAddr != ztvAddr:
192        out_string += ", "
193        state = CastIOKitClass(entry, 'IOService *').__state[0]
194        # kIOServiceRegisteredState
195        if 0 == state & 2:
196            out_string += "!"
197        out_string += "registered, "
198        # kIOServiceMatchedState
199        if 0 == state & 4:
200            out_string += "!"
201        out_string += "matched, "
202        #kIOServiceInactiveState
203        if 0 != state & 1:
204            out_string += "in"
205        busyCount = (CastIOKitClass(entry, 'IOService *').__state[1] & 0xff)
206        retCount = (CastIOKitClass(entry, 'IOService *').retainCount & 0xffff)
207        out_string += "active, busy {0}, retain count {1}>".format(busyCount, retCount)
208    return out_string
209
210######################################
211# Commands
212######################################
213@lldb_command('showallclasses')
214def ShowAllClasses(cmd_args=None):
215    """ Show the instance counts and ivar size of all OSObject subclasses.
216        See ioclasscount man page for details
217    """
218    idx = 0
219    count = unsigned(kern.globals.sAllClassesDict.count)
220
221    while idx < count:
222        meta = CastIOKitClass(kern.globals.sAllClassesDict.dictionary[idx].value, 'OSMetaClass *')
223        idx += 1
224        print(GetMetaClass(meta))
225
226@lldb_command('showobject')
227def ShowObject(cmd_args=None):
228    """ Show info about an OSObject - its vtable ptr and retain count, & more info for simple container classes.
229    """
230    if cmd_args is None or len(cmd_args) == 0:
231        raise ArgumentError("Please specify the address of the OSObject whose info you want to view. Type help showobject for help")
232        return
233
234    obj = kern.GetValueFromAddress(cmd_args[0], 'OSObject *')
235    print(GetObjectSummary(obj))
236
237#Macro: dumpobject
238@lldb_command('dumpobject')
239def DumpObject(cmd_args=None):
240    """ Dumps object information if it is a valid object confirmed by showobject
241        Usage: dumpobject <address of object to be dumped> [class/struct type of object]
242    """
243    if cmd_args is None or len(cmd_args) == 0:
244        raise ArgumentError("No arguments passed")
245        return False
246
247    if len(cmd_args) == 1:
248        try:
249            object_info = lldb_run_command("showobject {:s}".format(cmd_args[0]))
250        except:
251            print("Error!! showobject failed due to invalid value")
252            print(DumpObject.__doc__)
253            return False
254
255        srch = re.search(r'<vtable for ([A-Za-z][^>]*)>', object_info)
256        if not srch:
257            print("Error!! Couldn't find object in registry, input type manually as 2nd argument")
258            print(DumpObject.__doc__)
259            return False
260
261        object_type = srch.group(1)
262    else:
263        type_lookup = lldb_run_command("image lookup -t {:s}".format(cmd_args[1]))
264        if type_lookup.find(cmd_args[1])!= -1:
265            object_type = cmd_args[1]
266        else:
267            print("Error!! Input type {:s} isn't available in image lookup".format(cmd_args[1]))
268            return False
269
270    print("******** Object Dump for value \'{:s}\' with type \"{:s}\" ********".format(cmd_args[0], object_type))
271    print(lldb_run_command("p/x *({:s}*){:s}".format(object_type, cmd_args[0])))
272
273#EndMacro: dumpobject
274
275@lldb_command('setregistryplane')
276def SetRegistryPlane(cmd_args=None):
277    """ Set the plane to be used for the IOKit registry macros
278        syntax: (lldb) setregistryplane 0  - will display all known planes
279        syntax: (lldb) setregistryplane 0xaddr      - will set the registry plane to 0xaddr
280        syntax: (lldb) setregistryplane gIODTPlane  - will set the registry plane to gIODTPlane
281    """
282    if cmd_args is None or len(cmd_args) == 0:
283        raise ArgumentError("Please specify the name of the plane you want to use with the IOKit registry macros.")
284
285    if cmd_args[0] == "0":
286        print(GetObjectSummary(kern.globals.gIORegistryPlanes))
287    else:
288        global plane
289        plane = kern.GetValueFromAddress(cmd_args[0], 'IORegistryPlane *')
290    return
291
292@lldb_command('showregistryentry')
293def ShowRegistryEntry(cmd_args=None):
294    """ Show info about a registry entry; its properties and descendants in the current plane
295        syntax: (lldb) showregistryentry 0xaddr
296        syntax: (lldb) showregistryentry gIOPMRootDomain
297    """
298    if cmd_args is None or len(cmd_args) == 0:
299        raise ArgumentError("Please specify the address of the registry entry whose info you want to view.")
300        return
301
302    entry = kern.GetValueFromAddress(cmd_args[0], 'IORegistryEntry *')
303    ShowRegistryEntryRecurse(entry, "", True)
304
305@lldb_command('showregistry')
306def ShowRegistry(cmd_args=None):
307    """ Show info about all registry entries in the current plane
308        If prior to invoking this command no registry plane is specified
309        using 'setregistryplane', the command defaults to the IOService plane
310    """
311    ShowRegistryEntryRecurse(kern.globals.gRegistryRoot, "", False)
312
313@lldb_command('showregistryprops')
314def ShowRegistryProps(cmd_args=None):
315    """ Show info about all registry entries in the current plane, and their properties
316        If prior to invoking this command no registry plane is specified
317        using 'setregistryplane', the command defaults to the IOService plane
318    """
319    ShowRegistryEntryRecurse(kern.globals.gRegistryRoot, "", True)
320
321@lldb_command('findregistryentry')
322def FindRegistryEntry(cmd_args=None):
323    """ Search for registry entry that matches the given string
324        If prior to invoking this command no registry plane is specified
325        using 'setregistryplane', the command defaults to searching entries from the IOService plane
326        syntax: (lldb) findregistryentries AppleACPICPU - will find the first registry entry that matches AppleACPICPU
327    """
328    if cmd_args is None or len(cmd_args) == 0:
329        raise ArgumentError("Please specify the name of the registry entry you want to find")
330
331    FindRegistryEntryRecurse(kern.globals.gRegistryRoot, cmd_args[0], True)
332
333@lldb_command('findregistryentries')
334def FindRegistryEntries(cmd_args=None):
335    """ Search for all registry entries that match the given string
336        If prior to invoking this command no registry plane is specified
337        using 'setregistryplane', the command defaults to searching entries from the IOService plane
338        syntax: (lldb) findregistryentries AppleACPICPU - will find all registry entries that match AppleACPICPU
339    """
340    if cmd_args is None or len(cmd_args) == 0:
341        raise ArgumentError("Please specify the name of the registry entry/entries you want to find")
342
343    FindRegistryEntryRecurse(kern.globals.gRegistryRoot, cmd_args[0], False)
344
345@lldb_command('findregistryprop')
346def FindRegistryProp(cmd_args=None):
347    """ Given a registry entry, print out the contents for the property that matches
348        a specific string
349        syntax: (lldb) findregistryprop 0xaddr IOSleepSupported
350        syntax: (lldb) findregistryprop gIOPMRootDomain IOSleepSupported
351        syntax: (lldb) findregistryprop gIOPMRootDomain "Supported Features"
352    """
353    if cmd_args is None or len(cmd_args) < 2:
354        raise ArgumentError("Please specify the address of a IORegistry entry and the property you're looking for")
355
356    entry = kern.GetValueFromAddress(cmd_args[0], 'IOService *')
357    propertyTable = entry.fPropertyTable
358    print(GetObjectSummary(LookupKeyInPropTable(propertyTable, cmd_args[1])))
359
360@lldb_command('showuserserver')
361def ShowUserServer(cmd_args=None):
362    """ Show info about an IOUserServer object
363        syntax: (lldb) showuserserver 0xaddr
364    """
365    if cmd_args is None or len(cmd_args) == 0:
366        raise ArgumentError("Please specify the address of the IOUserServer object whose info you want to view.")
367        return
368    ShowUserServerSummary(cmd_args[0])
369
370@lldb_command('readioport8')
371def ReadIOPort8(cmd_args=None):
372    """ Read value stored in the specified IO port. The CPU can be optionally
373        specified as well.
374        Prints 0xBAD10AD in case of a bad read
375        Syntax: (lldb) readioport8 <port> [lcpu (kernel's numbering convention)]
376    """
377    if cmd_args is None or len(cmd_args) == 0:
378        raise ArgumentError("Please specify a port to read out of")
379
380    portAddr = ArgumentStringToInt(cmd_args[0])
381    if len(cmd_args) >= 2:
382        lcpu = ArgumentStringToInt(cmd_args[1])
383    else:
384        lcpu = xnudefines.lcpu_self
385
386    ReadIOPortInt(portAddr, 1, lcpu)
387
388@lldb_command('readioport16')
389def ReadIOPort16(cmd_args=None):
390    """ Read value stored in the specified IO port. The CPU can be optionally
391        specified as well.
392        Prints 0xBAD10AD in case of a bad read
393        Syntax: (lldb) readioport16 <port> [lcpu (kernel's numbering convention)]
394    """
395    if cmd_args is None or len(cmd_args) == 0:
396        raise ArgumentError("Please specify a port to read out of")
397
398    portAddr = ArgumentStringToInt(cmd_args[0])
399    if len(cmd_args) >= 2:
400        lcpu = ArgumentStringToInt(cmd_args[1])
401    else:
402        lcpu = xnudefines.lcpu_self
403
404    ReadIOPortInt(portAddr, 2, lcpu)
405
406@lldb_command('readioport32')
407def ReadIOPort32(cmd_args=None):
408    """ Read value stored in the specified IO port. The CPU can be optionally
409        specified as well.
410        Prints 0xBAD10AD in case of a bad read
411        Syntax: (lldb) readioport32 <port> [lcpu (kernel's numbering convention)]
412    """
413    if cmd_args is None or len(cmd_args) == 0:
414        raise ArgumentError("Please specify a port to read out of")
415
416    portAddr = ArgumentStringToInt(cmd_args[0])
417    if len(cmd_args) >= 2:
418        lcpu = ArgumentStringToInt(cmd_args[1])
419    else:
420        lcpu = xnudefines.lcpu_self
421
422    ReadIOPortInt(portAddr, 4, lcpu)
423
424@lldb_command('writeioport8')
425def WriteIOPort8(cmd_args=None):
426    """ Write the value to the specified IO port. The size of the value is
427        determined by the name of the command. The CPU used can be optionally
428        specified as well.
429        Syntax: (lldb) writeioport8 <port> <value> [lcpu (kernel's numbering convention)]
430    """
431    if cmd_args is None or len(cmd_args) < 2:
432        raise ArgumentError("Please specify a port to write to, followed by the value you want to write")
433
434    portAddr = ArgumentStringToInt(cmd_args[0])
435    value = ArgumentStringToInt(cmd_args[1])
436
437    if len(cmd_args) >= 3:
438        lcpu = ArgumentStringToInt(cmd_args[2])
439    else:
440        lcpu = xnudefines.lcpu_self
441
442    WriteIOPortInt(portAddr, 1, value, lcpu)
443
444@lldb_command('writeioport16')
445def WriteIOPort16(cmd_args=None):
446    """ Write the value to the specified IO port. The size of the value is
447        determined by the name of the command. The CPU used can be optionally
448        specified as well.
449        Syntax: (lldb) writeioport16 <port> <value> [lcpu (kernel's numbering convention)]
450    """
451    if cmd_args is None or len(cmd_args) < 2:
452        raise ArgumentError("Please specify a port to write to, followed by the value you want to write")
453
454    portAddr = ArgumentStringToInt(cmd_args[0])
455    value = ArgumentStringToInt(cmd_args[1])
456
457    if len(cmd_args) >= 3:
458        lcpu = ArgumentStringToInt(cmd_args[2])
459    else:
460        lcpu = xnudefines.lcpu_self
461
462    WriteIOPortInt(portAddr, 2, value, lcpu)
463
464@lldb_command('writeioport32')
465def WriteIOPort32(cmd_args=None):
466    """ Write the value to the specified IO port. The size of the value is
467        determined by the name of the command. The CPU used can be optionally
468        specified as well.
469        Syntax: (lldb) writeioport32 <port> <value> [lcpu (kernel's numbering convention)]
470    """
471    if cmd_args is None or len(cmd_args) < 2:
472        raise ArgumentError("Please specify a port to write to, followed by the value you want to write")
473
474    portAddr = ArgumentStringToInt(cmd_args[0])
475    value = ArgumentStringToInt(cmd_args[1])
476
477    if len(cmd_args) >= 3:
478        lcpu = ArgumentStringToInt(cmd_args[2])
479    else:
480        lcpu = xnudefines.lcpu_self
481
482    WriteIOPortInt(portAddr, 4, value, lcpu)
483
484@lldb_command('showioservicepm')
485def ShowIOServicePM(cmd_args=None):
486    """ Routine to dump the IOServicePM object
487        Syntax: (lldb) showioservicepm <IOServicePM pointer>
488    """
489    if cmd_args is None or len(cmd_args) == 0:
490        raise ArgumentError("Please enter the pointer to the IOServicePM object you'd like to introspect")
491
492    iopmpriv = kern.GetValueFromAddress(cmd_args[0], 'IOServicePM *')
493    out_string = "MachineState {0: <6d} (".format(iopmpriv.MachineState)
494
495    # Power state map
496    pstate_map = {
497            0:  'kIOPM_Finished',
498            1:  'kIOPM_OurChangeTellClientsPowerDown',
499            2:  'kIOPM_OurChangeTellClientsPowerDown',
500            3:  'kIOPM_OurChangeNotifyInterestedDriversWillChange',
501            4:  'kIOPM_OurChangeSetPowerState',
502            5:  'kIOPM_OurChangeWaitForPowerSettle',
503            6:  'kIOPM_OurChangeNotifyInterestedDriversDidChange',
504            7:  'kIOPM_OurChangeTellCapabilityDidChange',
505            8:  'kIOPM_OurChangeFinish',
506            9:  'Unused_MachineState_9',
507            10: 'kIOPM_ParentChangeTellPriorityClientsPowerDown',
508            11: 'kIOPM_ParentChangeNotifyInterestedDriversWillChange',
509            12: 'kIOPM_ParentChangeSetPowerState',
510            13: 'kIOPM_ParentChangeWaitForPowerSettle',
511            14: 'kIOPM_ParentChangeNotifyInterestedDriversDidChange',
512            15: 'kIOPM_ParentChangeTellCapabilityDidChange',
513            16: 'kIOPM_ParentChangeAcknowledgePowerChange',
514            17: 'kIOPM_NotifyChildrenStart',
515            18: 'kIOPM_NotifyChildrenOrdered',
516            19: 'kIOPM_NotifyChildrenDelayed',
517            20: 'kIOPM_SyncTellClientsPowerDown',
518            21: 'kIOPM_SyncTellPriorityClientsPowerDown',
519            22: 'kIOPM_SyncNotifyWillChange',
520            23: 'kIOPM_SyncNotifyDidChange',
521            24: 'kIOPM_SyncTellCapabilityDidChange',
522            25: 'kIOPM_SyncFinish',
523            26: 'kIOPM_TellCapabilityChangeDone',
524            27: 'kIOPM_DriverThreadCallDone'
525        }
526    powerstate = unsigned(iopmpriv.MachineState)
527    if powerstate in pstate_map:
528        out_string += "{0:s}".format(pstate_map[powerstate])
529    else:
530        out_string += "Unknown_MachineState"
531    out_string += "), "
532
533    if iopmpriv.MachineState != 20:
534        if hasattr(iopmpriv, "SettleTimeUS"):
535            out_string += "DriverTimer = {0: <6d}, SettleTime = {1: < 6d}, HeadNoteFlags = {2: #12x}, HeadNotePendingAcks = {3: #012x}, ".format(
536                    unsigned(iopmpriv.DriverTimer),
537                    unsigned(iopmpriv.SettleTimeUS),
538                    unsigned(iopmpriv.HeadNoteChangeFlags),
539                    unsigned(iopmpriv.HeadNotePendingAcks))
540        else:
541            out_string += "DriverTimer = {0: <6d}, HeadNoteFlags = {1: #12x}, HeadNotePendingAcks = {2: #012x}, ".format(
542                    unsigned(iopmpriv.DriverTimer),
543                    unsigned(iopmpriv.HeadNoteChangeFlags),
544                    unsigned(iopmpriv.HeadNotePendingAcks))
545
546    if iopmpriv.DeviceOverrideEnabled != 0:
547        out_string += "DeviceOverrides, "
548
549    out_string += "DeviceDesire = {0: <6d}, DesiredPowerState = {1: <6d}, PreviousRequest = {2: <6d}\n".format(
550            unsigned(iopmpriv.DeviceDesire),
551            unsigned(iopmpriv.DesiredPowerState),
552            unsigned(iopmpriv.PreviousRequestPowerFlags))
553
554    print(out_string)
555
556@lldb_type_summary(['IOPMWorkQueue *'])
557@header("")
558def GetIOPMWorkQueueSummary(wq):
559    out_str = ""
560    ioservicepm_header = "{:<20s}{:<4s}{:<4s}{:<4s}{:<4s}\n"
561    iopmrequest_indent = "    "
562    iopmrequest_header = iopmrequest_indent + "{:<20s}{:<6s}{:<20s}{:<20s}{:<12s}{:<12s}{:<20s}{:<20s}{:<20s}\n"
563    head = kern.StripKernelPAC(addressof(wq.fWorkQueue))
564    head = kern.GetValueFromAddress(head, 'queue_head_t *')
565
566    for next in IterateQueue(head, 'IOServicePM *', 'WorkChain'):
567        out_str += ioservicepm_header.format("IOService", "ps", "ms", "wr", "name")
568        out_str += "0x{:<16x}  {:<2d}  {:<2d}  {:<2d}  {:<s}\n".format(
569            next.Owner, next.CurrentPowerState, next.MachineState, next.WaitReason, next.Name)
570        out_str += iopmrequest_header.format("IOPMRequest", "type", "next_req", "root_req", "work_wait", "free_wait", "arg0", "arg1", "arg2")
571        next_head = kern.StripKernelPAC(addressof(next.RequestHead))
572        next_head = kern.GetValueFromAddress(next_head, 'queue_head_t *')
573        for request in IterateQueue(next_head, 'IOPMRequest *', 'fCommandChain'):
574            out_str += iopmrequest_indent
575            out_str += "0x{:<16x}  0x{:<2x}  0x{:<16x}  0x{:<16x}".format(
576                request, request.fRequestType, request.fRequestNext, request.fRequestRoot)
577            out_str += "  0x{:<8x}  0x{:<8x}".format(
578                request.fWorkWaitCount, request.fFreeWaitCount)
579            out_str += "  0x{:<16x}  0x{:<16x}  0x{:<16x}\n".format(
580                request.fArg0, request.fArg1, request.fArg2)
581    return out_str
582
583@lldb_command('showiopmqueues')
584def ShowIOPMQueues(cmd_args=None):
585    """ Show IOKit power management queues and IOPMRequest objects.
586    """
587    print("IOPMWorkQueue 0x{:<16x} ({:<d} IOServicePM)\n".format(
588        kern.globals.gIOPMWorkQueue, kern.globals.gIOPMWorkQueue.fQueueLength))
589    print(GetIOPMWorkQueueSummary(kern.globals.gIOPMWorkQueue))
590
591@lldb_command('showiouserserverpm')
592def ShowIOUserServerPM(cmd_args=None):
593    """ Show pending power requests managed by IOUserServer instances.
594    """
595    pendingServers = kern.globals.fUserServersWait
596    count = int(pendingServers.count)
597    if count == 0:
598        print("No user servers with pending power request found")
599        return
600    for idx in range(count):
601        server = CastIOKitClass(pendingServers.array[idx], "IOUserServer *")
602        print(f"IOUserServer: {hex(server)}")
603        services = server.fServices
604        serviceCount = services.count
605        services = services.array
606        for serviceIdx in range(serviceCount):
607            service = CastIOKitClass(services[serviceIdx], "IOService *")
608            uvars = service.reserved.uvars
609            powerState = uvars.powerState
610            pmPending = int(powerState)
611            if pmPending == 0:
612                continue
613            # blatantly copied from GetRegistryEntrySummary
614            name = None
615            registryTable = service.fRegistryTable
616            propertyTable = service.fPropertyTable
617            name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
618            if name is None:
619                name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
620            if name is None:
621                name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
622            name = GetString(CastIOKitClass(name, 'OSString *'))
623            print(f"{name}: {hex(service)}")
624        print("")
625
626@lldb_type_summary(['IOService *'])
627@header("")
628def GetIOPMInterest(service):
629    iopm = CastIOKitClass(service.pwrMgt, 'IOServicePM *')
630    if unsigned(iopm) == 0:
631        raise ArgumentError("error: no IOServicePM")
632        return
633
634    list = CastIOKitClass(iopm.InterestedDrivers, 'IOPMinformeeList *')
635    out_str = "IOServicePM 0x{:<16x} ({:<d} interest, {:<d} pending ack)\n".format(
636        iopm, list.length, iopm.HeadNotePendingAcks)
637    if list.length == 0:
638        return
639
640    out_str += "    {:<20s}{:<8s}{:<10s}{:<20s}{:<20s}{:<20s}{:<s}\n".format(
641        "informee", "active", "ticks", "notifyTime", "service", "regId", "name")
642    next = CastIOKitClass(list.firstItem, 'IOPMinformee *')
643    while unsigned(next) != 0:
644        driver = CastIOKitClass(next.whatObject, 'IOService *')
645        name = GetRegistryEntryName(driver)
646        reg_id = CastIOKitClass(driver, 'IORegistryEntry *').reserved.fRegistryEntryID;
647        out_str += "    0x{:<16x}  {:<6s}  {:<8d}  0x{:<16x}  0x{:<16x}  0x{:<16x}  {:<s}\n".format(
648            next, "Yes" if next.active != 0 else "No" , next.timer, next.startTime, next.whatObject, reg_id, name)
649        next = CastIOKitClass(next.nextInList, 'IOPMinformee *')
650    return out_str
651
652@lldb_command('showiopminterest')
653def ShowIOPMInterest(cmd_args=None):
654    """ Show the interested drivers for an IOService.
655        syntax: (lldb) showiopminterest <IOService>
656    """
657    if cmd_args is None or len(cmd_args) == 0:
658        raise ArgumentError("Please specify the address of the IOService")
659
660    obj = kern.GetValueFromAddress(cmd_args[0], 'IOService *')
661    print(GetIOPMInterest(obj))
662
663@lldb_command("showinterruptvectors")
664def ShowInterruptVectorInfo(cmd_args=None):
665    """
666    Shows interrupt vectors.
667    """
668
669    # Constants
670    kInterruptTriggerModeMask  = 0x01
671    kInterruptTriggerModeEdge  = 0x00
672    kInterruptTriggerModeLevel = kInterruptTriggerModeMask
673    kInterruptPolarityMask     = 0x02
674    kInterruptPolarityHigh     = 0x00
675    kInterruptPolarityLow      = kInterruptPolarityMask
676    kInterruptShareableMask    = 0x04
677    kInterruptNotShareable     = 0x00
678    kInterruptIsShareable      = kInterruptShareableMask
679    kIOInterruptTypePCIMessaged = 0x00010000
680
681    # Get all interrupt controllers
682    interrupt_controllers = list(SearchInterruptControllerDrivers())
683
684    print("Interrupt controllers: ")
685    for ic in interrupt_controllers:
686        print("  {}".format(ic))
687    print("")
688
689    # Iterate over all entries in the registry
690    for entry in GetMatchingEntries(lambda _: True):
691        # Get the name of the entry
692        entry_name = GetRegistryEntryName(entry)
693
694        # Get the location of the entry
695        entry_location = GetRegistryEntryLocationInPlane(entry, kern.globals.gIOServicePlane)
696        if entry_location is None:
697            entry_location = ""
698        else:
699            entry_location = "@" + entry_location
700
701        # Get the interrupt properties
702        (msi_mode, vectorDataList, vectorContList) = GetRegistryEntryInterruptProperties(entry)
703        should_print = False
704        out_str = ""
705        for (vector_data, vector_cont) in zip(vectorDataList, vectorContList):
706            # vector_cont is the name of the interrupt controller. Find the matching controller from
707            # the list of controllers obtained earlier
708            matching_ics = [ic for ic in interrupt_controllers if ic.name == vector_cont]
709
710            if len(matching_ics) > 0:
711                should_print = True
712                # Take the first match
713                matchingIC = matching_ics[0]
714
715                # Use the vector_data to determine the vector and any flags
716                data_ptr = vector_data.data
717                data_length = vector_data.length
718
719                # Dereference vector_data as a uint32_t * and add the base vector number
720                gsi = unsigned(dereference(Cast(data_ptr, 'uint32_t *')))
721                gsi += matchingIC.base_vector_number
722
723                # If data_length is >= 8 then vector_data contains interrupt flags
724                if data_length >= 8:
725                    # Add sizeof(uint32_t) to data_ptr to get the flags pointer
726                    flags_ptr = kern.GetValueFromAddress(unsigned(data_ptr) + sizeof("uint32_t"))
727                    flags = unsigned(dereference(Cast(flags_ptr, 'uint32_t *')))
728                    out_str += "  +----- [Interrupt Controller {ic}] vector {gsi}, {trigger_level}, {active}, {shareable}{messaged}\n" \
729                            .format(ic=matchingIC.name, gsi=hex(gsi),
730                                    trigger_level="level trigger" if flags & kInterruptTriggerModeLevel else "edge trigger",
731                                    active="active low" if flags & kInterruptPolarityLow else "active high",
732                                    shareable="shareable" if flags & kInterruptIsShareable else "exclusive",
733                                    messaged=", messaged" if flags & kIOInterruptTypePCIMessaged else "")
734                else:
735                    out_str += "  +----- [Interrupt Controller {ic}] vector {gsi}\n".format(ic=matchingIC.name, gsi=hex(gsi))
736        if should_print:
737            print("[ {entry_name}{entry_location} ]{msi_mode}\n{out_str}" \
738                .format(entry_name=entry_name,
739                        entry_location=entry_location,
740                        msi_mode=" - MSIs enabled" if msi_mode else "",
741                        out_str=out_str))
742
743@lldb_command("showiokitclasshierarchy")
744def ShowIOKitClassHierarchy(cmd_args=None):
745    """
746    Show class hierarchy for a IOKit class
747    """
748    if cmd_args is None or len(cmd_args) == 0:
749        raise ArgumentError("Usage: showiokitclasshierarchy <IOKit class name>")
750
751    class_name = cmd_args[0]
752    metaclasses = GetMetaClasses()
753    if class_name not in metaclasses:
754        print("Class {} does not exist".format(class_name))
755        return
756    metaclass = metaclasses[class_name]
757
758    # loop over superclasses
759    hierarchy = []
760    current_metaclass = metaclass
761    while current_metaclass is not None:
762        hierarchy.insert(0, current_metaclass)
763        current_metaclass = current_metaclass.superclass()
764
765    for (index, mc) in enumerate(hierarchy):
766        indent = ("    " * index) + "+---"
767        print("{}[ {} ] {}".format(indent, str(mc.className()), str(mc.data())))
768
769
770######################################
771#  Helper routines
772######################################
773def ShowRegistryEntryRecurse(entry, prefix, printProps):
774    """ prints registry entry summary and recurses through all its children.
775    """
776    # Setup
777    global plane
778    out_string = ""
779    plen = (len(prefix)//2)
780    registryTable = entry.fRegistryTable
781    propertyTable = entry.fPropertyTable
782
783    # Print entry details
784    print("{0:s}{1:s}".format(prefix, GetRegistryEntrySummary(entry)))
785    # Printing large property tables make it look like lldb is 'stuck'
786    if printProps:
787        print(GetRegDictionary(propertyTable, prefix + "  | "))
788
789    # Recurse
790    if plane is None:
791        childKey = kern.globals.gIOServicePlane.keys[1]
792    else:
793        childKey = plane.keys[1]
794    childArray = LookupKeyInOSDict(registryTable, childKey)
795    if childArray is not None:
796        idx = 0
797        ca = CastIOKitClass(childArray, 'OSArray *')
798        count = unsigned(ca.count)
799        array = ca.array
800        while idx < count:
801            if plen != 0 and plen != 1 and (plen & (plen - 1)) == 0:
802                ShowRegistryEntryRecurse(CastIOKitClass(array[idx], 'IORegistryEntry *'), prefix + "| ", printProps)
803            else:
804                ShowRegistryEntryRecurse(CastIOKitClass(array[idx], 'IORegistryEntry *'), prefix + "  ", printProps)
805            idx += 1
806
807def FindRegistryEntryRecurse(entry, search_name, stopAfterFirst):
808    """ Checks if given registry entry's name matches the search_name we're looking for
809        If yes, it prints the entry's summary and then recurses through its children
810        If no, it does nothing and recurses through its children
811    """
812    # Setup
813    global plane
814    registryTable = entry.fRegistryTable
815    propertyTable = entry.fPropertyTable
816
817    # Compare
818    name = None
819    name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
820    if name is None:
821        name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
822    if name is None:
823        name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
824
825    if name is not None:
826        if str(CastIOKitClass(name, 'OSString *').string) == search_name:
827            print(GetRegistryEntrySummary(entry))
828            if stopAfterFirst is True:
829                return True
830    elif (pwrMgt := CastIOKitClass(entry, 'IOService *').pwrMgt) and (name := pwrMgt.Name):
831        if str(name) == search_name:
832            print(GetRegistryEntrySummary(entry))
833            if stopAfterFirst is True:
834                return True
835
836    # Recurse
837    if plane is None:
838        childKey = kern.globals.gIOServicePlane.keys[1]
839    else:
840        childKey = plane.keys[1]
841    childArray = LookupKeyInOSDict(registryTable, childKey)
842    if childArray is not None:
843        idx = 0
844        ca = CastIOKitClass(childArray, 'OSArray *')
845        array = ca.array
846        count = unsigned(ca.count)
847        while idx < count:
848            if FindRegistryEntryRecurse(CastIOKitClass(array[idx], 'IORegistryEntry *'), search_name, stopAfterFirst) is True:
849                return True
850            idx += 1
851    return False
852
853def FindRegistryObjectRecurse(entry, search_name):
854    """ Checks if given registry entry's name matches the search_name we're looking for
855        If yes, return the entry
856        If no, it does nothing and recurses through its children
857        Implicitly stops after finding the first entry
858    """
859    # Setup
860    global plane
861    registryTable = entry.fRegistryTable
862    propertyTable = entry.fPropertyTable
863
864    # Compare
865    name = None
866    name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
867    if name is None:
868        name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
869    if name is None:
870        name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
871
872    if name is not None:
873        if str(CastIOKitClass(name, 'OSString *').string) == search_name:
874            return entry
875    elif (pwrMgt := CastIOKitClass(entry, 'IOService *').pwrMgt) and (name := pwrMgt.Name):
876        if str(name) == search_name:
877            return entry
878
879    # Recurse
880    if plane is None:
881        childKey = kern.globals.gIOServicePlane.keys[1]
882    else:
883        childKey = plane.keys[1]
884    childArray = LookupKeyInOSDict(registryTable, childKey)
885    if childArray is not None:
886        ca = CastIOKitClass(childArray, 'OSArray *')
887        array = ca.array
888        for idx in range(ca.count):
889            registry_object = FindRegistryObjectRecurse(CastIOKitClass(array[idx], 'IORegistryEntry *'), search_name)
890            if not registry_object or int(registry_object) == int(0):
891                continue
892            else:
893                return registry_object
894    return None
895
896def ShowUserServiceRecursive(service, prefix, last, childServices, sortedServices):
897    # blatantly copied from GetRegistryEntrySummary
898    name = None
899    registryTable = service.fRegistryTable
900    propertyTable = service.fPropertyTable
901    name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
902    if name is None:
903        name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
904    if name is None:
905        name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
906    name = GetString(CastIOKitClass(name, 'OSString *'))
907    sortedServices.append((service, f"{prefix}+-o {name}"))
908    if last:
909        prefix += "  "
910    else:
911        prefix += "| "
912    if int(service) not in childServices:
913        return
914    children = childServices[int(service)]
915    if len(children) == 0:
916        return
917    childrenCount = len(children)
918    for idx in range(childrenCount):
919        ShowUserServiceRecursive(children[idx], prefix, idx == childrenCount - 1, childServices, sortedServices)
920
921def ShowUserServerSummary(server):
922    reasonStrings = {
923        1: "jetsam",
924        2: "signal",
925        3: "codesigning",
926        6: "dyld",
927        9: "exec",
928        23: "guard",
929        25: "sandbox",
930        26: "security",
931        28: "PAC exception",
932        30: "port space",
933        34: "Rosetta"
934    }
935    server = kern.GetValueFromAddress(server, "IOUserServer *")
936    services = server.fServices
937    serviceCount = services.count
938    services = services.array
939    print(f"IOUserServer {hex(server)} (task {hex(server.fOwningTask)}):")
940    if int(server.fTaskCrashReason) != 0:
941        reasonString = "Dext crash reason: "
942        if server.fTaskCrashReason.osr_namespace in reasonStrings:
943            reasonString += reasonStrings[server.fTaskCrashReason.osr_namespace]
944            if server.fTaskCrashReason.osr_namespace == 2:
945                reasonString += f", {server.fTaskCrashReason.osr_namespace.osr_ode}"
946        print(reasonString)
947    # Attempt to reconstruct registry hierarchy
948    childServices = {}
949    for serviceIdx in range(serviceCount):
950        service = CastIOKitClass(services[serviceIdx], "IOService *")
951        provider = service.__provider
952        if int(provider) not in childServices:
953            childServices[int(provider)] = []
954        childServices[int(provider)].append(service)
955    rootServices = []
956    for provider in childServices:
957        provider = kern.GetValueFromAddress(provider, "IOService *")
958        if int(provider.__provider) not in childServices:
959            rootServices.append(provider)
960    sortedServices = []
961    for service in rootServices:
962        ShowUserServiceRecursive(service, "", True, childServices, sortedServices)
963    maxNameLen = -1
964    minNameLen = -1
965    for serviceData in sortedServices:
966        currNameLen = len(serviceData[1])
967        if maxNameLen < 0 or currNameLen > maxNameLen:
968            maxNameLen = currNameLen
969        if minNameLen < 0 or currNameLen < minNameLen:
970            minNameLen = currNameLen
971    nameLen = maxNameLen + 4
972    print("wt: willTerminate")
973    print("dt: didTerminate")
974    print("sd: serverDied")
975    print("it: instantiated")
976    print("sr: started")
977    print("sp: stopped")
978    print("wp: willPower")
979    print("ps: powerState")
980    print("Service" + (nameLen - len("Service")) * " ", end = "")
981    print("Address             wt  dt  sd  it  sr  sp  wp  ps")
982    for serviceData in sortedServices:
983        service = serviceData[0]
984        currNameLen = len(serviceData[1])
985
986        print(serviceData[1] + (nameLen - currNameLen) * " ", end = "")
987        print(f"{hex(serviceData[0])}  ", end = "")
988        if int(service.reserved) == 0 or int(service.reserved.uvars) == 0:
989            print("")
990            continue
991        #print(f"service {hex(service)}")
992        wt = service.reserved.uvars.willTerminate
993        wt = "N   " if int(wt) == 0 else "Y   "
994        dt = service.reserved.uvars.didTerminate
995        dt = "N   " if int(dt) == 0 else "Y   "
996        sd = service.reserved.uvars.serverDied
997        sd = "N   " if int(sd) == 0 else "Y   "
998        it = service.reserved.uvars.instantiated
999        it = "N   " if int(it) == 0 else "Y   "
1000        sr = service.reserved.uvars.started
1001        sr = "N   " if int(sr) == 0 else "Y   "
1002        sp = service.reserved.uvars.stopped
1003        sp = "N   " if int(sp) == 0 else "Y   "
1004        wp = service.reserved.uvars.willPower
1005        wp = "N   " if int(wp) == 0 else "Y   "
1006        ps = service.reserved.uvars.powerState
1007        ps = "N   " if int(ps) == 0 else "Y   "
1008        print(wt + dt + sd + it + sr + sp + wp + ps)
1009
1010def CompareStringToOSSymbol(string, os_sym):
1011    """
1012    Lexicographically compare python string to OSSymbol
1013    Params:
1014    string - python string
1015    os_sym - OSSymbol
1016
1017    Returns:
1018    0 if string == os_sym
1019    1 if string > os_sym
1020    -1 if string < os_sym
1021    """
1022    os_sym_str = GetString(os_sym)
1023    if string > os_sym_str:
1024        return 1
1025    elif string < os_sym_str:
1026        return -1
1027    else:
1028        return 0
1029
1030class IOKitMetaClass(object):
1031    """
1032    A class that represents a IOKit metaclass. This is used to represent the
1033    IOKit inheritance hierarchy.
1034    """
1035
1036    def __init__(self, meta):
1037        """
1038        Initialize a IOKitMetaClass object.
1039
1040        Args:
1041            meta (core.cvalue.value): A LLDB value representing a
1042                OSMetaClass *.
1043        """
1044        self._meta = meta
1045        self._superclass = None
1046
1047    def data(self):
1048        return self._meta
1049
1050    def setSuperclass(self, superclass):
1051        """
1052        Set the superclass for this metaclass.
1053
1054        Args:
1055            superclass (core.cvalue.value): A LLDB value representing a
1056                OSMetaClass *.
1057        """
1058        self._superclass = superclass
1059
1060    def superclass(self):
1061        """
1062        Get the superclass for this metaclass (set by the setSuperclass method).
1063
1064        Returns:
1065            core.cvalue.value: A LLDB value representing a OSMetaClass *.
1066        """
1067        return self._superclass
1068
1069    def className(self):
1070        """
1071        Get the name of the class this metaclass represents.
1072
1073        Returns:
1074            str: The class name
1075        """
1076        return self._meta.className.string
1077
1078    def inheritsFrom(self, other):
1079        """
1080        Check if the class represented by this metaclass inherits from a class
1081        represented by another metaclass.
1082
1083        Args:
1084            other (IOKitMetaClass): The other metaclass
1085
1086        Returns:
1087            bool: Returns True if this class inherits from the other class and
1088                False otherwise.
1089        """
1090        current = self
1091        while current is not None:
1092            if current == other:
1093                return True
1094            else:
1095                current = current.superclass()
1096
1097
1098def GetRegistryEntryClassName(entry):
1099    """
1100    Get the class name of a registry entry.
1101
1102    Args:
1103        entry (core.cvalue.value): A LLDB value representing a
1104            IORegistryEntry *.
1105
1106    Returns:
1107        str: The class name of the entry or None if a class name could not be
1108            found.
1109    """
1110    # Check using IOClass key
1111    result = LookupKeyInOSDict(entry.fPropertyTable, kern.globals.gIOClassKey)
1112    if result is not None:
1113        return GetString(result).replace("\"", "")
1114    else:
1115        # Use the vtable of the entry to determine the concrete type
1116        vt = dereference(Cast(entry, 'uintptr_t *')) - 2 * sizeof('uintptr_t')
1117        vt = kern.StripKernelPAC(vt)
1118        vtype = kern.SymbolicateFromAddress(vt)
1119        if len(vtype) > 0:
1120            vtableName = vtype[0].GetName()
1121            return vtableName[11:] # strip off "vtable for "
1122        else:
1123            return None
1124
1125
1126def GetRegistryEntryName(entry):
1127    """
1128    Get the name of a registry entry.
1129
1130    Args:
1131        entry (core.cvalue.value): A LLDB value representing a
1132            IORegistryEntry *.
1133
1134    Returns:
1135        str: The name of the entry or None if a name could not be found.
1136    """
1137    name = None
1138
1139    # First check the IOService plane nameKey
1140    result = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOServicePlane.nameKey)
1141    if result is not None:
1142        name = GetString(result)
1143
1144    # Check the global IOName key
1145    if name is None:
1146        result = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIONameKey)
1147        if result is not None:
1148            name = GetString(result)
1149
1150    # Check the IOClass key
1151    if name is None:
1152        result = LookupKeyInOSDict(entry.fPropertyTable, kern.globals.gIOClassKey)
1153        if result is not None:
1154            name = GetString(result)
1155
1156    # Remove extra quotes
1157    if name is not None:
1158        return name.replace("\"", "")
1159    else:
1160        return GetRegistryEntryClassName(entry)
1161
1162
1163def GetRegistryEntryLocationInPlane(entry, plane):
1164    """
1165    Get the registry entry location in a IOKit plane.
1166
1167    Args:
1168        entry (core.cvalue.value): A LLDB value representing a
1169            IORegistryEntry *.
1170        plane: An IOKit plane such as kern.globals.gIOServicePlane.
1171
1172    Returns:
1173        str: The location of the entry or None if a location could not be
1174            found.
1175    """
1176    # Check the plane's pathLocationKey
1177    sym = LookupKeyInOSDict(entry.fRegistryTable, plane.pathLocationKey)
1178
1179    # Check the global IOLocation key
1180    if sym is None:
1181        sym = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOLocationKey)
1182    if sym is not None:
1183        return GetString(sym).replace("\"", "")
1184    else:
1185        return None
1186
1187
1188@caching.cache_dynamically
1189def GetMetaClasses(target=None):
1190    """
1191    Enumerate all IOKit metaclasses. Uses dynamic caching.
1192
1193    Returns:
1194        Dict[str, IOKitMetaClass]: A dictionary mapping each metaclass name to
1195            a IOKitMetaClass object representing the metaclass.
1196    """
1197
1198    # This method takes a while, so it prints a progress indicator
1199    print("Enumerating IOKit metaclasses: ")
1200
1201    do_progress = os.isatty(sys.__stderr__.fileno())
1202
1203    # Iterate over all classes present in sAllClassesDict
1204    count = unsigned(kern.globals.sAllClassesDict.count)
1205    metaclasses_by_address = {}
1206    for idx in range(count):
1207        if do_progress and idx % 10 == 0:
1208            sys.stderr.write("\033[K  {} metaclass found...\r".format(idx))
1209
1210        # Address of metaclass
1211        address = kern.globals.sAllClassesDict.dictionary[idx].value
1212
1213        # Create IOKitMetaClass and store in dict
1214        metaclasses_by_address[int(address)] = IOKitMetaClass(CastIOKitClass(kern.globals.sAllClassesDict.dictionary[idx].value, 'OSMetaClass *'))
1215
1216    # At this point, each metaclass is independent of each other. We don't have superclass links set up yet.
1217
1218    for address, metaclass in metaclasses_by_address.items():
1219        # Get the address of the superclass using the superClassLink in IOMetaClass
1220        superclass_address = int(metaclass.data().superClassLink)
1221
1222        # Skip null superclass
1223        if superclass_address == 0:
1224            continue
1225
1226        # Find the superclass object in the dict
1227        if superclass_address in metaclasses_by_address:
1228            metaclass.setSuperclass(metaclasses_by_address[superclass_address])
1229        else:
1230            print("warning: could not find superclass for {}".format(str(metaclass.data())))
1231
1232    # This method returns a dictionary mapping each class name to the associated metaclass object
1233    metaclasses_by_name = {}
1234    for idx, (_, metaclass) in enumerate(metaclasses_by_address.items()):
1235        if do_progress and idx % 10 == 0:
1236            sys.stderr.write("\033[K  {} metaclass indexed...\r".format(idx))
1237
1238        metaclasses_by_name[str(metaclass.className())] = metaclass
1239
1240    print("  Indexed {} IOKit metaclasses.".format(count))
1241    return metaclasses_by_name
1242
1243
1244def GetMatchingEntries(matcher):
1245    """
1246    Iterate over the IOKit registry and find entries that match specific
1247        criteria.
1248
1249    Args:
1250        matcher (function): A matching function that returns True for a match
1251            and False otherwise.
1252
1253    Yields:
1254        core.cvalue.value: LLDB values that represent IORegistryEntry * for
1255            each registry entry found.
1256    """
1257
1258    # Perform a BFS over the IOKit registry tree
1259    bfs_queue = deque()
1260    bfs_queue.append(kern.globals.gRegistryRoot)
1261    while len(bfs_queue) > 0:
1262        # Dequeue an entry
1263        entry = bfs_queue.popleft()
1264
1265        # Check if entry matches
1266        if matcher(entry):
1267            yield entry
1268
1269        # Find children of this entry and enqueue them
1270        child_array = LookupKeyInOSDict(entry.fRegistryTable, kern.globals.gIOServicePlane.keys[1])
1271        if child_array is not None:
1272            idx = 0
1273            ca = CastIOKitClass(child_array, 'OSArray *')
1274            count = unsigned(ca.count)
1275            while idx < count:
1276                bfs_queue.append(CastIOKitClass(ca.array[idx], 'IORegistryEntry *'))
1277                idx += 1
1278
1279
1280def FindMatchingServices(matching_name):
1281    """
1282    Finds registry entries that match the given string. Works similarly to:
1283
1284    io_iterator_t iter;
1285    IOServiceGetMatchingServices(..., IOServiceMatching(matching_name), &iter);
1286    while (( io_object_t next = IOIteratorNext(iter))) { ... }
1287
1288    Args:
1289        matching_name (str): The class name to search for.
1290
1291    Yields:
1292        core.cvalue.value: LLDB values that represent IORegistryEntry * for
1293            each registry entry found.
1294    """
1295
1296    # Check if the argument is valid
1297    metaclasses = GetMetaClasses()
1298    if matching_name not in metaclasses:
1299        return
1300    matching_metaclass = metaclasses[matching_name]
1301
1302    # An entry matches if it inherits from matching_metaclass
1303    def matcher(entry):
1304        # Get the class name of the entry and the associated metaclass
1305        entry_name = GetRegistryEntryClassName(entry)
1306        if entry_name in metaclasses:
1307            entry_metaclass = metaclasses[entry_name]
1308            return entry_metaclass.inheritsFrom(matching_metaclass)
1309        else:
1310            return False
1311
1312    # Search for entries
1313    for entry in GetMatchingEntries(matcher):
1314        yield entry
1315
1316
1317def GetRegistryEntryParent(entry, iokit_plane=None):
1318    """
1319    Gets the parent entry of a registry entry.
1320
1321    Args:
1322        entry (core.cvalue.value): A LLDB value representing a
1323            IORegistryEntry *.
1324        iokit_plane (core.cvalue.value, optional): A LLDB value representing a
1325            IORegistryPlane *. By default, this method uses the IOService
1326            plane.
1327
1328    Returns:
1329        core.cvalue.value: A LLDB value representing a IORegistryEntry* that
1330            is the parent entry of the entry argument in the specified plane.
1331            Returns None if no entry could be found.
1332    """
1333    kParentSetIndex = 0
1334    parent_key = None
1335    if iokit_plane is None:
1336        parent_key = kern.globals.gIOServicePlane.keys[kParentSetIndex]
1337    else:
1338        parent_key = plane.keys[kParentSetIndex]
1339    parent_array = LookupKeyInOSDict(entry.fRegistryTable, parent_key)
1340    parent_entry = None
1341    if parent_array is not None:
1342        idx = 0
1343        ca = CastIOKitClass(parent_array, 'OSArray *')
1344        count = unsigned(ca.count)
1345        if count > 0:
1346            parent_entry = CastIOKitClass(ca.array[0], 'IORegistryEntry *')
1347    return parent_entry
1348
1349
1350def GetRegistryEntryInterruptProperties(entry):
1351    """
1352    Get the interrupt properties of a registry entry.
1353
1354    Args:
1355        entry (core.cvalue.value): A LLDB value representing a IORegistryEntry *.
1356
1357    Returns:
1358        (bool, List[core.cvalue.value], List[str]): A tuple with the following
1359            fields:
1360                - First field (bool): Whether this entry has a non-null
1361                    IOPCIMSIMode.
1362                - Second field (List[core.cvalue.value]): A list of LLDB values
1363                    representing OSData *. The OSData* pointer points to
1364                    interrupt vector data.
1365                - Third field (List[str]): A list of strings representing the
1366                    interrupt controller names from the
1367                    IOInterruptControllers property.
1368    """
1369    INTERRUPT_SPECIFIERS_PROPERTY = "IOInterruptSpecifiers"
1370    INTERRUPT_CONTROLLERS_PROPERTY = "IOInterruptControllers"
1371    MSI_MODE_PROPERTY = "IOPCIMSIMode"
1372
1373    # Check IOInterruptSpecifiers
1374    interrupt_specifiers = LookupKeyInPropTable(entry.fPropertyTable, INTERRUPT_SPECIFIERS_PROPERTY)
1375    if interrupt_specifiers is not None:
1376        interrupt_specifiers = CastIOKitClass(interrupt_specifiers, 'OSArray *')
1377
1378    # Check IOInterruptControllers
1379    interrupt_controllers = LookupKeyInPropTable(entry.fPropertyTable, INTERRUPT_CONTROLLERS_PROPERTY)
1380    if interrupt_controllers is not None:
1381        interrupt_controllers = CastIOKitClass(interrupt_controllers, 'OSArray *')
1382
1383    # Check MSI mode
1384    msi_mode = LookupKeyInPropTable(entry.fPropertyTable, MSI_MODE_PROPERTY)
1385
1386    result_vector_data = []
1387    result_vector_cont = []
1388    if interrupt_specifiers is not None and interrupt_controllers is not None:
1389        interrupt_specifiers_array_count = unsigned(interrupt_specifiers.count)
1390        interrupt_controllers_array_count = unsigned(interrupt_controllers.count)
1391        # The array lengths should be the same
1392        if interrupt_specifiers_array_count == interrupt_controllers_array_count and interrupt_specifiers_array_count > 0:
1393            idx = 0
1394            while idx < interrupt_specifiers_array_count:
1395                # IOInterruptSpecifiers is an array of OSData *
1396                vector_data = CastIOKitClass(interrupt_specifiers.array[idx], "OSData *")
1397
1398                # IOInterruptControllers is an array of OSString *
1399                vector_cont = GetString(interrupt_controllers.array[idx])
1400
1401                result_vector_data.append(vector_data)
1402                result_vector_cont.append(vector_cont)
1403                idx += 1
1404
1405    return (msi_mode is not None, result_vector_data, result_vector_cont)
1406
1407
1408class InterruptControllerDevice(object):
1409    """Represents a IOInterruptController"""
1410
1411    def __init__(self, device, driver, base_vector_number, name):
1412        """
1413        Initialize a InterruptControllerDevice.
1414
1415        Args:
1416            device (core.cvalue.value): The device object.
1417            driver (core.cvalue.value): The driver object.
1418            base_vector_number (int): The base interrupt vector.
1419            name (str): The name of this interrupt controller.
1420
1421        Note:
1422            Use the factory method makeInterruptControllerDevice to validate
1423            properties.
1424        """
1425        self.device = device
1426        self.driver = driver
1427        self.name = name
1428        self.base_vector_number = base_vector_number
1429
1430
1431    def __str__(self):
1432        """
1433        String representation of this InterruptControllerDevice.
1434        """
1435        return " Name {}, base vector = {}, device = {}, driver = {}".format(
1436            self.name, hex(self.base_vector_number), str(self.device), str(self.driver))
1437
1438    @staticmethod
1439    def makeInterruptControllerDevice(device, driver):
1440        """
1441        Factory method to create a InterruptControllerDevice.
1442
1443        Args:
1444            device (core.cvalue.value): The device object.
1445            driver (core.cvalue.value): The driver object.
1446
1447        Returns:
1448            InterruptControllerDevice: Returns an instance of
1449                InterruptControllerDevice or None if the arguments do not have
1450                the required properties.
1451        """
1452        BASE_VECTOR_PROPERTY = "Base Vector Number"
1453        INTERRUPT_CONTROLLER_NAME_PROPERTY = "InterruptControllerName"
1454        base_vector = LookupKeyInPropTable(device.fPropertyTable, BASE_VECTOR_PROPERTY)
1455        if base_vector is None:
1456            base_vector = LookupKeyInPropTable(driver.fPropertyTable, BASE_VECTOR_PROPERTY)
1457        device_name = LookupKeyInPropTable(device.fPropertyTable, INTERRUPT_CONTROLLER_NAME_PROPERTY)
1458        if device_name is None:
1459            device_name = LookupKeyInPropTable(driver.fPropertyTable, INTERRUPT_CONTROLLER_NAME_PROPERTY)
1460
1461        if device_name is not None:
1462            # Some interrupt controllers do not have a base vector number. Assume it is 0.
1463            base_vector_number = 0
1464            if base_vector is not None:
1465                base_vector_number = unsigned(GetNumber(base_vector))
1466            device_name = GetString(device_name)
1467            # Construct object and return
1468            return InterruptControllerDevice(device, driver, base_vector_number, device_name)
1469        else:
1470            # error case
1471            return None
1472
1473
1474def SearchInterruptControllerDrivers():
1475    """
1476    Search the IOKit registry for entries that match IOInterruptController.
1477
1478    Yields:
1479        core.cvalue.value: A LLDB value representing a IORegistryEntry * that
1480        inherits from IOInterruptController.
1481    """
1482    for entry in FindMatchingServices("IOInterruptController"):
1483        # Get parent
1484        parent = GetRegistryEntryParent(entry)
1485
1486        # Make the interrupt controller object
1487        ic = InterruptControllerDevice.makeInterruptControllerDevice(parent, entry)
1488
1489        # Yield object
1490        if ic is not None:
1491            yield ic
1492
1493
1494def LookupKeyInOSDict(osdict, key, comparer = None):
1495    """ Returns the value corresponding to a given key in a OSDictionary
1496        Returns None if the key was not found
1497    """
1498    if not osdict:
1499        return
1500    count = unsigned(osdict.count)
1501    result = None
1502    idx = 0
1503
1504    dictionary = osdict.dictionary
1505    key_value = unsigned(key) if type(key) is value else key
1506    while idx < count and result is None:
1507        elem = dictionary[idx]
1508        if comparer is not None:
1509            if comparer(key, elem.key) == 0:
1510                result = elem.value
1511        elif key_value == unsigned(elem.key):
1512            result = elem.value
1513        idx += 1
1514    return result
1515
1516def LookupKeyInPropTable(propertyTable, key_str):
1517    """ Returns the value corresponding to a given key from a registry entry's property table
1518        Returns None if the key was not found
1519        The property that is being searched for is specified as a string in key_str
1520    """
1521    if not propertyTable:
1522        return
1523    count = unsigned(propertyTable.count)
1524    result = None
1525    idx = 0
1526    while idx < count and result is None:
1527        if key_str == str(propertyTable.dictionary[idx].key.string):
1528            result = propertyTable.dictionary[idx].value
1529        idx += 1
1530    return result
1531
1532def GetRegDictionary(osdict, prefix):
1533    """ Returns a specially formatted string summary of the given OSDictionary
1534        This is done in order to pretty-print registry property tables in showregistry
1535        and other macros
1536    """
1537    out_string = prefix + "{\n"
1538    idx = 0
1539    count = unsigned(osdict.count)
1540
1541    dictionary = osdict.dictionary
1542    while idx < count:
1543        entry = dictionary[idx]
1544        out_string += prefix + "  " + GetObjectSummary(entry.key) + " = " + GetObjectSummary(entry.value) + "\n"
1545        idx += 1
1546    out_string += prefix + "}\n"
1547    return out_string
1548
1549def GetString(string):
1550    """ Returns the python string representation of a given OSString
1551    """
1552    out_string = "{0:s}".format(CastIOKitClass(string, 'OSString *').string)
1553    return out_string
1554
1555def GetNumber(num):
1556    out_string = "{0:d}".format(CastIOKitClass(num, 'OSNumber *').value)
1557    return out_string
1558
1559def GetBoolean(b):
1560    """ Shows info about a given OSBoolean
1561    """
1562    out_string = ""
1563    if b == kern.globals.gOSBooleanFalse:
1564        out_string += "No"
1565    else:
1566        out_string += "Yes"
1567    return out_string
1568
1569def GetMetaClass(mc):
1570    """ Shows info about a given OSSymbol
1571    """
1572    out_string = "{0: <5d}x {1: >5d} bytes {2:s}\n".format(mc.instanceCount, mc.classSize, mc.className.string)
1573    return out_string
1574
1575def GetArray(arr):
1576    """ Returns a string containing info about a given OSArray
1577    """
1578    out_string = ""
1579    idx = 0
1580    count = unsigned(arr.count)
1581
1582    array = arr.array
1583    while idx < count:
1584        obj = array[idx]
1585        idx += 1
1586        out_string += GetObjectSummary(obj)
1587        if idx < count:
1588            out_string += ","
1589    return out_string
1590
1591def GetDictionary(d):
1592    """ Returns a string containing info about a given OSDictionary
1593    """
1594    if d is None:
1595        return ""
1596    out_string = "{\n"
1597    idx = 0
1598    count = unsigned(d.count)
1599    dictionary = d.dictionary
1600    while idx < count:
1601        entry = dictionary[idx]
1602        key = entry.key
1603        value = entry.value
1604        out_string += "    \"{}\" = {}\n".format(GetString(key), GetObjectSummary(value))
1605        idx += 1
1606    out_string += "}"
1607    return out_string
1608
1609def GetSet(se):
1610    """ Returns a string containing info about a given OSSet
1611    """
1612    out_string = "[" + GetArray(se.members) + "]"
1613    return out_string
1614
1615def ReadIOPortInt(addr, numbytes, lcpu):
1616    """ Prints results after reading a given ioport
1617    """
1618    result = 0xBAD10AD
1619
1620    if "kdp" != GetConnectionProtocol():
1621        print("Target is not connected over kdp. Nothing to do here.")
1622        return
1623
1624    # Set up the manual KDP packet
1625    input_address = unsigned(addressof(kern.globals.manual_pkt.input))
1626    len_address = unsigned(addressof(kern.globals.manual_pkt.len))
1627    data_address = unsigned(addressof(kern.globals.manual_pkt.data))
1628    if not WriteInt32ToMemoryAddress(0, input_address):
1629        print("0x{0: <4x}: 0x{1: <1x}".format(addr, result))
1630        return
1631
1632    kdp_pkt_size = GetType('kdp_readioport_req_t').GetByteSize()
1633    if not WriteInt32ToMemoryAddress(kdp_pkt_size, len_address):
1634        print("0x{0: <4x}: 0x{1: <1x}".format(addr, result))
1635        return
1636
1637    kgm_pkt = kern.GetValueFromAddress(data_address, 'kdp_readioport_req_t *')
1638
1639    header_value = GetKDPPacketHeaderInt(request=GetEnumValue('kdp_req_t::KDP_READIOPORT'), length = kdp_pkt_size)
1640
1641    if( WriteInt64ToMemoryAddress((header_value), int(addressof(kgm_pkt.hdr))) and
1642        WriteInt16ToMemoryAddress(addr, int(addressof(kgm_pkt.address))) and
1643        WriteInt32ToMemoryAddress(numbytes, int(addressof(kgm_pkt.nbytes))) and
1644        WriteInt16ToMemoryAddress(lcpu, int(addressof(kgm_pkt.lcpu))) and
1645        WriteInt32ToMemoryAddress(1, input_address)
1646        ):
1647
1648        result_pkt = Cast(addressof(kern.globals.manual_pkt.data), 'kdp_readioport_reply_t *')
1649
1650        if(result_pkt.error == 0):
1651            if numbytes == 1:
1652                result = dereference(Cast(addressof(result_pkt.data), 'uint8_t *'))
1653            elif numbytes == 2:
1654                result = dereference(Cast(addressof(result_pkt.data), 'uint16_t *'))
1655            elif numbytes == 4:
1656                result = dereference(Cast(addressof(result_pkt.data), 'uint32_t *'))
1657
1658    print("{0: <#6x}: {1:#0{2}x}".format(addr, result, (numbytes*2)+2))
1659
1660def WriteIOPortInt(addr, numbytes, value, lcpu):
1661    """ Writes 'value' into ioport specified by 'addr'. Prints errors if it encounters any
1662    """
1663    if "kdp" != GetConnectionProtocol():
1664        print("Target is not connected over kdp. Nothing to do here.")
1665        return
1666
1667    # Set up the manual KDP packet
1668    input_address = unsigned(addressof(kern.globals.manual_pkt.input))
1669    len_address = unsigned(addressof(kern.globals.manual_pkt.len))
1670    data_address = unsigned(addressof(kern.globals.manual_pkt.data))
1671    if not WriteInt32ToMemoryAddress(0, input_address):
1672        print("error writing {0: #x} to port {1: <#6x}: failed to write 0 to input_address".format(value, addr))
1673        return
1674
1675    kdp_pkt_size = GetType('kdp_writeioport_req_t').GetByteSize()
1676    if not WriteInt32ToMemoryAddress(kdp_pkt_size, len_address):
1677        print("error writing {0: #x} to port {1: <#6x}: failed to write kdp_pkt_size".format(value, addr))
1678        return
1679
1680    kgm_pkt = kern.GetValueFromAddress(data_address, 'kdp_writeioport_req_t *')
1681
1682    header_value = GetKDPPacketHeaderInt(request=GetEnumValue('kdp_req_t::KDP_WRITEIOPORT'), length = kdp_pkt_size)
1683
1684    if( WriteInt64ToMemoryAddress((header_value), int(addressof(kgm_pkt.hdr))) and
1685        WriteInt16ToMemoryAddress(addr, int(addressof(kgm_pkt.address))) and
1686        WriteInt32ToMemoryAddress(numbytes, int(addressof(kgm_pkt.nbytes))) and
1687        WriteInt16ToMemoryAddress(lcpu, int(addressof(kgm_pkt.lcpu)))
1688        ):
1689        if numbytes == 1:
1690            if not WriteInt8ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
1691                print("error writing {0: #x} to port {1: <#6x}: failed to write 8 bit data".format(value, addr))
1692                return
1693        elif numbytes == 2:
1694            if not WriteInt16ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
1695                print("error writing {0: #x} to port {1: <#6x}: failed to write 16 bit data".format(value, addr))
1696                return
1697        elif numbytes == 4:
1698            if not WriteInt32ToMemoryAddress(value, int(addressof(kgm_pkt.data))):
1699                print("error writing {0: #x} to port {1: <#6x}: failed to write 32 bit data".format(value, addr))
1700                return
1701        if not WriteInt32ToMemoryAddress(1, input_address):
1702            print("error writing {0: #x} to port {1: <#6x}: failed to write to input_address".format(value, addr))
1703            return
1704
1705        result_pkt = Cast(addressof(kern.globals.manual_pkt.data), 'kdp_writeioport_reply_t *')
1706
1707        # Done with the write
1708        if(result_pkt.error == 0):
1709            print("Writing {0: #x} to port {1: <#6x} was successful".format(value, addr))
1710    else:
1711        print("error writing {0: #x} to port {1: <#6x}".format(value, addr))
1712
1713@lldb_command('showinterruptcounts')
1714def showinterruptcounts(cmd_args=None):
1715    """ Shows event source based interrupt counts by nub name and interrupt index.
1716        Does not cover interrupts that are not event source based.  Will report 0
1717        if interrupt accounting is disabled.
1718    """
1719
1720    header_format = "{0: <20s} {1: >5s} {2: >20s}"
1721    content_format = "{0: <20s} {1: >5d} {2: >20d}"
1722
1723    print(header_format.format("Name", "Index", "Count"))
1724
1725    for i in kern.interrupt_stats:
1726        owner = CastIOKitClass(i.owner, 'IOInterruptEventSource *')
1727        nub = CastIOKitClass(owner.provider, 'IORegistryEntry *')
1728        name = None
1729
1730        # To uniquely identify an interrupt, we need the nub name and the index.  The index
1731        # is stored with the stats object, but we need to retrieve the name.
1732
1733        registryTable = nub.fRegistryTable
1734        propertyTable = nub.fPropertyTable
1735
1736        name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
1737        if name is None:
1738            name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
1739        if name is None:
1740            name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
1741
1742        if name is None:
1743            nub_name = "Unknown"
1744        else:
1745            nub_name = GetString(CastIOKitClass(name, 'OSString *'))
1746
1747        # We now have everything we need; spew the requested data.
1748
1749        interrupt_index = i.interruptIndex
1750        first_level_count = i.interruptStatistics[0]
1751
1752        print(content_format.format(nub_name, interrupt_index, first_level_count))
1753
1754    return True
1755
1756@lldb_command('showinterruptstats')
1757def showinterruptstats(cmd_args=None):
1758    """ Shows event source based interrupt statistics by nub name and interrupt index.
1759        Does not cover interrupts that are not event source based.  Will report 0
1760        if interrupt accounting is disabled, or if specific statistics are disabled.
1761        Time is reported in ticks of mach_absolute_time.  Statistics are:
1762
1763        Interrupt Count: Number of times the interrupt context handler was run
1764        Interrupt Time: Total time spent in the interrupt context handler (if any)
1765        Workloop Count: Number of times the kernel context handler was run
1766        Workloop CPU Time: Total CPU time spent running the kernel context handler
1767        Workloop Time: Total time spent running the kernel context handler
1768    """
1769
1770    header_format = "{0: <20s} {1: >5s} {2: >20s} {3: >20s} {4: >20s} {5: >20s} {6: >20s} {7: >20s} {8: >20s} {9: >20s}"
1771    content_format = "{0: <20s} {1: >5d} {2: >20d} {3: >20d} {4: >20d} {5: >20d} {6: >20d} {7: >20d} {8: >20d} {9: >#20x}"
1772
1773    print(header_format.format("Name", "Index", "Interrupt Count", "Interrupt Time", "Avg Interrupt Time", "Workloop Count", "Workloop CPU Time", "Workloop Time", "Avg Workloop Time", "Owner"))
1774
1775    for i in kern.interrupt_stats:
1776        owner = CastIOKitClass(i.owner, 'IOInterruptEventSource *')
1777        nub = CastIOKitClass(owner.provider, 'IORegistryEntry *')
1778        name = None
1779
1780        # To uniquely identify an interrupt, we need the nub name and the index.  The index
1781        # is stored with the stats object, but we need to retrieve the name.
1782
1783        registryTable = nub.fRegistryTable
1784        propertyTable = nub.fPropertyTable
1785
1786        name = LookupKeyInOSDict(registryTable, kern.globals.gIOServicePlane.nameKey)
1787        if name is None:
1788            name = LookupKeyInOSDict(registryTable, kern.globals.gIONameKey)
1789        if name is None:
1790            name = LookupKeyInOSDict(propertyTable, kern.globals.gIOClassKey)
1791
1792        if name is None:
1793            nub_name = "Unknown"
1794        else:
1795            nub_name = GetString(CastIOKitClass(name, 'OSString *'))
1796
1797        # We now have everything we need; spew the requested data.
1798
1799        interrupt_index = i.interruptIndex
1800        first_level_count = i.interruptStatistics[0]
1801        second_level_count = i.interruptStatistics[1]
1802        first_level_time = i.interruptStatistics[2]
1803        second_level_cpu_time = i.interruptStatistics[3]
1804        second_level_system_time = i.interruptStatistics[4]
1805
1806        avg_first_level_time = 0
1807        if first_level_count != 0:
1808            avg_first_level_time = first_level_time // first_level_count
1809
1810        avg_second_level_time = 0
1811        if second_level_count != 0:
1812            avg_second_level_time = second_level_system_time // second_level_count
1813
1814        print(content_format.format(nub_name, interrupt_index, first_level_count, first_level_time, avg_first_level_time,
1815            second_level_count, second_level_cpu_time, second_level_system_time, avg_second_level_time, owner))
1816
1817    return True
1818
1819def GetRegistryPlane(plane_name):
1820    """
1821    Given plane_name, returns IORegistryPlane * object or None if there's no such registry plane
1822    """
1823    return LookupKeyInOSDict(kern.globals.gIORegistryPlanes, plane_name, CompareStringToOSSymbol)
1824
1825def DecodePreoslogSource(source):
1826    """
1827    Given preoslog source, return a matching string representation
1828    """
1829    source_to_str = {0 : "iboot"}
1830    if source in source_to_str:
1831        return source_to_str[source]
1832    return "UNKNOWN"
1833
1834def GetPreoslogHeader():
1835    """
1836    Scan IODeviceTree for preoslog and return a python representation of it
1837    """
1838    edt_plane = GetRegistryPlane("IODeviceTree")
1839    if edt_plane is None:
1840        print("Couldn't obtain a pointer to IODeviceTree")
1841        return None
1842
1843    # Registry API functions operate on "plane" global variable
1844    global plane
1845    prev_plane = plane
1846    plane = edt_plane
1847    chosen = FindRegistryObjectRecurse(kern.globals.gRegistryRoot, "chosen")
1848    if chosen is None:
1849        print("Couldn't obtain /chosen IORegistryEntry")
1850        return None
1851
1852    memory_map = FindRegistryObjectRecurse(chosen, "memory-map")
1853    if memory_map is None:
1854        print("Couldn't obtain memory-map from /chosen")
1855        return None
1856
1857    plane = prev_plane
1858
1859    mm_preoslog = LookupKeyInOSDict(memory_map.fPropertyTable, "preoslog", CompareStringToOSSymbol)
1860    if mm_preoslog is None:
1861        print("Couldn't find preoslog entry in memory-map")
1862        return None
1863
1864    if mm_preoslog.length != 16:
1865        print("preoslog entry in memory-map is malformed, expected len is 16, given len is {:d}".format(mm_preoslog.length))
1866        return None
1867
1868    data = cast(mm_preoslog.data, "dtptr_t *")
1869    preoslog_paddr = unsigned(data[0])
1870    preoslog_vaddr = kern.PhysToKernelVirt(preoslog_paddr)
1871    preoslog_size = unsigned(data[1])
1872
1873    preoslog_header = PreoslogHeader()
1874
1875    # This structure defnition doesn't exist in xnu
1876    """
1877    typedef struct  __attribute__((packed)) {
1878        char magic[4];
1879        uint32_t size;
1880        uint32_t offset;
1881        uint8_t source;
1882        uint8_t wrapped;
1883        char data[];
1884    } preoslog_header_t;
1885    """
1886    preoslog_header_ptr = kern.GetValueFromAddress(preoslog_vaddr, "uint8_t *")
1887    preoslog_header.magic = preoslog_header_ptr[0:4]
1888    preoslog_header.source = DecodePreoslogSource(unsigned(preoslog_header_ptr[12]))
1889    preoslog_header.wrapped = unsigned(preoslog_header_ptr[13])
1890    preoslog_header_ptr = kern.GetValueFromAddress(preoslog_vaddr, "uint32_t *")
1891    preoslog_header.size = unsigned(preoslog_header_ptr[1])
1892    preoslog_header.offset = unsigned(preoslog_header_ptr[2])
1893
1894    for i in range(len(preoslog_header.valid_magic)):
1895        c = chr(unsigned(preoslog_header.magic[i]))
1896        if c != preoslog_header.valid_magic[i]:
1897            string = "Error: magic doesn't match, expected {:.4s}, given {:.4s}"
1898            print(string.format(preoslog_header.valid_magic, preoslog_header.magic))
1899            return None
1900
1901    if preoslog_header.size != preoslog_size:
1902        string = "Error: size mismatch preoslog_header.size ({}) != preoslog_size ({})"
1903        print(string.format(preoslog_header.size, preoslog_size))
1904        return None
1905
1906    preoslog_data_ptr = kern.GetValueFromAddress(preoslog_vaddr + 14, "char *")
1907    preoslog_header.data = preoslog_data_ptr.GetSBValue().GetPointeeData(0, preoslog_size)
1908    return preoslog_header
1909
1910@lldb_command("showpreoslog")
1911def showpreoslog(cmd_args=None):
1912    """ Display preoslog buffer """
1913
1914    preoslog = GetPreoslogHeader()
1915    if preoslog is None:
1916        print("Error: couldn't obtain preoslog header")
1917        return False
1918
1919    header = "".join([
1920        "----preoslog log header-----\n",
1921        "size - {} bytes\n",
1922        "write offset - {:#x}\n",
1923        "wrapped - {}\n",
1924        "source - {}\n",
1925        "----preoslog log start------"
1926        ])
1927
1928    print(header.format(preoslog.size, preoslog.offset, preoslog.wrapped, preoslog.source))
1929
1930    err = lldb.SBError()
1931    if preoslog.wrapped > 0:
1932        print(preoslog.data.GetString(err, preoslog.offset + 1))
1933
1934    print(preoslog.data.GetString(err, 0).encode(errors='backslashreplace').decode())
1935    print("-----preoslog log end-------")
1936
1937    if not err.success:
1938        raise RuntimeError(f"SBError when retreiving preoslog data: {err.GetDescription()}")
1939
1940    return True
1941
1942@lldb_command('showeventsources')
1943def ShowEventSources(cmd_args=None):
1944    """ Show all event sources for a IOWorkLoop
1945        syntax: (lldb) showeventsources <IOWorkLoop *>
1946    """
1947    if cmd_args is None or len(cmd_args) == 0:
1948        raise ArgumentError("Please specify the address of the IOWorkLoop")
1949
1950    obj = kern.GetValueFromAddress(cmd_args[0], 'IOWorkLoop *')
1951    idx = 0
1952    event = obj.eventChain
1953    while event != 0:
1954        enabled = event.enabled
1955        print("{}: {} [{}]".format(idx, GetObjectSummary(event), "enabled" if enabled else "disabled"))
1956        event = event.eventChainNext
1957        idx += 1
1958
1959def GetRegionProp(propertyTable, pattern):
1960    """ Returns the list corresponding to a given pattern from a registry entry's property table
1961        Returns empty list if the key is not found
1962        The property that is being searched for is specified as a string in pattern
1963    """
1964    if not propertyTable:
1965        return None
1966
1967    count = unsigned(propertyTable.count)
1968    result = []
1969    res = None
1970    idx = 0
1971    while idx < count:
1972        res = re.search(pattern, str(propertyTable.dictionary[idx].key.string))
1973        if res:
1974            result.append(res.group())
1975        idx += 1
1976
1977    return result
1978
1979@lldb_command("showcarveouts")
1980def ShowCarveouts(cmd_args=None):
1981    """
1982    Scan IODeviceTree for every object in carveout-memory-map and print the memory carveouts.
1983    syntax: (lldb) showcarveouts
1984    """
1985    edt_plane = GetRegistryPlane("IODeviceTree")
1986    if edt_plane is None:
1987        print("Couldn't obtain a pointer to IODeviceTree")
1988        return None
1989
1990    # Registry API functions operate on "plane" global variable
1991    global plane
1992    prev_plane = plane
1993    plane = edt_plane
1994
1995    chosen = FindRegistryObjectRecurse(kern.globals.gRegistryRoot, "chosen")
1996    if chosen is None:
1997        print("Couldn't obtain /chosen IORegistryEntry")
1998        return None
1999
2000    memory_map = FindRegistryObjectRecurse(chosen, "carveout-memory-map")
2001    if memory_map is None:
2002        print("Couldn't obtain memory-map from /chosen/carveout-memory-map")
2003        return None
2004
2005    plane = prev_plane
2006
2007    """
2008    Dynamically populated by iBoot to store memory region description
2009    region-id-<n>: <region n base> <region n size>
2010    region-name-id-<n>: <region n name>
2011    """
2012    name_prop_list = []
2013    range_prop_list = []
2014    region_id_list = []
2015    region_name_id_list = []
2016
2017    region_id = re.compile(r"region-id-\d+")
2018    region_id_list = GetRegionProp(memory_map.fPropertyTable, region_id);
2019    region_name_id = re.compile(r"region-name-id-\d+")
2020    region_name_id_list = GetRegionProp(memory_map.fPropertyTable, region_name_id);
2021
2022    for names in region_name_id_list:
2023        mm_entry = LookupKeyInOSDict(memory_map.fPropertyTable, names, CompareStringToOSSymbol)
2024        if mm_entry is None:
2025            print("Couldn't find " + names + " entry in carveout-memory-map", file=sys.stderr)
2026            continue
2027        data = cast(mm_entry.data, "char *")
2028        string = "{:<32s}: "
2029        name_prop_list.append( string.format(data) );
2030
2031    for ids in region_id_list:
2032        mm_entry = LookupKeyInOSDict(memory_map.fPropertyTable, ids, CompareStringToOSSymbol)
2033        if mm_entry is None:
2034            print("Couldn't find " + ids + " entry in carveout-memory-map")
2035            continue
2036
2037        data = cast(mm_entry.data, "dtptr_t *")
2038        paddr = unsigned(data[0])
2039        size = unsigned(data[1])
2040
2041        string = "0x{:x}-0x{:x} (size: 0x{:x})"
2042        range_prop_list.append(string.format(paddr, paddr+size, size));
2043
2044    for namep, rangep in zip(name_prop_list, range_prop_list):
2045        print(namep, rangep)
2046
2047    return True
2048