Condividi tramite


Le migliori pratiche per lo sviluppo di driver del team Surface

Introduzione

Queste linee guida per lo sviluppo di driver sono state sviluppate in molti anni dagli sviluppatori di driver in Microsoft. Nel corso del tempo, quando i conducenti si comportavano male e le lezioni sono state apprese, queste lezioni sono state acquisite e sviluppate in modo da essere questo insieme di indicazioni. Queste procedure consigliate vengono usate dal team hardware di Microsoft Surface per sviluppare e gestire il codice del driver di dispositivo che supporta le esperienze hardware di Surface univoche.

Come qualsiasi set di linee guida, ci saranno eccezioni legittime e approcci alternativi che saranno ugualmente validi. È consigliabile incorporare queste linee guida nei propri standard di sviluppo o usarle per avviare linee guida specifiche del dominio per l'ambiente di sviluppo e i requisiti specifici.

Errori comuni eseguiti dagli sviluppatori di driver

Gestione delle operazioni di I/O

  1. Accesso ai buffer recuperati da IOCTLs senza convalidare la lunghezza. Vedere Errore durante la verifica delle dimensioni dei buffer.
  2. Esecuzione del blocco di I/O nel contesto di un thread utente o di un contesto di thread casuale. Consultare Introduzione agli oggetti Dispatcher kernel.
  3. Invio di operazioni di I/O sincrone a un altro driver senza timeout. Vedere Invio di richieste di I/O in modo sincrono.
  4. Uso di IOCTLs neither-io senza comprendere le implicazioni per la sicurezza. Vedere Uso di I/O né bufferizzato né diretto.
  5. Non controllando lo stato restituito di WdfRequestForwardToIoQueue o non gestendo correttamente l'errore e causando l'abbandono di WDFREQUESTs.
  6. Mantenere WDFREQUEST all'esterno della coda in uno stato non annullabile. Vedere Gestione delle code di I/O, completamento delle richieste di I/O e annullamento delle richieste di I/O.
  7. Tentativo di gestire l'annullamento usando la funzione Mark/UnmarkCancelable invece di usare IoQueues. Consulta Oggetti della coda del framework.
  8. Non conoscendo la differenza tra le operazioni di pulizia e chiusura dell'handle di file. Vedere Errori nella gestione delle operazioni di pulizia e chiusura.
  9. Ignorare le potenziali ricorsioni con il completamento di I/O e la reinviazione dalla routine di completamento.
  10. Non essendo espliciti sugli attributi di risparmio energia di WDFQUEUEs. Non documentando chiaramente la scelta del risparmio energia. Questa è la causa principale del controllo bug 0x9F:DRIVER_POWER_STATE_FAILURE nei driver WDF. Quando il dispositivo viene rimosso, il framework pulisce I/O dalla coda gestita dal risparmio energetico e dalla coda non gestita dal risparmio energia in diverse fasi del processo di rimozione. I codici non gestiti dall'alimentazione vengono eliminati quando viene ricevuto l'IRP_MN_REMOVE_DEVICE finale. Pertanto, se si mantiene l'I/O in una coda non gestita dall'alimentazione, è consigliabile eliminare esplicitamente l'I/O nel contesto di EvtDeviceSelfManagedIoFlush per evitare deadlock.
  11. Non seguendo le regole di gestione dei runtime di integrazione. Vedere Errori nella gestione delle operazioni di pulizia e chiusura.

Sincronizzazione

  1. Blocco per il codice che non richiede protezione. Non tenere premuto un blocco per un'intera funzione quando è necessario proteggere solo un numero ridotto di operazioni.
  2. Avviso ai conducenti con blocchi attivi. Si tratta delle cause principali dei deadlock.
  3. Uso di primitive interlocked per creare uno schema di blocco invece di usare primitive di blocco appropriate fornite dal sistema, ad esempio mutex, semaforo e spinlock. Vedere Introduzione agli oggetti Mutex, Oggetti semafori e Introduzione ai blocchi spin.
  4. Uso di uno spinlock in cui un tipo di blocco passivo sarebbe più appropriato. Vedere Mutex veloci e mutex sorvegliati e oggetti evento. Per altre prospettive sui blocchi, vedere l'articolo OSR - Stato della sincronizzazione.
  5. Acconsentire esplicitamente al modello a livello di sincronizzazione e esecuzione di WDF senza comprendere appieno le implicazioni. Vedere Uso dei blocchi del framework. A meno che il driver non sia monolitico driver di primo livello che interagisce direttamente con l'hardware, evitare di acconsentire esplicitamente alla sincronizzazione di WDF perché può causare deadlock a causa della ricorsione.
  6. Acquisizione di KEVENT, Semaforo, ERESOURCE, UnsafeFastMutex nel contesto di più thread senza entrare in un'area critica. Questa operazione può causare un attacco DOS perché un thread che contiene uno di questi blocchi può essere sospeso. Consultare Introduzione agli oggetti Dispatcher kernel.
  7. Allocazione di KEVENT nello stack di thread e restituzione al chiamante mentre l'EVENTO è ancora in uso. In genere eseguita quando viene usata con IoBuildSyncronousFsdRequest o IoBuildDeviceIoControlRequest. Il chiamante di queste chiamate deve assicurarsi di non rimuovere dallo stack fino a quando il gestore di I/O non ha segnalato l'evento al termine dell'IRP.
  8. Attesa indefinita nelle routine di invio. In generale, qualsiasi tipo di attesa nella routine dispatch è una cattiva pratica.
  9. Controllo inappropriato della validità di un oggetto (se blah == NULL) prima di eliminarlo. Ciò significa in genere che l'autore non ha una conoscenza completa del codice che controlla la durata dell'oggetto.

