Annotazioni IRQL per i driver

Tutti gli sviluppatori di driver devono prendere in considerazione i livelli di richiesta di interrupt (IRQLs). Un IRQL è un numero intero compreso tra 0 e 31; PASSIVE_LEVEL, DISPATCH_LEVEL e APC_LEVEL vengono in genere definiti simbolicamente e gli altri in base ai valori numerici. Aumentare e abbassare irQL deve seguire una rigorosa disciplina dello stack. Una funzione deve mirare a restituire lo stesso IRQL in corrispondenza del quale è stato chiamato. I valori IRQL devono essere non decrescente nello stack. E una funzione non può abbassare l'IRQL senza prima generarla. Le annotazioni IRQL consentono di applicare tali regole.

Quando il codice del driver include annotazioni IRQL, gli strumenti di analisi del codice possono migliorare l'inferenza dell'intervallo di livelli in cui deve essere eseguita una funzione e possono trovare in modo più accurato gli errori. Ad esempio, è possibile aggiungere annotazioni che specificano il valore IRQL massimo in corrispondenza del quale è possibile chiamare una funzione; se una funzione viene chiamata a un irQL superiore, gli strumenti di analisi del codice possono identificare le incoerenze.

Le funzioni driver devono essere annotate con tutte le informazioni sull'IRQL che potrebbe essere appropriato. Se sono disponibili informazioni aggiuntive, consente agli strumenti di analisi del codice di controllare successivamente sia la funzione chiamante che la funzione chiamata. In alcuni casi, l'aggiunta di un'annotazione è un buon modo per eliminare un falso positivo. Alcune funzioni, ad esempio una funzione di utilità, possono essere chiamate in qualsiasi IRQL. In questo caso, senza annotazione IRQL è l'annotazione corretta.

Quando si annota una funzione per IRQL, è particolarmente importante considerare l'evoluzione della funzione, non solo la relativa implementazione corrente. Ad esempio, una funzione implementata potrebbe funzionare correttamente in un irQL superiore a quello previsto dalla finestra di progettazione. Anche se si tenta di annotare la funzione in base a ciò che fa effettivamente il codice, la finestra di progettazione potrebbe essere consapevole dei requisiti futuri, ad esempio la necessità di ridurre il numero massimo di runtime di integrazione per alcuni miglioramenti futuri o requisiti di sistema in sospeso. L'annotazione deve essere derivata dall'intenzione della finestra di progettazione delle funzioni, non dall'implementazione effettiva.

È possibile usare le annotazioni nella tabella seguente per indicare il runtime di integrazione corretto per una funzione e i relativi parametri. I valori IRQL sono definiti in Wdm.h.

Annotazione IRQL Descrizione
_IRQL_requires_max_(irql) Irql è il valore IRQL massimo in corrispondenza del quale è possibile chiamare la funzione.
_IRQL_requires_min_(irql) Irql è il valore IRQL minimo in corrispondenza del quale è possibile chiamare la funzione.
_IRQL_requires_(irql) La funzione deve essere immessa in irQL specificato da irql.
_IRQL_raises_(irql) La funzione esce dall'oggetto irql specificato, ma può essere chiamata solo per generare (non abbassare) l'IRQL corrente.
_IRQL_saves_ Il parametro con annotazioni salva l'IRQL corrente per eseguire il ripristino in un secondo momento.
_IRQL_restores_ Il parametro con annotazioni contiene un valore IRQL da IRQL_saves che deve essere ripristinato al termine della funzione.
_IRQL_saves_global_(kind, param) Il runtime di integrazione corrente viene salvato in una posizione interna agli strumenti di analisi del codice da cui deve essere ripristinato irQL. Questa annotazione viene usata per annotare una funzione. La posizione è identificata dal tipo e ulteriormente perfezionata da param. Ad esempio, OldIrql potrebbe essere il tipo e FastMutex potrebbe essere il parametro che ha mantenuto il valore IRQL precedente.
_IRQL_restores_global_(kind, param) Il runtime di integrazione salvato dalla funzione annotata con IRQL_saves_global viene ripristinato da una posizione interna agli strumenti di analisi del codice.
_IRQL_always_function_min_(valore) Il valore IRQL è il valore minimo a cui la funzione può abbassare irQL.
_IRQL_always_function_max_(valore) Il valore IRQL è il valore massimo a cui la funzione può generare irQL.
_IRQL_requires_same_ La funzione annotata deve entrare e uscire allo stesso IRQL. La funzione può modificare IRQL, ma deve ripristinare il runtime di integrazione sul valore originale prima di uscire.
_IRQL_uses_cancel_ Il parametro con annotazioni è il valore IRQL che deve essere ripristinato da una funzione di callback DRIVER_CANCEL. Nella maggior parte dei casi, usare invece l'annotazione IRQL_is_cancel.

