KTRAP_FRAME

The KTRAP_FRAME structure is what the kernel uses to record the state of execution that gets interrupted for diversion to the kernel, whether from external hardware, the processor itself (for an exception) or by software executing an int instruction or similar.

In the ancient history of Windows, knowing this structure’s significance, layout and location was vital for effective debugging, especially of problems that occur while servicing user-mode system requests. Much of the burden of knowing has been relieved, however, because it eventually occurred to Microsoft to build the knowledge into the debugger and to document the relevant commands, most notably !trap, which later got promoted to .trap.

Documentation Status

The KTRAP_FRAME structure is not formally documented. Its name was disclosed as an opaque type by C-language declarations in NTDDK.H as far back as the Device Driver Kit (DDK) for Windows NT 3.51. The first known C-language definition is in the DDK for Windows Server 2003, and only then for 64-bit Windows. A C-language definition of the structure for 32-bit Windows is not known to have been published by Microsoft until the NTOSP.H from some editions of the Windows Driver Kit (WDK) for Windows 10.

That said, because of the structure’s role in all ways in and out of the kernel, the KTRAP_FRAME may be the best known of all formally undocumented kernel-mode structures. Indeed, this article exists only for the occasional convenience of having the names and offsets for ready reckoning (and, for the historian, of tracking the few changes). Or so I thought when first writing it! On reflection, it seems that the structure may be well known but less well understood.

Layout (i386)

The KTRAP_FRAME for 32-bit Windows is 0x8C bytes in all known versions, but there have been changes within, including that Windows 8.1 shifts members. Names, types and offsets in the following are from symbol files for the kernel starting with Windows 2000 SP3. Symbol files for earlier versions do not have type information for the structure. Continuity is inferred from the kernel’s use of the structure in earlier versions.

Offset (x86) Definition Versions
0x00
ULONG DbgEbp;
all
0x04
ULONG DbgEip;
all
0x08
ULONG DbgArgMark;
all
0x0C
ULONG DbgArgPointer;
3.10 to 6.2
0x10 (3.10 to 6.2);
0x0C
ULONG TempSegCs;
3.10 to 5.2
USHORT TempSegCs;
6.0 and higher
0x12 (6.0 to 6.2);
0x0E
UCHAR Logging;
6.0 and higher
0x13 (6.0 to 6.2);
0x0F
UCHAR Reserved;
6.0 to 6.1
UCHAR FrameType;
6.2 and higher
0x14 (3.10 to 6.2);
0x10
ULONG TempEsp;
all
0x18 (3.10 to 6.2);
0x14
ULONG Dr0;
all
0x1C (3.10 to 6.2);
0x18
ULONG Dr1;
all
0x20 (3.10 to 6.2);
0x1C
ULONG Dr2;
all
0x24 (3.10 to 6.2);
0x20
ULONG Dr3;
all
0x28 (3.10 to 6.2);
0x24
ULONG Dr6;
all
0x2C (3.10 to 6.2);
0x28
ULONG Dr7;
all
0x30 (3.10 to 6.2);
0x2C
ULONG SegGs;
all
0x34 (3.10 to 6.2);
0x30
ULONG SegEs;
all
0x38 (3.10 to 6.2);
0x34
ULONG SegDs;
all
0x3C (3.10 to 6.2);
0x38
ULONG Edx;
all
0x40 (3.10 to 6.2);
0x3C
ULONG Ecx;
all
0x44 (3.10 to 6.2);
0x40
ULONG Eax;
all
0x48 (3.10 to 6.2);
0x44
ULONG PreviousPreviousMode;
3.10 to 6.1
UCHAR PreviousPreviousMode;
6.2 and higher
0x49 (6.2);
0x45
UCHAR EntropyQueueDpc;
6.2 and higher
0x4A (6.2);
0x46
UCHAR Reserved [2];
6.2 and higher
0x48
ULONG MxCsr;
6.3 and higher
0x4C
PEXCEPTION_REGISTRATION_RECORD ExceptionList;
all
0x50
ULONG SegFs;
all
0x54
ULONG Edi;
all
0x58
ULONG Esi;
all
0x5C
ULONG Ebx;
all
0x60
ULONG Ebp;
all
0x64
ULONG ErrCode;
all
0x68
ULONG Eip;
all
0x6C
ULONG SegCs;
all
0x70
ULONG EFlags;
all
0x74
ULONG HardwareEsp;
all
0x78
ULONG HardwareSegSs;
all
0x7C
ULONG V86Es;
all
0x80
ULONG V86Ds;
all
0x84
ULONG V86Fs;
all
0x88
ULONG V86Gs;
all

See that the KTRAP_FRAME ends—from offset 0x64 onwards—with items that the processor itself either does or may put on the stack before the kernel sees the interrupt. The kernel builds the preceding members underneath. The result is typically not the full structure. For instance, the V86Es to V86Gs members are present only if the interruption was of virtual-8086 execution and the HardwareEsp and HardwareSegSs are present only on interrupts from an outer ring. Some ways into the kernel see the processor put nothing on the stack, in which case the kernel builds what it wants of the KTRAP_FRAME.