Gestione oggetti

  1. Non padre in modo esplicito di oggetti WDF. Vedere Introduzione agli oggetti framework.
  2. Seguire il modello parent-child per l'oggetto WDF con WDFDRIVER invece di un oggetto che offre una migliore gestione del ciclo di vita e ottimizza l'uso della memoria. Ad esempio, WDFREQUEST è associato a un WDFDEVICE invece che a un IOTARGET. Vedere Uso di oggetti framework generali, ciclo di vita degli oggetti framework e riepilogo degli oggetti framework.
  3. Non eseguendo la protezione di rundown delle risorse di memoria condivisa a cui si accede tra i driver. Vedere Funzione ExInitializeRundownProtection.
  4. Accodare per errore lo stesso elemento di lavoro mentre quello precedente è già nella coda o già in esecuzione. Questo può essere un problema se il client presuppone che ogni elemento di lavoro in coda venga eseguito. Vedere Uso di Framework WorkItems. Per altre informazioni sull'accodamento di WorkItems, vedere il modulo DMF_QueuedWorkitem nel progetto DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
  5. Timer di accodamento prima che il messaggio che ci si aspetta che il timer elabori venga pubblicato. Vedere Uso dei Timer.
  6. Esecuzione di un'operazione in un elemento di lavoro che può bloccare o richiedere tempo illimitato per il completamento.
  7. Progettazione di una soluzione che comporta la messa in coda di un gran numero di elementi di lavoro. Può causare un attacco DOS o di sistema non risponde se il cattivo può controllare l'azione (ad esempio pompando I/O in un driver che accoda un nuovo elemento di lavoro per ogni I/O). Vedere Uso degli elementi di lavoro del framework.
  8. Non dopo che i callback DPC dell'elemento di lavoro sono stati eseguiti fino al completamento prima di eliminare l'oggetto. Vedere Linee guida per la scrittura di routine DPC e la funzione WdfDpcCancel.
  9. Creazione di thread invece di usare elementi di lavoro per attività di breve durata/non polling. Consultare Thread di lavoro di sistema.
  10. Non assicurarsi che i thread siano stati eseguiti fino al completamento prima di eliminare o scaricare il driver. Per altre informazioni sulla sincronizzazione del rundown del thread, vedere il codice associato all'analisi del codice associato al modulo DMF_Thread nel progetto DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
  11. Uso di un singolo driver per gestire i dispositivi diversi ma interdipendenti e l'uso di variabili globali per condividere le informazioni.

Memoria

  1. Non contrassegnare il codice di esecuzione passiva come PAGEABLE, quando possibile. Il codice di paging dei driver può ridurre l'ingombro del codice del driver, liberando così spazio di sistema per altri usi. È consigliabile contrassegnare con cautela la tabella codici che genera IRQL >= DISPATCH_LEVEL o può essere chiamato in IRQL generato. Vedi Quando il codice e i dati devono essere pageable, rendere i driver pageable e rilevare il codice che può essere pageable.
  2. Dichiarazione di strutture di grandi dimensioni nello stack, usare invece l'heap/pool. Vedi Uso di KernelStack e allocazione della memoria System-Space.
  3. Azzeramento non necessario del contesto dell'oggetto WDF. Ciò può indicare una mancanza di chiarezza su quando la memoria verrà azzerato automaticamente.

