DRAFT: Take more than your usual care.

KDPC

The KDPC is the structure in which the kernel keeps the state of a Deferred Procedure Call (DPC). The latter is a routine that kernel-mode code can register with the kernel to be called back at DISPATCH_LEVEL. Since DISPATCH_LEVEL is not a friendly Interrupt Request Level (IRQL), the usual reason for scheduling a routine to execute at DISPATCH_LEVEL is that the IRQL at the time is even more restrictive, as when servicing a hardware interrupt.

In version 5.2 and higher, a KDPC can represent either a normal DPC, as described above, or a Threaded DPC. In the latter variant, if the kernel can arrange it, the scheduled procedure is called back at PASSIVE_LEVEL from a highest-priority thread. However, support can be disabled (or may have failed), and so a threaded DPC can be called at DISPATCH_LEVEL much as if it had been a normal DPC all along.

Documentation Status

Deferred Procedure Calls have been documented from the beginning. Threaded DPCs are documented as being “available in Windows Vista and later versions.” Why they are not documented for Windows Server 2003 may be a mystery even at Microsoft. After all, the NTIFS.H from the Windows Driver Kit (WDK) for Windows Vista wraps its declaration of the KeInitializeThreadedDpc function in a conditional block for Windows Server 2003 and higher.

Though DPCs have always been documented, the content of the KDPC that supports the functionality has always been explicitly not documented. The KDPC is said to be “an opaque structure” and programmers are warned “do not set members of this structure directly.” Explicit warnings are perhaps necessary because a C-language definition has been provided in every Device Driver Kit (DDK) or WDK from as far back as Windows NT 3.51. The layout seems to have been published only so that where drivers and other kernel-mode modules create a KDPC they can know how much space to allocate. Since what happens in the space is entirely in the hands of kernel functions that are provided for initialising and then working with the object, Microsoft might have defined the KDPC as containing an array of bytes, with no consequences for programmers at large except if the size ever changed.

Layout

In all versions, the KDPC is 0x20 and 0x40 bytes in 32-bit and 64-bit Windows respectively. Constancy of size is not strictly required by the expectation of opacity in user-supplied memory but is very nearly so. The same opacity, however, means that interpretation within the constant size is free to change completely even between builds. The following shorthands apply throughout this article:

One complication to the description is that Windows 8.1 overlays the first four bytes with a 32-bit integer for simultaneous access.

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
/*  individual members, see below  */
3.51 to 6.2
union {
    ULONG TargetInfoAsUlong;
    struct {
        /*  individual members, see below  */
    };
};
6.3 and higher

As the name suggests, these first four bytes mostly record the desired circumstances for executing the DPC. With the overlay aside, these first four bytes are:

Offset (x86) Offset (x64) Definition Versions Remarks
0x00  
SHORT Type;
3.51 to early 5.2  
0x00
UCHAR Type;
late 5.2 and higher  
0x01 0x01
UCHAR Importance;
late 5.2 and higher  
0x02 0x02
UCHAR Number;
3.51 to 5.2  
USHORT Number;
early 6.0 only  
USHORT volatile Number;
late 6.0 and higher  
0x03  
UCHAR Importance;
3.51 to early 5.2 moves ahead
0x03
UCHAR Expedite;
late 5.2 only  

As for other kernel objects, the Type at the start of a KDPC comes from the KOBJECTS enumeration. For the KDPC, the Type is specifically DpcObject for normal DPCs or, in version 5.2 and higher, ThreadedDpcObject for a threaded DPC. It is set by the KeInitializeDpc and KeInitializeThreadedDpc functions, and is then left alone.

The Importance takes its values from the KDPC_IMPORTANCE enumeration. It is MediumImportance (1) initially, but can be changed by calling the KeSetImportanceDpc function. When the KDPC is inserted into a per-processor list, it goes to the head of the list if Importance is HighImportance (2), else to the tail. For normal DPCs, the Importance also affects whether DPC processing is requested at the time of insertion.

Offset (x86) Offset (x64) Definition Versions
0x04 0x08
LIST_ENTRY DpcListEntry;
3.51 to 6.2
SINGLE_LIST_ENTRY DpcListEntry;
6.3 and higher
0x08 0x10
KAFFINITY ProcessorHistory;
6.3 and higher

