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 as well 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.10 to early 5.2  
0x00
UCHAR Type;
late 5.2 and higher  
0x01 0x01
UCHAR Importance;
late 5.2 and higher previously at offset 0x03
0x02 0x02
USHORT Size;
3.10 to 3.50  
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 next at offset 0x01
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. Note that the numerical values of DpcObject and ThreadedDpcObject are version-dependent.

It was not until version 3.51 that DPCs could either be prioritised or be targeted to a specific processor (represented by Number). 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.10 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 a double-linked or single-linked list, depending on the version. At first, with no targeting of the DPC’s eventual execution to a selected processor, there was only one list for all DPCs. Version 3.51 introduced one 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.10 and higher
0x10 0x20
PVOID DeferredContext;
3.10 and higher
0x14 0x28
PVOID SystemArgument1;
3.10 and higher
0x18 0x30
PVOID SystemArgument2;
3.10 and higher
0x1C  
BOOLEAN Inserted;
3.10 only
 
ULONG *Lock;
3.50 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.

It is mere supposition that Inserted is Microsoft’s name for the BOOLEAN with which version 3.10 records that the KDPC is inserted in the global list of all queued DPCs.

The C-language definitions in Microsoft’s headers have Lock pointing to a ULONG originally but 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 between ULONG_PTR and ULONG has no practical consequence. For all versions in question, an inserted KDPC has its Lock pointed to its target processor’s DpcLock in the KPRCB.

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.