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