Blog Post

Intricacies of WDF - Part 1

Difference between EvtCleanupCallback and EvtDestroyCallback.

Intricacies of WDF - Part 1 - Difference between EvtCleanupCallback and EvtDestroyCallback.

Intro

You probably know that you can specify a clean-up callback routine for most of the WDF objects that will be called before that object is deleted. But have you ever wondered why are there two such callbacks: EvtCleanupCallback and EvtDestroyCallback?

This blog post will shed some light on the important differences between the two.

WDF Object Creation & Parent-Child Hierarchy

To visualize it better, let's make a concrete example first that we can work with. Say, we have a custom struct:

C++[Copy]
struct MYSTRUCT
{
    size_t szcb;
    LIST_ENTRY listChain;
    int* pArray;
    KEVENT eventStopNow;
    WDFMEMORY wdfChild;
};

//This will declare the context for our struct
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(MYSTRUCT, WDFDEVICE_to_MYSTRUCT);

Then we can allocate and initialize a context for it as such:

C++[Copy]
WDF_OBJECT_ATTRIBUTES attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, MYSTRUCT);

//We will set two test callbacks for it here
attr.EvtCleanupCallback = cleanupCallback;
attr.EvtDestroyCallback = destroyCallback;

WDFDEVICE wdfDevice;
status = WdfDeviceCreate(&DeviceInit, &attr, &wdfDevice);
if(status == STATUS_SUCCESS)
{
    //Get pointer to our allocated context & clear it
    MYSTRUCT* pStruct = WDFDEVICE_to_MYSTRUCT(wdfDevice);
    memset(pStruct, 0, sizeof(*pStruct));

    //We'll track a chain of something in this linked list
    InitializeListHead(&pStruct->listChain);

    //Allocate memory for something else (using an older WDM function)
    pStruct->pArray = (int*)ExAllocatePool2(POOL_FLAG_PAGED, sizeof(int) * 0x100, '1gat');
    ASSERT(pStruct->pArray);

    //Create a signaling event (that will be used later)
    KeInitializeEvent(&pStruct->eventStopNow, NotificationEvent, FALSE);

    //Create a child WDF object just to illustrate the parent-child hierarchy in WDF
    WDF_OBJECT_ATTRIBUTES attChild;
    WDF_OBJECT_ATTRIBUTES_INIT(&attChild);
    attChild.ParentObject = wdfDevice;          //We'll make its parent to be our test WDF device

    status = WdfMemoryCreate(&attChild, PagedPool, '2gat', PAGE_SIZE, &pStruct->wdfChild, NULL);
    if(status == STATUS_SUCCESS)
    {
        //There's no need to increment the reference count on the child object here!
        //INFO: We're doing it just to illustrate this concept in the callbacks later...
        WdfObjectReference(pStruct->wdfChild);
    }

    //...
}

Let's use the kernel debugger to see the state of the WDF objects right after they were created. We can do so from a breakpoint after the code sequence above.

First the values inside the MYSTRUCT struct. We can get them by using its local variable pStruct that holds a pointer to it:

WinDbg Commands[Copy]
3: kd> ?? pStruct
struct MYSTRUCT * 0xffffbb86`963c6f80
   +0x000 szcb             : 0
   +0x008 listChain        : _LIST_ENTRY [ 0xffffbb86`963c6f88 - 0xffffbb86`963c6f88 ]
   +0x018 pArray           : 0xffffce8a`9f672c00  -> 0n0
   +0x020 eventStopNow     : _KEVENT
   +0x038 wdfChild         : 0x00004479`7a3e90a8 WDFMEMORY__

Then let's pull up the value of the wdfDevice handle and its properties:

WinDbg Commands[Copy]
3: kd> ?? wdfDevice
struct WDFDEVICE__ * 0x00004479`69c39368
   +0x000 unused           : ??

3: kd> !wdfhandle 0x00004479`69c39368
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x0000447969c39368
=============================
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xffffbb86963c6f80 MyDriver!MYSTRUCT (size is 0x40 bytes)
    EvtCleanupCallback fffff802ac9b4960 MyDriver!cleanupCallback
    EvtDestroyCallback fffff802ac9a11c0 MyDriver!destroyCallback

Parent: !wdfhandle 0x0000447978fb9198, type is WDFDRIVER
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb86963c6c90

Thus, we can see that the initial reference count for our wdfDevice was 1. Which is normal for an object that was just created.

Then we can do the same for our wdfChild using its handle value that we got from reading the pStruct struct above:

WinDbg Commands[Copy]
3: kd> !wdfhandle 0x00004479`7a3e90a8
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x000044797a3e90a8
=============================
Refcount: 2
Contexts:
    <no associated contexts or attribute callbacks>