Even in that part of the KTRAP_FRAME that is built only by the kernel, not all members need be meaningful. Much of the point to the structure is that members preserve a resource, typically a register, that will change during the handling such that it must be saved on entry and restored on exit. Though such preservation is needed in general, the kernel may know that particular circumstances mean that the resource is expected not to change during the kernel-mode execution or that its change should have no consequence for the interrupted execution.

For instance, Dr0 to Dr7 are loaded from the corresponding dr0 to dr7 registers if the current thread is being debugged and the interrupt has come from user mode. In this circumstance, dr0 to dr7 had better be changed so that the kernel’s continued execution is subject to whatever debugging is expected in kernel mode. In all other circumstances, the registers are left unchanged, Dr7 is set to 0 and Dr0 to Dr6 are left undefined (and clearing Dr7 didn’t start until version 5.2).

Another important example is when the transition to kernel mode is explicitly initiated by executing an int or sysenter instruction as if to call the kernel’s interface for providing system services (or, more obscurely, to return from the kernel’s simulation of calling out to user mode). In this case, the registers before and after are not arbitrary. The interface has the calling convention that eax is used to return the result and ecx and edx are to be regarded as corrupt. The Eax, Ecx and Edx members are not loaded from the corresponding registers on entry—and neither are SegDs and SegEs.

Curiously, it’s not until version 6.2 that the structure is provided with any explicit classification such as might help identify which members are meaningful. The FrameType member may be

Microsoft’s assembly-language names for these are known from KS386.INC in the WDKs for Windows 8 and higher. That they are also C-language names is confirmed by the NTOSP.H from the WDK for Windows 10.

The shifting of most members in progressing to Windows 8.1 has surely complicated Microsoft’s own use of the structure from outside the kernel in modules that need not be the same version—including on other computers, as during kernel-mode debugging. Symbol files for versions 6.3 and 10.0 show X86_KTRAP_FRAME and X86_KTRAP_FRAME_BLUE structures that reproduce the old and new layouts, respectively. Neither name is known in any header from any development kit.

The shift seems to have been motivated by wanting version 6.3 to allow the easy use of XMM instructions in kernel mode—not for floating-point arithmetic, which is and arguably should be unusual in kernel mode, but as a typical compiler optimisation for moving data more efficiently than with the 32-bit general registers. For this to work when the interrupted code may have been using the XMM registers, transition to and from kernel mode must save and restore the XMM registers much as for the general registers. The eight 128-bit registers xmm0 through xmm7 are saved beneath the KTRAP_FRAME, typically with a gap for 16-byte alignment. Note, however, that the registers are known not to matter to callers of system services and are therefore not saved on these transitions.

Since the Control and Status Register, mxcsr, must be refreshed on each entry to kernel mode, the pre-transition contents of mxcsr must be preserved too. To make space for an MxCsr member in the KTRAP_FRAME, Microsoft removed the ancient DbgArgPointer. Why the new MxCsr was not inserted simply as a replacement, without shifting other members, can’t be known with certainty but it seems at least plausible that the KTRAP_FRAME is thought of in regions, one of which is for resources that the kernel changes on every entry and restores on every exit, and that MxCsr belongs more naturally there.

Except that versions 6.0 and 6.2 each recovered a byte from the previous use of a whole dword for TempSegCs (which needs only a word), the start of the KTRAP_FRAME has always been set aside for debugging. Though the first four members are known from symbol files to have been defined as early as Windows 2000 SP3, it is not until Windows Server 2003 SP1 that any get used (in the retail builds, anyway). Even then, this use is only that they are set on entry. It is not known where they are ever interpreted (except in debug builds). The DbgEbp and DbgEip members are copies of Ebp and Eip as set on entry. The DbgArgMarker is set to 0xBADB0D00, which the assembly-language header KS386.INC equates to TRAP_FRAME_MARKER. The DbgArgPointer records the address at which the caller provided arguments for a system service (and appears to be meaningless when entry to the kernel has any other cause).

The TempSegCs and TempEsp members are meaningful only for handling exceptions that occur in kernel-mode execution and only then if the handler seeks to change the esp register with which execution is to continue. Because the processor was already in kernel mode for the exception, it will have pushed the eflags, cs and eip registers to wherever ss:esp pointed at the time. These become the EFlags, SegCs and Eip members of the new KTRAP_FRAME, which does not extend further. To return from the exception with a changed esp, the new esp (which is, by the way, necessarily higher than the old) is placed in TempEsp and SegCs is made into a null selector having saved the pre-exception value in TempSegCs. Then, just as the kernel would return by executing an iret while esp points to the Eip, SegCs and EFlags in the KTRAP_FRAME, it sees the null selector and instead executes the iret after pointing esp to an Eip, TempSegCs and EFlags that it builds beneath the desired new esp from TempEsp.

