August 30th, 2006 - Fernando Roberto

Like most driver developers, I also have to build drivers for various Windows versions. Although I will not comment about VXDs on this post, I can comment about having a single .SYS file that can run either in Windows Server 2003 and Windows NT. I know that this OS is no longer sold and has no support from Microsoft, but it is impressive to see how we still find Windows 95 and Windows NT both in corporate environments and in end user’s homes. So, we always have to stay in dodging each operating system limitations when we are planning to develop a system.

A function that received a new version was ExFreePool(), its new version is the ExFreePoolWithTag(), which is implemented since Windows 2000. To get a single binary that runs in both versions, we have to use the oldest one that still is supported by recent systems.

Let’s consider the source below for a driver that performs the amazing task of allocating memory dynamically.

#include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                     PUNICODE_STRING pusRegistryPath)
   PVOID pTemp;
   //-f--> I'm thinking it's easy allocating memory.
    if (!(pTemp = ExAllocatePool(NonPagedPool, 10))
       return STATUS_NO_MEMORY;
   //-f--> Memory leak is vulgarity.

Using the build environment for Windows XP to compile this driver, we can see the same binary runs on Windows Server 2003, Windows XP, Windows 2000 and Windows NT. Or at least, it should. When we try to start our wonderful driver on a Windows NT, we get the following message:

But how? If we take a closer look at our driver, we find that it is a miserable traitor. As a DLL, drivers are executable modules that use the classical structure PE to declare their dependencies. Thus, we can open our driver in the “Dependency Walker” and see what it expects from life to be loaded. You may not believe, but it really statically depends on the new allocating memory function. A bastard traitor!

This is because, within the file ntddk.h, there is a set of definitions that ends up changing the calls from ExFreePool() to  ExFreePoolWithTag(). So, we can get those tons of code written for NT and use them in builds for newer systems without changing a single line, or we can share some source between drivers for Windows NT and newer systems.

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

Then you ask me the following questions: But what is the real difference between old and new version? Can my driver experience problems if you try to use the old version running on a newer system? Is there intelligent life outside earth?

The new version came to help detecting memory leaks. Each memory allocation is associated with a tag. These tags can be viewed using debugging tools such as WinDbg. For example: If your entire library setup uses a tag, and its communication library uses another, it will be easy to know which ones are leaving allocated memory. This makes it easy to know which programmer on your team you will be fired. The macros in ntddk.h use only one tag to the default memory allocations which has no associated tags.

But, what about drivers compiled with the DDK for Windows NT, running on newer systems? These drivers actually use the old functions. If we use Depends on them we could see that. As a matter of compatibility, the old functions are still exported in newer systems to support older drivers. Let’s take a look at the implementation of ExAllocatePool() Windows 2000.

Implementation of the old function simply forwards the memory allocation for the new function and uses the tag ‘None’. This assures us that we will not die of cancer if we continue using old functions in a new system.

Well, what were we intending to do? Oh yes, build a new driver that can run on both Windows NT and later systems. We could simply use the Windows NT DDK and have backward compatibility to run on newer systems. This is possible, but some of the functions we see in the DDK are actually defined as macros. The IoSetCompletionRoutine() function is a classic example of this. Using an old DDK means not having some of these functions defined and we’d have to patch DDK definitions if we wanted to enjoy them. There are also those cases of missing people on desert islands with only the latest DDK.

#include <ntddk.h>
#undef ExAllocatePool
#undef ExFreePool
NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject,
                     PUNICODE_STRING pusRegistryPath)

To resolve this, just after including ntddk.h, let’s take a #undef in macros that will redirect older calls to new version of ExAllocatePool() and that’s it, your problems have gone away. Rebuilding, we will have a new driver that uses the old allocation functions. Thus, it can be used in systems since Windows NT.

This method disadvantage is that your drivers can not use the tags when running on a system that supports them. Actually, you can have a single binary that, when running on Windows NT can use ExFreePool(), whereas when running on a newer system it uses ExFreePoolWithTag(), but that’s another post.

Leave a Reply