1120 Alameda Orquidea, Atibaia, SP,  BRA

fernando@driverentry.com.br

ExAllocatePool(WithoutTag)

Como a maioria dos desenvolvedores de drivers, eu também tenho que construir versões de drivers para várias versões do Windows. Ainda não vou comentar sobre VXDs neste post, mas já posso comentar sobre ter um único arquivo .SYS que possa rodar tanto em Windows Server 2003 como em Windows NT. Sei que este sistema operacional não é mais vendido e nem conta mais com o suporte da Microsoft, mas é impressionante ver como ainda encontramos Windows 95 e Windows NT tanto em ambientes corporativos como nas casas de usuários finais. Assim temos sempre que ficar nos esquivando das limitações de cada sistema operacional quando estamos planejando desenvolver um sistema.

Uma função que recebeu uma nova versão foi a ExFreePool, sua nova versão é a ExFreePoolWithTag, que é implementada a partir Windows 2000. Para termos um único binário que seja executado em ambas as versões, temos que utilizar a mais velha que ainda é suportada pelos sistemas recentes.

Vamos considerar o fonte abaixo para um driver que realiza a incrível tarefa de alocar memória dinamicamente.

#include 
 
NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                     PUNICODE_STRING pusRegistryPath)
{
   PVOID pTemp;
 
   //-f--> Tô pensando que é fácil alocar memória.
    if (!(pTemp = ExAllocatePool(NonPagedPool, 10))
       return STATUS_NO_MEMORY;
 
   //-f--> Memory leak é falta de educação.
    ExFreePool(pTemp);
   return STATUS_SUCCESS;
}

Utilizando o ambiente de build para Windows XP para compilar este driver, podemos ver que o mesmo binário funciona em Windows Server 2003, Windows XP, Windows 2000 e Windows NT. Ou pelo menos deveria. Quando tentamos iniciar nosso maravilhoso driver em Windows NT, obtemos a seguinte mensagem:

Mas como? Se dermos uma olhada mais de perto em nosso driver, vamos descobrir que ele é um traidor miserável. Como uma DLL, drivers são módulos executáveis que utilizam a clássica estrutura PE para declarar suas dependências. Assim, podemos abrir nosso driver no “Dependency Walker” e ver o que ele espera da vida para poder ser carregado. Por encreça que parível, ele realmente depende estaticamente das funções novas de alocação de memória. Traidor maldito!

Isso acontece porque dentro do arquivo ntddk.h existe um conjunto de defines que acabam mudando as chamadas ExFreePool para ExFreePoolWithTag. Assim podemos pegar todos aqueles 7 quilos de código escritos para NT e utilizá-los em builds para sistemas mais novos sem mudar uma só linha, ou ainda podemos ter código fonte compartilhado entre drivers para Windows NT e para sistemas mais novos.

#define POOL_TAGGING 1
 
...
 
#ifdef POOL_TAGGING
#define ExAllocatePool(a,b) ExAllocatePoolWithTag(a,b,' kdD')
#define ExAllocatePoolWithQuota(a,b) ExAllocatePoolWithQuotaTag(a,b,' kdD')
#endif

Então você me faz as seguintes perguntas: Mas qual a real diferença entre versão velha e a nova? Meu driver pode ter problemas se tentar utilizar a versão velha rodando em um sistema mais novo? Existe vida inteligente fora da terra?

A versão nova veio para ajudar a detectar memory leaks. Cada alocação de memória fica associada a uma Tag. Estas Tags podem ser visualizadas utilizando ferramentas de depuração tais como o WinDbg. Por exemplo: Se toda a sua biblioteca de configuração utilizar uma Tag, e a sua biblioteca de comunicação utilizar outra, fica fácil saber quais delas está deixando memória alocada. Desta forma fica mais fácil saber qual programador do seu time você vai mandar pra rua. As macros no ntddk.h apenas utilizam uma Tag default para as alocações de memória que não tem uma Tag associada.

Mas o que acontece com os drivers que foram compilados com o DDK do Windows NT e que rodam sobre o sistemas posteriores? Estes drivers de fato utilizam as funções velhas. Se utilizarmos o Depends neles poderemos ver isso. Por uma questão de compatibilidade, as funções velhas ainda são exportadas em sistemas novos para dar suporte aos drivers antigos. Vamos dar uma olhada na implementação da ExAllocatePool do Windows 2000.

A implementação da função antiga simplesmente encaminha a alocação de memória para a função nova e utiliza a Tag ‘None’. Isso nos assegura que não morreremos de câncer se continuarmos utilizando as funções antigas em um sistema novo.

Bom, o que estávamos querendo mesmo? Ah sim, construir um driver novo que possa rodar tanto em Windows NT como em sistemas posteriores. Poderíamos simplesmente utilizar o DDK do Windows NT e contar com a compatibilidade reversa para rodar em sistemas mais novos. Isso é possível, mas algumas das funções que vemos no DDK são definidas como macros. A função IoSetCompletionRoutine é um exemplo clássico disso. Utilizar um DDK antigo significa não ter algumas destas funções definidas e teríamos que ficar remendando definições do DDK se quiséssemos desfrutar delas. Existem ainda aqueles casos de pessoas perdidas em ilhas desertas apenas com o DDK mais novo.

#include 
 
#undef ExAllocatePool
#undef ExFreePool
 
NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                     PUNICODE_STRING pusRegistryPath)
{
 
...

Para resolver isso, logo depois do include para ntddk.h, vamos dar um #undef nas macros que redirecionam as chamadas do ExAllocatePool para a nova versão e prontio, seus pobremas se acabaram-se. Recompilando teremos um driver novo que utiliza as funções antigas de alocação. Desta forma ele pode ser utilizado em sistemas desde o Windows NT.

A desvantagem deste método é que seus drivers não poderão utilizar as Tags quando rodando em um sistema que as suporte. Na verdade é possível ter um único binário que se rodando em Windows NT utilize ExFreePool e se rodando em um sistema mais novo utilize ExFreePoolWithTag, mas isso é outro Post.

2 Responses

  1. Muito legal!

    O que você acha de escrever uns posts sobre as diferenças entre a alocação de memória em user mode e kernel mode? Seria bem legal para os driver writers wannabe.

    1. Acho muito interessante. Isso é algo que realmente merece uma atenção.

      Estou com alguns posts em mente e, como sabe, estou me controlando para não escrever todos de uma só vez. Penso em demostrar os passos básicos para um “Hello World!” em Kernel e com o tempo pretendo mostrar algumas coisas interessantes que acabei aprendendo com o tempo.

      Valew,
      []s.

Leave a Reply

Your email address will not be published.