Welcome to MSDN Blogs Sign in | Join | Help

Let's say that you allocated a PIRP and sent it down your device stack.  You free the PIRP in the completion routine and then return STATUS_MORE_PROCESSING_REQUIRED.  To make life more fun, you decide that you want to be able to cancel the sent IRP after you have sent it so you try to do it simple like this

typedef struct _DEVICE_EXTENSION {
    KSPIN_LOCK SentIrpLock;
    PIRP SentIrp;
} DEVICE_EXTENSION;

Sending thread:

KeAcquireSpinLock(&devext->SentIrpLock, ...);
devext->SentIrp = Irp;
KeReleaseSpinLock(&devext->SentIrpLock, ...);

Canceling thread:

KeAcquireSpinLock(&devext->SentIrpLock, ...);
if (devext->AllocatedIrp != NULL) {
   IoCancelIrp(devext->SentIrp);
}
KeReleaseSpinLock(&devext->SentIrpLock, ...);

Completion routine:

PIRP irp;

KeAcquireSpinLock(&devext->SentIrpLock, ...);
irp = devext->SentIrp;
devext->SentIrp = NULL;
KeReleaseSpinLock(&devext->SentIrpLock, ...);

IoFreeIrp(irp);

return STATUS_MORE_PROCESSING_REQUIRED;

And it then deadlocks ;). If the call to IoCancelIrp causes the IRP to be completed in the calling context (e.g. the one which has acquired the lock), the completion routine will run and try to acquire the lock (SentIrpLock) on the same thread which holds it.

So, life is not that simple and you have to do something more. The basic solution is that you need extra state to track who is touching the PIRP and who can free it. Walter Oney's book has a solution (IIRC, it is in the self initiated I/O section, but I do not have the book handy), but IMHO it is a bit complicated. KMDF has a solution to this problem which I like much more (imagine that ;)).

You need an extra LONG, calling it CompletionCount per PIRP that you want to be able to cancel.

 
  1. You initialize CompletionCount to 1 before sending it down the stack and storing it in devext.
  2. Whenever there is a thread that wants to cancel the PIRP, it tries to interlocked increment CompletionCount only if and only if the current CompletionCount value is > 0. For this you need to roll your own InterlockedIncrementWithFloor which is fortunately not that hard and I have already shown you how to do that, http://blogs.msdn.com/doronh/archive/2006/12/06/creating-your-own-interlockedxxx-operation.aspx.
  3. After the canceling thread has called InterlockedIncrementWithFloor and IoCancelIrp, it calls InterlockedDecrement.
  4. Whomever wants to complete the PIRP, like the completion routine, interlock decrements CompletionCount.

If the returned value from InterlockedDecrement is zero, the caller can complete the PIRP. If not, somebody else is trying to touch the PIRP and you must leave the PIRP alone.  So here is the revised code:

typedef struct _DEVICE_EXTENSION {
    KSPIN_LOCK SentIrpLock;
    PIRP SentIrp;
    ULONG CompletionCount;
} DEVICE_EXTENSION;

Sending thread:

KeAcquireSpinLock(&devext->SentIrpLock, ...);
devext->CompletionCount = 1;
devext->SentIrp = Irp;
KeReleaseSpinLock(&devext->SentIrpLock, ...);

Canceling thread:

PIRP irp = NULL;
KeAcquireSpinLock(&devext->SentIrpLock, ...);
if (devext->AllocatedIrp != NULL && MyInterlockedIcrementeWithFloor(&devext->CompletionCount, 0) > 0) {
   irp = devext->SentIrp;
}
KeReleaseSpinLock(&devext->SentIrpLock, ...);

if (irp != NULL) {
    IoCancelIrp(irp);
    if (InterlockedDecrement(&devext->CompletionCount) == 0) {
        IoFreeIrp(irp);
    }
}

Completion routine:

PIRP irp = NULL;

KeAcquireSpinLock(&devext->SentIrpLock, ...);
irp = devext->SentIrp;
devext->SentIrp = NULL;
KeReleaseSpinLock(&devext->SentIrpLock, ...);

if (InterlockedDecrement(&devext->CompletionCount) == 0) {
    IoFreeIrp(irp);
}

return STATUS_MORE_PROCESSING_REQUIRED;

The beauty of this solution is that if you add more actors (let's say a timer for an async timeout, all you have to do is bump the CompletionCount to account for them to asynchronously rundown if you cannot cancel them.

I was going to write about how to do this, but the awesome folks at WHDC got to it before I did.  I did get to review it before it was published, so I did have some influence in what is in the tip ;). So on this one my job is easy, just go read the tip!

