### 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`).
14 KiB
14 KiB
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_RESERVEDIONetworkController2Evjusqu'à7Ev. - Cause Racine :
Dans le Kernel Collection
BootKernelExtensions.kcde 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: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'
IO80211Controllercomptait 448 entrées. Sous macOS Tahoe 26.x (Darwin 25), elle a été portée à 466 entrées (taille binaire de0xe90= 3728 octets). Tout écart dimensionnel invalide la correspondance des slots mémoires. - Résolution :
Vérification systématique par script de la vtable
__ZTV12QualcommWificontreBootKernelExtensions.kc. Tous les slots jusqu'à l'offset0xe90sont rigoureusement alignés sur l'ABI Tahoe.
INC-03 : Échec de IO80211Controller::start sans WorkQueue
- Symptôme :
Le pilote démarrait dans
init()etprobe(), maisIO80211Controller::startaffichaitIO80211Controller::start failset retournaitfalse. - Cause Racine :
L'analyse désassemblée de
IO80211Controller::start(0xffffff8002291f30) a révélé qu'à l'offset+0x41, le code teste le retour deIONetworkController::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: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'adresse0xffffff8002291f30dansBootKernelExtensions.kc:- À
+0x272, le pilote invoquefindAndAttachToFaultReporter()(0xffffff8002292628). - À
+0x13e, cette routine appelle la méthode virtuellethis->getFaultReporterFromDriver()(callq *0xd80(%rax), vtable slot 434). - Notre code renvoyait initialement
nullptr. - À l'adresse
0xffffff8002292737(+0x227),findAndAttachToFaultReportercontient un appel direct et inconditionnel à_panic:panic("\"No ivars->_faultReporter\" @%s:%d", "IO80211Controller.cpp", 2877) - Faute d'argument empêchant le redémarrage, macOS déclenchait immédiatement un hardware reset.
- À
- Résolution :
- Instanciation dans
init()d'un objetOSObjectvalide :fFaultReporter = OSString::withCString("QualcommFaultReporter"); - Surcharge de
getFaultReporterFromDriver()retournantfFaultReporter. - Libération propre dans
free()viaOSSafeReleaseNULL(fFaultReporter). - Dans le noyau,
findAndAttachToFaultReporter()constate que le pointeur est non-nul, stocke l'adresse dansivars->_faultReporter, incrémente la référence viaobj->retain()(*0x20(%rax)), et aucun panic n'est émis.
- Instanciation dans
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 appellethis->getLogger()(vtable slot 426,*0xd40(%rax)). Si ce pointeur est nul, elle saute directement en erreur (+0x12f), détruit le descripteur et renvoieNULL. - Résolution :
Instanciation dans
init()d'un objetfLogger = OSString::withCString("QualcommLogger"), et implémentation degetLogger()retournant(void*)fLogger.RNGAgenteffectue son appellogger->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,OSBundleRequiredavait été temporairement passé àRoot. Or,IO80211Family.kextd'Apple spécifie impérativementNetwork-Root. DéclarerRootforce 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 :
<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 :
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 :
#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=0dans lesboot-argsOpenCore du fichierconfig.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.kextactif, 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) :
- Lors du commit
5357ca6,getLogger()avait été implémenté en renvoyant une instance d'OSString(OSString::withCString("QualcommLogger")). - Lors de l'exécution de
IO80211Controller::start()->createIOReporters()->addReporterLegend(), le noyau macOS invoqueCCLogStream::shouldLog(this->getLogger(), level)(0xffffff80032ff9e6). - Le désassemblage de
CCLogStream::shouldLoga révélé :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 - L'objet
OSStringne mesurant que 32 octets (0x20), l'accès à+0x90pointait sur une adresse mémoire arbitraire ou non mappée dans le heap kernel. Le déréférencement immédiat de cette valeur viamovq 0x58(%rax), %raxdé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. - Parallèlement,
IO80211RNGAgent::withOptions()(0xffffff80022dd55c) etIO80211ControllerMonitor::initWithControllerAndProvider()(0xffffff80023929d0) requièrent impérativement quegetLogger()retourne un pointeur non-nul et appellentlogger->retain()(*0x20(%rax)).
- Lors du commit
- Résolution Technique :
- Conception d'une classe
FakeLogStream : public OSObjectrespectant au millimètre l'ABI deCCLogStream:- Taille minimale supérieure à
0x98octets avecp1placé exactement à l'offset+0x90(validé parstatic_assert(__builtin_offsetof(FakeLogStream, p1) == 0x90)). p1pointe vers une zone mémoirefP1Mem(0x1000octets) dont l'offset+0x58pointe versfP2Mem(0x100octets initialisés à 0).CCLogStream::shouldLogetCCLogStream::log*lisent ainsi0x90 -> 0x58 -> 0, constatent un niveau de log nul, et retournent proprementfalsesans tenter d'émettre sur un pipe inexistant.FakeLogStreamétant unOSObject, les appelsretain()etrelease()effectués parRNGAgents'exécutent avec une fidélité parfaite.
- Taille minimale supérieure à
- Implémentation locale de
QualcommWifi::getActionFramePoolCapacity()(slot 440) renvoyant0x100(256 frames). - Suppression de l'écriture directe
kIOPCIConfigCommandsur le bus PCIe AMD pour éviter toute déstabilisation du root port.
- Conception d'une classe