Anotações IRQL para drivers
Todos os desenvolvedores de driver devem considerar os IRQLs (níveis de solicitação de interrupção). Um IRQL é um inteiro entre 0 e 31; PASSIVE_LEVEL, DISPATCH_LEVEL e APC_LEVEL normalmente são referenciados simbolicamente e os outros por seus valores numéricos. Elevar e reduzir o IRQL deve seguir uma disciplina de pilha estrita. Uma função deve ter como objetivo retornar no mesmo IRQL em que foi chamada. Os valores IRQL devem não estar diminuindo na pilha. E uma função não pode diminuir o IRQL sem primeiro criá-lo. As anotações IRQL destinam-se a ajudar a impor essas regras.
Quando o código do driver tem anotações IRQL, as ferramentas de análise de código podem fazer uma inferência melhor sobre o intervalo de níveis em que uma função deve ser executada e pode encontrar erros com mais precisão. Por exemplo, você pode adicionar anotações que especificam o IRQL máximo no qual uma função pode ser chamada; se uma função for chamada em um IRQL superior, as ferramentas de análise de código poderão identificar as inconsistências.
As funções de driver devem ser anotadas com o máximo de informações sobre o IRQL que possa ser apropriado. Se as informações adicionais estiverem disponíveis, elas ajudarão as ferramentas de análise de código na verificação subsequente da função de chamada e da função chamada. Em alguns casos, adicionar uma anotação é uma boa maneira de suprimir um falso positivo. Algumas funções, como uma função de utilitário, podem ser chamadas em qualquer IRQL. Nesse caso, não ter nenhuma anotação IRQL é a anotação adequada.
Ao anotar uma função para IRQL, é especialmente importante considerar como a função pode evoluir, não apenas sua implementação atual. Por exemplo, uma função como implementada pode funcionar corretamente em um IRQL mais alto do que o designer pretendia. Embora seja tentador anotar a função com base no que o código realmente faz, o designer pode estar ciente dos requisitos futuros, como a necessidade de reduzir o IRQL máximo para algum aprimoramento futuro ou requisitos pendentes do sistema. A anotação deve ser derivada da intenção do designer de função, não da implementação real.
Você pode usar as anotações na tabela a seguir para indicar o IRQL correto para uma função e seus parâmetros. Os valores IRQL são definidos em Wdm.h.
Anotação IRQL | Descrição |
---|---|
_IRQL_requires_max_(irql) | O irql é o IRQL máximo no qual a função pode ser chamada. |
_IRQL_requires_min_(irql) | O irql é o IRQL mínimo no qual a função pode ser chamada. |
_IRQL_requires_(irql) | A função deve ser inserida no IRQL especificado por irql. |
_IRQL_raises_(irql) | A função é encerrada no irql especificado, mas só pode ser chamada para gerar (não menor) o IRQL atual. |
_IRQL_saves_ | O parâmetro anotado salva o IRQL atual para restaurar mais tarde. |
_IRQL_restores_ | O parâmetro anotado contém um valor IRQL de IRQL_saves que deve ser restaurado quando a função retornar. |
_IRQL_saves_global_(kind, param) | O IRQL atual é salvo em um local interno para as ferramentas de análise de código das quais o IRQL deve ser restaurado. Essa anotação é usada para anotar uma função. O local é identificado por tipo e ainda mais refinado por parâmetro. Por exemplo, OldIrql poderia ser do tipo e FastMutex poderia ser o parâmetro que mantinha esse valor IRQL antigo. |
_IRQL_restores_global_(tipo, parâmetro) | O IRQL salvo pela função anotada com IRQL_saves_global é restaurado de um local interno para as ferramentas de Análise de Código. |
_IRQL_always_function_min_(value) | O valor IRQL é o valor mínimo para o qual a função pode reduzir o IRQL. |
_IRQL_always_function_max_(value) | O valor IRQL é o valor máximo para o qual a função pode gerar o IRQL. |
_IRQL_requires_same_ | A função anotada deve inserir e sair no mesmo IRQL. A função pode alterar o IRQL, mas deve restaurar o IRQL para seu valor original antes de sair. |
_IRQL_uses_cancel_ | O parâmetro anotado é o valor IRQL que deve ser restaurado por uma função de retorno de chamada DRIVER_CANCEL. Na maioria dos casos, use a anotação IRQL_is_cancel. |
Anotações para DRIVER_CANCEL
Há uma diferença entre as anotações _IRQL_uses_cancel_ e _IRQL_is_cancel_. A anotação _IRQL_uses_cancel_ simplesmente especifica que o parâmetro anotado é o valor IRQL que deve ser restaurado por uma função de retorno de chamada DRIVER_CANCEL. A anotação _IRQL_is_cancel_ é uma anotação composta que consiste em _IRQL_uses_cancel_ além de várias outras anotações que garantem o comportamento correto de uma função de utilitário de retorno de chamada DRIVER_CANCEL. Por si só, a anotação _IRQL_uses_cancel_ é apenas ocasionalmente útil; por exemplo, se o restante das obrigações descritas por _IRQL_is_cancel_ já tiverem sido cumpridas de alguma outra forma.
Anotação IRQL | Descrição |
---|---|
_IRQL_is_cancel_ | O parâmetro anotado é o IRQL passado como parte da chamada para uma função de retorno de chamada DRIVER_CANCEL. Essa anotação indica que a função é um utilitário chamado de Rotinas de cancelamento e que conclui os requisitos para DRIVER_CANCEL funções, incluindo a versão do bloqueio de rotação de cancelamento. |
Como as anotações do IRQL interagem
As anotações de parâmetro IRQL interagem entre si mais do que outras anotações porque o valor IRQL é definido, redefinido, salvo e restaurado pelas várias funções chamadas.
Especificando IRQL máximo e mínimo
As anotações _IRQL_requires_max_ e _IRQL_requires_min_ especificam que a função não deve ser chamada de um IRQL maior ou inferior ao valor especificado. Por exemplo, quando PREfast vê uma sequência de chamadas de função que não alteram o IRQL, se encontrar um com um valor _IRQL_requires_max_ que está abaixo de um _IRQL_requires_min_ próximo, ele relatará um aviso na segunda chamada encontrada. O erro pode realmente ocorrer na primeira chamada; a mensagem indica onde a outra metade do conflito ocorreu.
Se as anotações em uma função menção o IRQL e não se aplicarem explicitamente _IRQL_requires_max_, a ferramenta análise de código aplicará implicitamente a anotação _IRQL_requires_max_(DISPATCH_LEVEL), que normalmente está correta com raras exceções. Aplicar isso implicitamente como padrão elimina muita desordem de anotação e torna as exceções muito mais visíveis.
A anotação _IRQL_requires_min_(PASSIVE_LEVEL) está sempre implícita porque o IRQL não pode ficar mais baixo; consequentemente, não há nenhuma regra explícita correspondente sobre o IRQL mínimo. Pouquíssimas funções têm um limite superior diferente de DISPATCH_LEVEL e um limite inferior diferente de PASSIVE_LEVEL.
Algumas funções são chamadas em um contexto no qual a função chamada não pode elevar com segurança o IRQL acima de algum máximo ou, com mais frequência, não pode reduzi-lo com segurança abaixo de algum mínimo. As anotações _IRQL_always_function_max_ e _IRQL_always_function_min_ ajudam o PREfast a encontrar casos em que isso ocorre involuntariamente.
Por exemplo, funções do tipo DRIVER_STARTIO são anotadas com _IRQL_always_function_min_(DISPATCH_LEVEL). Isso significa que, durante a execução de uma função DRIVER_STARTIO, é um erro reduzir o IRQL abaixo DISPATCH_LEVEL. Outras anotações indicam que a função deve ser inserida e encerrada em DISPATCH_LEVEL.
Especificando um IRQL explícito
Use a anotação _IRQL_raises_ ou _IRQL_requires_ para ajudar o PREfast a relatar melhor uma inconsistência descoberta com anotações _IRQL_requires_max_ ou _IRQL_requires_min_ porque, em seguida, ele conhece o IRQL.
A anotação _IRQL_raises_ indica que uma função retorna com o IRQL definido como um novo valor. Quando você usa a anotação _IRQL_raises_, ela também define efetivamente a anotação _drv_maxFunctionIRQL com o mesmo valor IRQL. No entanto, se a função aumentar o IRQL do que o valor final e, em seguida, reduzi-lo para o valor final, você deverá adicionar uma anotação _IRQL_always_function_max_ explícita após a anotação _IRQL_raises_ para permitir o valor IRQL mais alto.
Gerando ou baixando IRQL
A anotação _IRQL_raises_ indica que a função deve ser usada apenas para gerar IRQL e não deve ser usada para reduzir o IRQL, mesmo que a sintaxe da função a permita. KeRaiseIrql é um exemplo de uma função que não deve ser usada para reduzir o IRQL.
Salvando e restaurando IRQL
Use as anotações _IRQL_saves_ e _IRQL_restores_ para indicar que o IRQL atual (se é conhecido exatamente ou apenas aproximadamente) é salvo ou restaurado do parâmetro anotado.
Algumas funções salvam e restauram o IRQL implicitamente. Por exemplo, a função do sistema ExAcquireFastMutex salva o IRQL em um local opaco associado ao objeto mutex rápido que o primeiro parâmetro identifica; o IRQL salvo é restaurado pela função ExReleaseFastMutex correspondente para esse objeto mutex rápido. Para indicar essas ações explicitamente, use as anotações _IRQL_saves_global_ e _IRQL_restores_global_. Os parâmetros kind e param indicam onde o valor IRQL é salvo. O local em que o valor é salvo não precisa ser especificado com precisão, desde que as anotações que salvam e restaurem o valor sejam consistentes.
Mantendo o mesmo IRQL
Você deve anotar todas as funções criadas pelo driver que alterem o IRQL usando a anotação _IRQL_requires_same_ ou uma das outras anotações IRQL para indicar que a alteração no IRQL é esperada. Na ausência de anotações que indiquem qualquer alteração no IRQL, as ferramentas de análise de código emitirão um aviso para qualquer função que não saia no mesmo IRQL em que a função foi inserida. Se a alteração no IRQL for pretendida, adicione a anotação apropriada para suprimir o erro. Se a alteração no IRQL não for pretendida, o código deverá ser corrigido.
Salvando e restaurando IRQL para rotinas de cancelamento de E/S
Use a anotação _IRQL_uses_cancel_ para indicar que o parâmetro anotado é o valor IRQL que deve ser restaurado por uma função de retorno de chamada DRIVER_CANCEL. Essa anotação indica que a função é um utilitário chamado de rotinas de cancelamento e que preenche os requisitos que foram feitos em funções DRIVER_CANCEL (ou seja, ela quita a obrigação para o chamador).
Por exemplo, veja a seguir a declaração do tipo de função de retorno de chamada DRIVER_CANCEL. Um dos parâmetros é o IRQL que deve ser restaurado por essa função. As anotações indicam todos os requisitos de uma função cancel.
// 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;