After a long wait (thank you for your patience!), the WDF 1.7 coinstallers are now up on the connect site.  To get the bits

  1. go to http://connect.microsoft.com
  2. Log in using your passport account
  3. Navigate to the WDF page (I don't know where it lives in the connection directory, sigh)
  4. Choose Downloads on the left
  5. The package is dated 4/17/2008, you may be able to get it directly from here once you have logged on

Enjoy and let the signing and shipping of v1.7 WDF drivers begin!

This is a pretty cool and somewhat obscure debugger command.  It allows you to tell the debugger what functions to skip if you are using the trace command ('t').  I think of the trace command as the 'step into' command though, but that is just me.  Let's say we have the following simple application:

#include <stdio.h>

struct Foo {
    Foo() : m_value(0) { }

    int Increment() { return ++m_value; }
    static void Print(int i) { printf("%d\n", i); }

    int m_value;
};

int _cdecl main(int argc, char *argv[])
{
    Foo f;
    Foo::Print(f.Increment());
    return 0;
}

If I were to run the program under the debugger and use the 't' command for each line, it would step into every function. I typically use 't' instead of 'p' because I usually want to step into a function at some point in time and I tend to press 'p' one too many times ;). Here is an example of the debugger session:

0:000> g test!main
>   13: {
0:000> t
>   14:     Foo f;
0:000> t
>    4:     Foo() : m_value(0) { }
0:000> t
>   15:     Foo::Print(f.Increment());
0:000> t
>    6:     int Increment() { return ++m_value; } [1]
0:000> t
>   15:     Foo::Print(f.Increment());
0:000> t
>    7:     static void Print(int i) { printf("%d\n", i); } [2]
0:000> gu
>   16:     return 0;
0:000> t
>   17: }
0:000> t
test!__mainCRTStartup+0x102:

Let's look at the statement Foo::Print(f.Increment()); When using the trace command, it will first step into Foo::Increment ([1]) before stepping into Foo::Print() ([2]). But let's say that I never want to step into Foo::Increment because I know that it is a simple function that I do not want to debug. I can tell the debugger to ignore trace commands into this function with the .step_filter command. The command takes a semi-colon delineated list of fully qualified symbol names (which can include wildcards so you can filter out entire modules) to ignore. Let's see the debugger session again with this command:

0:000> g test!main
>   13: {
0:000> .step_filter "test!Foo::Increment"
Filter out code symbols matching:
  test!Foo::Increment
0:000> t
>   14:     Foo f;
0:000> t
>    4:     Foo() : m_value(0) { }
0:000> t
>   15:     Foo::Print(f.Increment());
0:000> t
>    7:     static void Print(int i) { printf("%d\n", i); }
0:000> gu
>   16:     return 0;
0:000> t
>   17: }
0:000> t
test!__mainCRTStartup+0x102:

You will see now that when I trace into Foo::Print(f.Increment()); that the f.Increment() call is executed but not trace into (ignored is not the right word because it has run, I just didn't see it line by line) and I step directly into Foo::Print(). I think this is a pretty powerful debugger command, it can save you a lot of time if you are always accidentally stepping into the wrong function like I always do ;).

One of the WDM escapes in KMDF is EvtDeviceWdmIrpPreprocess (or EvtDevicePreprocessWdmIrp in the API in which you register it) which you can register for by calling WdfDeviceInitAssignWdmIrpPreprocessCallback.  This function allows you to process a WDM PIRP before KMDF sees it and potentially processes it.  From a KMDF adoption point of view, this functionaltiy was a very strong requirement.  Without, there would be not be a defined way for a KMDF to support IRPs that KMDF did not natively support.  For instance, the KMDF serial example registers a preprocess routine for IRP_MJ_FLUSH_BUFFERS, from [ddkroot]\src\kmdf\serial\pnp.c:

//
// Since framework queues doesn't handle IRP_MJ_FLUSH_BUFFERS,
// IRP_MJ_QUERY_INFORMATION and IRP_MJ_SET_INFORMATION requests,
// we will register a preprocess callback to handle them.
//
status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
    DeviceInit,
    SerialFlush
    IRP_MJ_FLUSH_BUFFERS,
    NULL, // pointer minor function table
    0); // number of entries in the table