Parent: !wdfhandle 0x0000447969c39368, type is WDFDEVICE
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb8685c16f50

The value of its reference count was 2 since we also incremented it with the WdfObjectReference macro.

EvtCleanupCallback

I would say that this is the most popular callback among the two. You can get more details about it from the official documentation here.

The main distinction of the "cleanup" callback is that it is invoked as soon as its associated WDF object is slated for deletion, but before any removal had been done. This usually happens when the WdfObjectDelete is called on that object. Such could be done explicitly by your code, or implicitly by the framework itself.

When the EvtCleanupCallback is invoked, the reference count on the object is unchanged, its memory is still available and you can generally do the same "usual stuff" with that object as you would otherwise.

The "cleanup" callback is the framework's notification that it is starting the deletion of the underlying WDF object.

Note that there's no way to cancel, or stop the deletion of WDF objects from this callback.

When the associated wdfDevice is deleted, the framework will invoke the EvtCleanupCallback callback, or our cleanupCallback first.

Thus, we can still use all of our MYSTRUCT members, the WDF object that is being deleted itself, and its child WDF objects, as if those are "alive and well." For instance:

C++[Copy]
static void cleanupCallback(__in WDFOBJECT wdfDevice)
{
    MYSTRUCT* pStruct = WDFDEVICE_to_MYSTRUCT(wdfDevice);
    ASSERT(pStruct);

    //Notify the work item to begin its own tear-down
    //NOTE that the work item thread will be responsible for the tear-down process itself ...
    KeSetEvent(&pStruct->eventStopNow, 0, FALSE);

    //Remove (IRP) request cancelation (and wait for it if it's pending)
    //
    //INFO: I didn't include 'wdfRequest' in this example for brevity.
    //      In real-world situation, it may be some IRP request that your driver may be still processing...
    //
    NTSTATUS status = WdfRequestUnmarkCancelable(wdfRequest);
    if(status == STATUS_CANCELLED)
    {
        //Need to wait for the request cancelation routine to finish processing
        //
        //INFO: The 'eventFinishedCancelation' will be signaled by the cancelation routine
        //      after it finishes the cancelation, and especially after it is done using any of the
        //      underlying WDF objects that are being deleted here (including their children.)
        //      (I didn't include that logic in this example for brevity.)
        //
        KeWaitForSingleObject(&eventFinishedCancelation, Executive, KernelMode, FALSE, NULL);
    }
    else if(status != STATUS_SUCCESS)
    {
        //Some break-down of our logic - this needs further debugging!
        ASSERT(FALSE);
        KeBugCheckEx(MANUALLY_INITIATED_CRASH, status, wdfRequest, NULL, NULL);
    }

    //Finally, since we incremented the reference count on our 'wdfChild' object we need to decrement it as well
    //INFO: Without this call we will have a memory leak because our 'wdfChild' will never be freed!
    WdfObjectDereference(pStruct->wdfChild);

    //...
}

