1120 Alameda Orquidea, Atibaia, SP,  BRA

contact@driverentry.com.br

Nós queremos exemplos

Alguns meses após minha entrada na Open, pediram me que eu fizesse uma participação em uma palestra sobre Código Seguro. Minha parte falava sobre o estouro de pilha e como tirar proveito desse descuido do programador para invadir o programa. O grande ponto a notar foi que além dos programadores, o público era composto por engenheiros e arquitetos de software, o pessoal do comercial, que tinha mais contato com os clientes, também estava lá, havia uma ou duas pessoas do administrativo lá também, ou seja, um público que poucos já ouviram falar em pilha. Teve um que disse: “Eu lembro mais ou menos disso na minha certificação de .Net, que dizia que estruturas são criadas na pilha enquanto objetos são criados no heap, ou vice-versa”. Enfim, as coisas começaram a complicar quando comecei a destrinchar o código de exemplo que forneci com PUSHs, POPs e MOVs. Resultado, alguns só dormiam enquanto outros babavam e falavam naquela língua alienígena que só falamos enquanto dormimos. Conheço bem essa língua porque minha esposa sempre troca a maior idéia comigo enquanto ela dorme e eu estou sentado na cama com o notebook. Apesar dela dizer palavras completamente incompreensíveis, ela sempre responde quando faço alguma pergunta à respeito. Ela introduz o assunto dizendo: “Admivoza bumizav”, então eu pergunto: “Por que você acha isso?”, e ela responde: “Zumirag abmish mua”. Mas voltando ao assunto, ficou claro que a quantidade de detalhes técnicos ficou incompatível com o público. Por isso, há pouco tempo atrás, em uma palestra sobre drivers para Windows, tentei passar uma visão pouco detalhada, apenas dar a idéia do que eles são e como eles contribuem para funcionamemto do sistema. Afinal, todo o departamento de desenvolvimento estava lá, incluindo arquitetos, engenheiros e .Net coders (nada contra). Para a minha surpresa, ao final da palestra, todos ficaram com aquela cara de “Ué, e o resto? Não tem nem um exemplinho?”. Então tá bom… Neste post vou escrever um driver mínimo, mas que já tenha alguma interação com uma aplicação de teste.

E no início…

Aqui vou partir do princípio que você já sabe como criar um projeto de driver do zero e como utilizar o Visual Studio para codificar drivers, indo direto ao ponto onde escrevemos o driver. O exemplo de hoje será um driver de eco bem básico que receberá comandos de escrita e leitura. Então seria assim, você inicialmente escreve buffers, que em nosso exemplo serão strings, utilizando a função WriteFile e tudo será armazenado no driver. As leituras subsequentes a partir da função ReadFile trarão os mesmos dados que foram escritos. Este exemplo vai nos ser muito útil em outros posts, e também vai servir como ponto de partida para os que estão querendo escrever seu primeiro driver.

Sabendo que vamos armazenar os dados enviados em uma lista, então criaremos uma lista de buffers formada pelos nós definidos como mostra abaixo.

//-f--> Definição para o tipo para ser armazenado
//      em nossa lista de buffers.
typedef struct _BUFFER_ENTRY
{
    PVOID       pBuffer;    //-f--> Buffer enviado
    ULONG       ulSize;     //      Tamanho do buffer
    LIST_ENTRY  Entry;      //      Nó para a lista
 
} BUFFER_ENTRY, *PBUFFER_ENTRY;
 
 
//-f--> Ponta da nossa lista e mutex de proteção
LIST_ENTRY      g_BufferList;
KMUTEX          g_Mutex;

Depois de definida a estrutura, criamos uma variável global que será a ponta de nossa lista e também um mutex para proteger nossa lista de possíveis acessos em paralelo. Isso aconteceria se tivéssemos duas aplicações de teste rodando ao mesmo tempo. Se quiser mais detalhes sobre listas ligadas do DDK, existe um post que fala sobre isso também.

Escrevendo a DriverEntry