You should use this functionality in your driver only if this is the last resort you have; it does not come for free.  When you register for any preprocess routine, KMDF will increase the StackSize in the underlying WDM PDEVICE_OBJECT.  This is called out in the documention for "Preprocessing and Postprocessing IRPs" in KMDF. By increasing the stack size, all PIRPs that are sent to your device will have an extra IO stack location.  This means that the PIRP is a bit larger than it would otherwise be; larger not only for the IRP_MJ code that you registered for, but for each and every PIRP that is sent to your device regardless of IRP_MJ code.  Additionally, if your StackSize goes over an internal threshhold in the IO manager, the way the PIRP is allocated could be different (for instance, devices with a StackSize of <= N might have a PIRP allocated out of a lookaside list while a StackSize > N might always be allocated by calling ExAllocatePool each time).  Mind you, the internal threshhold is a bit high so if you have a root enumerated device or your initial StackSize is low, this is not something that you will see.

So while registering a preprocess routine is a very useful thing to be able to do, you should consider your options carefully and only register a preprocess routine if you have no other option.

Well, that is certainly a long title ;).

First, let us look at an approximate implementation of KeAcquireSpinLock and KeRaiseIrql (and yes I know that KeRaiseIrql is really a #define to KfRaiseIrql, but it is the same thing that happens in the end…)

KIRQL KeAcquireSpinLock(PKSPIN_LOCK SpinLock, PKIRQL PreviousIrql)
{
    KeRaiseIrql(DISPATCH_LEVEL, PreviousIrql);
    [spin on the lock until it has been acquired]
}

VOID KeRaiseIrql(KIRQL NewIrql, PKIRQL, PKIRQL OldIrql)
{
    OldIrql = KeGetCurrentIrql();
    [raise IRQL to NewIrql]
}

What I want to emphasize is that KeAcquireSpinLock will retrieve the current IRQL (to know what to restore the IRQL to when the lock is released) as a part of acquiring the spin lock. Retrieving the current irql is a relatively expensive operation. Enter KeAcquireSpinLockAtDpcLevel. KeAcquireSpinLockAtDpcLevel does away with the IRQL change and just implements [spin on the lock until it has been acquired], but it does this with a large caveat…you must be running at DISPATCH_LEVEL (in reality it requires IRQL >= DISPATCH_LEVEL, but that is another discussion for another day) . It requires DISPATCH_LEVEL or higher so that you do not deadlock. Another caveat to effectively use KeAcquireSpinLockAtLevel you must know 100% that you are at DISPATCH_LEVEL. Naively, one could think that the following code optimizes for both cases

if (KeGetCurrentIrql() < DISPATCH_LEVEL) {
    KeAcquireSpinLock(&lock, &oldIrql);
}
else {
    KeAcquireSpinLockAtDpcLevel(&lock);
}

But the problem here is that in the case of the current IRQL < DISPATCH_LEVEL, the current IRQL is being retrieved twice (once in your code, once in KeAcquireSpinLock), so this relatively expensive operation is being performed twice when it should be performed only once. What all of this boils down to is that you must know with 100% certainty that the current IRQL is DISPATCH_LEVEL before you can use KeAcquireSpinLockAtDpcLevel effectively. Here are a couple of contexts here you can know for certain that the IRQL is DISPATCH_LEVEL.

  • In a DPC (for ISR, for a timer, your own)
  • After you have acquired a spin lock. In the case where you have nested locked being acquired (first A, then B), after you have called KeAcquireSpinLock(&A) you can then call KeAcquireSpinLockAtDpcLevel(&B)

Notice that a completion routine is not guaranteed to be called at IRQL == DISPATCH_LEVEL! While you may see that it is called at dispatch, it is not something that you can rely on 100% of the time. For instance, the lower driver could complete the IRP at passive level in an error condition.

I have no idea who created the name for PNP_DEVICE_NOT_DISABLEABLE, but I probably have the same reaction as you ... "seriously?  that is what they named?"  I mean come on, I think it could have at least been named PNP_DEVICE_CANNOT_BE_DISABLED.  I am sure you can think of some better names too. If so, please leave a comment with your suggestions!  While we had a chance to rectify this in KMDF in the WDF_DEVICE_STATE structure, we chose to keep the field name (NotDisableable) similar to the WDM name to avoid confusion.

For any readers who have not encountered this bit, it is a part of PNP_DEVICE_STATE.  You set this bit in the IRP_MJ_PNP/IRP_MN_QUERY_PNP_DEVICE_STATE IRP after calling IoInvalidateDeviceState

One interesting quirk about the PNP_DEVICE_NOT_DISABLEABLE state is that once it has been set and the PnP manager has processed it, the state is sticky.  By sticky I mean that even if you attempt to clear this bit on a subsequent IRP_MN_QUERY_PNP_DEVICE_STATE IRP, the PnP manager ignores your changes to this state.  This state remains stuck until any of the following occur

  1. The machine is rebooted and the device is reenumerated
  2. The device (or any device in its ancestry) is surprise removed
  3. The device (or any device in its ancestry) is ejected

One thing that is easily overlooked about implementing DriverEntry is that upon return !NT_SUCCESS, DriverUnload is not called.  I mentioned this anecdotally in a previous post, but it is worth expanding on.  I was bit by this oversight when I was working on the Bluetooth stack.  Driver verifier  correctly identified that my driver had leaked pool.  The code looked something like this

// Globals
UNICODE_STRING gRegistryPath = { 0 };

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;

    DriverObject->DriverUnload = DriverUnload;

    gRegistryPath.Length = RegistryPath->Length;
    gRegistryPath.MaximumLength = RegistryPath->MaximumLength;
    gRegistryPath.Buffer = (PWCHAR) ExAllocatePoolWithTag(PagedPool, gRegistryPath.MaximumLength, [tag]);
    if (gRegistryPath.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
    RtlCopyMemory(gRegistryPath.Buffer, RegistryPath->Buffer, gRegistryPath.Length);

    status = RegisterWithPortDriver(DriverObject, ...);
    if (!NT_SUCCESS(status)) { return status; } <== leak right here!

    // ... other init ...

    return status;
}

void DriverUnload(PDRIVER_OBJECT DriverObject)
{
    ExFreePool(gRegistryPath.Buffer);
    RtlZeroMemory(&gRegistryPath, sizeof(gRegistryPath));
}

While many WDM drivers do very little outside of initializing the dispatch table and other fields in their DriverObject, a miniport driver or a KMDF driver must register with their port driver (like ScsiPortInitialize) or framework (WdfDriverCreate) and this registration can introduce failure in DriverEntry (just like in my code sample above).  What to do? 

In a WDM driver you have to be very careful and manage this manually.  Either you have a common error exit path out of DriverEntry which performs the cleanup (or manually calls your DriverUnload routine) or cleanup on each possible point of error.  This pattern is very easy to get wrong and is not very maintainable, it is quite easy to add a new allocation and forget to cleanup it up later.

In a KMDF driver things are a bit easier to manage if you follow a particular pattern.  While EvtDriverUnload has the same problems as the WDM DriverUnload, the EvtObjectCleanup routine registered on the WDFDRIVER is called in both scenarios.  To re-emphasize, the EvtObjectCleanup registered on WDFDRIVER will be called when either DriverEntry returns !NT_SUCCESS or if your driver is gracefully unloaded later.  This means that if you put all of your cleanup in the cleanup routine your DriverEntry implemention becomes much simpler.  The one caveat is that the call to WdfDriverCreate must come before any allocations in your driver or state chaning APIs.  WPP_INIT_TRACING is one such state changing API where you must undo its effects by calling WPP_CLEANUP.  Quite a few WDK samples show this pattern (although suprisingly to me, not all!), let us look at the nonpnp sample (%wdk%\src\kmdf\nonpnp\sys\nonpnp.c)

NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN PUNICODE_STRING      RegistryPath
    )
{
    NTSTATUS                       status;
    WDF_DRIVER_CONFIG              config;
    WDFDRIVER                      hDriver;
    PWDFDEVICE_INIT                pInit = NULL;
    WDF_OBJECT_ATTRIBUTES          attributes;

    WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);

    // Tell the framework that this is non-pnp driver so that it doesn't set the default AddDevice routine.
    config.DriverInitFlags |= WdfDriverInitNonPnpDriver;

    // NonPnp driver must explicitly register an unload routine for the driver to be unloaded.
    config.EvtDriverUnload = NonPnpEvtDriverUnload;

    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = NonPnpEvtDriverContextCleanup;

    status = WdfDriverCreate(DriverObject,
                            RegistryPath,
                            &attributes,
                            &config,
                            &hDriver);
    if (!NT_SUCCESS(status)) {
        KdPrint (("NonPnp: WdfDriverCreate failed with status 0x%x\n", status));
        return status;
    }

    // Since we are calling WPP_CLEANUP in the DriverContextCleanup
    // callback we should initialize WPP Tracing after WDFDRIVER
    // object is created to ensure that we cleanup WPP properly
    // if we return failure status from DriverEntry. This
    // eliminates the need to call WPP_CLEANUP in every path
    // of DriverEntry.
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    ...

    return status;
}