The most reasonable actions within the EvtCleanupCallback callback is to prepare your custom objects for deletion, by doing either of:

  1. Dereference all objects that the underlying object that is being deleted owns. You would normally do so by calling WdfObjectDereference, or a lower-level ObDereferenceObject macros.
    It is important to keep in mind the parent-child hierarchy that WDF maintains, and the fact that child objects are deleted automatically by the framework before the parent object is deleted. That is why we don't really need to worry about deleting the wdfChild in our example.

    Another gotcha here are the default parent objects that many WDF objects maintain. This means that if you didn't specify a parent when you created, or became an owner of a WDF object, it may implicitly be initialized to a certain parent anyway. For instance, if you created an WDFFILEOBJECT without specifying its parent, the framework will assign your "WDF device object" as its parent anyway. And as this table shows, there's no way for you to override this behavior for the WDFFILEOBJECT.

  2. Signal all work item (threads) to stop, and tear themselves down, that the underlying object that is being deleted has references to. In our example we used the eventStopNow for that.
    Note that due to the asynchronous nature of the kernel, the actual work to tear down the objects associated with the work item threads will be done in those work items themselves. So make sure to use proper reference counting on those objects to prevent their premature deletion.
  3. Close or stop I/O targets, queues, timers, etc., that the underlying object that is being deleted owns.
  4. Unregister any callbacks that may fire later and touch the underlying object that is being deleted.
    The important part here is to ensure that those callbacks were indeed stopped. Otherwise, if any of the callbacks are still pending, or active, proceeding with the teardown of the underlying object may create a use-after-free bug, which will be very difficult to pinpoint and fix.

    So unless the framework function can wait for any pending callbacks to finish, that the underlying object that is being deleted owns, make sure to wait for them to finish in your own code. This is especially true with the cancelable WDFREQUESTs that you might have set up using the WdfRequestMarkCancelableEx and WdfRequestUnmarkCancelable functions. For instance, if WdfRequestUnmarkCancelable returns STATUS_CANCELLED this would indicate that the request (or IRP) cancelation callback is either pending or had already run. In that case you need to use your own method of determining the outcome, and if the cancelation callback is still pending, wait for it to finish before returning from your EvtCleanupCallback routine.

    I showed a brief excerpt of how do it in the code sample above, using the eventFinishedCancelation event. A more complete example deserves its own blog post, that I should write later.

  5. Account for any DPC's that might be still pending and properly cancel those.
    The same warning that I gave for the cancelable WDFREQUEST's above goes for DPCs as well. If you queued a work item to process your DPC make sure to cancel that work item, and if it is already pending, wait for it to finish before returning from your EvtCleanupCallback routine.

    Otherwise, leaving your DPC processing work item routine running after you return from the EvtCleanupCallback callback may create a use-after-free bug that will be very difficult to diagnose and fix.

Even though this was not intended by the authors of the WDF, many drivers also free other resources and non-WDF objects in the EvtCleanupCallback. Doing so in this callback is much safer than in the EvtDestroyCallback.

Just to illustrate the fact that the object that is being deleted and its children are in the same state as they were right before deletion, let's use the kernel debugger to look up information on the wdfDevice and its wdfChild from a breakpoint in the beginning of the cleanupCallback routine.

First, we can check the state of our wdfDevice:

WinDbg Commands[Copy]
3: kd> ?? wdfDevice
struct WDFDEVICE__ * 0x00004479`69c39368
   +0x000 unused           : ??

3: kd> !wdfhandle 0x00004479`69c39368
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x0000447969c39368
=============================
Handle type is WDFDEVICE
Refcount: 2
Contexts:
    context:  dt 0xffffbb86963c6f80 MyDriver!MYSTRUCT (size is 0x40 bytes)
    EvtCleanupCallback fffff802ac9b4960 MyDriver!cleanupCallback
    EvtDestroyCallback fffff802ac9a11c0 MyDriver!destroyCallback

Parent: !wdfhandle 0x0000447978fb9198, type is WDFDRIVER
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb86963c6c90

Its reference count is 2. It was incremented by the framework after creation to keep our device alive.

Let's retrieve the state of MYSTRUCT by its pointer in the local variable pStruct next:

WinDbg Commands[Copy]
3: kd> ?? pStruct
struct MYSTRUCT * 0xffffbb86`963c6f80
   +0x000 szcb             : 0
   +0x008 listChain        : _LIST_ENTRY [ 0xffffbb86`963c6f88 - 0xffffbb86`963c6f88 ]
   +0x018 pArray           : 0xffffce8a`9f672c00  -> 0n0
   +0x020 eventStopNow     : _KEVENT
   +0x038 wdfChild         : 0x00004479`7a3e90a8 WDFMEMORY__

Then we can read the state of the wdfChild object using the handle from the pStruct that we obtained earlier:

WinDbg Commands[Copy]
0: kd> !wdfhandle 0x00004479`7a3e90a8
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x000044797a3e90a8
=============================
Refcount: 2
Contexts:
    <no associated contexts or attribute callbacks>

Parent: !wdfhandle 0x0000447969c39368, type is WDFDEVICE
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb8685c16f50

Its reference count is the same as it was right after creation: 2. The framework did not touch it because our code owns it.

Finally, let's see that our wdfChild refers to a valid chunk of memory:

WinDbg Commands[Copy]
3: kd> !wdfmemory 0x00004479`7a3e90a8
Treating handle as a KMDF handle!

WDFMEMORY 0x00004479`7a3e90a8:  Buffer 0xffffab07fa72b000, Length 0x1000 (4096) bytes