The Logging member which dates from Windows Vista is non-zero to indicate that this entry (or re-entry) to the kernel is to have its entry and exit traced to an NT Kernel Logger session. To have this member get set on entry, at least one such session must be currently enabled for this tracing. The documented way is to set ENABLE_TRACE_FLAG_SYSCALL in the EnableFlags member of the EVENT_TRACE_PROPERTIES structure that is given for starting or controlling the session. Entry and exit show as events whose hook ID is PERFINFO_LOG_TYPE_SYSCALL_ENTER (0x0F33) and PERFINFO_LOG_TYPE_SYSCALL_EXIT (0x0F34) respectively.

Layout (amd64)

The KTRAP_FRAME for 64-bit Windows is 0x0190 bytes in all known versions. Names, types and offsets in the following are from NTDDK.H in various driver kits, checked against the kernel’s symbol files.

Offset (x64) Definition Versions
0x00
ULONG64 P1Home;
all
0x08
ULONG64 P2Home;
all
0x10
ULONG64 P3Home;
all
0x18
ULONG64 P4Home;
all
0x20
ULONG64 P5;
all
0x28
KPROCESSOR_MODE PreviousMode;
all
0x29
KIRQL PreviousIrql;
all
0x2A
UCHAR FaultIndicator;
all
0x2B
UCHAR ExceptionActive;
all
0x2C
ULONG MxCsr;
all
0x30
ULONG64 Rax;
all
0x38
ULONG64 Rcx;
all
0x40
ULONG64 Rdx;
all
0x48
ULONG64 R8;
all
0x50
ULONG64 R9;
all
0x58
ULONG64 R10;
all
0x60
ULONG64 R11;
all
0x68
union {
    ULONG64 GsBase;
    ULONG64 GsSwap;
};
all
0x70
M128A Xmm0;
all
0x80
M128A Xmm1;
all
0x90
M128A Xmm2;
all
0xA0
M128A Xmm3;
all
0xB0
M128A Xmm4;
all
0xC0
M128A Xmm5;
all
0xD0
union {
    ULONG64 FaultAddress;
    ULONG64 ContextRecord;
    ULONG64 TimeStamp;
};
5.2 only
union {
    ULONG64 FaultAddress;
    ULONG64 ContextRecord;
    ULONG64 TimeStampCKCL;
};
6.0 and higher
0xD8
ULONG64 Dr0;
all
0xE0
ULONG64 Dr1;
all
0xE8
ULONG64 Dr2;
all
0xF0
ULONG64 Dr3;
all
0xF8
ULONG64 Dr6;
all
0x0100
ULONG64 Dr7;
all
0x0108
union {
    struct {
        ULONG64 DebugControl;
        ULONG64 LastBranchToRip;
        ULONG64 LastBranchFromRip;
        ULONG64 LastExceptionToRip;
        ULONG64 LastExceptionFromRip;
    };
    struct {
        ULONG64 LastBranchControl;
        ULONG LastBranchMSR;
    };
};
5.2 to 6.3
struct {
    ULONG64 DebugControl;
    ULONG64 LastBranchToRip;
    ULONG64 LastBranchFromRip;
    ULONG64 LastExceptionToRip;
    ULONG64 LastExceptionFromRip;
};
10.0 and higher
0x0130
USHORT SegDs;
all
0x0132
USHORT SegEs;
all
0x0134
USHORT SegFs;
all
0x0136
USHORT SegGs;
all
0x0138
ULONG64 TrapFrame;
all
0x0140
ULONG64 Rbx;
all
0x0148
ULONG64 Rdi;
all
0x0150
ULONG64 Rsi;
all
0x0158
ULONG64 Rbp;
all
0x0160
union {
    ULONG64 ErrorCode;
    ULONG64 ExceptionFrame;
};
5.2 only
union {
    ULONG64 ErrorCode;
    ULONG64 ExceptionFrame;
    ULONG64 TimeStampKlog;
};
6.0 and higher
0x0168
ULONG64 Rip;
all
0x0170
USHORT SegCs;
all
0x0172
USHORT Fill1 [3];
5.2 only
UCHAR Fill0;
6.0 and higher
0x0173
UCHAR Logging;
6.0 and higher
0x0174
USHORT Fill1 [2];
6.0 and higher
0x0178
ULONG EFlags;
all
0x017C
ULONG Fill2;
all
0x0180
ULONG64 Rsp;
all
0x0188
USHORT SegSs;
all
0x018A
USHORT Fill3 [1];
5.2 only
USHORT Fill3;
6.0 and higher
0x018C
LONG CodePatchCycle;
5.2 to 6.2
ULONG Fill4;
6.3 and higher

As with the 32-bit layout, the KTRAP_FRAME ends—from offset 0x0160 onwards—with items that the processor itself either does or may put on the stack before the kernel sees the interrupt. The structure’s formal layout has always provided that the kernel may squeeze in items of its own where the processor allows 8 bytes though only 16 or 32 bits are meaningful.

Note that the structure does not provide for saving all the general registers. The kernel does not itself use r12 to r15 during entry or exit, and does not save them in anticipation that code deeper into the handling may use them. The understanding is instead that all kernel-mode routines that use r12 to r15 (or xmm6 or xmm7) will do the saving and restoring themselves. In practice, of course, this knowledge is built into the compiler.