### Breakthroughs & Rétro-ingénierie
- Analyse forensique du redémarrage instantané ("coupure de courant" sans log) survenu au commit 5357ca6 :
1. Le désassemblage de `CCLogStream::shouldLog` (0xffffff80032ff9e6) dans `BootKernelExtensions.kc` a révélé un déréférencement en cascade non protégé : `movq 0x90(%rdi), %rax` puis `movq 0x58(%rax), %rax`.
2. `fLogger` retournant un `OSString*` de seulement 32 octets (0x20), l'accès à +0x90 lisait une adresse arbitraire du tas kernel dont le déréférencement en Ring 0 déclenchait un #PF/#GP transformé en Triple Fault CPU immédiat (reset matériel sans panic handler).
3. Parallèlement, `IO80211RNGAgent::withOptions` et `IO80211ControllerMonitor::withControllerAndProvider` requièrent impérativement un pointeur non-nul et invoquent `logger->retain()` (*0x20(%rax)).
4. La résolution consiste en une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI Apple : un pointeur `p1` placé à l'offset strict +0x90 (validé par `static_assert`) pointant vers une zone mémoire `fP1Mem` dont l'offset +0x58 pointe vers `fP2Mem` initialisé à zéro. `shouldLog` et `log*` retournent ainsi false sans aucun déréférencement illégal et sans risquer d'écrire sur un pipe non configuré.
### Modifications apportées
- `QualcommWifi/QualcommWifi.cpp` :
- Définition et enregistrement IOKit de `FakeFaultReporter` et `FakeLogStream` avec méthodes usine `create()`.
- Initialisation de `fFaultReporter = FakeFaultReporter::create()` et `fLogger = FakeLogStream::create()` dans `init()`.
- Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant 0x100 (256 frames).
- Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pendant `start()` afin d'éviter tout conflit de bridge ou transaction d'arbitrage invalidée.
- Nettoyage du buffer statique non-thread-safe dans `logNvramStage`.
- `QualcommWifi/QualcommWifi.hpp` :
- Déclaration de `virtual UInt32 getActionFramePoolCapacity(void) override`.
- `build_wifi.sh` :
- Ajout du flag `-Wno-invalid-offsetof` pour compilation propre de l'assertion ABI.
- `docs/ISSUES_AND_POSTMORTEMS.md` :
- Documentation détaillée du post-mortem INC-10 expliquant le mécanisme de triple fault et la solution apportée.
### Validation
- Compilation réussie avec 0 erreur.
- Vérification de la vtable `__ZTV12QualcommWifi` : taille exacte de 3728 octets (0xe90, 466 slots) conforme à OpenCore et Apple `IO80211Controller`.
- Validation de conformité des symboles et dépendances à 100% via `kmutil libraries`.
- Déploiement du kext compilé dans `/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext`.
- Activation confirmée dans `/Volumes/EFI/EFI/OC/config.plist` (`Enabled = True`).
197 lines
14 KiB
Markdown
197 lines
14 KiB
Markdown
# Historique & Post-Mortems des Problèmes Rencontrés
|
|
|
|
Ce document répertorie de manière exhaustive l'ensemble des anomalies, blocages, panics et bootloops rencontrés lors du développement du pilote natif Wi-Fi Qualcomm WCN7850 (`QualcommWifi`), avec leurs analyses techniques de cause racine et leurs résolutions vérifiées.
|
|
|
|
---
|
|
|
|
## Sommaire des Incidents
|
|
|
|
| ID | Incident / Symptôme | Composant Touché | Cause Racine | Statut |
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
| **INC-01** | Erreur d'édition de liens : symboles de réservation `IONetworkController` non résolus | Linker / KPI macOS | Méthodes virtuelles de réserve non exportées par le noyau Tahoe | **Résolu** |
|
|
| **INC-02** | Crash précoce OpenCore : `padslot mismatch` | ABI / Vtable | Vtable `IO80211Controller` allongée à 466 slots sous Darwin 25 | **Résolu** |
|
|
| **INC-03** | Échec silencieux de démarrage : `IO80211Controller::start fails` | WorkQueue / IOKit | `IONetworkController::start` requiert une `IO80211WorkQueue` | **Résolu** |
|
|
| **INC-04** | **Bootloop de 5 secondes** : réinitialisation matérielle brutale du PC | Kernel Panic Apple | `panic("No ivars->_faultReporter")` dans `findAndAttachToFaultReporter` | **Résolu** |
|
|
| **INC-05** | Échec d'allocation interne : `Failed to allocate RNG Agent` | RNG / Logger | `IO80211RNGAgent` requiert un logger non-nul lors de l'initialisation | **Résolu** |
|
|
| **INC-06** | Désynchronisation du cycle d'initialisation au boot | Cycle Kexts (`Info.plist`) | `OSBundleRequired` positionné à `Root` au lieu de `Network-Root` | **Résolu** |
|
|
| **INC-07** | Blocage du chargement firmware AMSS lors du téléversement BHIe | PCIe ASPM | Désynchronisation BAR0 causée par les transitions d'énergie L0s/L1 | **Résolu** |
|
|
| **INC-08** | Corruptions mémoires & gel sur plateforme AMD avec > 4 Go RAM | Moteur DMA 36-bit | Pointeur DMA physique tronqué au-delà de 64 Go | **Résolu** |
|
|
| **INC-09** | Impossibilité de lire les rapports de panique (redémarrage instantané) | OpenCore / NVRAM | Paramètres de débogage du noyau par défaut provoquant un reboot | **Résolu** |
|
|
|
|
---
|
|
|
|
## Détail des Post-Mortems
|
|
|
|
### INC-01 : Erreur d'édition de liens sur les réservations `IONetworkController`
|
|
* **Symptôme** :
|
|
Lors de la compilation et de l'édition de liens du binaire Mach-O Kext, le compilateur Clang échouait avec des erreurs `Undefined symbols for architecture x86_64` :
|
|
`_ZN19IONetworkController29_RESERVEDIONetworkController2Ev` jusqu'à `7Ev`.
|
|
* **Cause Racine** :
|
|
Dans le Kernel Collection `BootKernelExtensions.kc` de macOS Tahoe, Apple conserve les slots virtuels de réservation pour préserver l'ABI C++, mais n'exporte pas leurs symboles dans les tables d'exportation globales (`libkern`/`KPI`).
|
|
* **Résolution** :
|
|
Définition de stubs d'export locaux C dans [`QualcommWifi.cpp`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/QualcommWifi.cpp) :
|
|
```cpp
|
|
extern "C" {
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController2Ev(void) {}
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController3Ev(void) {}
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController4Ev(void) {}
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController5Ev(void) {}
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController6Ev(void) {}
|
|
void _ZN19IONetworkController29_RESERVEDIONetworkController7Ev(void) {}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### INC-02 : Crash OpenCore `padslot mismatch` sur la Vtable
|
|
* **Symptôme** :
|
|
Le Kext provoquait un plantage immédiat au chargement par OpenCore ou un KP dans `Vtables.c`.
|
|
* **Cause Racine** :
|
|
OpenCore valide strictement la taille de la table de fonctions virtuelles de chaque classe dérivée. Sous macOS Sonoma/Sequoia (Darwin 24), la vtable d'`IO80211Controller` comptait 448 entrées. Sous macOS Tahoe 26.x (Darwin 25), elle a été portée à 466 entrées (taille binaire de `0xe90` = 3728 octets). Tout écart dimensionnel invalide la correspondance des slots mémoires.
|
|
* **Résolution** :
|
|
Vérification systématique par script de la vtable `__ZTV12QualcommWifi` contre `BootKernelExtensions.kc`. Tous les slots jusqu'à l'offset `0xe90` sont rigoureusement alignés sur l'ABI Tahoe.
|
|
|
|
---
|
|
|
|
### INC-03 : Échec de `IO80211Controller::start` sans WorkQueue
|
|
* **Symptôme** :
|
|
Le pilote démarrait dans `init()` et `probe()`, mais `IO80211Controller::start` affichait `IO80211Controller::start fails` et retournait `false`.
|
|
* **Cause Racine** :
|
|
L'analyse désassemblée de `IO80211Controller::start` (`0xffffff8002291f30`) a révélé qu'à l'offset `+0x41`, le code teste le retour de `IONetworkController::start(provider)`. Cette dernière exige impérativement un gestionnaire de queue de travail valide pour instancier ses boucles d'événements.
|
|
* **Résolution** :
|
|
Implémentation des surcharges de création de la file de travail dans [`QualcommWifi.cpp`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/QualcommWifi.cpp) :
|
|
```cpp
|
|
bool QualcommWifi::createWorkQueue(void) {
|
|
if (!fWorkQueue) {
|
|
fWorkQueue = IO80211WorkQueue::workQueue();
|
|
}
|
|
return (fWorkQueue != nullptr);
|
|
}
|
|
IO80211WorkQueue* QualcommWifi::getWorkQueue(void) const { return fWorkQueue; }
|
|
bool QualcommWifi::createWorkLoop(void) { return createWorkQueue(); }
|
|
IOWorkLoop* QualcommWifi::getWorkLoop(void) const { return getWorkQueue(); }
|
|
```
|
|
|
|
---
|
|
|
|
### INC-04 : Le Bootloop de 5 Secondes - Kernel Panic `_faultReporter`
|
|
* **Symptôme** :
|
|
L'utilisateur subissait un redémarrage en boucle constant du Mac : OpenCore se lance, commence le chargement de macOS, puis au bout de 5 secondes environ, le PC s'éteint et redémarre instantanément sans message visible.
|
|
* **Cause Racine (Breakthrough Majeur)** :
|
|
Désassemblage complet du flux de `IO80211Controller::start` à l'adresse `0xffffff8002291f30` dans `BootKernelExtensions.kc` :
|
|
1. À `+0x272`, le pilote invoque `findAndAttachToFaultReporter()` (`0xffffff8002292628`).
|
|
2. À `+0x13e`, cette routine appelle la méthode virtuelle `this->getFaultReporterFromDriver()` (`callq *0xd80(%rax)`, vtable slot 434).
|
|
3. Notre code renvoyait initialement `nullptr`.
|
|
4. À l'adresse `0xffffff8002292737` (`+0x227`), `findAndAttachToFaultReporter` contient un appel direct et inconditionnel à `_panic` :
|
|
```text
|
|
panic("\"No ivars->_faultReporter\" @%s:%d", "IO80211Controller.cpp", 2877)
|
|
```
|
|
5. Faute d'argument empêchant le redémarrage, macOS déclenchait immédiatement un hardware reset.
|
|
* **Résolution** :
|
|
1. Instanciation dans `init()` d'un objet `OSObject` valide :
|
|
```cpp
|
|
fFaultReporter = OSString::withCString("QualcommFaultReporter");
|
|
```
|
|
2. Surcharge de `getFaultReporterFromDriver()` retournant `fFaultReporter`.
|
|
3. Libération propre dans `free()` via `OSSafeReleaseNULL(fFaultReporter)`.
|
|
4. Dans le noyau, `findAndAttachToFaultReporter()` constate que le pointeur est non-nul, stocke l'adresse dans `ivars->_faultReporter`, incrémente la référence via `obj->retain()` (`*0x20(%rax)`), et **aucun panic n'est émis**.
|
|
|
|
---
|
|
|
|
### INC-05 : Échec d'allocation `IO80211RNGAgent` / `getLogger()`
|
|
* **Symptôme** :
|
|
Échec dans `IO80211Controller::start` à l'étape d'initialisation du générateur aléatoire Wi-Fi avec le log : `Failed to allocate RNG Agent`.
|
|
* **Cause Racine** :
|
|
L'inspection binaire de `IO80211RNGAgent::withOptions()` (`0xffffff80022dd55c`) a montré qu'à l'offset `+0xe6`, la fonction appelle `this->getLogger()` (vtable slot 426, `*0xd40(%rax)`). Si ce pointeur est nul, elle saute directement en erreur (`+0x12f`), détruit le descripteur et renvoie `NULL`.
|
|
* **Résolution** :
|
|
Instanciation dans `init()` d'un objet `fLogger = OSString::withCString("QualcommLogger")`, et implémentation de `getLogger()` retournant `(void*)fLogger`. `RNGAgent` effectue son appel `logger->retain()` et s'initialise avec succès.
|
|
|
|
---
|
|
|
|
### INC-06 : Mésalignement du Cycle Kext `OSBundleRequired`
|
|
* **Symptôme** :
|
|
Tentative de matching du pilote PCI trop précoce lors de la séquence de boot, provoquant des échecs d'allocation de services réseau.
|
|
* **Cause Racine** :
|
|
Dans [`Info.plist`](file:///Users/erwan/Documents/QualcommWireless/QualcommWifi/Info.plist), `OSBundleRequired` avait été temporairement passé à `Root`. Or, `IO80211Family.kext` d'Apple spécifie impérativement `Network-Root`. Déclarer `Root` force le chargement du pilote avant que la pile des protocoles réseau et des sockets du noyau ne soit prête.
|
|
* **Résolution** :
|
|
Rétablissement strict de :
|
|
```xml
|
|
<key>OSBundleRequired</key>
|
|
<string>Network-Root</string>
|
|
```
|
|
|
|
---
|
|
|
|
### INC-07 : Blocage du Téléversement BHIe par ASPM PCIe
|
|
* **Symptôme** :
|
|
Timeout lors du transfert de l'image AMSS complète (~6 Mo) via BHIe, le statut BHI restant figé en boucle.
|
|
* **Cause Racine** :
|
|
La gestion d'énergie active du bus PCIe (Active State Power Management - ASPM) tente de basculer la liaison en veille L0s ou L1 pendant les transferts DMA volumineux, corrompant les accès aux registres MMIO de notification de sonnette (`ATH12K_BHIE_TXVECDB`).
|
|
* **Résolution** :
|
|
Désactivation dynamique d'ASPM avant tout transfert firmware :
|
|
```cpp
|
|
UInt8 pciCapOffset = 0;
|
|
if (fPciDevice->findPCICapability(kIOPCIPCIExpressCapability, &pciCapOffset) && pciCapOffset != 0) {
|
|
UInt16 linkCtrl = fPciDevice->configRead16(pciCapOffset + 0x10);
|
|
linkCtrl &= ~0x3; // Désactive L0s et L1
|
|
fPciDevice->configWrite16(pciCapOffset + 0x10, linkCtrl);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### INC-08 : Échec et Corruption DMA sur Plateformes AMD 64-bit (> 4 Go RAM)
|
|
* **Symptôme** :
|
|
Gel ou reboot brutal lors de l'accès aux anneaux de commandes ou lors du démarrage du Primary Boot Loader.
|
|
* **Cause Racine** :
|
|
Le SoC WCN7850 possède une unité DMA adressable sur 36 bits maximum (plage physique `0x000000000` à `0xFFFFFF000`). Sur les cartes mères équipées de 32 Go ou 64 Go de RAM, les allocations kernel classiques peuvent renvoyer des tampons au-dessus de 64 Go. Les bits de poids fort étaient tronqués par le matériel, écrivant dans des zones mémoires arbitraires du noyau.
|
|
* **Résolution** :
|
|
Toutes les allocations DMA contiguës sont contraintes par un masque physique 36 bits :
|
|
```cpp
|
|
#define ATH12K_DMA_MASK_36BIT_PAGE 0xFFFFFF000ULL
|
|
IOBufferMemoryDescriptor::inTaskWithPhysicalMask(
|
|
kernel_task,
|
|
kIODirectionInOut | kIOMemoryPhysicallyContiguous,
|
|
size,
|
|
ATH12K_DMA_MASK_36BIT_PAGE
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### INC-09 : Redémarrages Automatiques Invisibles sans Affichage de KP
|
|
* **Symptôme** :
|
|
En cas de panic du noyau, le PC redémarrait instantanément sans donner le temps à l'utilisateur de lire la pile d'appels ou la cause du crash.
|
|
* **Cause Racine** :
|
|
Par défaut, le noyau XNU sous OpenCore réinitialise le processeur en cas de panique non interceptée.
|
|
* **Résolution** :
|
|
Injection de `panic=0` dans les `boot-args` OpenCore du fichier `config.plist`. En cas d'erreur critique, le noyau gèle l'affichage, permettant de lire textuellement le fichier source et la ligne fautive (ex: `IO80211Controller.cpp:2877`).
|
|
|
|
---
|
|
|
|
### INC-10 : Triple Fault Processeur ("Coupure de Courant") lors de l'appel `CCLogStream::shouldLog` sur `fLogger`
|
|
* **Symptôme** :
|
|
Lors du démarrage d'OpenCore avec `QualcommWifi.kext` actif, le PC s'éteint et redémarre instantanément comme une coupure de courant brutale, sans panic log ni rapport d'erreur NVRAM.
|
|
* **Cause Racine (Breakthrough Fondamental)** :
|
|
1. Lors du commit `5357ca6`, `getLogger()` avait été implémenté en renvoyant une instance d'`OSString` (`OSString::withCString("QualcommLogger")`).
|
|
2. Lors de l'exécution de `IO80211Controller::start()` -> `createIOReporters()` -> `addReporterLegend()`, le noyau macOS invoque `CCLogStream::shouldLog(this->getLogger(), level)` (`0xffffff80032ff9e6`).
|
|
3. Le désassemblage de `CCLogStream::shouldLog` a révélé :
|
|
```assembly
|
|
movq 0x90(%rdi), %rax ; Lit l'offset +0x90 de l'objet passé en rdi (fLogger)
|
|
movq 0x58(%rax), %rax ; Déréférence ce pointeur en Ring 0 !
|
|
cmpl 0x8(%rax), %esi ; Compare le niveau de log
|
|
setle %al
|
|
retq
|
|
```
|
|
4. L'objet `OSString` ne mesurant que 32 octets (`0x20`), l'accès à `+0x90` pointait sur une adresse mémoire arbitraire ou non mappée dans le heap kernel. Le déréférencement immédiat de cette valeur via `movq 0x58(%rax), %rax` déclenchait une faute de page (`#PF`) ou une protection générale (`#GP`) non interceptée en mode superviseur (Ring 0), provoquant un Triple Fault CPU réinitialisant la carte mère instantanément.
|
|
5. Parallèlement, `IO80211RNGAgent::withOptions()` (`0xffffff80022dd55c`) et `IO80211ControllerMonitor::initWithControllerAndProvider()` (`0xffffff80023929d0`) requièrent impérativement que `getLogger()` retourne un pointeur non-nul et appellent `logger->retain()` (`*0x20(%rax)`).
|
|
* **Résolution Technique** :
|
|
1. Conception d'une classe `FakeLogStream : public OSObject` respectant au millimètre l'ABI de `CCLogStream` :
|
|
- Taille minimale supérieure à `0x98` octets avec `p1` placé exactement à l'offset `+0x90` (validé par `static_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90)`).
|
|
- `p1` pointe vers une zone mémoire `fP1Mem` (`0x1000` octets) dont l'offset `+0x58` pointe vers `fP2Mem` (`0x100` octets initialisés à 0).
|
|
- `CCLogStream::shouldLog` et `CCLogStream::log*` lisent ainsi `0x90 -> 0x58 -> 0`, constatent un niveau de log nul, et retournent proprement `false` sans tenter d'émettre sur un pipe inexistant.
|
|
- `FakeLogStream` étant un `OSObject`, les appels `retain()` et `release()` effectués par `RNGAgent` s'exécutent avec une fidélité parfaite.
|
|
2. Implémentation locale de `QualcommWifi::getActionFramePoolCapacity()` (slot 440) renvoyant `0x100` (256 frames).
|
|
3. Suppression de l'écriture directe `kIOPCIConfigCommand` sur le bus PCIe AMD pour éviter toute déstabilisation du root port.
|
|
|