O primeiro ponto a notar aqui é que como nosso exemplo foi codificado em um arquivo .CPP, por isso precisaremos colocar um sonoro extern “C” à definicão da função DriverEntry. Caso contrário, o linker não encontrará o ponto de entrada do driver. Logo no início da implementação, temos a mensagem que será lançada ao depurador via KdPrint. Em seguida vou inicializando a ponta da nossa lista ligada bem como o mutex que a protege. Agora vamos setar alguns membros na estrutura DriverObject, e o primeiro deles será o membro DriverUnload, que recebe um ponteiro de função de callback que informará ao driver que este está sendo descarregado. Os próximos membros são as rotinas que serão chamadas quando o driver receber as solicitações de Create/Open, Close, Read e Write. Falaremos destas rotinas com mais detalhes um pouco adiante. Depois disso, vamos criar o DeviceObject, que vai ser nosso meio de comunicação com o driver. Conforme eu disse na palestra, todas as solicitações que um driver recebe, são por meio de um device. A função IoCreateDevice faz isso para nós. Criado o device vamos configurá-lo de maneira que este utilize buffers intermediários. Para isso devemos setar o bit DO_BUFFERED_IO no campo Flags do DeviceObject que acabou de ser criado. Buffers intermediários? Falaremos sobre BufferedIo versus DirectIo numa próxima oportunidade. Existem muitos conceitos novos neste post e não vamos nos ater a todos eles, caso contrário, ninguém vai ler isso com medo de nunca acabar.

Ter um DeviceObject é legal, mas não é tudo na vida de um driver, para que uma aplicação User Mode possa se comunicar com um driver, é necessário criar um Symbolic Link. Isso é feito logo em seguida com a função IoCreateSymbolicLink. O restante do código desta função não deve causar grandes surpresas à grande maioria de vocês, mas em caso de dúvidas, é só mandar um e-mail que a gente resolve na porrada.

/****
***     DriverEntry
**
**      Ponto de entrada do nosso driver.
**      Faca nos dentes e sangue nos olhos.
*/
 
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj,
                     IN PUNICODE_STRING pusRegistryPath)
{
    NTSTATUS        nts;
    PDEVICE_OBJECT  pDeviceObj = NULL;
 
    __try
    {
        //-f--> Dizendo olá para o Kernel Debugger
        KdPrint(("Starting KernelEcho driver...\n"));
 
        //-f--> Inicializando a lista de buffers e mutex
        InitializeListHead(&g_BufferList);
        KeInitializeMutex(&g_Mutex, 0);
 
        //-f--> Setando nossa função de descarga do driver
        pDriverObj->DriverUnload = OnDriverUnload;
 
        //-f--> Setando as rotinas que meu driver vai dar
        //      suporte.
        pDriverObj->MajorFunction[IRP_MJ_CREATE] = OnCreate;
        pDriverObj->MajorFunction[IRP_MJ_CLOSE] = OnClose;
        pDriverObj->MajorFunction[IRP_MJ_WRITE] = OnWrite;
        pDriverObj->MajorFunction[IRP_MJ_READ] = OnRead;
 
        //-f--> Criando device de controle
        nts = IoCreateDevice(pDriverObj,
                             0,
                             &g_usDeviceName,
                             FILE_DEVICE_UNKNOWN,
                             0,
                             FALSE,
                             &pDeviceObj);
        if (!NT_SUCCESS(nts))
            ExRaiseStatus(nts);
 
        //-f--> Vamos fazer I/O com buffer intermediário
        pDeviceObj->Flags |= DO_BUFFERED_IO;
 
        //-f--> Criando symbolic link para que aplicações
        //      possam ver este device.
        nts = IoCreateSymbolicLink(&g_usSymbolicLink,
                                   &g_usDeviceName);
        if (!NT_SUCCESS(nts))
            ExRaiseStatus(nts);
 
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //-f--> Obtendo código da cagada
        nts = GetExceptionCode();
 
        //-f--> Isso vai fazer o depurador parar aqui.
        //      Mas só se compilado em Checked
        ASSERT(FALSE);
        KdPrint(("An exception occurred at " __FUNCTION__ "\n"));
 
        //-f--> Como tivemos problemas na inicialização, vamos
        //      desfazer o que foi feito.
        if (pDeviceObj)
            IoDeleteDevice(pDeviceObj);
    }
 
    return nts;
}

Escrevendo Dispatch Functions

Também vou partir do princípio de que você já tem uma idéia do que é uma IRP. Agora vamos escrever as funções que às manipulam. São as chamadas Dispatch Functions. Estas funções são setadas na inicialização do driver como vocês puderam observar no código acima. Na estrutura DriverObject, o membro MajorFunction é um array de ponteiros de função indexado pelas macros do tipo IRP_MJ_READ. O protótipo da função é o mesmo para todas as funções e será exibido logo abaixo. Uma Dispatch Function tem que manipular IRPs seguindo algumas regras, como tudo no DDK. Uma função mínima poderia ser escrita como segue abaixo.