VOID NonPnpEvtDriverContextCleanup(WDFDRIVER Driver)
{
    WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver));
}

The red comments show what is going on.  Hopefully the code is self explanatory. 

Incidentally, another pattern you can use for global memory allocations is to allocate the memory with WdfMemoryCreate without specifying a parent object.  The WDFDRIVER will be the parent object by default and since all child objects are destroyed when the parent is destroyed, all of your allocations will be destroyed after EvtDriverUnload has been called when the WDFDRIVER is destroyed in the unload path.

During my sophomore year at Cal Poly, I decided that I wanted to learn about threads, synchronization techniques and other topics associated modern operating systems.  Windows 95 had made its debut (yes, it is not a modern OS, but I didn't know that at the time!) and I had heard about Windows NT, but had never seen or used it.  Since I had Win95 on my machine at home, I decided that I would go to the bookstore and buy a book on threading. 

Well, needless to say there was not a book that was dedicated to threading, but I found a couple of books on Windows programming that included threading as topics.  It came down to 2 books, Jeffrey Richter's "Advanced Windows for Win95 and NT" (with Napoleon on the cover) and some other book.  I obviously picked Richter's book ;), although now it is called "Windows via C/C++."

This book was, and still is, awesome.  I have both the original and new editions.  Great detail, very easy to read and met it my needs perfectly (unlike "Inside OLE" by Kraig Brockschmidt which was full of information, I just could not stay awake reading it :) ).  IMHO, it successfully started me down the road to becoming a well rounded developer and I think it helped in getting my foot in the door at Microsoft.  I have even had the pleasure of letting Jeff know personally what a great book it is and how it shaped me for years to com.

