KTRAP_FRAME (i386)

The KTRAP_FRAME (formally a _KTRAP_FRAME) is a structure in which the kernel saves the state of execution that gets interrupted for diversion to the kernel, whether from external hardware, the processor itself (for a trap or fault) or by software executing an int or sysenter instruction. The KTRAP_FRAME is highly specific to the processor architecture. This page concerns itself only with 32-bit Windows for the processor architecture that’s variously named i386 or x86. The x64 KTRAP_FRAME is presented separately.

Knowing this structure’s significance, layout and location is valuable for effective debugging. In the ancient history of Windows, it was not just valuable but vital. Although even the first of Microsoft’s kernel-mode debuggers has a !trap command, later promoted to .trap, whose work is to “dump trap frame” at a known address, there was initially little help with finding the address, especially when debugging without good enough symbols. The programmer was left to recognise a KTRAP_FRAME in a stack dump or to know where to expect one.

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 strikingly 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.

Documentation Status

Though the KTRAP_FRAME structure is not formally documented, its name is disclosed as an opaque type by C-language declarations in NTDDK.H all the way back to the Device Driver Kit (DDK) for Windows NT 3.10. No C-language definition of the x86 KTRAP_FRAME is known to have been published in a DDK or Windows Driver Kit (WDK) until the NTOSP.H from the WDK for the original Windows 10 and for Version 1511—and this header’s disclosure looks to have been an oversight which Microsoft has cared not to repeat.

That said, a C-language definition of the x86 KTRAP_FRAME was published in 1995, just not in a DDK. It instead slipped out in a Software Development Kit (SDK) for user-mode programming. Among the samples in the Win32 SDK from August 1995 for Windows NT 3.51 is source code for IMAGEHLP.DLL. The x86 KTRAP_FRAME is there defined in a header named I386.H. Whether this disclosure too was an oversight is not known, but the whole directory was gone from ostensibly the same kit one year later for Windows NT 4.0.

Even without a C-language definition, Microsoft’s names and types for the structure’s members have long been disclosed in debugger output and for slightly less long as type information in public symbol files for the kernel. Symbol files before Windows 2000 SP3 do not have type information for the KTRAP_FRAME, but type information for Windows NT 4.0 somehow found its way into a statically linked library, named LIBCNTPR.LIB, in the contemporaneous DDK and into another, named CRASHLIB.LIB, for the Dr. Watson tool among the user-mode programming samples in the Win32 SDK.

Layout

The KTRAP_FRAME for 32-bit Windows is 0x8C bytes in all known versions, but there have been changes within, mostly just to squeeze in a new member or two for Windows Vista and again for Windows 8. A new member for Windows 8.1, however, removes another and shifts more. Names, types and offsets in the following are from public symbol files in the applicable versions. For earlier versions, continuity is inferred where inspection of the kernel over multiple versions shows similar use of the structure.

Offset (x86) Definition Versions
0x00
ULONG DbgEbp;
all
0x04
ULONG DbgEip;
all
0x08
ULONG DbgArgMark;
all
0x0C (3.10 to 6.2)
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 push onto the stack before the kernel sees the interrupt. The kernel builds the preceding members underneath whatever it gets from the processor, mostly by continuing with the pushing. 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 this part of the KTRAP_FRAME.

Even though the KTRAP_FRAME before offset 0x64 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 kernel-mode 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 be used during kernel-mode execution or that its preservation is the responsibility of whatever kernel-mode code uses it or that preservation is unnecessary because 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, if any, 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 that Microsoft published in the Windows 10 WDK.

That Windows 8.1 shifted most members 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. Public symbols for the kernel in versions 6.3 and 10.0 show X86_KTRAP_FRAME and X86_KTRAP_FRAME_BLUE structures that reproduce the old and new layouts, respectively (except for having ExceptionList as a simple ULONG). Neither name is known in any header from any development kit. Indeed, both structures are defined separately from the KTRAP_FRAME, the latter in I386_X.H, the other two in NTDBG.H, apparently just for debugger support.

The shift seems to have been motivated by wanting version 6.3 to allow the easy use of XMM instructions in kernel mode. This is not for floating-point arithmetic, which arguably should be used only sparingly in kernel mode and always requires explicit saving and restoration of state. It is instead so that the 128-bit XMM registers are available 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 presumed 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 without changing the size, 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, the middle 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 debugger support. Though the first four members are known to have their present definitions from as long ago as Windows NT 3.51, 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 EVENT_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.