/****
***     OnDispatch
**
**      Exemplo de uma Dispatch Function mínima
*/
 
NTSTATUS
OnDispatch(IN PDEVICE_OBJECT  pDeviceObj,
           IN PIRP            pIrp)
{
    //-f--> Aqui preencho o status desta IRP para a aplicação.
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
 
    //-f--> Completo a IRP. Depois disso, é terminantemente
    //      proibido tocar na estrutura pIRP. Isso não te pertence mais.
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    //-f--> Retorna o status para o IoManager.
    return STATUS_SUCCESS;
}

Mas o que aconteceria se chamássemos a função ReadFile com um handle para o nosso device quando não preenchemos a posição IRP_MJ_READ? Queimariamos eternamente no mármore do inferno? Na verdade, se dermos uma olhada na tabela MajorFunction antes de preenchê-la, veremos que existe o mesmo endereço em todas as posições da tabela. Vamos colocar um break-point logo na entrada da DriverEntry, dar uma fuçada na tabela antes de ser preenchida e ver o que tem por lá.


Como vemos, essa tabela é toda inicializada com um ponteiro de função que na sua implementação, considerando que as IRPs de gerenciamento de energia têm um tratamento especial, completa a IRP com o status de STATUS_INVALID_DEVICE_REQUEST.

Uma Dispatch Function basicamente segue uma das três alternativas para tratar uma IRP. Em casos de filtros, nosso driver poderia repassar a IRP para o driver o qual estivesse atachado. Tudo bem, já está anotado aqui… “Fazer um post dando um exemplo de filtro”. A segunda alternativa seria reter a IRP para fazer um tratamento assíncrono, e por último e não menos importante, simplesmente completar a IRP. Notem que em nosso exemplo, tudo o que fazemos é dizer à aplicação que a IRP foi executada com sucesso e completamos a mesma. Para completar a IRP, utilizamos a função IoCompleteRequest, que recebe a IRP a ser completada e o Boost de Prioridade. Ah? Supondo que sua IRP tivesse alguma interação com hardware, isso iria consumir algum tempo da thread em Kernel Mode, esse tempo iria gerar um atraso na thread atual e que seria compensado por este Boost. Como esse não é o nosso caso, vamos utilizar a macro de define nenhum Boost. O DDK tem uma lista de constantes que determina qual o Boost que a thread deveria receber para cada tipo de dispositivo. Veja um deles extraído do meu wdm.h (essa definição pode estar no ntddk.h dependendo da sua versão de DDK).

//
// Priority increment for completing CD-ROM I/O.  This is used by CD-ROM device
// and file system drivers when completing an IRP (IoCompleteRequest)
//
 
#define IO_CD_ROM_INCREMENT             1

Ao final deste post , haverá um link para baixar todos os arquivos necessários para gerar a aplicação e o driver. Reparem nos fontes de exemplo que nossas Dispath Functions OnCreate e OnClose se parecem muito com o exemplo acima. Isso porque não tomamos nenhuma ação quando se abre ou se fecha um handle para o device que criamos.

Obtendo parâmetros da IRP

Já nas outras Dispatch Functions OnRead e OnWrite, temos que obter os dados que o driver precisa para executar a IRP, tais como buffer enviado pelo usuário e tamanho do mesmo, tanto na escrita como na leitura. Estes parâmetros estão em uma Stack Location dentro da IRP. Nossa, quanto mais eu rezo, mais nominho esquisito me aparece… Stack Locations são estruturas de parâmetros que são alocados junto com a IRP. Existe uma Stack Location para cada device na pilha de dispositivos que foi chamada. Essa conversa pode ficar bem divertida, mas temos um post para terminar. Vamos deixar para falar sobre Stack Locations em nosso exemplo de filtro. Lá esse assunto fará mais sentido. Mas se você não se agüenta de curiosidade e quiser saber mais sobre o assunto, veja o que a referência diz a respeito de Stack Locations. Por agora vamos apenas considerar que estes parâmetros estão lá e que para ter acesso a esta estrutura devemos utilizar a macro IoGetCurrentIrpStackLocation. Para se ter uma idéia mais prática de todo esse blablablá, segue todo o código da função OnWrite com comentários a dar com pau.

/****
***     OnWrite
**
**      Esta rotina é chamana quando a API WriteFile é chamada
**      utilizando o handle do nosso device.
*/
 