A bit over a year ago I had to figure out why my Mail key started behaving differently on Vista vs XP and wrote about how I fixed it.  Well, my dev box was so slow that I was able to employ enough sympathy that I got a new one.  While my old box was a 32 bit machine, the new one came preinstalled with 64 bit Vista.  Cool.  I finally had a 64 bit box that was powerful enough to develop on (previously all I had a very noisy 64 bit test machine). 

When I get a new machine that I will be using for dev work I do the following:

  1. Blow away the preinstalled OS and install the latest OS release (beta or RTM) that I can do work on.  In this case, I left the OS alone ;).
  2. Enlist in windows sources
  3. Install the latest WDK
  4. Install Office Pro and Visio Pro (for the lovely WDF pnp/power/power policy state machines)
  5. Apply the mail key registry change so Outlook works the way I want it to.

Well steps 1 through 4 went by flawlessly and I updated the registry to fix the mail key.  All happy with myself I made the change and pressed the key.  Nothing happened.  The focus did not change nor did Outlook move to the default folder.  Phooey.  I double checked the changes in the registry and lo and behold I mistyped the value name and fixed it.  Pressed the mail key.  Still nothing happened.  Rats. 

So now my only recourse was to debug it ;).  First searched for "RegisteredApp" in the source tree (via content indexing) to find the function which processed the WM_APPCOMMAND message.  Once found, I traced it into the internal shlwapi function which looked up the appropriate locations in the registry and traced through it as well.  Not the most enjoyable task either....first, I do not own the code so I am figuring out it intent while determining if it is behaving correctly...and second, it is optimized code so debugging with source was not an option.  Thank god it was not IA64 assembly though.  PhewThe one positive through this part of the debugging was that it was 100% reproducible, so when I got lost, I could just hit 'g' and press the mail key again to restart.

After looking at this for a half an hour or so, the logic was this

  • Query for the default mail client in HKCU
  • if (WIN64) { If that failed, 1) open the default mail client in the 32 bit HKLM software key and 2) query for the default mail client }
  • If that failed, query for the default mail client in the normal HKLM software key (which would be the 64 bit HKLM on 64 bit, the 32 bit HKLM on 32 bit).

Upon successfully querying for the default mail client, attempt to execute its open verb as registered in the key 64 bit HKLM\Software\Clients\Mail\<application>\shell\open\command. If this failed, attempt the same thing under the 32 bit version of the key. 

The bug was that the result of the 2nd bullet (query on the 32 bit key on 64 bit machines) was being misinterpreted.  The code treated the return value as if a RegQueryValueEx call was made, but the call was to RegOpenKeyEx.  Because the return value was not being evaluated correct, step 2) was never performed (querying for the client on the new key) which led to an error when formatting the string that was used to query for the verb. 

OK, great.  Now at least I knew where the bug was, but I still wanted my mail key to work and I was definitely not going to run my own custom shlwapi.dll on my own machine.  It is used by too many components for me to have confidence in making that kind of change.  I needed a solution that was as minimal as possible and the least invasive.  In the end I ended up attaching windbg and using the assembler (the 'a' command) to remove the incorrect checks.  My initial removal was to nop the entire sequence...