Linee guida generali per i driver

  1. Combinazione di primitivi WDM e WDF. Uso di primitive WDM in cui è possibile usare le primitive WDF. L'uso di primitive WDF consente di proteggere l'utente da gotchas, migliora il debug e rende più importante il driver portabile in modalità utente.
  2. Denominazione di fdO e creazione di collegamenti simbolici quando non necessario. Vedere Gestire il controllo di accesso dei driver.
  3. Copiare e incollare i GUID e altri valori costanti dai driver di esempio.
  4. Prendere in considerazione l'uso del codice open source DMF (Driver Module Framework) nel progetto driver. DMF è un'estensione di WDF che consente funzionalità aggiuntive per uno sviluppatore di driver WDF. Vedere Introducing Driver Module Framework (Introduzione a Driver Module Framework).
  5. Uso del Registro di sistema come meccanismo di notifica tra processi o come cassetta postale. Per un'alternativa, vedere DMF_NotifyUserWithEvent e DMF_NotifyUserWithRequest moduli disponibili nel progetto DMF - https://github.com/Microsoft/DMF.
  6. Supponendo che tutte le parti del Registro di sistema siano disponibili per l'accesso durante la fase di avvio anticipato del sistema.
  7. Dipendere dall'ordine di caricamento di un altro driver o servizio. Poiché l'ordine di carico può essere modificato al di fuori del controllo del tuo driver, questo può comportare un driver che all'inizio funziona, ma poi smette di funzionare in maniera imprevedibile.
  8. Ricreazione delle librerie di driver già disponibili, come quelle fornite da WDF per PnP descritto in Supporto Plug and Play e gestione alimentazione nel driver o in quelle fornite nell'interfaccia del bus, come descritto nell'articolo OSR Uso delle interfacce del bus per la comunicazione tra driver.

PnP/Power

  1. Interfacciarsi con un altro driver in modo non compatibile con PnP, senza registrarsi per le notifiche di cambiamento del dispositivo PnP. Vedere Registrazione per la notifica delle modifiche dell'interfaccia del dispositivo.
  2. Creazione di nodi ACPI per enumerare i dispositivi e creare dipendenze di alimentazione tra di esse anziché usare driver bus o interfacce di creazione di dispositivi software fornite dal sistema per PNP e dipendenze di alimentazione in modo elegante. Vedere Supporto di PnP e risparmio energia nei driver di funzione.
  3. Contrassegnare il dispositivo come non disabilitabile - forzando un riavvio durante l'aggiornamento del driver.
  4. Nascondere il dispositivo in Gestione dispositivi. Vedere Nascondere i dispositivi da Gestione dispositivi.
  5. Presupponendo che il driver venga usato per una sola istanza del dispositivo.
  6. Facendo ipotesi che il conducente non venga mai scaricato. Consulta Routine di scaricamento del driver PnP.
  7. Non gestisce la notifica di arrivo dell'interfaccia spuriosa. Ciò può verificarsi e i driver devono gestire questa condizione in modo sicuro.
  8. La mancata implementazione di una politica di alimentazione inattiva S0, importante per i dispositivi che sono vincoli DRIPS o derivati. Vedere il supporto dell'inattività del risparmio energetico .
  9. Il mancato controllo dello stato restituito di WdfDeviceStopIdle porta a una perdita di riferimenti di alimentazione a causa dello squilibrio tra WdfDeviceStopIdle e ResumeIdle e, infine, al bug check 9F.
  10. Non sapendo che PrepareHardware/ReleaseHardware può essere chiamato più volte a causa del ribilanciamento delle risorse. Questi callback devono essere limitati all'inizializzazione delle risorse hardware. Vedere EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Uso di PrepareHardware/ReleaseHardware per l'allocazione di risorse software. L'allocazione delle risorse software statica al dispositivo deve essere eseguita in AddDevice o in SelfManagedIoInit se le risorse da allocare richiedono l'interazione con l'hardware. Vedere EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.

