Driver plus plus

October 10th, 2006 - Fernando Roberto

Developing low-level software is really good. I personally love the kind of work I do. I have close friends who marvel at C++ beauty, how things appear to fit perfectly among STL, iterators and templates. My business is definitely another. Despite using C/C++, Assembly and appreciating these languages a lot, I don’t deeply dedicate myself to study C++. My practical side to solve problems leads me to do some things that would make my fried Slug want to beat me, but fortunately he is a calm person. 😛

After my brother (Kabloc) had read my post Getting Started…, he asked me whether it’s be possible tp use C++ instead of C for developing drivers. In this post I will summarize the problems to get an object-oriented Blue Screen. I will not opine whether C++ is better or worse than C to write drivers. This is a discussion that seems to have no end. If you participate in any discussion list about driver development, you know what I mean. I personally prefer create classes, namespaces and templates that address in a modular way to my problems. That seems to be more comfortable. In fact, we can feel a certain tendency in Kernel Mode development for object-oriented programming. This is something increasingly present with DDIs (device-driver interfaces) using COM model for communication between layers in Kernel and the coming of WDF. Remember that using COM model does not mean using COM, but only the concept of derived interfaces well known as IUnknown to implement reference counters and exchange interfaces.

First of all, let us build the sample project created in my previous post to show a driver that makes use of a class. As the tradition dictates, we’ll start thinking it’s easy and simply use the CPP file extension at source below and see what happens.

#include  
 
//-f--> A class with a very original name
class MyClass
{
public:
    MyClass()
    {
        DbgPrint("Constructor %p\n", this);
    }
 
    ~MyClass()
    {
        DbgPrint("Destructor %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--> A life sign
    DbgPrint("DriverEntry called\n");
 
    //-f--> Hello...
    Instance.SayHello();
 
    //-f--> Everything is fine so far
    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

We find some definitions in ntddk.h that need to be compiled in C, and not in C++. This problem will also happen with the driver’s entry point DriverEntry. DDK expects to find this exported symbol as C. But this is still very easy to solve, just change the way we include this header and the way we declare our entry point so, that it’ll look as shown below.

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

So, it has compiled properly. Loading this driver, we can see the following messages in DebugMon.

Are we done? Do we already have a driver in C++? Not yet. We can already enjoy some concepts like classes, templates and overloads, for example, but let’s see what happens when we try to use the new operator.

extern "C"
NTSTATUS DriverEntry(IN PDRIVER_OBJECT  pDriverObject,
                     IN PUNICODE_STRING pusRegistryPath)
{
    MyClass *pInstance = NULL;
 
    //-f--> Creating an instance dynamically
    pInstance = new MyClass();
 
    //-f--> Crashphobia
    if (!pInstance)
        return STATUS_NO_MEMORY;
 
    //-f--> Hello from heap
    pInstance->SayHello();
 
    //-f--> Releasing
    delete pInstance;
 
    //-f--> Everything is fine so far
    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

Oops! To resolve these dependencies, we have to overload new and delete operators so, that allocations would be made through the ExAllocatePool(). Let’s pass an additional parameter to the new operator so that we can choose between NonPagedPool, PagedPool and so on …

typedef unsigned int size_t;
 
//-f--> Overloading the new operator
__inline void* __cdecl operator new(size_t size,
                                    POOL_TYPE pool)
{
    return ExAllocatePool(pool, size);
}
 
//-f--> Overloading the delete operator
__inline void __cdecl operator delete(void *pVoid)
{
    ExFreePool(pVoid);
}
 
...
 
    //-f--> We should specify the pool type here
    pInstance = new(NonPagedPool) MyClass();
 
...

Now it compiles again and the memory allocations are made in the way it should do, but there is another problem that happens when you try to create static or global objects. Let’s demonstrate this by creating a global instance and again, trying to compile this example.

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

Since we have no support to C++ in Kernel, we have no run-time that implements all static object initialization by calling all global constructors and destructors of them. This is a process somewhat complicated and it will require some more concepts. There is a very good article from Matt Pietrek that describes this process. Looks like this will give us a lot of work, but fortunately there is a MSI that brings an implementation of Kernel C++ run-time at Hollis Technology Solutions site . The package is an open source, and thus, it brings all the source code with it (including an example) and the libraries of this implementation.

This run-time implementation is broader than described in this post and we will have enough to play with C++ in Kernel. For C users, it brings even the definitions of malloc() and free(). The only uncomfortable thing I found in this package is a need to replace the definition of our DriverEntry() entry point to CPP_DRIVER_ENTRY.

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


I’ll leave an example for downloading about how to use this package, admitting that you have installed MSI on C:\Library\HtsCpp. This example may also serve as the basis for future posts that would use object orientation feature. Anyway, the following is a preview of how your SOURCES file should be.

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

See you! 🙂

KrnClass.zip

Leave a Reply