0:004> u xxxxxxxxx`xxxxxx6c  (somewhere in the guts of shlwapi.dll)
$ the call whose result will be misinterpreted
xxxxxxxxx`xxxxxx6c ff153e08fcff    call    qword ptr [SHLWAPI!_imp_RegOpenKeyExW (000007fe`fee918b0)]

$ incorrect tests
xxxxxxxxx`xxxxxx72 85c0            test    eax,eax
xxxxxxxxx`xxxxxx74 8bf8            mov     edi,eax
xxxxxxxxx`xxxxxx76 7474            je      xxxxxxxxx`xxxxxxec
xxxxxxxxx`xxxxxx78 3bc3            cmp     eax,ebx
xxxxxxxxx`xxxxxx7a 742c            je      xxxxxxxxx`xxxxxxa8

$ call to query the value
xxxxxxxxx`xxxxxx7c 488b4c2438      mov     rcx,qword ptr [rsp+38h]
xxxxxxxxx`xxxxxx81 4c8d4c2430      lea     r9,[rsp+30h]
xxxxxxxxx`xxxxxx86 4c8d442440      lea     r8,[rsp+40h]
xxxxxxxxx`xxxxxx8b 33d2            xor     edx,edx
xxxxxxxxx`xxxxxx8d c7442430a0000000 mov     dword ptr [rsp+30h],0A0h
xxxxxxxxx`xxxxxx95 ff150d08fcff    call    qword ptr [SHLWAPI!_imp_RegQueryValueW (000007fe`fee918a8)]

$ now nop out the check, always do the query.  Stop @ xxxx7c since that is the start of the query call
0:004> a xxxxxxxxx`xxxxxx72 
xxxxxxxxx`xxxxxx72 nop
xxxxxxxxx`xxxxxx73 nop
xxxxxxxxx`xxxxxx74 nop
xxxxxxxxx`xxxxxx75 nop
xxxxxxxxx`xxxxxx76 nop
xxxxxxxxx`xxxxxx77 nop
xxxxxxxxx`xxxxxx78 nop
xxxxxxxxx`xxxxxx79 nop
xxxxxxxxx`xxxxxx7a nop
xxxxxxxxx`xxxxxx7b nop

$ and dump out the old code again to make sure the right thing was nop'ed
0:004> u xxxxxxxxx`xxxxxx6c 
xxxxxxxxx`xxxxxx6c ff153e08fcff    call    qword ptr [SHLWAPI!_imp_RegOpenKeyExW (000007fe`fee918b0)]
xxxxxxxxx`xxxxxx72 90              nop
xxxxxxxxx`xxxxxx73 90              nop
xxxxxxxxx`xxxxxx74 90              nop
xxxxxxxxx`xxxxxx75 90              nop
xxxxxxxxx`xxxxxx76 90              nop
xxxxxxxxx`xxxxxx77 90              nop
xxxxxxxxx`xxxxxx78 90              nop
xxxxxxxxx`xxxxxx79 90              nop
xxxxxxxxx`xxxxxx7a 90              nop
xxxxxxxxx`xxxxxx7b 90              nop
xxxxxxxxx`xxxxxx7c 488b4c2438      mov     rcx,qword ptr [rsp+38h]
xxxxxxxxx`xxxxxx81 4c8d4c2430      lea     r9,[rsp+30h]
xxxxxxxxx`xxxxxx86 4c8d442440      lea     r8,[rsp+40h]
xxxxxxxxx`xxxxxx8b 33d2            xor     edx,edx
xxxxxxxxx`xxxxxx8d c7442430a0000000 mov     dword ptr [rsp+30h],0A0h
xxxxxxxxx`xxxxxx95 ff150d08fcff    call    qword ptr [SHLWAPI!_imp_RegQueryValueW (000007fe`fee918a8)]

...but my final (and more refined) approach was the actual comparisons in the assembly.  After the in place edits, my mail key now works again!  The only issue is that I have to reapply every time explorer is started, I can live with that. I also filed a bug for the next release of Windows, so I will not have to make the edits in the future and neither will you ;).

I saw a book,  Advanced Windows Debugging, in the Microsoft company store and quickly read through it. It looked pretty awesome in the level of detail and breadth that it covered.  I ordered my own copy and I think it would be an invaluable resource for anyone who develops drivers or applications on Windows.  I learn something new everytime I read it.

d

There is a hotfix for setupapi.dll, read the KB article http://support.microsoft.com/kb/937187 and see if it applies to your machine
The first driver I owned when I started at Microsoft in 1997 was i8042prt.sys, the driver that controls your PS2 mouse and keyboard.  I had the job of upgrading it from an NT4 legacy style driver to a PnP enabled Windows 2000 driver.  Of course my dad asked "didn't keyboards and mice work before you got there?  what are you doing over there anyways?"  My manager at the time said all I had to do was get the mouse and keyboard working after resume from Sx and that would be it.  I ended up owning the driver for 5+ years and not the advertised couple of months ;), but I learned a lot.

 

One of things I did while owning this driver was unify crash dump support for all builds.  Previous to my owning the driver, Microsoft Far East Asia PSS added the ability to crash (aka crash dump) the machine for any given key and modifier combination (see I8xServiceCrashDump() for initialization of state and I8xProcessCrashDump() on processing the keys at runtime in 6000\src\input\pnpi8042 in the WDK for the gory details).  This code was under #ifdefs and was only live for far east builds of the driver.  This was a universally useful feature and I was asked to make it available for all builds.  I decided to make it easy for folks to enable it as well.  Configuration was not easy. First, you had to know the scan code for the trigger key (instead of the character itself).  Second you had to describe the modifiers using a set of flags that were only documented in the driver's header.  Third, you had to know where to put these registry values (which typically meant you also had to create a reg key as well). Yuck, not friendly.

To fix this I created one registry value (CrashOnCtrlScroll) that setup the default crash dump key sequence for you, right ctrl + scroll lock.  This KB article describes how to set the key. That worked well ... except that many laptops did not have a right control key!  I didn't want to create another key for crashing on left ctrl + scroll lock (or something as arbitrary but completely different) so I created a GUI application that let you type the key and select the modifiers.  Not rocket science, but functional.  Here is a snapshot

 

Issue #1:  While functional, it's ugly

Issue #2:  I named it cdsetup.exe (c[rash]d[ump]setup.exe).  Logical name except that many cdrom setup applications are also called cdsetup.exe so there was a bit of confusion.  To add to the confusion, I think my version of cdsetup.exe was a part of the Windows 200 resource kit.  The one saving grace to this name is that it automatically requests elevation on Vista (probably because it has "setup" its name).

Issue #3:  After recompiling the application yesterday to test it on Vista the red X, Cancel and OK buttons all stopped working which meant I could not dismiss the dialog box.  Weird.  I found an old build and it worked just fine so something I did during the recompile altered the behavior.  To get the application to build and run I had to add the following 2 lines to the sources file

USE_MSVCRT=1
_NT_TARGET_VERSION=$(_NT_TARGET_VERSION_WIN2K)

Specifying the taret version has no effect on the runtime so it must have been the conversion to the MS VC runtime instead of the old C runtime that shipped with Windows. I decided to debug the application's DlgProc.  It is rather simple:

LRESULT CALLBACK CrashDumpSetup::s_DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    CrashDumpSetup *cdi;

    if (message == WM_INITDIALOG) {
        cdi = new CrashDumpSetup(hDlg);

        if (!cdi)
            return FALSE;

        SetWindowLongPtr(hDlg, DWLP_USER, (ULONG_PTR) cdi);
        return (LRESULT) cdi->Initialize();
    }

    cdi = (CrashDumpSetup *) GetWindowLongPtr(hDlg, DWLP_USER);
    if (cdi) {
        return cdi->DialogProc(message, wParam, lParam);
    }

    return FALSE;
}

LRESULT CrashDumpSetup::DialogProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDC_DUMP_DISABLE:
        case IDC_DUMP_DEFAULT:
        case IDC_DUMP_ALT:
            ToggleAltUI(LOWORD(wParam) == IDC_DUMP_ALT);
            break;

        case IDOK:
            if (Apply() == FALSE) {
                return FALSE;
            }
            __fallthrough;

        case IDCANCEL:
            delete this;
            EndDialog(m_hDlg, LOWORD(wParam));
            break;

        }
    }

    return FALSE;
}