Linee guida per la codifica

  1. Non si usano funzioni stringa e integer sicure. Vedere Uso di funzioni stringa sicure e uso di funzioni integer sicure.
  2. Non si usano typedef per la definizione delle costanti.
  3. Uso di variabili globali e statiche. Evita di memorizzare in variabili globali il contesto specifico per dispositivo. Le variabili globali sono progettate per la condivisione di informazioni tra più dispositivi. In alternativa, è consigliabile usare il contesto dell'oggetto WDFDRIVER per condividere informazioni tra più istanze di dispositivi.
  4. Non si usano nomi descrittivi per le variabili.
  5. La mancata coerenza nella denominazione delle variabili - consistenza nell'uso di maiuscole e minuscole. Non seguendo lo stile esistente di codifica quando si apportano aggiornamenti al codice esistente. Ad esempio, usando nomi di variabili diversi per strutture comuni in funzioni diverse.
  6. Non commentando scelte di progettazione importanti: gestione dell'energia, blocchi, gestione dello stato, uso di elementi di lavoro, DPC, timer, utilizzo globale delle risorse, pre-allocazione delle risorse, espressioni complesse/istruzioni condizionali.
  7. Commenti sulle cose che sono ovvie dal nome dell'API in uso. Rendere il commento equivalente in inglese al nome della funzione, ad esempio scrivendo il commento "Create the Device Object" quando si chiama WdfDeviceCreate.
  8. Non creare macro con una chiamata di ritorno. Vedere Funzioni (C++).
  9. Assenza o incompletezza delle annotazioni del codice sorgente (SAL). Vedere Annotazioni SAL 2.0 per i driver di Windows.
  10. Uso di macro invece di funzioni inline.
  11. Uso di macro per le costanti al posto di constexpr quando si usa C++
  12. Compilazione del driver con il compilatore C, invece del compilatore C++ per assicurarsi di ottenere un controllo dei tipi sicuro.

Gestione degli errori

  1. Non segnala errori critici del driver e contrassegna correttamente il dispositivo non funzionante.
  2. Non restituendo lo stato di errore NT appropriato che si traduce in uno stato di errore WIN32 significativo. Vedere Uso dei valori NTSTATUS.
  3. Non si usano macro NTSTATUS per controllare lo stato restituito delle funzioni di sistema.
  4. Non asserzione su variabili di stato o flag, se necessario.
  5. Verifica se il puntatore è valido prima di accedervi per aggirare le race condition.
  6. ASSERTING su puntatori NULL. Se si tenta di usare un puntatore NULL per accedere alla memoria, Windows verificherà bug. I parametri del controllo dei bug forniranno le informazioni necessarie per correggere il puntatore Null. Nel tempo, quando al codice vengono aggiunte molte istruzioni ASSERT non usate, consumano memoria e rallentano il sistema.
  7. ASSERTING sul puntatore al contesto dell'oggetto. Il framework del driver garantisce che l'oggetto venga sempre allocato con il contesto.

Tracciamento

  1. Non definire tipi personalizzati WPP e usarli nelle chiamate di traccia per ottenere messaggi di traccia comprensibili. Vedere Aggiunta di traccia software WPP a un driver Windows.
  2. Non si usa la traccia IFR. Vedere Uso di IfR (Inflight Trace Recorder) nei driver KMDF e UMDF 2.
  3. Chiamata di nomi di funzione nelle chiamate di traccia WPP. WPP tiene già traccia dei nomi delle funzioni e dei numeri di riga.
  4. Non usare eventi ETW per misurare le prestazioni e altre esperienze utente critiche che influisce sugli eventi. Consultare Aggiunta di Traccia-Eventi ai driver Kernel-Mode.
  5. Non segnala errori critici nel log eventi e contrassegna normalmente il dispositivo non funzionante.

Verifica

  1. La mancata esecuzione del verificatore dei driver con impostazioni sia standard che avanzate durante lo sviluppo e i test. Vedi Driver Verifier. Nelle impostazioni avanzate è consigliabile abilitare tutte le regole, ad eccezione di quelle correlate alla simulazione di risorse basse. È preferibile eseguire i test di simulazione delle risorse scarsi in isolamento per semplificare il debug dei problemi.
  2. Non è in esecuzione il test DevFund sul driver o sulla classe di dispositivo di cui fa parte il driver con le impostazioni avanzate del verificatore abilitate. Vedere Come eseguire i test DevFund tramite la riga di comando.
  3. Non verificando che il driver sia conforme a HVCI. Vedere Implementare il codice compatibile HVCI.
  4. Non è in esecuzione AppVerifier in WUDFhost.exe durante lo sviluppo e il test dei driver in modalità utente. Vedere Application Verifier(Verifica applicazione).
  5. Non controllare l'utilizzo della memoria usando l'estensione del debugger !wdfpoolusage in fase di esecuzione per assicurarsi che gli oggetti WDF non vengano abbandonati. La memoria, le richieste e gli elementi di lavoro sono vittime comuni di questi problemi.
  6. Non usando l'estensione del debugger !wdfkd per ispezionare l'albero degli oggetti per assicurarsi che gli oggetti siano associati a un genitore correttamente e controllare gli attributi degli oggetti principali come WDFDRIVER, WDFDEVICE, IO.