3: kd> db 0xffffab07fa72b000
ffffab07`fa72b000  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b010  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b020  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b030  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b040  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b050  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b060  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................
ffffab07`fa72b070  15 15 15 15 15 15 15 15-15 15 15 15 15 15 15 15  ................

The framework initialized it to some random byte for us.

WDFMEMORY is WDF's way of allocating and managing a chunk of memory on the heap, what WDM would do with ExAllocatePool2.

Next let's look at another type of the deletion callback.

EvtDestroyCallback

Unlike the first callback, the EvtDestroyCallback is invoked after the reference count for the underlying WDF object is decremented but before its memory is freed.

This creates a very unique state of the underlying WDF objects when they remain in a sort of "half-alive, half-dead" state if their reference count reaches 0. Because of that, I'm not a big fan of the EvtDestroyCallback.

You can technically do the following in the EvtDestroyCallback, but you have to be very careful with it and not deviate from these rules:

  1. The context of the underlying object that is being deleted is still alive. So you can use it. Although many WDF objects in it may not be.
    Because of this weird state of associated WDF objects, I would try to avoid calling any methods on those WDF objects, other than the ones that delete them, such as WdfObjectDelete.

    Additionally, do not touch any child WDF objects for the object that is being deleted.

  2. We can delete any WDF objects that the underlying object that is being deleted owns using WdfObjectDelete.
    Make sure to account for the implicit (or default) parent association between WDF objects. Many objects are automatically deleted when their parent object is deleted as well. Not realizing that may create its own slew of the use-after-free bugs.

    This automatic parenting structure of the WDF is my main praise and also the pet peeve. On one hand, it is very convenient to let it delete all child objects for you. But then, there seems to be no easy way to tell the WDF to back off and let me handle the life-cycle of certain WDF objects on my own.

    So be very careful with this concept. It created quite a few bugs that I've seen in the wild.

  3. All WDF child objects of the underlying WDF object that is being deleted will initiate their deletion. Thus, make sure not to use those child objects any longer. Do not call WdfObjectDelete on the child objects either!
    I will show an example of what happens with WDF child objects later.
  4. We can free any non-WDF objects that are not parented by WDF, such as: WDM file handles, registry handles, OB kernel objects, etc.
  5. I have to give one very important caveat here: if you incremented the reference count on the underlying WDF object that is being deleted (say, by using WdfObjectReference) and forgot to dereference it later, this will create a memory leak that can jeopardize the stability of the target OS.

    So make sure to account for all manual reference counting that you do on the objects!

You probably realized by now that the main mantra of the EvtDestroyCallback is to only free the underlying objects, and not to do anything else with them!

This is especially true about any logging functions, such as WPP traces. A driver writer may inadvertently include a WPP trace that prints out the object(s) being deleted, not realizing that some of those object properties and child objects may no longer be available.

So if you do include any logging in EvtDestroyCallback, make sure to limit its output to a minimum. For instance, you may safely print the values of WDF handles. Just make sure not to call any of the WDF functions on those objects.

Because of what I said above, I would advise you to avoid using the EvtDestroyCallback altogether and to go with the EvtCleanupCallback instead. The latter callback is much safer to work with, provided that all your cleanup goes into EvtCleanupCallback and you completely avoid using EvtDestroyCallback. Using both will introduce a slew of bugs of their own.

Having said that, let me show how you can handle the EvtDestroyCallback for our previous example:

C++[Copy]
static void destroyCallback(__in WDFOBJECT wdfDevice)
{
    MYSTRUCT* pStruct = WDFDEVICE_to_MYSTRUCT(wdfDevice);
    ASSERT(pStruct);

    //You can still do WPP tracing, but be very careful about what you do there
    TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_QUEUE, 
        "wdfDevice=%p, listChain(%p):{F=%p,B=%p}",
        wdfDevice,                                //I'm only using the value of the WDF handle itself
        &pStruct->listChain,                     //I can only safely use primitive data types in the context as well
        pStruct->listChain.Flink,
        pStruct->listChain.Blink);
    
    //Free the non-WDF allocated memory
    if(pStruct->pArray)
    {
        ExFreePool(pStruct->pArray);
        pStruct->pArray = NULL;                  //I usually also null-out the pointer for safely of a possible reuse later
    }
}

As you can see, my destroyCallback handler is very light and only tends to free objects and does some very basic logging.

Then let's use the kernel debugger again to pull up the state of our WDF objects inside my destroyCallback.

