Driver plus plus

10 de October de 2006 - Fernando Roberto

Desenvolver software de baixo nível é muito bom. Eu pessoalmente adoro o tipo de trabalho que realizo. Tenho amigos muito próximos que se maravilham com a beleza do C++, como as coisas parecem se encaixar perfeitamente em meio ao STL, iterators e templates. Meu negócio definitivamente é outro. Apesar de utilizar C/C++, algumas pontinhas de Assembly e gostar muito destas linguagens, não me dedico em estudar as entranhas do C++. Meu lado prático de resolver os problemas me leva a fazer algumas coisas que fariam o Lesma querer me bater, mas felizmente ele é uma pessoa calma. 😛

Depois que meu irmão (Kabloc) leu meu post Getting Started…, ele me perguntou se não poderiamos utilizar C++ no lugar de C para desenvolver drivers. Neste post vou resumir os problemas enfrentados para se ter uma Tela Azul orientada a objetos. Não vou opinar se C++ é melhor ou pior que C para escrever drivers. Essa é uma discussão que parece não ter fim. Se você participa de alguma lista de discussão de drivers, sabe do que estou falando. Eu pessoalmente prefiro sair criando classes, namespaces e templates que resolvam de maneira modular os meus problemas. Isso me parece ser mais confortável. De fato podemos sentir uma certa tendência do desenvolvimento Kernel Mode para a programação orientada a objetos. Este é um aspecto cada vez mais presente com as DDIs (device-driver interfaces), que utilizam o modelo COM para comunicação entre camadas no Kernel, e a chegada do WDF. Vale lembrar que utilizar o modelo do COM não significa utilizar o COM, mas apenas o conceito de interfaces derivadas da tão conhecida IUnknown para implementar contadores de referência e intercâmbio de interfaces.

Primeiro de tudo, vamos nos basear no projeto exemplo criado em meu post anterior para criar um driver que faça uso de uma classe. Como manda a tradição, vamos começar pensando que é fácil e simplesmente usar a extensão CPP no arquivo fonte abaixo e ver no que dá.

#include 
 
//-f--> Uma classe com um nome bem original
class MyClass
{
public:
    MyClass()
    {
        DbgPrint("Construtor %p\n", this);
    }
 
    ~MyClass()
    {
        DbgPrint("Destrutor %p\n", this);
    }
 
    VOID SayHello(VOID)
    {
        DbgPrint("Hello from %p\n", this);
    }
};
 
 
/****
***
**      DriverEntry 
*/
 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    MyClass Instance;
 
    //-f--> Um sinal de vida
    DbgPrint("DriverEntry called\n");
 
    //-f--> Hello...
    Instance.SayHello();
 
    //-f--> Até aqui tudo bem
    return STATUS_SUCCESS;
}

Round #1…

Compiling - krnclass.cpp for i386
...\ntddk.h(2152) : error C2220: warning treated as error - no object
                    file generated
...\ntddk.h(2152) : error C4162: '_ReturnAddress' : no function with
                    C linkage found
...\ntddk.h(6889) : error C4162: '_InterlockedExchange' : no function
                    with C linkage found
...\ntddk.h(6915) : error C4162: '_InterlockedIncrement' : no function
                    with C linkage found
...\ntddk.h(6928) : error C4162: '_InterlockedDecrement' : no function
                    with C linkage found
...\ntddk.h(6942) : error C4162: '_InterlockedExchangeAdd' : no function
                    with C linkage found
...\ntddk.h(6972) : error C4162: '_InterlockedCompareExchange' : no
                    function with C linkage found
...\ntddk.h(7024) : error C4162: '_InterlockedOr' : no function with C
                    linkage found
...\ntddk.h(7034) : error C4162: '_InterlockedAnd' : no function with C
                    linkage found
...\ntddk.h(7044) : error C4162: '_InterlockedXor' : no function with C
                    linkage found

Vamos encontrar no ntddk.h algumas definições que precisam ser compiladas em C e não em C++. Este problema também vai acontecer com a função de entrada do driver DriverEntry. O DDK espera encontrar este símbolo exportado como C. Mas isso ainda é mamão com açúcar, basta mudar a maneira como incluímos este header e como declaramos do nosso entry point para que fique como mostra abaixo.

extern "C"
{
    #include 
}
 
...
 
extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{

Agora sim, compilou direitinho. Carregando este driver poderemos ver as seguintes mensagens no DebugMon.

Pronto? Já temos um driver em C++? Ainda não. Já podemos desfrutar de alguns conceitos como classes, templates e sobrecargas por exemplo, mas vejamos o que acontece quando tentamos utilizar o operador new.

extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    MyClass *pInstance = NULL;
 
    //-f--> Criando uma instância dinamicamente
    pInstance = new MyClass();
 
    //-f--> Telazulfobia
    if (!pInstance)
        return STATUS_NO_MEMORY;
 
    //-f--> Hello from heap
    pInstance->SayHello();
 
    //-f--> Liberando
    delete pInstance;
 
    //-f--> Até aqui tudo bem
    return STATUS_SUCCESS;
}
 

Round #2…

Linking Executable - objchk_wxp_x86\i386\krnclass.sys for i386
krnclass.obj : error LNK2019: unresolved external symbol "void * __cdecl
               operator new(unsigned int)" (??2@YAPAXI@Z) referenced in
               function _DriverEntry@8
krnclass.obj : error LNK2019: unresolved external symbol "void __cdecl
               operator delete(void *)" (??3@YAXPAX@Z) referenced in
               function "public: void * __thiscall MyClass::`scalar
               deleting destructor'(unsigned int)" (??_GMyClass@@QAEPAXI@Z)
objchk_wxp_x86\i386\krnclass.sys : error LNK1120: 2 unresolved externals

Ops! Para resolver estas dependências, teremos que sobrecarregar os operadores new e delete para que as alocações sejam feitas através do ExAllocatePool. Vamos passar um parâmetro adicional ao operador new para que possamos escolher entre NonPagedPool, PagedPool and so on…

typedef unsigned int size_t;
 
//-f--> Sobrecarga do operador new
__inline void* __cdecl operator new(size_t size,
                                    POOL_TYPE pool)
{
    return ExAllocatePool(pool, size);
}
 
//-f--> Sobrecarga do operador delete
__inline void __cdecl operator delete(void *pVoid)
{
    ExFreePool(pVoid);
}
 
...
 
    //-f--> Devemos especificar o pool type aqui
    pInstance = new(NonPagedPool) MyClass();
 
...

Agora já compila novamente e as alocações de memória são realizadas de maneira que se deve, entretanto existe um outro problema que acontecerá quando você tentar criar objetos estáticos ou globais. Vamos demonstrar isso criando uma instância global e mais uma vez compilar este exemplo.

Round #3…

Linking Executable - objchk_wxp_x86\i386\krnclass.sys for i386
krnclass.obj : error LNK2019: unresolved external symbol _atexit
               referenced in function _$E1
objchk_wxp_x86\i386\krnclass.sys : error LNK1120: 1 unresolved externals

Como não temos suporte a C++ em Kernel, não temos a run time que implementa toda a inicialização dos objetos estáticos chamando todos os construtores e destrutores globais. Este é um processo um tanto complicado e que vai nos exigir um cadim de conceito. Existe um artigo muito bom do Matt Pietrek que descreve este processo. Parece que isso vai nos dar um belo trabalho, mas felizmente no site da Hollis Technology Solutions existe um MSI que traz uma implementação da run time do C++ para Kernel. O pacote é Open Source, e assim traz todo o código fonte (incluindo um exemplo) e as bibliotecas desta implementação.

Esta implementação da run time é bem mais ampla que a descrita neste post, e teremos o suficiente para brincar bastante com C++ em Kernel. Para os malloqueiros de plantão, ela traz inclusive as definições do malloc e free. A única coisa desconfortável que encontrei neste pacote é a necessidade de substituir a definição do nosso ponto de entrada DriverEntry para CPP_DRIVER_ENTRY.

#include 
 
...
 
/****
***
**      DriverEntry 
*/
CPP_DRIVER_ENTRY(IN PDRIVER_OBJECT  pDriverObject,
                 IN PUNICODE_STRING pusRegistryPath)
{
 
...

Vou deixar um exemplo de como utilizar este pacote para download admitindo que você fez a instalação do MSI em C:\Library\HtsCpp. Este exemplo também poderá nos servir de base para futuros posts que venham a utilizar algum recurso de orientação a objeto. De qualquer forma, segue o preview de como deve ficar seu sources.

TARGETNAME=KrnClass
TARGETPATH=obj
TARGETTYPE=DRIVER
 
!if "$(DDKBUILDENV)" == "fre"
BUILD_CONFIG=libFre
!else
BUILD_CONFIG=libChk
!endif
 
TARGETLIBS=C:\Library\HtsCpp\$(BUILD_CONFIG)\i386\HtsCpp.lib
 
INCLUDES=C:\Library\HtsCpp\sys\inc;
 
SOURCES=KrnClass.cpp

Até mais mais! 🙂

KrnClass.zip

Deixe um comentário