- Breakthroughs & Rétro-ingénierie : * Rédaction détaillée du Postmortem 16 (analyse du panic lié à l'incompatibilité vtable IO80211WorkQueue / IOWorkLoop dans super::start()). * Documentation du mécanisme de non-volatilité de la NVRAM SPI Flash UEFI et explication de l'impossibilité d'écriture sur fichier APFS en early-boot. * Cartographie complète des appels internes de IO80211SkywalkInterface::start() vers CCPipe, CCStream, IO80211PeerManager et allocIO80211RecursiveLock(). - Modifications apportées : * docs/ISSUES_AND_POSTMORTEMS.md : ajout de la section Postmortem 16. * docs/NATIVE_WIFI.md : ajout de la section 6 décrivant les 7 paliers Skywalk (qcom_step) et la télémétrie NVRAM (qcom_stage). - Déploiement : * QualcommWifi.kext déployé dans /Volumes/EFI/EFI/OC/Kexts/. * config.plist configuré avec QualcommWifi.kext Enabled=True et qcom_step=5.
58 KiB
58 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 |
| INC-10 | Triple Fault Processeur ("Coupure de Courant") | Logger / CoreCapture | Déréférencement de +0x90 -> +0x58 dans CCLogStream::shouldLog |
Résolu |
| INC-11 | Freeze Apple Logo (non-verbose) / Triple Fault (verbose) | NVRAM & Matching IOKit | Matching prématuré de registerService() & heap dynamique précoce |
Résolu |
| INC-12 | Triple Fault / Freeze Apple Logo persistant | FakeLogStream / FakeFaultReporter |
Vtables tronquées à 38 slots (au lieu de 283/269) & 0x88->0x48 Page 0 #PF dans logv |
Résolu |
| INC-13 | Page Fault CR2: 0x658 dans ml_thread_policy au boot |
IOWorkLoop vs IO80211WorkQueue |
IO80211WorkQueue::getThread() retourne 0, deref thread->mutex (+0x658) |
Résolu |
| INC-14 | Page Fault CR2: 0x1 dans printDataPath |
IO80211SkywalkInterface.h |
Méthodes init et setInterfaceRole déclarées virtual par erreur |
Résolu |
| INC-15 | Kernel Trap / Triple Fault dans initIvars & attachInterface |
Skywalk Heap & Attachment ABI | Tas non initialisé (bzero manquant), header incorrect, ordre d'attachement et registerService prématuré |
Résolu |
| INC-16 | Bootloop par démarrage complet Skywalk & Piège de libération free() |
Skywalk Lifecycle & Ivars Guard | Exécution aveugle du stade 6 (start), #PF CR2:0xb8 si free() sans initIvars, et stop non-gardé |
Résolu |
| INC-17 | Page Fault sur table de propriétés NULL par court-circuit de super::init(0) |
Skywalk Init Architecture | initIvars prématuré faisait croire à init() que super::init(0) avait déjà eu lieu |
Résolu |
| 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 |
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
INC-11 : Divergence Verbose / Non-Verbose, Heap Dynamique Précoce et Matching Prématuré
- Symptômes :
- Avec mode verbose (
-v) : Réinitialisation instantanée du PC ("reset comme une coupure de courant"). - Sans mode verbose (non-verbose) : Blocage complet et statique sur le logo Apple OpenCore sans redémarrage.
- Avec mode verbose (
- Causes Racines (Breakthroughs Majeurs) :
- Comportement XNU &
debug=0x100:- En mode non-verbose,
debug=0x100désactive l'écran graphique de panique ("Your computer restarted...") et fige l'exécution sur le framebuffer existant (logo Apple). - En mode verbose (
-v), la routinedisplay_panictente d'écrire la trace sur le framebuffer console en cours d'initialisation par le pilote NVIDIA RTX 4070 SUPER (nvfb=1 nvaccel=1 nvfbheads=4). L'accès concurrent au contrôleur vidéo déclenche une double faute / triple faute CPU (reset immédiat). - Le phénomène sous-jacent est donc rigoureusement identique dans les deux cas.
- En mode non-verbose,
- Vulnérabilité d'Allocation Précoce dans
FakeLogStream:FakeLogStream::init()utilisait des allocations dynamiquesIOMallocZero(0x1000)etIOMallocZero(0x100).- Durant le boot très précoce (phase IOKit matching des kexts
Network-Root), le heap kernel peut refuser les allocations dynamiques, laissantp1nul.CCLogStream::shouldLogdéréférençait alors la page 0 (0x90(%rdi) -> 0x58(%rax)).
- Anti-Patron IOKit
registerService()sur le Contrôleur :QualcommWifi::start()appelait directementregisterService()sur le contrôleurIO80211Controlleret publiait des dictionnaires de médiums Ethernet (publishMediumDictionary).- Sous macOS Tahoe 26.x,
IO80211Controllerne publie aucun médium Ethernet, et ne doit JAMAIS appelerregisterService()sur lui-même. C'est l'interface Skywalk fille (attachInterface) qui enregistre son propre service. - L'appel de
registerService()sur le contrôleur déclenchait un matching prématuré parairportdetIO80211Familyavant que toute interface Skywalk ne soit attachée, provoquant une panique immédiate.
- Comportement XNU &
- Résolutions Techniques :
- Buffers Statiques BSS pour
FakeLogStream:- Remplacement de
IOMallocZeropar des tampons statiques BSSgP1Mem[0x1000]etgP2Mem[0x100]alignés sur 64 octets. - Garantis 100% alloués, mappés et initialisés dès le chargement du binaire Mach-O sans solliciter le heap.
- Remplacement de
- Persistance Réelle NVRAM (
logNvramStage) :- Implémentation directe via
IORegistryEntry::fromPath("/options", gIODTPlane)->setProperty("qcom_stage", tag). - Permet une traçabilité absolue des étapes atteintes même à travers une réinitialisation brutale (
nvram -p | grep qcom_stage).
- Implémentation directe via
- Suppression de
publishMediumDictionaryet Contrôle deregisterService:- Élimination des médiums réseau obsolètes.
- Contrôle strict de
registerService()via le boot-argqcom_register=1(désactivé par défaut) pour garantir un démarrage fluide et sécurisé jusqu'au bureau macOS.
- Buffers Statiques BSS pour
INC-12 : Vtables Tronquées à 38 Slots et Déréférencement 0x88 -> 0x48 Page 0 dans CCLogStream::logv
- Symptômes :
- Malgré la désactivation du matching automatique (
qcom_register=0), le contournement des accès MMIO (qcom_mmio=0), et l'utilisation de buffers statiques BSS, le système continue de redémarrer brutalement en mode verbose ("coupure de courant") ou de figer sur le logo Apple en mode non-verbose. - La variable NVRAM
qcom_stagen'apparaît pas au redémarrage car l'UEFI OpenCore write-protecte l'écriture des variables à l'exécution du noyau (general error).
- Malgré la désactivation du matching automatique (
- Causes Racines (Breakthroughs Majeurs & Rétro-ingénierie Noyau) :
- Asymétrie critique des Vtables (
CCLogStreamvsOSObject) :- Dans le Kernel Collection
BootKernelExtensions.kc,CCLogStreamhérite deCCStream, qui hérite lui-même deIOService. IOServicetotalise 266 slots virtuels (jusqu'à l'offset0x848).CCLogStreamy adjoint 14 méthodes virtuelles spécifiques (slots 266 à 279, jusqu'à0x8b8) :profileLoaded(),profileRemoved(),initWithPipeAndName(),setLevel(int)(0x868),getLevel()(0x870),setFlags(uint64_t)(0x878),getFlags()(0x880),setConsoleLogFlag(uint64_t)(0x8b0), etc. Sa vtable totale__ZTV11CCLogStreammesure exactement0x8d8octets (283 slots).- Or, notre classe
FakeLogStreamhéritait initialement deOSObject, dont la vtable ne compte que 38 slots (0x130octets). - Dès que le contrôleur stockait
fLoggerdans la variable globale du noyau_sIO80211ControllerGlobalLogger, tout appel àsetLevel(slot 269, offset0x868) ou àIOService::attach(slot 56, offset0x1c0dansIO80211ControllerMonitor::createReportersAndLegend) déréférençait la vtable plus de 1800 octets au-delà de sa limite, exécutant des données corrompues en Ring 0 et déclenchant un Triple Fault immédiat.
- Dans le Kernel Collection
- Déréférencement de Page 0 dans
CCLogStream::logv(movq 0x48(%rax), %rdi) :- Le désassemblage de
CCLogStream::logv(0xffffff80032fd7d4) a démontré qu'en cas de message filtré/supprimé (quand le niveau requis dépasse le seuil configurégP2Mem[0]), la routine tente d'incrémenter le compteur statistique via :movq 0x88(%rbx), %rax ; Lit streamData à l'offset +0x88 de FakeLogStream movq 0x48(%rax), %rdi ; Déréférence streamData->fCoreCaptureStreamReporter ! callq IOSimpleReporter::incrementValue ; Appelle slot 45 (+0x168) - À l'offset
+0x88, notre implémentation contenait une zone mise à zéro. Le déréférencement0x48(%rax)avec%rax == 0générait un Page Fault (#PF) Ring 0 à l'adresse virtuelle0x0000000000000048(Page 0).
- Le désassemblage de
- Troncature analogue sur
FakeFaultReporter:CCFaultReporterhérite deIOServiceavec une vtable de 269 slots (0x868octets) et une taille d'instance de0x90octets.FakeFaultReporterhéritait d'OSObject(38 slots).
- Asymétrie critique des Vtables (
- Résolutions Techniques Appliquées :
- Héritage
IOServiceet Déclaration des 14 SlotsCCLogStream:class FakeLogStream : public IOServiceimplémente les 14 méthodes virtuelles (slots 266 à 279) avec des retours sûrs no-op.- Sa vtable
__ZTV13FakeLogStreamatteint exactement0x8d8octets (283 slots), en conformité binaire stricte à 100% avec l'ABI Apple deBootKernelExtensions.kc.
- Topologie Mémoire 0x98 Octets et Bouchon Reporter Factice :
streamDataà l'offset+0x88pointe versgStreamMem[0x100].- L'offset
gStreamMem + 0x48pointe vers un objetgDummyReporterdont la table de pointeursgDummyReporterVtable[256]intercepte le slot 45 (getFirstElementIndexà+0x168) avecdummyReporterFunc(retourkIOReturnSuccess). p1à l'offset+0x90pointe versgP1Mem[0x1000], dont+0x58pointe versgP2Mem[0x100].- Taille totale de l'instance fixée à
0x98octets (static_assert(sizeof(FakeLogStream) == 0x98)).
- Mise en Conformité de
FakeFaultReporter:class FakeFaultReporter : public IOServiceavec padding0x90 - sizeof(IOService). Vtable de 269 slots (0x868octets) et taille0x90octets (static_assert(sizeof(FakeFaultReporter) == 0x90)).
- Héritage
INC-13 : Page Fault Kernel Trap CR2: 0x0000000000000658 dans ml_thread_policy / IONetworkController::start
- Symptômes :
- Bootloop instantané ("coupure de courant" / reboot immédiat en mode verbose, ou blocage sur logo Apple en mode normal).
- Trace matérielle extraite du log de panique (
/Library/Logs/DiagnosticReports/.contents.panic) :panic(cpu 2 caller 0xffffff80130d528a): Kernel trap at 0xffffff80130cc673, type = 14=page fault, CR2: 0x0000000000000658 Backtrace: _IOLockLock + 0x3 (avec %rdi == 0x658) _ml_thread_policy + 0x27 (_thread_policy_set_internal) IONetworkController::start(IOService*) + 0x1d4 (0xffffff80026f36b0) IO80211Controller::start(IOService*) + 0x41 (0xffffff8002291f71) QualcommWifi::start(IOService*) + 0x61
- Causes Racines (Breakthroughs Majeurs & Rétro-ingénierie Noyau) :
- Désassemblage critique de
IONetworkController::start(provider)(0xffffff80026f3650) :ffffff80026f365b: callq *0x688(%rax) ; Appelle this->getWorkLoop() (slot 209 = 0x688) ffffff80026f3661: movq %rax, 0x88(%rbx) ; Stocke dans ivars->workLoop ffffff80026f3676: movq 0x88(%rbx), %r15 ffffff80026f367d: movq (%r14), %rax ; r14 = provider (IOPCIDevice) ffffff80026f3683: callq *0x688(%rax) ; Appelle provider->getWorkLoop() ffffff80026f3689: cmpq %rax, %r15 ; Compare le workloop provider avec notre workloop ffffff80026f368c: je 0xffffff80026f36b0 ; SI IDENTIQUE, SAUTE ml_thread_policy ! ffffff80026f368e: movq 0x88(%rbx), %rdi ; Si différent, utilise notre workloop ffffff80026f3695: movq (%rdi), %rax ; vtable de notre workloop ffffff80026f3698: callq *0x128(%rax) ; APPELLE workloop->getThread() (slot 37 = 0x128) ffffff80026f369e: movq %rax, %rdi ; TRANSMET LE THREAD RETOURNÉ À ml_thread_policy ! ffffff80026f36ab: callq ml_thread_policy ; -> thread_policy_set_internal(thread, ...) - Origine du Pointeur Null et Incompatibilité d'ABI
IOWorkLoopvsIO80211WorkQueue:- Dans
QualcommWifi.cpp, l'implémentation initiale degetWorkLoop()redirigeait directement versgetWorkQueue()(un objetIO80211WorkQueue). - Le désassemblage de
IO80211WorkQueue::getThread()(0xffffff80022ee1ea) dansBootKernelExtensions.kca révélé son implémentation exacte :ffffff80022ee1ea <IO80211WorkQueue::getThread()>: pushq %rbp movq %rsp, %rbp xorl %eax, %eax ; RETOURNE CONSTAMMENT NULL (0) ! popq %rbp retq - Contrairement à un véritable
IOWorkLoopqui maintient un Mach thread dédié (0x30(%rdi)),IO80211WorkQueueretourne toujours0. thread_policy_set_internal(thread_t thread, ...)présume que le thread passé en paramètre est non-nul et exécuteIOLockLock(&thread->mutex)où le mutex de structurethreadse situe à l'offset+0x658.- Le déréférencement
IOLockLock(0 + 0x658)a déclenché le Page Fault#PFsurCR2: 0x0000000000000658.
- Dans
- Désassemblage critique de
- Résolutions Techniques Appliquées :
- Séparation Stricte des Interfaces
IOWorkLoopetIO80211WorkQueue:- Ajout d'une instance dédiée
IOWorkLoop *fWorkLoop;allouée viaIOWorkLoop::workLoop()dansQualcommWifi::createWorkLoop(). QualcommWifi::getWorkLoop()retournefWorkLoop ? fWorkLoop : (getProvider() ? getProvider()->getWorkLoop() : nullptr);.QualcommWifi::createWorkQueue()etgetWorkQueue()conserventIO80211WorkQueue *fWorkQueue;allouée viaIO80211WorkQueue::workQueue().
- Ajout d'une instance dédiée
- Séquence d'Initialisation et Nettoyage :
- Dans
QualcommWifi::start(provider): instanciation séquentielle decreateWorkLoop()puiscreateWorkQueue()avant l'invocation desuper::start(provider). - Dans
IONetworkController::start,workLoop->getThread()invoque désormais l'implémentation native deIOWorkLoop::getThread(movq 0x30(%rdi), %rax), fournissant unthread_tvalide àml_thread_policy, et permettant l'attachement propre duIOCommandGateviafWorkLoop->addEventSource. - Dans
QualcommWifi::stop()etQualcommWifi::free(): libération propre des deux objets viaOSSafeReleaseNULL(fWorkLoop)etOSSafeReleaseNULL(fWorkQueue).
- Dans
- Séparation Stricte des Interfaces
INC-14 : Page Fault Kernel Trap CR2: 0x0000000000000001 par Déroutement Vtable Virtuel dans IO80211SkywalkInterface::printDataPath
- Symptômes :
- Bootloop immédiat (triple fault / redémarrage instantané du kernel) dès l'activation de
qcom_skywalk=1. - Machine bloquée en boucle de redémarrage sans affichage de panic log si NVRAM inaccessible.
- Bootloop immédiat (triple fault / redémarrage instantané du kernel) dès l'activation de
- Causes Racines (Breakthroughs Majeurs & Rétro-ingénierie Noyau) :
- Déclarations
virtualindues dans les headers C++ (IO80211SkywalkInterface.h) :- Dans le header reconstruit
QualcommWireless/include/IOKit/80211/IO80211SkywalkInterface.h, les méthodesinit(IOService*, ether_addr*)etsetInterfaceRole(UInt32)étaient déclarées avec le mot-clévirtual. - Or, l'analyse binaire approfondie de
BootKernelExtensions.kcdémontre sans équivoque :IO80211SkywalkInterface::init(IOService*, ether_addr*)(0xffffff80022f56e2) est une fonction membre NON-VIRTUELLE (seuleinit(void)à l'offset0x88hérite d'OSObject::init).IO80211SkywalkInterface::setInterfaceRole(UInt32)(0xffffff80022f60f0) est une fonction membre NON-VIRTUELLE qui écrit directementroledansthis->ivars + 0x58.
- Dans le header reconstruit
- Collision catastrophique de table virtuelle (Vtable Desynchronization) :
- En déclarant ces deux méthodes comme
virtual, Clang leur a assigné deux nouveaux slots virtuels à la fin de la vtableIOSkywalkEthernetInterface, aux offsets+0xc50(slot 394) et+0xc58(slot 395). - Dans l'objet réel
IO80211SkywalkInterfaceinstancié par le noyau (BootKernelExtensions.kc), la vtable native possède 461 slots (0xe68octets) :- Le slot
+0xc50correspond en réalité àIO80211SkywalkInterface::wowEnabled(). - Le slot
+0xc58correspond àIO80211SkywalkInterface::printDataPath(userPrintCtx*).
- Le slot
- L'appel
fSkywalkInterface->setInterfaceRole(1)a émis l'instruction assembleurcallq *0xc58(%rax). - Le noyau a donc exécuté
printDataPathavec%rdi = fSkywalkInterfaceet%rsi = 0x1(le rôle 1 transmis comme pointeuruserPrintCtx*). - Lors du premier déréférencement mémoire de
%rsià l'adresse0x1, le processeur a déclenché un Page Fault#PFsurCR2: 0x0000000000000001, entraînant un crash critique et un redémarrage instantané (triple fault).
- En déclarant ces deux méthodes comme
- Déclarations
- Résolutions Techniques Appliquées :
- Suppression du Qualificatif
virtual:- Retrait strict de
virtualdevantbool init(IOService *controller, ether_addr *mac);etvoid setInterfaceRole(UInt32 role);dansQualcommWireless/include/IOKit/80211/IO80211SkywalkInterface.h.
- Retrait strict de
- Vérification Binaire de l'Assembleur Généré :
- Le désassemblage de
build/Release/QualcommWifi.kextatteste de la conversion en appels directs non-virtuels :0x2d9e: callq __ZN23IO80211SkywalkInterface4initEP9IOServiceP10ether_addr 0x2dc3: callq __ZN23IO80211SkywalkInterface16setInterfaceRoleEj 0x2de1: callq *0x670(%rax) ; fSkywalkInterface->attach(this) 0x2e08: callq *0x5c0(%rax) ; fSkywalkInterface->start(this) 0x2e31: callq *0xdc8(%rax) ; this->attachInterface(fSkywalkInterface, nullptr) 0x2e4f: callq *0x5b0(%rax) ; fSkywalkInterface->registerService()
- Le désassemblage de
- Conformité des Dépendances :
kmutil libraries -p build/Release/QualcommWifi.kextvalide la résolution intégrale des symboles auprès deBootKernelExtensions.kc.
- Suppression du Qualificatif
INC-15 : Kernel Trap / Triple Fault dans initIvars & Incohérence d'Attachement Skywalk
- Symptômes :
- Bootloop persistant et redémarrage brutal de la machine lors de la phase d'instanciation de
IO80211SkywalkInterface. - Machine incapable de franchir le stade d'attachement réseau natif sans désactivation manuelle de
QualcommWifi.kextdans OpenCore.
- Bootloop persistant et redémarrage brutal de la machine lors de la phase d'instanciation de
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Piège du tas non initialisé (Uninitialized Heap Trap) dans
initIvars(0xffffff80022f520c) :_ZN23IO80211SkywalkInterfacenwEm(0x128)alloue la mémoire de l'objet viakalloc_type. Le noyau ne met pas la mémoire à zéro.- Dans le constructeur et dans
IO80211SkywalkInterface::initIvars, le noyau évalue :0xffffff80022f5215: cmpq $0x0, 0x120(%rdi) 0xffffff80022f521d: je 0xffffff80022f5265 ; Alloue ivars si NULL ... 0xffffff80022f5244: movq 0x120(%rbx), %rdi ; Garbage pointer ! 0xffffff80022f524b: movl $0xf0, %esi 0xffffff80022f5250: movl $0xf0, %ecx 0xffffff80022f5255: xorl %edx, %edx 0xffffff80022f5257: callq memset ; memset(garbage, 0, 0xf0) -> Page Fault Ring 0 ! - Si
mem + 0x120contient des résidus non nuls de la mémoire du tas, le noyau tente unmemsetde0xf0octets sur cette adresse invalide, causant un Crash Kernel Page Fault / Triple Fault instantané.
- Header erroné
network/IOSkywalkEthernetInterface.h:- Le fichier
QualcommWireless/include/IOKit/network/IOSkywalkEthernetInterface.hdéclarait de manière erronéeclass IOSkywalkEthernetInterface : public IOEthernetController. - Dans l'architecture Apple Skywalk,
IOSkywalkEthernetInterfacehérite en réalité deIOSkywalkNetworkInterface->IOSkywalkInterface->IOService, sans aucun lien d'héritage avecIOEthernetController.
- Le fichier
- Ordre d'attachement et second argument du contrôleur (Slot 441) :
- Dans
AppleBCMWLANCore::createSkywalkInterface(0xffffff80016aaa8a), Apple invoquethis->attachInterface(fSkywalkInterface, this)(slot 441 d'IO80211Controller) avantfSkywalkInterface->attach(this)etfSkywalkInterface->start(this). - De plus, le second paramètre transmis doit être
this(le contrôleur), ce qui renseignecontroller->ivars + 0x188(fPrimaryInterface). L'ancien code passaitnullptraprèsstart().
- Dans
- Publication prématurée via
registerService():- Appeler
registerService()surfSkywalkInterfacepublie le service auprès du système IOKit matching et d'airportdavant que les queues Skywalk et les pools de buffers ne soient négociés, provoquant une panique réseau immédiate.
- Appeler
- Piège du tas non initialisé (Uninitialized Heap Trap) dans
- Résolutions Techniques Appliquées :
- Remise à zéro explicite du bloc mémoire avant construction :
void *mem = _ZN23IO80211SkywalkInterfacenwEm(0x128); if (!mem) return nullptr; bzero(mem, 0x128); _ZN23IO80211SkywalkInterfaceC1EPK11OSMetaClass(mem, &_ZN23IO80211SkywalkInterface10gMetaClassE); - Correction des inclusions d'en-têtes Skywalk :
- Redirection du faux header vers
<IOKit/skywalk/IOSkywalkEthernetInterface.h>et ajout dutypedef uint32_t kern_packet_svc_class_t;dansIOSkywalkTypes.h.
- Redirection du faux header vers
- Ordonnancement conforme du cycle d'attachement Skywalk :
- Invocation de
attachInterface(fSkywalkInterface, this)au slot 441 avantattach(this)etstart(this).
- Invocation de
- Contrôle granulaire d'exécution par palier via le boot-arg
qcom_step:- Introduction du palier
qcom_step(par défaut6= exécution complète destart(this)sans appeler prématurémentregisterService()). - Validation binaire par désassemblage et validation
kmutil librariesà 100%.
- Introduction du palier
- Remise à zéro explicite du bloc mémoire avant construction :
INC-16 : Bootloop par Démarrage Complet Skywalk & Piège de Libération free()
- Symptômes :
- Bootloop persistant et redémarrage spontané de la machine lors du chargement de
QualcommWifi.kext(commit506df09). - Nécessité de désactiver manuellement
QualcommWifi.kextdans OpenCore pour atteindre le bureau macOS.
- Bootloop persistant et redémarrage spontané de la machine lors du chargement de
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Piège de Libération sans
initIvarsdansIO80211SkywalkInterface::free()(0xffffff80022f6184) :- Le désassemblage de
IO80211SkywalkInterface::freerévèle :0xffffff80022f618e: movq 0x120(%rdi), %rax ; rax = ivars 0xffffff80022f6195: movq 0xb8(%rax), %rdi ; Déréférencement direct rax + 0xb8 sans vérification NULL ! - Si l'interface est allouée (
operator new + C1) mais queinitIvarsn'a pas encore été invoqué,0x120estNULL. Tout appel subséquent àrelease()/free()déclenche un Kernel Page Fault instantané surCR2: 0x00000000000000b8!
- Le désassemblage de
- Démarrage complet prématuré par défaut (
skywalk_step=6) :- Dans le commit
506df09,skywalk_stepétait fixé à 6 par défaut. Lors du boot standard (sans boot-arg explicite), le code franchissait d'un coup l'allocation, l'initialisation, le rôle, l'attachement au contrôleur (slot 441), l'attachement IORegistry ET l'appelfSkywalkInterface->start(this). - Dans
start(this), Apple initialiseCCPipe,CCStream,IO80211PeerManager, la gestion d'énergiejoinPMtree/registerPowerDriver/changePowerStateTo(0),IO80211Glue,WCLGlue, etIO80211MacAddressAgent. Un échec non géré dans ces sous-systèmes sans isolation pas-à-pas provoquait le bootloop.
- Dans le commit
- Nettoyage non gardé dans
stop():QualcommWifi::stopinvoquait inconditionnellementdetachInterface,fSkywalkInterface->stop(this)etfSkywalkInterface->detach(this). Or,IO80211SkywalkInterface::stopdéréférence égalementivars + 0x80(movq 0x80(%rax), %rdi), causant un crash en cascade si l'interface n'était pas entièrement démarrée.
- Piège de Libération sans
- Résolutions Techniques Appliquées :
- Invocation explicite et systématique de
initIvars()dès la création :- Déclaration et appel de
_ZN23IO80211SkywalkInterface9initIvarsEv(mem)danscreateSkywalkInterfacedès l'allocation, garantissant queivarsest toujours non-nul et immunisé contre tout#PFdansfree().
- Déclaration et appel de
- Réduction sécurisée du palier par défaut à l'étape 1 (
skywalk_step=1) :int skywalk_step = 1;par défaut, permettant de valider isolément la stabilité de la création de l'objet avant d'activer les étapes suivantes viaqcom_step.
- Protection conditionnelle du cycle de vie dans
stop()via des drapeaux d'état :- Ajout de
fSkywalkCtrlAttached,fSkywalkAttached,fSkywalkStartedpour ne détacher ou arrêter que ce qui a été effectivement attaché ou démarré avec succès.
- Ajout de
- Validation intégrale de la chaîne de compilation :
build_wifi.shvalidé à 100% aveckmutil librariesauprès deBootKernelExtensions.kc.
- Invocation explicite et systématique de
INC-17 : Page Fault sur Table de Propriétés NULL par Court-Circuit de super::init(0)
- Symptômes :
- Bootloop lors du passage au stade 5 (commit
79d7726), alors que le stade 1 (commitb7e32db) démarrait impeccablement jusqu'au bureau macOS.
- Bootloop lors du passage au stade 5 (commit
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Analyse de la condition d'initialisation dans
IO80211SkywalkInterface::init(0xffffff80022f56e2) :- Le désassemblage précis montre la logique suivante :
0xffffff80022f5710: cmpq $0x0, 0x120(%r14) ; ivars est-il NULL ? 0xffffff80022f5718: jne 0xffffff80022f5733 ; Si NON-NULL, SAUTE super::init(0) ! 0xffffff80022f571a: movq 0x15aa07(%rip), %rax ; __ZTV26IOSkywalkEthernetInterface 0xffffff80022f5724: movq %r14, %rdi 0xffffff80022f5729: callq *0x258(%rax) ; super::init(0) -> IOService::init() 0xffffff80022f5733: movq %r14, %rdi 0xffffff80022f5736: callq initIvars ; initIvars() - Dans le commit
b7e32db/79d7726, nous appelionsinitIvarsdèscreateSkywalkInterface()pour éviter le piège defree(). - Par conséquent, lors de l'appel à
fSkywalkInterface->init(...),mem + 0x120était déjà non-nul ! - Le saut conditionnel
jne 0x5733a donc été emprunté, court-circuitant totalement l'appelsuper::init(0)(IOService::init()) !
- Le désassemblage précis montre la logique suivante :
- Conséquence fatale sur
IOServiceet les tables de propriétés :- Sans
IOService::init(), l'infrastructure de base de l'objet (dictionnaire de propriétésfPropertyTable, table de registre IORegistry, verrous de synchronisation) n'était pas allouée. - Dès l'étape suivante (
setInterfaceRole(1)), le noyau exécutaitsetPropertyHelper(this, "IO80211InterfaceRole", ...)qui tente d'écrire dans la table de propriétés NULL de l'objet, déclenchant un Kernel Page Fault instantané !
- Sans
- Analyse de la condition d'initialisation dans
- Résolutions Techniques Appliquées :
- Suppression de l'appel prématuré à
initIvarsdanscreateSkywalkInterface:createSkywalkInterfacene fait plus queoperator new+ mise à zéro explicite (bzero(0x128)) + constructeur C1.mem + 0x120reste strictement NULL.fSkywalkInterface->init(...)constate0x120 == 0, invoque légitimementsuper::init(0)pour initialiserIOService, puis invoqueinitIvars().- L'objet est alors 100% cohérent à la fois côté
IOServiceet côtéIO80211SkywalkInterface.
- Calibrage sécurisé du palier par défaut sur l'étape 2 (
skywalk_step=2) :- Permet de valider isolément que
createSkywalkInterface+init(this, mac)démarre avec succès sans risque d'effets de bord ultérieurs.
- Permet de valider isolément que
- Validation binaire et dépendances :
- Compilation
build_wifi.shet validationkmutil librariesà 100%.
- Compilation
- Suppression de l'appel prématuré à
INC-18 : Page Fault CR2: 0xb8 dans free() et Manque de boot-arg OpenCore pour le Stade 1
- Symptômes :
- Bootloop sur le stade 2 (commit
0519af4) lorsqueinitIvarsa été retiré decreateSkywalkInterface. - L'utilisateur n'a pas pu forcer le retour au stade 1 car le boot-arg
qcom_step=1n'était pas présent dans leconfig.plistd'OpenCore.
- Bootloop sur le stade 2 (commit
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Déréférencement aveugle dans
IO80211SkywalkInterface::free()(0xffffff80022f6184) :- Le désassemblage précis de
free()révèle :0xffffff80022f618e: mov rax, qword ptr [rdi + 0x120] ; rax = ivars 0xffffff80022f6195: mov rdi, qword ptr [rax + 0xb8] ; [ivars + 0xb8] (sans test rax!=0 !) - Si
init(...)échoue, retournefalseou sifree()est déclenché alors que0x120est NULL, le processeur exécutemov rdi, [0 + 0xb8], provoquant un#PFimmédiat surCR2: 0x00000000000000b8. - Le stade 1 (commit
b7e32db) avait démarré jusqu'au bureau avec un succès total précisément parce queinitIvarsallouait0x120dèscreateSkywalkInterface, immunisant l'objet contre tout crash de libération.
- Le désassemblage précis de
- Absence du boot-arg
qcom_stepdans OpenCore :- Le
config.plistne contenait pasqcom_step, forçant le pilote à utiliser la valeur hardcodée en C++ (skywalk_step = 2).
- Le
- Déréférencement aveugle dans
- Résolutions Techniques Appliquées :
- Restauration de
initIvarsdanscreateSkywalkInterface:- Garantit que
mem + 0x120est toujours non-nul, éliminant définitivement le piège defree()(CR2: 0xb8).
- Garantit que
- Rétablissement du palier par défaut sur l'étape 1 (
skywalk_step = 1) :- Garantit le boot immédiat jusqu'au bureau macOS comme au commit
b7e32db.
- Garantit le boot immédiat jusqu'au bureau macOS comme au commit
- Injection explicite de
qcom_step=1dans lesboot-argsOpenCore (config.plist) :- Ajouté dans
/Volumes/EFI/EFI/OC/config.plistsous NVRAM7C436110-AB2A-4BBB-A880-FE41995C9F82->boot-args. - Permet à l'utilisateur de modifier le palier directement depuis OpenCore ou NVRAM.
- Ajouté dans
- Déploiement et synchronisation EFI :
QualcommWifi.kextcompilé et copié sur/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext(MD5:388636543393e9cf34e5816e4a011cce).- Vérifié
QualcommWifi.kextactivé (Enabled: True) dansconfig.plist.
- Restauration de
INC-19 : Découverte de IO80211SkywalkInterface::init() (Sans Arguments) et Résolution Définitive de la Table de Propriétés pour le Stade 3
- Symptômes & Constats :
- Succès total du Stade 2 (
qcom_step=2) :- Démarrage 100% propre jusqu'au bureau macOS avec
QualcommWifi.kext(1.1.0) etQualcommWireless.kext(1.1.0) chargés simultanément. - Journal noyau (
/usr/bin/log) confirmant l'exécution parfaite :IO80211SkywalkInterface::init with controller start IO80211SkywalkInterface::initIvars complete IO80211SkywalkInterface::init complete - IORegistry confirmant l'attachement propre de l'objet
QualcommWifiavec ses pipes CoreCapture (CCLogPipeUserClient).
- Démarrage 100% propre jusqu'au bureau macOS avec
- Succès total du Stade 2 (
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Désassemblage exhaustif de
IO80211SkywalkInterface::init()sans arguments (0xffffff80022f568e) :Cette méthode effectue exactement la chaîne complète attendue :0xffffff80022f56b5: mov rax, qword ptr [rip + 0x15aa6c] ; vtable de IOSkywalkEthernetInterface 0xffffff80022f56bc: mov rdi, rbx 0xffffff80022f56bf: xor esi, esi ; OSDictionary* = NULL 0xffffff80022f56c1: call qword ptr [rax + 0x258] ; IOSkywalkEthernetInterface::init(nullptr) 0xffffff80022f56c7: test al, al 0xffffff80022f56c9: je 0x22f56d9 0xffffff80022f56cb: mov rdi, rbx 0xffffff80022f56d4: jmp 0xffffff80022f520c ; jmp initIvars() !- Appel de
IOSkywalkEthernetInterface::init(nullptr)->IOSkywalkNetworkInterface::init(nullptr)->IOService::init(nullptr). - Allocation de
fPropertyTable,fRegistryTable, etfLocksurIOService. - Allocation du verrou interne à l'offset
+0x118(IOLockAlloc). - Appel terminal à
initIvars()pour allouer la structureivarsà l'offset+0x120.
- Appel de
- Comportement de
IO80211SkywalkInterface::setInterfaceRole(UInt32 role)(0xffffff80022f60f0) :- Écrit le rôle dans
ivars + 0x58(1 = kIO80211InterfaceRoleInfra). - Appelle
IOService::SetProperties()avec la clé"IO80211InterfaceRole"et valeur"Infra". - Grâce à l'appel préalable à
IO80211SkywalkInterface::init(),fPropertyTableest garantie allouée et valide, éliminant totalement le risque de Page Fault lors desetInterfaceRole.
- Écrit le rôle dans
- Désassemblage exhaustif de
- Résolutions Techniques Appliquées :
- Intégration du symbole exporté
_ZN23IO80211SkywalkInterface4initEvdanscreateSkywalkInterface:- Invoqué immédiatement après le constructeur C1 pour initialiser la chaîne complète d'héritage (
IOService+IOSkywalkEthernetInterface+ lock+0x118+ivars+0x120).
- Invoqué immédiatement après le constructeur C1 pour initialiser la chaîne complète d'héritage (
- Passage au palier 3 (
qcom_step=3) dans OpenCore :boot-argsmis à jour dans/Volumes/EFI/EFI/OC/config.plist:qcom_step=3pour exécuterfSkywalkInterface->setInterfaceRole(1).
- Compilation et déploiement :
build_wifi.shvalidé à 100% aveckmutil libraries.- Kext déployé sur
/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext.
- Validation Live du Stade 3 (
qcom_step=3) :- Boot 100% propre jusqu'au bureau macOS.
QualcommWifi.kext(1.1.0) etQualcommWireless.kext(1.1.0) chargés simultanément.- Initialisation complète et configuration du rôle
kIO80211InterfaceRoleInfra(IO80211InterfaceRole= "Infra") sans incident.
- Validation Live du Stade 4 (
qcom_step=4) :- Boot 100% propre jusqu'au bureau macOS avec les deux kexts chargés.
- Exécution nominale de
attachInterface(fSkywalkInterface, this)(slot 441 deIO80211Controller). IONetworkController::prepare()etIOEthernetController::publishProperties()exécutés avec succès.- Publication dans l'IORegistry des propriétés réseau Ethernet fondamentales (
IOMACAddress = <00037f123456>,IOMaxPacketSize = 1518,IOMinPacketSize = 64,IOPacketFilters). - Liaison établie dans les structures internes du contrôleur (
controller->ivars + 0x188 = fSkywalkInterface).
- Validation Live du Stade 5 (
qcom_step=5) :- Boot 100% propre jusqu'au bureau macOS avec les deux kexts chargés.
- Confirmation dans l'IORegistry de l'attachement effectif de
IO80211SkywalkInterfacesous l'arborescenceQualcommWifi. - Propriété
"IO80211InterfaceRole" = "Infrastructure"vérifiée sur le nœudIO80211SkywalkInterface. - L'arborescence matérielle IOKit complète est désormais formée (
QualcommWifi->CCPipe,CCLogStream,IO80211SkywalkInterface).
- Armement du Stade 6 (
qcom_step=6) :boot-argsconfiguré surqcom_step=6dans/Volumes/EFI/EFI/OC/config.plistpour démarrer le sous-système Skywalk (fSkywalkInterface->start(this): initialisation PMtree, PeerManager, IO80211Glue et association au WorkQueue).
- Intégration du symbole exporté
INC-20 : Analyse de l'Échec du Stade 6 (qcom_step=6) & Résolution par Implémentation du Power Management IOKit
- Date : 18 Octobre 2026
- Composant :
QualcommWifi.kext/IO80211SkywalkInterface::start(IOService *controller) - Symptômes & Constats :
- Lors du test initial de
qcom_step=6, la machine a rencontré un redémarrage en boucle (bootloop). - L'utilisateur est revenu temporairement sur
qcom_step=5, qui a redémarré de manière 100% stable jusqu'au bureau avecIO80211SkywalkInterfaceparfaitement attaché (IO80211InterfaceRole= "Infrastructure").
- Lors du test initial de
- Causes Racines (Breakthroughs & Rétro-ingénierie du Kernel Collection) :
- Analyse de la séquence interne de
IO80211SkywalkInterface::start(IOService *provider)(0xffffff80022f5792) :- Le désassemblage Capstone direct de
BootKernelExtensions.kca révélé les étapes précises :- Vérification
ivars + 0x28 == NULL(protection contre appel réentrant : panic"Calling start twice in a row is not allowed"). - Appel de
super::start(provider)(IOService::start(provider)). - Cast dynamique du contrôleur via
OSDynamicCast(IO80211Controller, provider). - Récupération et conservation de
controller->getWorkQueue(). - Création des canaux CoreCapture
CCPipeet flux de journalisationCCStream. - Instanciation du
IO80211PeerManagerviacreatePeerManager()(0xffffff80022f7be6) qui alloue le verrou récursifcontroller->allocIO80211RecursiveLock()(slot 435 / offset+0xd98) et leIO80211CommandGate. - Étape critique de gestion de l'énergie (Power Management) :
0xffffff80022f5c6c: mov rax, qword ptr [r12] 0xffffff80022f5c70: mov rdi, r12 0xffffff80022f5c73: call qword ptr [rax + 0x790] ; fSkywalkInterface->PMinit() (slot 242) 0xffffff80022f5c79: mov rax, qword ptr [r15] ; r15 = QualcommWifi (contrôleur) 0xffffff80022f5c7c: mov rdi, r15 0xffffff80022f5c7f: mov rsi, r12 ; rsi = fSkywalkInterface 0xffffff80022f5c82: call qword ptr [rax + 0x7a0] ; QualcommWifi->joinPMtree(fSkywalkInterface) (slot 244) 0xffffff80022f5c88: mov rax, qword ptr [r12] 0xffffff80022f5c8c: lea rdx, [rip + 0x19564d] ; tableau des 2 états IOPMPowerState 0xffffff80022f5c93: mov ecx, 2 0xffffff80022f5c98: mov rdi, r12 0xffffff80022f5c9b: mov rsi, r12 0xffffff80022f5c9e: call qword ptr [rax + 0x7a8] ; fSkywalkInterface->registerPowerDriver(...) (slot 245) 0xffffff80022f5ca4: mov esi, 1 0xffffff80022f5ca9: mov rdi, r12 0xffffff80022f5cac: call 0xffffff8000abbb80 ; changePowerStateToPriv(1) 0xffffff80022f5cb1: mov rdi, r12 0xffffff80022f5cb4: xor esi, esi 0xffffff80022f5cb6: call 0xffffff8000abbaf0 ; changePowerStateTo(0)
- Vérification
- Le désassemblage Capstone direct de
- Origine du Panic / Page Fault : Absence de PM sur le Contrôleur Parent :
- À
0xffffff80022f5c82,IO80211SkywalkInterfaceinvoqueQualcommWifi->joinPMtree(fSkywalkInterface)afin d'intégrer l'interface réseau dans l'arbre d'alimentation du pilote hôte. - Dans le noyau XNU (
0xffffff8000ab9a00),IOService::joinPMtreetransmet àgIOPMrootDomain->joinPMtree(QualcommWifi, fSkywalkInterface). - Cette méthode manipule obligatoirement le contexte de puissance du parent (
QualcommWifi->pm_varsà l'offset+0x60). - Puisque
QualcommWifin'avait jamais exécutéPMinit(), son pointeurpm_varsétait nul (0x0), provoquant un déréférencement immédiat de pointeur nul et un Kernel Trap Page Fault (CR2: 0x00000000000000d8/_IOLockLock).
- À
- Dangerosité du gestionnaire d'erreur de
QualcommWifi.cpp:- En cas de retour
falsedestart(), l'ancien code de repli appelaitdetachInterface(fSkywalkInterface, false)(qui est une méthode d'IOEthernetControllerattendant unIONetworkInterface*) et libéraitfSkywalkInterfacealors qu'il était déjà enregistré dans l'IORegistry et danscontroller->ivars + 0x188, garantissant un panic même sistart()échouait proprement.
- En cas de retour
- Analyse de la séquence interne de
- Résolutions Techniques Appliquées :
- Intégration du cycle de vie Power Management complet dans
QualcommWifi:- Ajout de
PMinit()etjoinPMtree(provider)dansQualcommWifi::start()dès l'ouverture du périphérique PCIe. - Enregistrement du modèle standard d'alimentation à 2 états (State 0: Off, State 1: On /
IOPMDeviceUsable | IOPMPowerOn) viaregisterPowerDriver(this, sPowerStates, 2)et transition initiale vers l'état actif (changePowerStateToPriv(1)). - Surcharge de
setPowerState(unsigned long powerStateOrdinal, IOService *whatDevice)dansQualcommWifi.hppetQualcommWifi.cpprenvoyantIOPMAckImplied. - Appel de
PMstop()dansQualcommWifi::stop().
- Ajout de
- Sécurisation du gestionnaire d'erreur de
start():- Suppression de tous les appels destructeurs
detachInterface()etOSSafeReleaseNULL()dans les branches d'erreur deskywalk_start,skywalk_attachetskywalk_init: l'interface est préservée intacte pour analyse des journaux système sans déstabiliser le noyau.
- Suppression de tous les appels destructeurs
- Validation et Déploiement :
- Recompilation avec
./build_wifi.sh: validation à 100% aveckmutil librariescontreBootKernelExtensions.kc. - Déploiement du nouveau kext sur
/Volumes/EFI/EFI/OC/Kexts/QualcommWifi.kext. - Configuration du boot-arg dans
/Volumes/EFI/EFI/OC/config.plist:qcom_step=6prêt pour test en direct.
- Recompilation avec
- Intégration du cycle de vie Power Management complet dans
Postmortem 16 : Diagnostic par Persistance NVRAM du Crash Early-Boot (Régression de l'Alias WorkLoop et Isolation Chirurgicale du Stade 6)
- Date : 18-19 Octobre 2026
- Cible : macOS Tahoe 26.x (Darwin 25.6.0 x86_64), AMD Ryzen 7 5800X3D,
QualcommWifi.kext(WCN78500x17cb:0x1107) - Symptômes & Constats :
- Après avoir testé le commit
e0414a9avecqcom_step=6puis en revenant surqcom_step=5, la machine est restée bloquée en bootloop immédiat dès les premières lignes du noyau XNU. - La pause visuelle de 10 secondes prévue dans
qcom_substep=1ne s'exécutait même plus. - L'utilisateur a dû désactiver temporairement
QualcommWifi.kext(Enabled: false) via Linux pour redémarrer sur le bureau macOS. - Interrogation en direct de la mémoire NVRAM macOS (
nvram -p | grep qcom) :qcom_stage createWorkQueue boot-args ... qcom_step=5 qcom_substep=1
- Après avoir testé le commit
- Causes Racines (Breakthroughs & Rétro-ingénierie du Noyau) :
- Origine du Panic Instantané : Régression de l'Alias
createWorkLoop():- Dans le commit
e0414a9, une tentative d'unification avait été faite pour quecreateWorkLoop()appellecreateWorkQueue()et quegetWorkLoop()retourne(IOWorkLoop*)fWorkQueue. - Dans la hiérarchie IOKit,
IONetworkController::start(provider)invoquecreateWorkLoop()puisgetWorkLoop(), s'attendant impérativement à une instance allouée viaIOWorkLoop::workLoop(). IO80211WorkQueuen'hérite pas deIOWorkLoop: leurs tables virtuelles (vtables) sont incompatibles.- Dès le déréférencement de
fWorkLoopdanssuper::start(provider)(ligne 706 deQualcommWifi.cpp), le noyau a subi un Kernel Panic immédiat avant même d'atteindre le Stade 1 ou le Stade 5, et avant leIOSleep(10000). - La variable NVRAM
qcom_stagefigée surcreateWorkQueuea fourni la preuve irréfutable du lieu exact du crash.
- Dans le commit
- Confirmation du Comportement Matériel de la NVRAM UEFI :
- La NVRAM UEFI réside physiquement dans la puce SPI Flash de la carte mère.
- Elle est totalement non-volatile : un reboot, une coupure de courant ou un démarrage sous Linux ne supprime aucunement les propriétés NVRAM.
- L'impossibilité d'écrire dans un fichier disque en early boot : au chargement des kexts de boot, les volumes APFS sont scellés cryptographiquement en lecture seule (snapshot système) et le VFS BSD n'est pas encore monté.
PEWriteNVRAMPropertyconstitue ainsi l'unique canal de diagnostic persistant bas-niveau disponible.
- Cartographie Précise du Chemin d'Exécution entre
getWorkQueue()etallocIO80211RecursiveLock()dansIO80211SkywalkInterface::start():- Le désassemblage Capstone complet a révélé la séquence exacte exécutée par
IO80211SkywalkInterface::start(IOService *provider)(0xffffff80022f5792) :0xffffff80022f5867:call [rax + 0xc68]-> 1er appel àQualcommWifi::getWorkQueue().0xffffff80022f5888:fWorkQueue->retain().0xffffff80022f59f4:call CCPipe::withOwnerNameCapacity(CoreCapture data/log pipe).0xffffff80022f5a62:call IO80211Controller::io80211isDebuggable(bool*).0xffffff80022f5a8f:call CCStream::withPipeAndName.0xffffff80022f5abc:call CCPipe::startPipe.0xffffff80022f5bca:call IO80211SkywalkInterface::createPeerManager()(0xffffff80022f7be6).- Dans
IO80211PeerManager::initWithInterface()(0xffffff8002246c88) :0xffffff8002246ed5:call [rax + 0xc68]-> 2ème appel àQualcommWifi::getWorkQueue().0xffffff8002246ee6:fWorkQueue->retain().0xffffff8002246f04:call [rax + 0xd88]->QualcommWifi::allocIO80211RecursiveLock().
- Le désassemblage Capstone complet a révélé la séquence exacte exécutée par
- Origine du Panic Instantané : Régression de l'Alias
- Résolutions Techniques Appliquées :
- Restauration de l'Allocation Dédiée
IOWorkLoop:- Réintégration de
fWorkLoop = IOWorkLoop::workLoop()danscreateWorkLoop()et du retour defWorkLoopdansgetWorkLoop().
- Réintégration de
- Suppression de
kprintf():- Élimination de
kprintfdelogNvramStage()pour prévenir tout blocage spinlock/MSR avant l'attachement de la console série.
- Élimination de
- Traçage Numéroté Granulaire dans la NVRAM :
getWorkQueue()incrémente un compteur statique et enregistrecb_getWorkQueue_1,cb_getWorkQueue_2dans la NVRAM.allocIO80211RecursiveLock()enregistrecb_allocLockdans la NVRAM.
- Validation :
- Compilation réussie sans avertissement (
./build_wifi.sh), validationkmutil librariesà 100% contreBootKernelExtensions.kc.
- Compilation réussie sans avertissement (
- Restauration de l'Allocation Dédiée