First, the wdfDevice:

WinDbg Commands[Copy]
3: kd> ?? wdfDevice
struct WDFDEVICE__ * 0x00004479`69c39368
   +0x000 unused           : ??

3: kd> !wdfhandle 0x00004479`69c39368
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x0000447969c39368
=============================
Handle type is WDFDEVICE
Refcount: 0
Contexts:
    context:  dt 0xffffbb86963c6f80 MyDriver!MYSTRUCT (size is 0x40 bytes)
    EvtCleanupCallback fffff802ac9b4960 MyDriver!cleanupCallback
    EvtDestroyCallback fffff802ac9a11c0 MyDriver!destroyCallback

Parent: !wdfhandle 0x0000447978fb9198, type is WDFDRIVER
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb86963c6c90

As you can see its reference count is 0.

Then let's pull up the state of wdfChild using the pStruct local variable as a pointer to MYSTRUCT:

WinDbg Commands[Copy]
3: kd> ?? pStruct
struct MYSTRUCT * 0xffffbb86`963c6f80
   +0x000 szcb             : 0
   +0x008 listChain        : _LIST_ENTRY [ 0xffffbb86`963c6f88 - 0xffffbb86`963c6f88 ]
   +0x018 pArray           : 0xffffce8a`9f672c00  -> 0n0
   +0x020 eventStopNow     : _KEVENT
   +0x038 wdfChild         : 0x00004479`7a3e90a8 WDFMEMORY__

0: kd> !wdfhandle 0x00004479`7a3e90a8
Treating handle as a KMDF handle!

Dumping WDFHANDLE 0x000044797a3e90a8
=============================
Refcount: 0
Contexts:
    <no associated contexts or attribute callbacks>

Parent: !wdfhandle 0x0000447969c39368, type is WDFDEVICE
Owning device: !wdfdevice 0x0000447969c39368

!wdfobject 0xffffbb8685c16f50

Its reference count is also 0, which illustrates the fact that the WDF decremented the reference count on the child object before it attempted to delete the parent object.

As an interesting aside let's see if our wdfChild still refers to the valid memory:

WinDbg Commands[Copy]
3: kd> !wdfmemory 0x00004479`7a3e90a8
Treating handle as a KMDF handle!
could not retrieve vtable from ffffc78f00588fb8

Oops, no it is not. As you can see the framework tore it down before our destroyCallback was invoked. That is one of the reasons why the EvtDestroyCallback is dangerous, and also why you should not rely on the WDF objects related to the deleted object to be in a usable state in that callback.

Mismatched WDF Reference Counts & Unreleased Resources

As an experiment, let's see what happens if you forget to decrement the reference count on a WDF object.

Say, let's comment out the following line in our cleanupCallback example:

C++[Copy]
    //WdfObjectDereference(pStruct->wdfChild);

Because the reference count on the wdfChild will never reach 0, this will create a memory leak and even the WDF with its automatic parent-child hierarchy and automatic deletion of child objects will not be able to help. All because we artificially incremented the reference count on the object that we owned and never decremented it.

That is by the way a "hack" to prevent the framework from automatically deleting a child WDF object. In case you prefer to manage its life-cycle on your own.

But, there are built-in mechanisms to detect other memory leaks. Say, if we forgot to free the memory in our MYSTRUCT::pArray member. Let's comment out the following line in our destroyCallback routine to show what happens:

C++[Copy]
    //ExFreePool(pStruct->pArray);

If we enabled Driver Verifier for our driver, along with the NT module (i.e. ntoskrnl.exe) for the "Special pool" and "Pool tracking" options, the verifier will crash the target OS when our driver attempts to unload with the following bugcheck:

WinDbg Commands[Copy]
DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught.  This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, BugChecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arguments:
Arg1: 0000000000000062, A driver has forgotten to free its pool allocations prior to unloading.
Arg2: ffffbb86a7690fb8, name of the driver having the issue.
Arg3: ffffbb86869d6ed0, verifier internal structure with driver information.
Arg4: 0000000000000001, total # of (paged+nonpaged) allocations that weren't freed.
	Type !verifier 3 drivername.sys for info on the allocations
	that were leaked that caused the bugcheck.

We can expand on it, by retrieving the name of the offending driver:

WinDbg Commands[Copy]
3: kd> du ffffbb86a7690fb8
ffffbb86`a7690fb8  "MyDriver.sys"

