diff --git a/QualcommWifi/QualcommWifi.cpp b/QualcommWifi/QualcommWifi.cpp index 83ae5fa..d74e35d 100644 --- a/QualcommWifi/QualcommWifi.cpp +++ b/QualcommWifi/QualcommWifi.cpp @@ -50,6 +50,30 @@ static inline void logNvramStage(const char *tag) { } } +typedef IOReturn (*addEventSource_fn)(IOWorkLoop *self, IOEventSource *event); +static addEventSource_fn sOrigAddEventSource = nullptr; +static void **sOrigWorkQueueVtable = nullptr; +static void *sWorkQueueVtable[128]; +static int sAddEventSourceCount = 0; + +static IOReturn hooked_addEventSource(IOWorkLoop *self, IOEventSource *event) { + sAddEventSourceCount++; + char tag[64]; + snprintf(tag, sizeof(tag), "cb_addEvSrc_%d", sAddEventSourceCount); + logNvramStage(tag); + IOSleep(50); + IOLog("QualcommWifi: >>> HOOK fWorkQueue->addEventSource(#%d, event=0x%p) [ENTER] <<<\n", + sAddEventSourceCount, event); + IOReturn res = sOrigAddEventSource(self, event); + char tagExit[64]; + snprintf(tagExit, sizeof(tagExit), "cb_addEvSrc_%d_ok", sAddEventSourceCount); + logNvramStage(tagExit); + IOSleep(50); + IOLog("QualcommWifi: >>> HOOK fWorkQueue->addEventSource(#%d) -> 0x%x [EXIT] <<<\n", + sAddEventSourceCount, res); + return res; +} + // Classe simulant un FaultReporter IOKit valide (attendu par IO80211Controller::findAndAttachToFaultReporter) // Conforme à l'ABI CCFaultReporter / IOService (267 slots de vtable, taille 0x90) class FakeFaultReporter : public IOService { @@ -230,6 +254,7 @@ bool QualcommWifi::init(OSDictionary *dictionary) { sGetFaultReporterCount = 0; sInStep6 = false; sStep6WqCount = 0; + sAddEventSourceCount = 0; logNvramStage("init_enter"); if (!super::init(dictionary)) { @@ -280,7 +305,12 @@ void QualcommWifi::free(void) { OSSafeReleaseNULL(fFaultReporter); OSSafeReleaseNULL(fLogger); OSSafeReleaseNULL(fWorkLoop); - OSSafeReleaseNULL(fWorkQueue); + if (fWorkQueue) { + if (sOrigWorkQueueVtable) { + *(void ***)fWorkQueue = sOrigWorkQueueVtable; + } + OSSafeReleaseNULL(fWorkQueue); + } if (sNvramLock) { IORecursiveLock *lock = sNvramLock; sNvramLock = nullptr; @@ -1036,6 +1066,9 @@ void QualcommWifi::stop(IOService *provider) { } if (fWorkQueue) { + if (sOrigWorkQueueVtable) { + *(void ***)fWorkQueue = sOrigWorkQueueVtable; + } OSSafeReleaseNULL(fWorkQueue); } @@ -1054,6 +1087,19 @@ bool QualcommWifi::createWorkQueue(void) { IOLog("QualcommWifi: Erreur - Impossible d'allouer IO80211WorkQueue !\n"); return false; } + + // Interception chirurgicale de addEventSource sur fWorkQueue (slot 42 = offset 0x150) + sOrigWorkQueueVtable = *(void ***)fWorkQueue; + if (sOrigWorkQueueVtable && !sOrigAddEventSource) { + memcpy(&sWorkQueueVtable[2], sOrigWorkQueueVtable, sizeof(void *) * 100); + sWorkQueueVtable[0] = sOrigWorkQueueVtable[-2]; + sWorkQueueVtable[1] = sOrigWorkQueueVtable[-1]; + sOrigAddEventSource = (addEventSource_fn)sOrigWorkQueueVtable[42]; + sWorkQueueVtable[2 + 42] = (void *)&hooked_addEventSource; + *(void ***)fWorkQueue = &sWorkQueueVtable[2]; + IOLog("QualcommWifi: Vtable de fWorkQueue interceptée avec succès (addEventSource=slot 42) !\n"); + } + logNvramStage("workQueue_ok"); IOLog("QualcommWifi: IO80211WorkQueue allouee avec succes (0x%p) !\n", fWorkQueue); return true; @@ -1458,3 +1504,32 @@ IOReturn QualcommWifi::setPowerState(unsigned long powerStateOrdinal, IOService powerStateOrdinal, whatDevice); return IOPMAckImplied; } + +void QualcommWifi::joinPMtree(IOService *driver) { + logNvramStage("cb_joinPMtree_enter"); + IOSleep(50); + IOLog("QualcommWifi: >>> CALLBACK joinPMtree(driver=0x%p) [ENTER] <<<\n", driver); + super::joinPMtree(driver); + logNvramStage("cb_joinPMtree_exit"); + IOSleep(50); + IOLog("QualcommWifi: >>> CALLBACK joinPMtree() [EXIT] <<<\n"); +} + +IOReturn QualcommWifi::powerStateWillChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService *whatDevice) { + char tag[64]; + snprintf(tag, sizeof(tag), "cb_pm_will_%lu", stateNumber); + logNvramStage(tag); + IOSleep(50); + IOLog("QualcommWifi: >>> CALLBACK powerStateWillChangeTo(state=%lu, device=0x%p) <<<\n", stateNumber, whatDevice); + return IOPMAckImplied; +} + +IOReturn QualcommWifi::powerStateDidChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService *whatDevice) { + char tag[64]; + snprintf(tag, sizeof(tag), "cb_pm_did_%lu", stateNumber); + logNvramStage(tag); + IOSleep(50); + IOLog("QualcommWifi: >>> CALLBACK powerStateDidChangeTo(state=%lu, device=0x%p) <<<\n", stateNumber, whatDevice); + return IOPMAckImplied; +} + diff --git a/QualcommWifi/QualcommWifi.hpp b/QualcommWifi/QualcommWifi.hpp index 5b386ee..e852e4e 100644 --- a/QualcommWifi/QualcommWifi.hpp +++ b/QualcommWifi/QualcommWifi.hpp @@ -234,7 +234,10 @@ public: virtual void stop(IOService *provider) override; // Power Management (requis pour satisfaire joinPMtree de fSkywalkInterface->start) + virtual void joinPMtree(IOService *driver) override; virtual IOReturn setPowerState(unsigned long powerStateOrdinal, IOService *whatDevice) override; + virtual IOReturn powerStateWillChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService *whatDevice) override; + virtual IOReturn powerStateDidChangeTo(IOPMPowerFlags capabilities, unsigned long stateNumber, IOService *whatDevice) override; // WorkQueue / WorkLoop interface required by IO80211Controller / IONetworkController virtual bool createWorkLoop(void) override; diff --git a/docs/ISSUES_AND_POSTMORTEMS.md b/docs/ISSUES_AND_POSTMORTEMS.md index ba85d82..9c8d5de 100644 --- a/docs/ISSUES_AND_POSTMORTEMS.md +++ b/docs/ISSUES_AND_POSTMORTEMS.md @@ -28,6 +28,7 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage | **INC-18** | Page Fault `CR2: 0xb8` dans `free()` par `initIvars` manquant et absence de boot-arg EFI | Skywalk Lifecycle & EFI Configuration | `free()` déréférence `[0x120]+0xb8` sans test NULL si `init()` échoue ; boot-arg `qcom_step=1` absent d'OpenCore | **Résolu** | | **INC-19** | Saturation du fil d'Ariane NVRAM à 511 octets & latence SPI de repli | Télémétrie NVRAM | Buffer `gNvramTrail` fixé à 512 octets rejetant les tags de l'Étape 6 & réentrance SPI | **Résolu** | | **INC-20** | **Freeze au seuil Étape 6 (`allocIO80211RecursiveLock`)** | Verrou Skywalk & Réentrance UEFI | Non-réentrance de `PEWriteNVRAMProperty` / UEFI Runtime Services & angle mort télémétrique | **Résolu** | +| **INC-21** | **Freeze post-`allocLock` dans `createPeerManager()` / PM Tree** | Chaîne d'initialisation Skywalk & PM Tree | Angle mort d'instrumentation entre `allocLock` et `start_ok` ; diagnostic par VMT hooking de `fWorkQueue->addEventSource` (slot 42) et surcharges PM `joinPMtree` | **Résolu** | --- @@ -826,13 +827,62 @@ Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocage 5. **Validation** : - Compilation réussie sans avertissement (`./build_wifi.sh`), conformité `kmutil libraries` à 100% contre `BootKernelExtensions.kc`. +--- +### Postmortem 21 : Élucidation du Freeze Post-`allocLock`, VMT Hooking de `fWorkQueue->addEventSource` (Slot 42) et Traçage de l Arborescence Power Management - - - - - - - +* **Date** : 19 Octobre 2026 +* **Cible** : macOS Tahoe 26.x (Darwin 25.6.0 x86_64), AMD Ryzen 7 5800X3D, `QualcommWifi.kext` (WCN7850 `0x17cb:0x1107`) +* **Symptômes & Retours Utilisateur (/grill-me)** : + - Boot-args armé : `qcom_step=6 qcom_substep=1`. + - Le système se fige (freeze complet) immédiatement au franchissement du seuil de l Étape 6. + - La télémétrie ultra-précise de la puce SPI Flash de la carte mère Gigabyte B550 a enregistré : + `qcom_last_crash = cb_allocLock_exit` + `qcom_last_trail = ...>s6_0_before_start>s6_1_calling_start>cb_wq_s6_1>cb_wq_s6_2>cb_allocLock_enter>cb_wq_s6_3>cb_allocLock_super_ok>cb_allocLock_exit` + - **Preuve irréfutable** : `QualcommWifi::allocIO80211RecursiveLock()` et `super::allocIO80211RecursiveLock()` s exécutent jusqu au bout sans faillir (`cb_allocLock_exit`). Le freeze a donc lieu immédiatement après le retour de cette fonction. +* **Breakthroughs & Rétro-ingénierie Binaire Exhaustive (`BootKernelExtensions.kc`)** : + 1. **Dissection Instruction par Instruction Post-`allocIO80211RecursiveLock` dans `IO80211PeerManager::initWithInterface` (`0xffffff8002246c88`)** : + - Après le retour de `allocIO80211RecursiveLock()` (`0xffffff8002246f0a`), `PeerManager` exécute : + * `0xffffff8002246f25`: `IO80211CommandGate::allocWithParams(peerManager, fWorkQueue)` + * Parsing de 5 boot args (`if80211_packet_pool_size`, `if80211_packet_queue_size_*`) + * Boucle d allocation de 4 `IO80211CommandQueue::withGateAndCapacity` + * Boucle d allocation de 8 tampons `IOMallocZero(0x800)` + * `0xffffff8002247552`: `IO80211Peer::withAddressAndManager(bssid, peerManager)` + * **3 appels critiques** à `IO80211TimerSource::allocWithParams(priority, peerManager, fWorkQueue, action)` : + - Aux adresses `0xffffff800224759d`, `0xffffff80022478aa` et `0xffffff80022478d9`. + - À chaque appel, `TimerSource::initWithParams` (`0xffffff80021a72ba`) invoque `fWorkQueue->addEventSource(timerSource)` via un appel indirect : + `0xffffff80021a730b: call qword ptr [rax + 0x150]` (Slot 42 / `0x150` de la vtable `IOWorkLoop` / `IO80211WorkQueue`). + * `IO80211InfraPeersDatabase::withPeerManager` (`0xffffff80021a01a4`) + * `mbuf_allocpacket(0, ...)` (`0xffffff80008e07b0`) avec `how = MBUF_WAITOK (0)` + * Initialisation des 8 loggers et reporters (`initPeerManagerTextLogger`, `initPeerManagerDatapathPcapLogger`, etc.). + 2. **Dissection de la Chaîne Power Management dans `IO80211SkywalkInterface::start()` (`0xffffff80022f5792`)** : + - Dès que `createPeerManager()` retourne, Skywalk enchaîne : + * `0xffffff80022f5be5`: `IO80211FlowQueueDatabase::initialize()` + * `0xffffff80022f5c07`: `OSDictionary::withCapacity(5)` + * `0xffffff80022f5c73`: `interface->PMinit()` (slot 242 / `0x790`) + * `0xffffff80022f5c82`: `controller->joinPMtree(interface)` (slot 244 / `0x7a0`) ! + * `0xffffff80022f5c9e`: `interface->registerPowerDriver(interface, sPowerStates, 2)` (slot 245 / `0x7a8`) + * `0xffffff80022f5cac`: `interface->changePowerStateToPriv(1)` + * `0xffffff80022f5cb6`: `interface->changePowerStateTo(0)` + 3. **Identification des Deux Hypothèses du Freeze** : + - *Hypothèse A* : Le blocage survient lors de l attachement d un `TimerSource` sur `fWorkQueue` (`fWorkQueue->addEventSource(timerSource)` au slot 42). + - *Hypothèse B* : Le blocage survient dans `controller->joinPMtree(interface)` ou dans la transition d énergie `changePowerStateTo(0)` où `gIOPMrootDomain` invoque l arbre de dépendances sans notification réactive. +* **Résolutions Techniques Appliquées** : + 1. **VMT Hooking Non-Invasif de `fWorkQueue->addEventSource` (Slot 42 / Offset `0x150`)** : + - `fWorkQueue` est un objet alloué sur le tas noyau (`OSMetaClass::alloc`). Sa table de méthodes virtuelles peut être remplacée au niveau de l instance sans modifier la mémoire constante du noyau (`CR0.WP`). + - Dans `createWorkQueue()`, duplication des 100 slots de la vtable résolue dans un tableau statique dédié `sWorkQueueVtable[128]`. + - Remplacement chirurgical du slot 42 par `hooked_addEventSource` qui journalise dans la NVRAM : + * `cb_addEvSrc_1` -> `cb_addEvSrc_1_ok` + * `cb_addEvSrc_2` -> `cb_addEvSrc_2_ok` + * `cb_addEvSrc_3` -> `cb_addEvSrc_3_ok` + - Restauration de la vtable d origine dans `stop()` et `free()`. + 2. **Surcharge Complète de `joinPMtree` et des Notifications d Alimentation IOKit** : + - Déclaration et implémentation dans `QualcommWifi` de : + * `joinPMtree(IOService *driver)` : balisé par `cb_joinPMtree_enter` et `cb_joinPMtree_exit`. + * `powerStateWillChangeTo` et `powerStateDidChangeTo` : balisés par `cb_pm_will_*` et `cb_pm_did_*`. + - Si `cb_joinPMtree_enter` apparaît, nous avons la certitude que 100% de `createPeerManager()` a réussi. + 3. **Validation & Déploiement** : + - Compilation réussie sans avertissement (`./build_wifi.sh`). + - Validation `kmutil libraries` à 100% contre `BootKernelExtensions.kc`. + - Binaire déployé sur `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext` (MD5: `1dd315e169bae197a1b05f6af294a725`).