Annotazioni per DRIVER_CANCEL

Esiste una differenza tra le annotazioni _IRQL_uses_cancel_ e _IRQL_is_cancel_. L'annotazione _IRQL_uses_cancel_ specifica semplicemente che il parametro con annotazioni è il valore IRQL che deve essere ripristinato da una DRIVER_CANCEL funzione di callback. L'annotazione _IRQL_is_cancel_ è un'annotazione composita costituita da _IRQL_uses_cancel_ più diverse altre annotazioni che garantiscono il comportamento corretto di una funzione dell'utilità di callback DRIVER_CANCEL. Da solo, l'annotazione _IRQL_uses_cancel_ è utile solo occasionalmente; ad esempio, se il resto degli obblighi descritti da _IRQL_is_cancel_ è già stato soddisfatto in qualche altro modo.

Annotazione IRQL Descrizione
_IRQL_is_cancel_ Il parametro con annotazioni è il valore IRQL passato come parte della chiamata a una funzione di callback DRIVER_CANCEL. Questa annotazione indica che la funzione è un'utilità chiamata dalle routine Cancel e che completa i requisiti per le funzioni di DRIVER_CANCEL, incluso il rilascio del blocco di selezione annulla.

Interazione delle annotazioni IRQL

Le annotazioni dei parametri IRQL interagiscono tra loro più di altre annotazioni perché il valore IRQL è impostato, reimpostato, salvato e ripristinato dalle varie funzioni chiamate.

Specifica del numero massimo e minimo di runtime di integrazione

Le annotazioni _IRQL_requires_max_ e _IRQL_requires_min_ specificano che la funzione non deve essere chiamata da un IRQL superiore o inferiore al valore specificato. Ad esempio, quando PREfast vede una sequenza di chiamate di funzione che non modificano IRQL, se ne trova uno con un valore _IRQL_requires_max_ inferiore a un _IRQL_requires_min_ nelle vicinanze, segnala un avviso nella seconda chiamata che rileva. L'errore potrebbe verificarsi effettivamente nella prima chiamata; il messaggio indica dove si è verificato l'altra metà del conflitto.

Se le annotazioni in una funzione menzionano IRQL e non si applicano in modo esplicito _IRQL_requires_max_, lo strumento di analisi del codice applica in modo implicito l'annotazione _IRQL_requires_max_(DISPATCH_LEVEL), che in genere è corretta con eccezioni rare. L'applicazione implicita di questa impostazione come impostazione predefinita elimina un sacco di confusione di annotazioni e rende le eccezioni molto più visibili.

L'annotazione _IRQL_requires_min_(PASSIVE_LEVEL) è sempre implicita perché IRQL non può scendere; di conseguenza, non esiste alcuna regola esplicita corrispondente relativa a IRQL minima. Pochissime funzioni hanno un limite superiore diverso da DISPATCH_LEVEL e un limite inferiore diverso da PASSIVE_LEVEL.

Alcune funzioni vengono chiamate in un contesto in cui la funzione chiamata non può generare in modo sicuro irQL al di sopra di un valore massimo o, più spesso, non può abbassarla in modo sicuro al di sotto di un minimo. Le annotazioni _IRQL_always_function_max_ e _IRQL_always_function_min_ consentono a PREfast di trovare casi in cui ciò si verifica involontariamente.

Ad esempio, le funzioni di tipo DRIVER_STARTIO vengono annotate con _IRQL_always_function_min_(DISPATCH_LEVEL). Ciò significa che durante l'esecuzione di una funzione DRIVER_STARTIO, si tratta di un errore per abbassare il runtime di integrazione sotto DISPATCH_LEVEL. Altre annotazioni indicano che la funzione deve essere immessa e chiusa all'DISPATCH_LEVEL.

