Geoff Chappell - Software Analyst
The KINTERRUPT structure (formally _KINTERRUPT) is the supporting data for the kernel’s distribution of a hardware interrupt to device drivers that register for the interrupt’s handling. Each processor has its own Interrupt Descriptor Table (IDT) of addresses to divert execution to. A given interrupt vector can therefore be handled differently on different processors. Drivers do not themselves provide the interrupt handler. They instead provide one or another sort of Interrupt Service Routine (ISR) that the kernel is to call from the true interrupt handler. The ISR and various parameters related to its calling are modelled by the KINTERRUPT. There is at least one KINTERRUPT for each processor that the interrupt is prepared for. That there can be more than one is because the kernel provides that its handler can call multiple ISRs, typically each from a different driver that indicates its willingness to share the interrupt’s handling.
Drivers register their interrupt handling through the I/O Manager’s IoConnectInterrupt function or, more recently, IoConnectInterruptEx. These allow the driver to specify multiple processors for the interrupt. They present the driver with apparently one KINTERRUPT structure but it is in a larger allocation with other data, which can include additional KINTERRUPT structures or at least pointers to them in their separate allocations. This note is not concerned with this larger-scale representation, just with the KINTERRUPT as representing the Core Kernel’s part of one interrupt’s handling for one processor.
On modern versions of Windows, the KINTERRUPT object that governs the handling of an interrupt vector for any one processor is relatively accessible from the processor’s KPRCB. In version 6.3 and higher, the x86 KPRCB has a member named VectorToInterruptObject which is formally an array of 0xD0 pointers to KINTERRUPT objects for the possible hardware interrupts numbered 0x30 to 0xFF. The x64 KPRCB does not have a similar array until version 10.0 and it is inevitably named differently, as InterruptObject. Formally, it is an array of pointers to void, but each pointer is indeed the address of a KINTERRUPT. Less of a formality is that the x64 array has 0x0100 pointers for the whole range of interrupt vectors 0x00 to 0xFF.
Through much of the history of Windows, however, KINTERRUPT objects were much harder to locate. The only known way, as used for instance by the debugger extension command !idt, depends on knowing that connecting a hardware interrupt diverts the corresponding IDT entry to an interrupt handler that is inside the KINTERUPT object. Yes, even as late as 64-bit Windows 8.1, the KINTERRUPT is not just a data structure but contains executable code. This is more easily presented after the layout, below.
The KINTERRUPT structure is documented only as an opaque object. Drivers obtain one by calling some such documented function as IoConnectInterrupt. They are then given its address as an argument to their Interrupt Service Routine and they may pass this address to other documented functions such as KeSynchronizeExecution and KeAcquireInterruptSpinLock. They may eventually release their access to the KINTERRUPT by calling some such documented function as IoDisconnectInterrupt. All the while, the KINTERRUPT is something whose existence they know of and which is theirs, in some sense, but it is not for their interpretation.
As an opaque structure that is allocated to drivers by the kernel, the KINTERRUPT has no compatibility constraints even for its size. The KINTERRUPT is, however, shared with the HAL and so it varies less within versions than do many other structures whose internal detail is undocumented. The following changes of size are known:
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 to 3.5 1 | 0x01DC | |
4.0 to 5.2 | 0x01E4 | 0x80 |
6.0 | 0x0270 | 0xA0 |
6.1 | 0x0278 | 0xA0 |
6.2 | 0x02A0 | 0xB0 |
6.3 | 0xA8 | 0x0100 |
10.0 to 1903 | 0xB0 | 0x0100 |
2004 | 0xD0 | 0x0120 |
These sizes, and the offsets, types and names in the table that follows, are from type information in public symbol files for the kernel, starting with Windows 2000 SP3. For earlier versions, Microsoft’s names and types are something of a guess from comparing different versions of the binaries. Where use of a member corresponds closely with that of a version for which type information is available in Microsoft’s symbol files, it seems reasonable to infer continuity.
As with other kernel objects, the KINTERRUPT begins with a Type from the KOBJECTS enumeration. For a KINTERRUPT, the Type is specifically InterruptObject. Note that the numerical value of this Type took a few versions to settle on 0x16. The Size is in bytes.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 |
SHORT Type; |
all | |
0x02 | 0x02 |
SHORT Size; |
all | |
0x04 | 0x08 |
LIST_ENTRY InterruptListEntry; |
all | |
0x0C | 0x18 |
KSERVICE_ROUTINE *ServiceRoutine; |
all | |
0x10 | 0x20 |
KMESSAGE_SERVICE_ROUTINE *MessageServiceRoutine; |
6.0 and higher | |
0x14 | 0x28 |
ULONG MessageIndex; |
6.0 and higher | |
0x10 (3.10 to 5.2); 0x18 |
0x20 (5.2); 0x30 |
PVOID ServiceContext; |
all | |
0x14 (3.10 to 5.2); 0x1C |
0x28 (5.2); 0x38 |
KSPIN_LOCK SpinLock; |
all | |
0x18 (4.0 to 5.2); 0x20 |
ULONG Spare1; |
4.0 to 5.0 | ||
0x30 (5.2); 0x40 |
ULONG TickCount; |
5.1 and higher | ||
0x18 (3.10 to 3.51); 0x1C (4.0 to 5.2); 0x24 |
0x38 (5.2); 0x48 |
KSPIN_LOCK *ActualLock; |
all | |
0x1C (3.10 to 3.51); 0x20 (4.0 to 5.2); 0x28 |
0x40 (5.2); 0x50 |
VOID (*DispatchAddress) (VOID); |
all | |
0x20 (3.10 to 3.51); 0x24 (4.0 to 5.2); 0x2C |
0x48 (5.2); 0x58 |
ULONG Vector; |
all | |
0x24 (3.10 to 3.51); 0x28 (4.0 to 5.2); 0x30 |
0x4C (5.2); 0x5C |
KIRQL Irql; |
all | |
0x25 (3.10 to 3.51); 0x29 (4.0 to 5.2); 0x31 |
0x4D (5.2); 0x5D |
KIRQL SynchronizeIrql; |
all | |
0x26 (3.10 to 3.51); 0x2A (4.0 to 5.2); 0x32 |
0x4E (5.2); 0x5E |
BOOLEAN FloatingSave; |
all | |
0x27 (3.10 to 3.51); 0x2B (4.0 to 5.2); 0x33 |
0x4F (5.2); 0x5F |
BOOLEAN Connected; |
all | |
0x28 (3.10 to 3.51); 0x2C (4.0 to 5.2); 0x34 |
0x50 (5.2); 0x60 |
CHAR Number; |
3.10 to 6.0 | |
ULONG Number; |
6.1 and higher | |||
0x2D (4.0 to 5.2); 0x35 (6.0); 0x38 |
0x51 (5.2); 0x61 (6.0); 0x64 |
BOOLEAN ShareVector; |
4.0 and higher | previously 0x30 |
0x39 (6.1) | 0x65 (6.1) |
CHAR Pad [3]; |
6.1 only | |
0x39 | 0x65 |
BOOLEAN EmulateActiveBoth; |
6.3 and higher | |
0x3A | 0x66 |
USHORT ActiveCount; |
6.2 and higher | |
0x3C | 0x68 |
LONG InternalState; |
6.2 and higher | |
0x2C (3.10 to 3.51); 0x30 (4.0 to 5.2); 0x38 (6.0); 0x3C (6.1); 0x40 |
INT Mode; |
3.10 to 5.0 | ||
0x54 (5.2); 0x64 (6.0); 0x68 (6.1); 0x6C |
KINTERRUPT_MODE Mode; |
5.1 and higher | ||
0x30 (3.10 to 3.51) |
BOOLEAN ShareVector; |
3.10 to 3.51 | next at 0x2D | |
0x3C (6.0); 0x40 (6.1); 0x44 |
0x68 (6.0); 0x6C (6.1); 0x70 |
KINTERRUPT_POLARITY Polarity; |
6.0 and higher | |
0x34 (4.0 to 5.2); 0x40 (6.0); 0x44 (6.1); 0x48 |
ULONG Spare2; |
4.0 only | ||
0x58 (5.2); 0x6C (6.0); 0x70 (6.1); 0x74 |
ULONG ServiceCount; |
5.0 and higher | ||
0x38 (4.0 to 5.2); 0x44 (6.0); 0x48 (6.1); 0x4C |
ULONG Spare3; |
5.0 only | ||
0x5C (5.2); 0x70 (6.0); 0x74 (6.1); 0x78 |
ULONG DispatchCount; |
5.1 and higher | ||
0x48 (6.0); 0x50 (6.1) |
0x78 (6.0 to 6.1) |
ULONGLONG Rsvd1; |
6.0 to 6.1 | |
0x50 | 0x80 |
KEVENT *PassiveEvent; |
6.2 and higher | |
0x60 (5.2); 0x80 (6.0 to 6.1); 0x88 |
KTRAP_FRAME *TrapFrame; |
5.2 and higher | ||
0x68 (5.2); 0x88 (6.0 to 6.1) |
PVOID Reserved; |
5.2 to 6.1 | ||
0x34 (3.10 to 3.51); 0x3C (4.0 to 5.2); 0x50 (6.0); 0x58 (6.1); 0x54 (6.2) |
0x70 (5.2); 0x90 (6.0 to 6.3) |
ULONG DispatchCode [DISPATCH_LENGTH]; |
3.10 to 6.2 (x86); 5.2 to 6.3 (x64) |
|
0x0298 (6.2); 0x54 |
0xA0 (6.2 to 6.3); 0x90 |
PVOID DisconnectData; |
6.2 and higher | |
0x029C (6.2); 0x58 |
0xA8 (6.2 to 6.3); 0x98 |
KTHREAD * volatile ServiceThread; |
6.2 and higher | |
0x5C | 0xA0 |
INTERRUPT_CONNECTION_DATA *ConnectionData; |
10.0 and higher | previously 0xA0 and 0xF0 |
0x60 | 0xA8 |
PVOID IntTrackEntry; |
10.0 and higher | |
0x60 (6.3); 0x68 |
0xB0 |
ISRDPCSTATS IsrDpcStats; |
6.3 and higher | |
0xA0 (6.3) | 0xF0 (6.3) |
INTERRUPT_CONNECTION_DATA *ConnectionData; |
6.3 only | next at 0x5C and 0xA0 |
0xA8 (10.0 to 1903); 0xC8 |
0xF0 (10.0 to 1903); 0x0110 |
PVOID RedirectObject; |
10.0 and higher | |
0xF8 (6.3 to 1903) |
UCHAR Padding [8]; |
6.3 to 1903 | ||
0xCC | 0x0118 |
PVOID PhysicalDeviceObject; |
2004 and higher |
No use is known of the ServiceCount before version 5.0. The name Spare2 is proposed as fitting the sequence frrom Spare1 to Spare3 that is known from later symbol files.
The DispatchCode truly is code. Initialisation of a KINTERRUPT copies code from a template in the kernel. Connection of the KINTERRUPT sets this copy as the interrupt handler whose address is in the IDT entry. Before the WDK for Windows Vista, the NTDDK.H defines the allowed length of this DispatchCode in dwords as DISPATCH_LENGTH. How or why this slipped into the header is not known—the macro is not referenced from any other header—but had its definition continued to be published, its values would be: