Condividi tramite


Annotazioni IRQL per i driver

Tutti gli sviluppatori di driver devono prendere in considerazione i livelli di richiesta di interrupt (IRQL). Un IRQL è un numero intero compreso tra 0 e 31; PASSIVE_LEVEL, DISPATCH_LEVEL e APC_LEVEL vengono normalmente definiti simbolicamente e gli altri in base ai valori numerici. L'aumento e l'abbassamento dell'IRQL devono seguire una rigorosa disciplina a livello di stack. Una funzione dovrebbe mirare a tornare allo stesso livello IRQL al quale è stata chiamata. I valori IRQL devono essere non decrescente nello stack. E una funzione non può abbassare IRQL senza prima innalzarla. 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 sull'intervallo di livelli in cui deve essere eseguita una funzione e trovare in modo più accurato gli errori. Ad esempio, è possibile aggiungere annotazioni che specificano il massimo IRQL al 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 del driver devono essere annotate con quante più informazioni appropriate sull'IRQL possibile. 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, non avere alcuna annotazione IRQL è l'annotazione corretta.

Quando si annota una funzione per IRQL, è particolarmente importante considerare come la funzione potrebbe evolversi, non solo la sua implementazione corrente. Ad esempio, una funzione implementata potrebbe funzionare correttamente a un livello di richiesta di interruzione (IRQL) più alto rispetto a quello previsto dal progettista. Anche se è allettante annotare la funzione in base a ciò che il codice fa effettivamente, il progettista potrebbe essere a conoscenza di requisiti futuri, come la necessità di abbassare il massimo IRQL per alcuni miglioramenti futuri o requisiti di sistema in sospeso. L'annotazione deve essere derivata dall'intenzione del progettista della funzione, 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 cui è possibile chiamare la funzione.
_IRQL_requires_(irql) La funzione deve essere immessa in irQL specificata da irql.
_IRQL_raises_(irql) La funzione termina al IRQL specificato, ma può essere chiamata solo per elevare (non abbassare) l'IRQL corrente.
_IRQL_saves_ Il parametro con annotazioni salva l'IRQL corrente per il ripristino in un secondo momento.
_IRQL_restores_ Il parametro con annotazioni contiene un valore IRQL di IRQL_saves che deve essere ripristinato quando la funzione restituisce.
_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 il runtime di integrazione. Questa annotazione viene usata per annotare una funzione. La posizione è identificata dal tipo e ulteriormente raffinata dal parametro. 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) L'IRQL salvato dalla funzione annotata con IRQL_saves_global viene restaurato da una posizione di memoria 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ò innalzare l'IRQL.
_IRQL_requires_same_ La funzione annotata deve immettere e uscire allo stesso IRQL. La funzione può modificare l'IRQL, ma deve ripristinare l'IRQL al suo 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 funzione di callback DRIVER_CANCEL. L'annotazione _IRQL_is_cancel_ è un'annotazione composita costituita da _IRQL_uses_cancel_ più altre annotazioni che garantiscono il comportamento corretto di una funzione dell'utilità di callback DRIVER_CANCEL. Da solo, l'annotazione _IRQL_uses_cancel_ è solo occasionalmente utile; ad esempio, se il resto degli obblighi descritti da _IRQL_is_cancel_ è già stato soddisfatto in altro modo.

Annotazione IRQL Descrizione
_IRQL_is_cancel_ Il parametro con annotazioni è l'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 DRIVER_CANCEL, incluso il rilascio dello spin lock di annullamento.

Interazione delle annotazioni IRQL

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

Specificare il valore massimo e minimo di IRQL

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

Se le annotazioni in una funzione indicano 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 opzione come impostazione predefinita elimina un sacco di confusione di annotazione 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 su IRQL minimo. 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 sopra 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 individuare i 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, è un errore abbassare il livello IRQL al di sotto di DISPATCH_LEVEL. Altre annotazioni indicano che la funzione deve essere immessa e chiusa alla DISPATCH_LEVEL.

Specificare un IRQL esplicito

Usare l'annotazione _IRQL_raises_ o _IRQL_requires_ per consentire a PREfast di segnalare meglio un'incoerenza individuata con le annotazioni _IRQL_requires_max_ o _IRQL_requires_min_ perché allora conosce l'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 alza l'IRQL a un valore superiore rispetto al valore finale e allora la abbassa nuovamente al valore finale, dovresti aggiungere un'annotazione esplicita _IRQL_always_function_max_ dopo l'annotazione _IRQL_raises_ per supportare il valore IRQL più alto.

Aumento o riduzione di IRQL

L'annotazione _IRQL_raises_ indica che la funzione deve essere usata solo per generare IRQL e non deve essere usata per ridurre IRQL, anche se la sintassi della funzione lo consentirà. 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'attuale livello di richiesta di interruzione (IRQL), noto esattamente o approssimativamente, viene salvato o ripristinato dal parametro annotato.

Alcune funzioni salvano e ripristinano in modo implicito irQL. 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 in modo esplicito queste azioni, 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 IRQL

È consigliabile annotare tutte le funzioni create dal driver che modificano l'IRQL utilizzando l'annotazione _IRQL_requires_same_ o una delle altre annotazioni IRQL per indicare che la modifica dell'IRQL è prevista. 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 routine di annullamento di I/O

Usare l'annotazione _IRQL_uses_cancel_ per indicare che il parametro con annotazioni è il valore IRQL che deve essere ripristinato da una funzione di callback DRIVER_CANCEL. Questa annotazione indica che la funzione è una funzionalità chiamata da routine di annullamento e che soddisfa i requisiti richiesti sulle funzioni DRIVER_CANCEL (cioè, solleva l'obbligo del chiamante).

Ad esempio, di seguito è riportata la dichiarazione per il tipo DRIVER_CANCEL di funzione di callback. Uno dei parametri è l'IRQL 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