Yep, it's us. Then let's use suggested verifier command to get more details about the memory leak:

WinDbg Commands[Copy]
3: kd> !verifier 3 MyDriver.sys

Verify Flags Level 0x0002099b

  STANDARD FLAGS:
    [X] (0x00000000) Automatic Checks
    [X] (0x00000001) Special pool
    [X] (0x00000002) Force IRQL checking
    [X] (0x00000008) Pool tracking
    [X] (0x00000010) I/O verification
    [ ] (0x00000020) Deadlock detection
    [X] (0x00000080) DMA checking
    [X] (0x00000100) Security checks
    [X] (0x00000800) Miscellaneous checks
    [X] (0x00020000) DDI compliance checking

  ADDITIONAL FLAGS:
    [ ] (0x00000004) Randomized low resources simulation
    [ ] (0x00000200) Force pending I/O requests
    [ ] (0x00000400) IRP logging
    [ ] (0x00002000) Invariant MDL checking for stack
    [ ] (0x00004000) Invariant MDL checking for driver
    [ ] (0x00008000) Power framework delay fuzzing
    [ ] (0x00010000) Port/miniport interface checking
    [ ] (0x00040000) Systematic low resources simulation
    [ ] (0x00080000) DDI compliance checking (additional)
    [ ] (0x00200000) NDIS/WIFI verification
    [ ] (0x00800000) Kernel synchronization delay fuzzing
    [ ] (0x01000000) VM switch verification
    [ ] (0x02000000) Code integrity checks

    [X] Indicates flag is enabled


Summary of All Verifier Statistics

  RaiseIrqls           0x0
  AcquireSpinLocks     0x0
  Synch Executions     0x0
  Trims                0x0

  Pool Allocations Attempted             0x48f7078
  Pool Allocations Succeeded             0x48f7075
  Pool Allocations Succeeded SpecialPool 0x48f7074
  Pool Allocations With NO TAG           0x4
  Pool Allocations Failed                0x0

  Current paged pool allocations         0x20eaf for 038E3C2A bytes
  Peak paged pool allocations            0x22835 for 03D8CE8D bytes
  Current nonpaged pool allocations      0x210f4 for 0386E9B4 bytes
  Peak nonpaged pool allocations         0x24520 for 04452E86 bytes

Driver Verification List
------------------------

nt!_VF_TARGET_DRIVER 0xffffbb86806d4fc0: MyDriver.sys (Loaded and Unloaded)

    Pool Allocation Statistics: ( NonPagedPool / PagedPool )

      Current Pool Allocations: ( 0x00000000 / 0x00000001 )
      Current Pool Bytes:       ( 0x00000000 / 0x00000400 )
      Peak Pool Allocations:    ( 0x00000003 / 0x00000002 )
      Peak Pool Bytes:          ( 0x000020a8 / 0x00000600 )
      Contiguous Memory Bytes:       0x00000000
      Peak Contiguous Memory Bytes:  0x00000000

    Pool Allocations:

      Address             Length      Tag   Caller Address    
      ------------------  ----------  ----  ------------------
      0xffffce8a9f672c00  0x00000400  tag1  0xfffff802ac9b3537  MyDriver!addVirtualDevice+0x1d7

    Contiguous allocations are not displayed with public symbols.

Such bugcheck is merely a helpful tip to show that there was an issue in our driver.

We can see immediately at the bottom of the Driver Verifier's output which memory allocation tag was not released, i.e. tag1. After that it takes just a few searches in our source code to find that memory allocation.

One thing to keep in mind is that the tag name is often inverted by the allocation DDI. For instance, the tag1 tag may appear in source code as '1gat'. So search for both variations to be sure.
Enabling Driver Verifier during the development phase of your driver will greatly facilitate your debugging process further down the road. I will try to cover the topic of the Driver Verifier in some future blog posts.

As you can see above the Driver Verifier can help in detecting memory issues, but as the example above showed, it is not perfect. So do your own due diligence to check the code in your drivers.

Additionally, consider using WDF Verifier to check for WDF-specific issues. I will have to leave that topic for another blog post though.

Conclusion

From my previous experience debugging issues in Windows kernel drivers, the most dangerous and error-prone part of the code is the tear-down of the asynchronous objects. Anything that deals with cancelation routines and pending callbacks creates a nightmare of bugs to root out. So, please try to follow the basic rules that I outlined above which should help you write bug-free kernel drivers.

Related Articles