The KeInsertQueueDpc function schedules a DPC by inserting the KDPC into the appropriate per-processor list. Early versions have one such list per processor, as the DpcListHead member of the KPRCB. In version 5.2 and higher, each processor has two lists, one for normal DPCs and one for threaded DPCs. Whichever list a KDPC is inserted into, it is linked into the list through the DpcListEntry member.

Offset (x86) Offset (x64) Definition Versions
0x0C 0x18
VOID 
(*DeferredRoutine) (
    KDPC *,
    PVOID,
    PVOID,
    PVOID);
3.51 and higher
0x10 0x20
PVOID DeferredContext;
3.51 and higher
0x14 0x28
PVOID SystemArgument1;
3.51 and higher
0x18 0x30
PVOID SystemArgument2;
3.51 and higher
0x1C  
ULONG *Lock;
3.51 to 5.1
0x38
PVOID DpcData;
5.2 and higher

The DeferredRoutine is, of course, the address of the routine that is to be called back. It is specified when the KDPC is initialised, and is thereafter left alone (unless the KDPC is re-initialised). It receives four arguments: the address of the KDPC; plus others that are retrieved from the KDPC. Of these, the DeferredContext is set with the DeferredRoutine when initialising the KDPC, but SystemArgument1 and SystemArgument2 are set afresh whenever the KDPC is inserted.

The C-language definitions in Microsoft’s headers have Lock pointing to a ULONG_PTR starting with the DDK for Windows XP. This is appropriate, since what’s pointed to is specifically a spin lock, but as far as concerns x86 and x64 builds, at least while no x64 build of version 5.1 is known, the difference has no practical consequence. Originally, an inserted KDPC has its Lock pointed to its target processor’s DpcLock in the KPRCB.

KDPC_DATA

Though the DpcData member of the KDPC is declared as pointing to void in version 5.2 and higher, what it actually points to is a KDPC_DATA structure. The KDPC_DATA is 0x14 and 0x20 bytes in 32-bit and 64-bit Windows, respectively, until expansion for Windows 8.1 brings the sizes to 0x18 and 0x28.

Offset (x86) Offset (x64) Definition Versions Remarks
0x00 0x00
LIST_ENTRY DpcListHead;
5.2 to 6.1  
KDPC_LIST DpcList;
6.3 and higher  
0x08 0x10
KSPIN_LOCK DpcLock;
5.2 and higher  
0x0C 0x18
ULONG volatile DpcQueueDepth;
5.2 only  
LONG volatile DpcQueueDepth;
6.0 and higher  
0x10 0x1C
ULONG DpcCount;
5.2 and higher  
0x14 0x20
KDPC * volatile ActiveDpc;
6.3 and higher  

Before version 5.2, each processor has one DPC list. The list’s head and lock and other control data are in the processor’s KPRCB, as the DpcListHead, DpcLock, DpcQueueDepth and DpcCount members. To support threaded DPCs, version 5.2 introduces a second per-processor DPC list. The two are handled so very nearly identically that the same control data works for both, in separate instantiations for independent operation. Thus did the previously separate members get collected into the new DPC_DATA structure, which every KPRCB has two of, in an array named DpcData. While a KDPC is inserted in the per-processor list of ordinary DPCs or of threaded DPCs, its DpcData points to the first or second element, respectively, of the DpcData array in the KPRCB. The macro definitions DPC_NORMAL and DPC_THREADED in WDM.H, which might otherwise seem mysterious for being referenced from nowwhere among all the WDK headers, correspond to the array indices.

KDPC_LIST

To make space for the ProcessorHistory, Windows 8.1 changes the link in each KDPC from double to single. Accessing a particular KDPC at its arbitrary position in the list is necessarily less efficient with single linkage, but is needed only for the KeRemoveQueueDpc function, whose use is relatively infrequent in practice. Quick access to the last KDPC in the list, is another matter, being needed at every insertion of a KDPC that doesn’t have HighImportance. Where the DPC_DATA had a double-linked list head it instead has separate single links to both head and tail, modelled together as the DPC_LIST structure:

Offset (x86) Offset (x64) Definition Versions Remarks
0x00 0x00
SINGLE_LIST_ENTRY ListHead;
6.3 and higher  
0x04 0x08
SINGLE_LIST_ENTRY *LastEntry;
6.3 and higher