NTSTATUS
OnWrite(IN PDEVICE_OBJECT  pDeviceObj,
        IN PIRP            pIrp)
{
    PIO_STACK_LOCATION  pStack;
    PVOID               pUserBuffer;
    ULONG               ulSize;
    PBUFFER_ENTRY       pBufferEntry = NULL;
    NTSTATUS            nts;
    BOOLEAN             bMutexAcquired = FALSE;
 
    __try
    {
        //-f--> Um olá para o depurador
        KdPrint(("Writing into EchoDevice...\n"));
 
        //-f--> O buffer é um dos parâmetros que vêm
        //      na própria IRP
        pUserBuffer = (PCHAR)pIrp->AssociatedIrp.SystemBuffer;
        ASSERT(pUserBuffer != NULL);
 
        //-f-->Obtém endereço da Stack Location corrente
        pStack = IoGetCurrentIrpStackLocation(pIrp);
 
        //-f--> Obtém o tamanho do buffer
        ulSize = pStack->Parameters.Write.Length;
 
        //-f--> Aloca o nó junto com o buffer que será ocupado
        //      pelo buffer enviado pelo usuário.
        pBufferEntry = (PBUFFER_ENTRY) ExAllocatePoolWithTag(
            PagedPool,
            sizeof(BUFFER_ENTRY) + ulSize,
            ECHO_TAG);
 
        //-f--> Se não tem memória já viu...
        if (!pBufferEntry)
            ExRaiseStatus(STATUS_NO_MEMORY);
 
        //-f--> Inicializando a estrutura
        pBufferEntry->pBuffer = (pBufferEntry + 1);
        pBufferEntry->ulSize = ulSize;
 
        //-f--> Copia o buffer enviado pelo usuário
        //      para o buffer alocado aqui.
        RtlCopyMemory(pBufferEntry->pBuffer,
                      pUserBuffer,
                      ulSize);
 
        //-f--> Obtém o mutex que protege a lista
        //      de acessos em paralelo.
        nts = KeWaitForMutexObject(&g_Mutex,
                                   UserRequest,
                                   KernelMode,
                                   FALSE,
                                   NULL);
        if (!NT_SUCCESS(nts))
            ExRaiseStatus(nts);
 
        //-f--> Temos que lembrar disso caso algo muito
        //      ruim aconteça
        bMutexAcquired = TRUE;
 
        //-f--> Insere novo elemento no final da lista
        InsertTailList(&g_BufferList,
                       &pBufferEntry->Entry);
 
        //-f--> Informa ao IoManager que todos os dados enviados
        //      ao driver foram lidos com sucesso.
        pIrp->IoStatus.Information = ulSize;
        nts = STATUS_SUCCESS;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //-f--> Obtém código da cagada
        nts = GetExceptionCode();
 
        //-f--> Isso vai fazer o depurador parar aqui.
        //      Mas só se compilado em Checked
        ASSERT(FALSE);
        KdPrint(("An exception occurred at " __FUNCTION__ "\n"));
 
        //-f--> Se deu algo errado e já alocamos
        //      este buffer, então vamos desalocar
        if (pBufferEntry)
            ExFreePool(pBufferEntry);
 
        //-f--> Informa ao IoManager que nada foi transferido.
        pIrp->IoStatus.Information = 0;
    }
 
    //-f--> Libera mutex
    if (bMutexAcquired)
        KeReleaseMutex(&g_Mutex,
                       FALSE);
 
    //-f--> Completando a IRP.
    pIrp->IoStatus.Status = nts;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return nts;
}

Outro ponto importante a se notar aqui é o preenchimento do campo Information da estrutura IoStatus que fica na IRP. Nestas funções de transferência de dados, este campo informa ao IoManager a quantidade de dados que foi transferida da aplicação para o driver e vice-versa. Este campo se reflete diretamente sobre o quarto parâmetro da API WriteFile, que tem exatamente a mesma função. Depois de recebidos e validados os parâmetros, alocamos o nó que vai receber o buffer. Reparem que estamos alocando em memória paginada, afinal de contas, todas as nossas funções serão executadas em PASSIVE_LEVEL. Apesar desta função ser uma Dispatch Function, isso não significa que ela seja executada em DISPATCH_LEVEL. Vamos com calma, essas são coisas bem diferentes. Anotando… “Post sobre IRQLs e POOL_TYPEs”. A função OnRead é similar à OnWrite, assim vou poupá-los de colocar todo o código aqui.