Specifica di un runtime di integrazione esplicito

Usare l'annotazione _IRQL_raises_ o _IRQL_requires_ per consentire a PREfast di segnalare meglio un'incoerenza individuata con _IRQL_requires_max_ o _IRQL_requires_min_ annotazioni perché conosce quindi irQL.

L'annotazione _IRQL_raises_ indica che una funzione restituisce con IRQL impostato su un nuovo valore. Quando si usa l'annotazione _IRQL_raises_, imposta anche l'annotazione _drv_maxFunctionIRQL sullo stesso valore IRQL. Tuttavia, se la funzione genera irQL superiore al valore finale e la riduce al valore finale, è necessario aggiungere un'annotazione esplicita _IRQL_always_function_max_ dopo l'annotazione _IRQL_raises_ per consentire il valore IRQL superiore.

Aumento o abbassamento di IRQL

L'annotazione _IRQL_raises_ indica che la funzione deve essere usata solo per generare IRQL e non deve essere usata per abbassare IRQL, anche se la sintassi della funzione lo consente. KeRaiseIrql è un esempio di una funzione che non deve essere usata per abbassare IRQL.

Salvataggio e ripristino di IRQL

Usare le annotazioni _IRQL_saves_ e _IRQL_restores_ per indicare che l'IRQL corrente (noto esattamente o solo approssimativamente) viene salvato o ripristinato dal parametro con annotazioni.

Alcune funzioni salvano e ripristinano il runtime di integrazione in modo implicito. Ad esempio, la funzione di sistema ExAcquireFastMutex salva irQL in una posizione opaca associata all'oggetto mutex veloce identificato dal primo parametro; l'IRQL salvato viene ripristinato dalla funzione ExReleaseFastMutex corrispondente per l'oggetto mutex rapido. Per indicare queste azioni in modo esplicito, usare le annotazioni _IRQL_saves_global_ e _IRQL_restores_global_. I parametri kind e param indicano dove viene salvato il valore IRQL. La posizione in cui viene salvato il valore non deve essere specificata con precisione, purché le annotazioni che salvano e ripristinano il valore siano coerenti.

Gestione dello stesso RUNTIME di integrazione

È consigliabile annotare tutte le funzioni create dal driver che modificano irQL usando l'annotazione _IRQL_requires_same_ o una delle altre annotazioni IRQL per indicare che è prevista la modifica in IRQL. In assenza di annotazioni che indicano qualsiasi modifica in IRQL, gli strumenti di analisi del codice genereranno un avviso per qualsiasi funzione che non esce dallo stesso IRQL in corrispondenza del quale è stata immessa la funzione. Se la modifica in IRQL è prevista, aggiungere l'annotazione appropriata per eliminare l'errore. Se la modifica in IRQL non è prevista, il codice deve essere corretto.

Salvataggio e ripristino di IRQL per le routine di annullamento di I/O

Usare l'annotazione _IRQL_uses_cancel_ per indicare che il parametro annotato è il valore IRQL che deve essere ripristinato da una funzione di callback DRIVER_CANCEL. Questa annotazione indica che la funzione è un'utilità chiamata da routine di annullamento e che completa i requisiti che sono stati eseguiti sulle funzioni di DRIVER_CANCEL (ovvero, scarica l'obbligo per il chiamante).

Ad esempio, di seguito è riportata la dichiarazione per il DRIVER_CANCEL tipo di funzione di callback. Uno dei parametri è il runtime di integrazione che deve essere ripristinato da questa funzione. Le annotazioni indicano tutti i requisiti di una funzione di annullamento.

// Define driver cancel routine type.  //    
__drv_functionClass(DRIVER_CANCEL)  
_Requires_lock_held_(_Global_cancel_spin_lock_)  
_Releases_lock_(_Global_cancel_spin_lock_)  
_IRQL_requires_min_(DISPATCH_LEVEL)  
_IRQL_requires_(DISPATCH_LEVEL)  
typedef  
VOID  
DRIVER_CANCEL (  
    _Inout_ struct _DEVICE_OBJECT *DeviceObject,  
    _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp  
    );  
  
typedef DRIVER_CANCEL *PDRIVER_CANCEL;  

Annotazioni SAL 2.0 per i driver