When pressing the red X, Cancel or OK keys my WM_COMMAND processing code ran and called EndDialog() as I expected, but like I said before, the dialog didn't dismiss itself.  I looked at the docs for EndDialog and saw that it returns a BOOL, so I rewrote EndDialog to capture the return value and it was returning FALSE!  Ugh.  Since I was single stepping through the function, !gle came to the rescue

0:000> !gle
LastErrorValue: (Win32) 0x578 (1400) - Invalid window handle.
LastStatusValue: (NTSTATUS) 0xc0000034 - Object Name not found.

An invalid window handle? m_hDlg worked before, maybe some component was overwriting it. So dumped the variable and then the hDlg from s_DlgProc,

0:000> dt this m_hDlg
Local var @ 0x1ff798 Type CrashDumpSetup*
0x00a01f88 
   +0x000 m_hDlg : 0x00a064d0 HWND__
0:000> .f+
01 001ff7cc 775af512 cdsetup!CrashDumpSetup::s_DlgProc+0x8f [d:\work\cdsetup\cdsetup.cpp @ 576]
0:000> dt hDlg
Local var @ 0x1ff7d4 Type HWND__*
0x004e05f0 
   +0x000 unused           : 49595140

Looking at the 2 values, it is obvious that m_hDlg was altered. But how? After staring at the code I wrote 7 years ago, I thought about what I changed (using the MS C runtime) and I immediate saw it. I was deleting the object and then touching freed memory. The old C runtime left the freed memory as it was before itw as freed so the old code had the bug the entire time, it was just that the new runtime caught made the mistake apparent immediately. The fix was simple, capture the value before deleting the object and then calling EndDialog (I guess I could have called EndDialog in the destructor but that made cleanup in the failure to initialize the object potentially problematic). After applying the fix, all of the buttons now worked as expected ;).

        case IDCANCEL:
            HWND h = m_hDlg;
            delete this;
            EndDialog(h m_hDlg, LOWORD(wParam));
            break;