Quando meu driver for descarregado

A função OnDriverUnload será chamada quando o driver estiver sendo terminado. Aqui, além de esvaziar a lista de buffers que podem ter ficado esquecidos no driver, vamos apagar o Symbolic Link e o DeviceObject que foi criado na inicialização. Simples assim…

/****
***     OnDriverUnload
**
**      A festa acabou, vai pra casa, um abraço pra muié,
**      e bejo nas criança.
*/
 
VOID OnDriverUnload(IN PDRIVER_OBJECT   pDriverObj)
{
    PLIST_ENTRY     pEntry;
    PBUFFER_ENTRY   pBufferEntry;
 
    //-f--> Diga boa noite
    KdPrint(("Terminating KernelEcho driver...\n"));
 
    //-f--> Aqui removemos todos os nós que ainda não foram lidos
    //      pela aplicação. Isso aconteceria se a aplicação chamasse
    //      WriteFile e não chamasse ReadFile.
    while(!IsListEmpty(&g_BufferList))
    {
        //-f--> Pega o primeiro nó da lista
        pEntry = RemoveHeadList(&g_BufferList);
 
        //-f--> Obtém o endereço a partir do nó
        pBufferEntry = CONTAINING_RECORD(pEntry, BUFFER_ENTRY, Entry);
 
        //-f--> Finalmente libera a memória utilizada por
        //      este nó
        ExFreePool(pBufferEntry);
    }
 
    //-f--> Apagando DeviceObject e SymbolicLink
    //      criados na inicialização.
    IoDeleteSymbolicLink(&g_usSymbolicLink);
    IoDeleteDevice(pDriverObj->DeviceObject);
}

Nossa, que erro horrível! E se o driver for terminado enquanto alguma leitura ou escrita estiver sendo realizada? Será que um futuro negro nos aguarda e nossas almas serão amaldiçoadas pelo resto da eternidade? Será que a Pequena Sereia tem algo a ver com isso?

Bom, melhor deixar nossas crenças de lado e focar no DDK. Um driver não pode ser terminado enquanto houverem referências para algum device deste driver. Note que esta rotina não tem retorno para que possamos informar ao sistema que o driver pode ou não ser descarregado. Se alguma aplicação ainda tiver um handle aberto para algum device enquanto você solicita a parada do mesmo, o sistema responderá que o driver não poderá ser terminado. Nessa condição, a rotina OnDriverUnload nem será chamada. Mas caso contrário, se nada impedir do driver ser descarregado e nossa rotina for chamada, já era… Seu driver já está a caminho do céu dos drivers.

O mundo encantado de User Mode

Não vou colocar todo o código fonte da aplicação aqui no post, mas todos os fontes estão no arquivo disponível para download. Creio que uma coisa que vale a pena mostrar aqui é a sintaxe de como obter o handle para o device que criamos em nosso driver de exemplo.

    //-f--> Aqui abrimos um handle para o nosso device que
    //      foi criado pelo driver. Vale lembrar que nosso
    //      driver de exemplo tem que ser instalado e iniciado
    //      para que a chamada abaixo funcione corretamente.
    hDevice = CreateFile("\\\\.\\EchoDevice",
                         GENERIC_ALL,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL);

Depois que o handle para o device foi obtido, as operações de Read, Write e Close seguem exatamente como se estivéssemos realizando as mesmas operações com arquivos. Não precisa ser nenhum mestre Jedi para conseguir utilizar estas funções.

    //-f--> Envia a string recebida ao driver via
    //      WriteFile.
    if (!WriteFile(hDevice,
                   szBuffer,
                   dwBytes,
                   &dwBytes,
                   NULL))

Instalando e testando

Já mostrei em um outro post como instalar um driver na mão. Entretanto, existem meios mais civilizados de instalar um driver. Um deles é utilizando o OSR Driver Loader, uma ferramenta que oferecida pela OSR que instala seu driver sem a necessidade de reiniciar a máquina. Na verdade, este é um procedimento bem simples de se fazer, mas não simples o suficiente para comentar sobre isso ainda neste post, então vamos utilizar a ferramenta mesmo.

Depois de compilar o driver, coloque uma cópia dele no System32\drivers da máquina vítima. Em seguida execute o DriverLoader e preencha os campos como exibido na figura abaixo.


Depois é só clicar em Register Service para instalar o novo driver e em seguida clicar em Start Service para iniciar o driver. Pronto, agora você já poderá utilizar a aplicação de teste. A aplicação é muito simples de utilizar. Depois de iniciada, digite as strings que deveriam ser enviadas ao driver. Uma string vazia indica o fim das strings e então começa a leitura das mesmas strings enfiladas no driver.

Ufa! Como vimos, mesmo um driver que faça algo muito simples requer uma quantidade considerável de código e muitos conceitos diferentes. Sei que ficaram algumas lacunas durante o post, mas espero ter ajudado. Caso tenham dúvidas em algum ponto do driver ou mesmo da aplicação de teste, não hesitem em perguntar ou mandar seus comentários. Os contatos ajudam muito a definir os próximos posts.
Have fun!

KernelEcho.zip

7 Responses

  1. Muito bom esse artigo, Fernando! Especialmente a parte da Pequena Sereia.

    Tenho algumas dúvidas que acredito serem dignas de serem compartilhadas entre os que lêem seu blog. 1. Quando você escreve:

    “… considerando que as IRPs de gerenciamento de energia têm um tratamento especial, completa a IRP com o status de STATUS_INVALID_DEVICE_REQUEST.”

    Qual a relação entre as IRPs de gerenciamento de energia e sua análise em que a função wrapper da tabela de dispatch retorna STATUS_INVALID_DEVICE_REQUEST?

    2. Percebo que você usa muito nts como o nome da variável de retorno. Esse é um uso comum no DDK, entre os programadores de kernel, ou ambos?

    []s e continue nos brindando com esses posts!

    1. Mano Lesma,

      As IRPs de gerenciamento de energia são lançadas pelo Power Manager e precisam ser sincronizadas em todo o sistema. Para ajudar à manter este sicronismo, seu driver precisa chamar a rotina PoStartNextPowerIrp antes de completar a IRP. Esta função avisa ao Power Manager que este driver já pode receber a próxima IRP de energia.
      Estas IRPs sempre tiveram uma frescurinha a mais. Para se ter uma idéia, elas são as únicas que não deveriam ser passadas para a IoCallDriver, e sim, para a PoCallDriver. Que chique hein?
      Agora, com a chegada do Windows Vista, esse tratamento especial não é mais necessário.

      Com relação ao nts, é cacuete meu mesmo.

      []s,

  2. Ahhhhh, tá. Agora eu entendi a relação entre aquele pedaço de código e o gerenciador de energia. Pela seu próximo parágrafo após o screenshot eu havia entendido que (comentários entre parênteses):

    “Como vemos, essa tabela é toda inicializada com um ponteiro de função que na sua implementação, (já que o código está) considerando que as IRPs de gerenciamento de energia têm um tratamento especial, (então o código) completa a IRP com o status de STATUS_INVALID_DEVICE_REQUEST.”

    Valeu, mano Ferdinando!

  3. Fernando,

    Uma perguntinha…no exemplo, quando aloca os dados, o que especificamente significa o parametro TAG, e depois quando você inicializa a estrutura o pBufferEntry->pBuffer não teria que apontar para o final da estrutura?

    POnga.

    1. Olá POnga,

      Vamos lá:
      1)O parâmetro TAG é uma marca que a API de alocação de memória coloca a cada alocação. Isso serve para que mais tarde, num eventual problema de memory leak, possamos utilizar uma ferramenta e nos mostra quantas alocações existem associadas a cada TAG, permitindo assim que detectemos uma TAG com um número absurdo de alocações, o que sinalizaria um memory leak. Esta TAG também é verificada quando se utiliza a função de desalocação de memória, o que detectaria possíveis invasões de memória. Este post fala um pouco a respeito.

      2) A próxima pergunta é um pouco mais fácil de responder. Creio que a linha de código em questão é a seguinte:

      pBufferEntry->pBuffer = (pBufferEntry + 1);

      De fato (pBufferEntry + 1) apontará para o final da estrutura, já que pBufferEntry é um ponteiro para a própria estrutura. Quando incrementamos um ponteiro, o deslocamento será do tamanho da estrutura apontada.

      Exemplo;

      PULONG p = 0x10000;

      // aqui o ponteiro se desloca 4 bytes, que é sizeof(ULONG)
      p = p + 1;

      ASSERT(p == 0x10004);

      Essa talvez seja uma pergunta que meu amigo Lesma gostaria de falar a respeito.

      Espero ter ajudado.
      []s.

Leave a Reply

Your email address will not be published.