First, I have to say that I don't agree with this design pattern at all.  I think it leads to too many problems and complications that are not worth the pain.  The only reason I am writing this entry is that I have seen so many people get this wrong or not account for some details and I want to make sure that if such a driver is installed, the stability of the system is not compromised.  The scenario I am covering is not the case where you are enumerating child devices and want to share resources assigned to the parent amongst the children.  Rather, this is sharing HW resources assigned to your device (device A) with another device (device B).  Device B can be a legacy NT4 style device or another PnP device stack, it doesn't matter.

There are 3 aspects to the issue that you must solve:

  1. Handing off the resources to device B.  This means that device B will open a handle against device A and send an internal IOCTL (e.g. IOCTL_INTERNAL_GET_RESOURCES) which device A completes with the appropriate values.  If the number of resources is fixed, this is easy.  If the number of resources varies, this can get complicated.
  2. Coordinating access to the resources between device A and device B.  If both drivers want to touch the resources at the same time, you now have to synchronize between 2 components and that can get very complicated, especially if there are callbacks involved.
  3. Only touching the resources when the device has power.  This is easy for device A to do since it is the power policy owner for the device and sees pnp state changing and device power irps.  This is not easy for device B to do though.

Aspects number 1 and 2 I will leave for you to solve.  Number 3 is where things get interesting.  There are scenarios where the device can lose power:

  1. PnP graceful remove (query remove, remove)
  2. PnP surprise removal
  3. PnP stop
  4. System power state change (e.g. a standby or hibernate)

 The first 2 scenarios in green are easily solved.  Device B registers for PnP notifications on the file handle that it opens and it will be told when a PnP remove or surprise removal occurs.  If you are using KMDF, the WDFIOTARGET automatically does this for you.  But there is a catch to the surprise removal notification...it is sent to device B after device A has already processed the PnP IRP.  This means that there is a window where device B thinks it has valid resources, but in reality it does not.  File handle notifications do not solve the last 2 scenarios though and the surprise removal case does not work well, so it would be nice if there was one solution for all 4 scenarios.

The solution is that device B must register 2 callbacks with device A, a power up and power down callback (they can be condensed to one callback with an additional passed in parameter indicating power state if you want).  These callbacks should be set at the same time that the resources are acquired so that there is no window where the resources have been acquired, the device loses power and the callback has not yet been set notifying device B of the power state change.  These callbacks can be passed from device B to device in any number of ways.  Two traditional ways are

  • To pass the callbacks in a buffer in an internal IOCTL (you can overload IOCTL_INTERNAL_GET_RESOURCES if you want)
  • To send a IRP_MN_QUERY_INTERFACE with these callbacks as input values in the INTERFACE based structure as I describe in this post

Once the callbacks have been set, device A must call them at the right time.  In a KMDF driver, it is a very simple implementation.  You call device B's power up callback in EvtDeviceSelfManagedIoRestart and device B's power down routine in EvtDeviceSelfManagedIoSuspend.  This covers all cases because KMDF treats PnP state changes as implicit power changes, powering down the device and using the same callbacks for all cases.  This also covers the surprise removal window because device B is now notified at the beginning of device A's power down instead of after it.  One final thing to consider is what device B thinks the initial power state of device A is in.  Ideally, device B should assume device A is in low power and should not touch the retrieved resources until its power up callback has been invoked.  This would mean that the initial call to device B's power up call might occur outside of EvtDeviceSelfManagedIoRestart, perhaps immediately after completing the IOCTL_INTERNAL_GET_RESOURCES request.

If you are writing a WDM driver, well, I will leave that implementation up to you ;). 

More Posts Next page »
 
Page view tracker