Matt Pietrek
It’s a feeling you probably know all too well. You’ve just banged out a bunch of new code and run it for the first time when bam! Another pesky access violation rears its ugly head. You’ve probably also seen the dreaded number 0xC0000005, AKA STATUS_ACCESS_VIOLATION. What’s not so well known is how the number 0xC0000005 came to represent “Something bad just happened,” or the Win32® support for different exception types. In this month’s column, I’ll dig into Win32 exceptions and how they correlate to hardware exceptions. On the hardware side of things I’ll focus on the Intel x86 architecture. (Alas, the Alpha I was using was reclaimed by Digital.)
If you’ve ever programmed for Windows® 3.x or used an MS-DOS® extender, you’ve no doubt encountered exception 0xD (General Protection Fault, or GPF for short). There’s a good chance you’ve seen other faults, such as the invalid opcode fault (exception 6). These numbers aren’t made up; any Intel manual shows you that these exception codes are what the CPU uses to signal various problems or events. You won’t see these exception codes in Win32 because Windows NT®, the flagship Win32 operating system, is designed to run on multiple hardware platforms. It simply wouldn’t do to have an Alpha or MIPS version of Windows NT using exception codes from an Intel CPU.
Instead, Win32 uses its own numbering system to represent the various kinds of exceptions. On any given Win32-based platform, the system maps each of the CPU’s exception codes to one or more generic Win32 exception codes. For example, on Intel CPUs exception 0xD may become a STATUS_ACCESS_VIOLATION (0xC0000005). Alternatively, exception 0xD may become a Win32 STATUS_
PRIVILEGED_INSTRUCTION (0xC0000096) exception. The underlying cause for the hardware exception determines which Win32 exception it maps to.
To begin the journey into Win32 exceptions, let’s start at the beginning: CPU exceptions and interrupts. Exceptions and interrupts are a means by which the CPU’s execution switches to a completely different code path to handle some external stimulus or condition in the executing code. An interrupt is typically caused by an external stimulus—for example, a keystroke being hit. An exception is caused by an internal condition in the code or data that causes the processor to generate an exception. A classic example of an exception is the CPU attempting to read from an address that doesn’t have physical RAM mapped to it.
Intel CPUs reserve 32 interrupt/exception codes to handle various conditions. Figure 1 shows some of the more common numbers. It’s pretty obvious what some of the numbers mean, but there’s a good chance you haven’t encountered others (at least not before running this column’s sample program). Veterans of the MS-DOS wars may be surprised that INT 5H isn’t listed as a print-screen, while INT 8H isn’t listed as the timer interrupt. What’s up with this? The descriptions in Figure 1 are Intel’s definitions for the exceptions and interrupts. Unfortunately, before the Intel CPU architecture had fully evolved, the authors of MS-DOS used some of these interrupt numbers for other purposes. The wisdom of this decision became apparent when programmers using the BOUND instruction unexpectedly received printouts of their screens.
Figure 1: Intel-Defined Exceptions and Interrupts
Code | Definition |
00 | Divide error |
01 | Debug exception (single-step and hardware debugging) |
02 | NMI interrupt |
03 | Breakpoint |
04 | INTO detected overflow |
05 | BOUND range exceeded |
06 | Invalid opcode |
07 | Coprocessor device not available |
08 | Double fault |
0A | Invalid Task State Segment (TSS) |
0B | Segment not present |
0C | Stack fault |
0D | General protection fault (GPF) |
0E | Page fault |
To keep things simple, for the rest of the column I’ll use the word “exception” to mean exception or interrupt. As I indicated earlier, interrupts and exceptions are technically different. In addition, exceptions can be further subdivided into faults, traps, and aborts. I’ll skip over a bunch of geek-speak about what these things mean and simply say that for now you can consider all of these terms to mean effectively the same thing.
When an exception occurs, the CPU suspends the current path of execution in preparation for transferring control to the exception handler. The CPU saves the current executing state by pushing the flags register (EFLAGS), the code segment register (CS), and the instruction pointer register (EIP) onto the stack. Next, the exception code is used to look up and transfer control to the address where the designated handler for this exception resides. At the most fundamental level, the exception code is merely an index into the Interrupt Descriptor Table (IDT), which indicates where the exception should be handled.
The IDT is a fundamental data structure used by Intel CPUs that’s comprised of an array of up to 256 interrupt descriptors, each eight bytes in length. The IDT is created and maintained by the operating system and is thus considered a CPU data structure, but it also falls under the control of the operating system. If the operating system messes up the IDT, the whole system comes down in a hurry.
On most operating systems, including those based on Win32, the IDT is kept in privileged memory, away from access by lowly application programs. This is quite different from real-mode MS-DOS programming, where application programs commonly usurp slots in the interrupt table (a real-mode version of the IDT). The lack of coordination between multiple MS-DOS-based programs, drivers, and TSRs is what caused a great deal of the legendary instability of systems running MS-DOS and 16-bit Windows. With the newer 32-bit operating systems, the CPU restricts access to the IDT, with a corresponding increase in overall stability. Nonetheless, Win32 supervisor-level device drivers have access to the IDT and can modify its entries.
Now back to what happens during an exception. The CPU takes the exception number and indexes it to the appropriate 8-byte descriptor. Within the descriptor are a variety of fields. Figure 2 shows a simplified version of an interrupt descriptor. Note that for each exception there’s a corresponding exception handler address (CS:EIP) to which control will transfer. Figure 3 shows the sequence of events during a GPF (exception 0xD).
Figure 2: An Interrupt Descriptor
Figure 3: Exception Sequence of Events
At this point, I’d ordinarily grind out a program that displays the contents of the IDT. Unfortunately (for me at least), application programs don’t have access to the IDT. This is because applications under Win32 run at Ring 3, which is the lowest privilege level. The core of a Win32 operating system runs at Ring 0 (kernel or supervisor mode), which is the highest privilege level. Likewise, critical data structures such as the IDT are accessible only via Ring 0 code. (Rings 1 and 2 aren’t used by Win32 implementations. Intel put them in way back starting with the 80286, but to my knowledge nobody’s ever used these privilege levels.)
Since I can’t write an application that reads from the IDT, I’ll fall back a notch and cheat. Figure 4 shows the first 0x30 interrupt descriptor table entries as shown by the IDT command in SoftIce/NT. SoftIce runs as a Ring 0 device driver, so it has full read/write access to the IDT.
Figure 4: SoftIce IDT Output
Int | Type | Sel:Offset | Attributes | Symbol/Owner | |
IDTbase=80036400 Limit=07FF | |||||
0000 | IntG32 | 0008:8013C354 | DPL=0 | P | _KiTrap00 |
0001 | IntG32 | 0008:8013C49C | DPL=3 | P | _KiTrap01 |
0002 | IntG32 | 0008:0000137E | DPL=0 | P | |
0003 | IntG32 | 0008:8013C764 | DPL=3 | P | _KiTrap03 |
0004 | IntG32 | 0008:8013C8B8 | DPL=3 | P | _KiTrap04 |
0005 | IntG32 | 0008:8013C9F4 | DPL=0 | P | _KiTrap05 |
0006 | IntG32 | 0008:8013CB4C | DPL=0 | P | _KiTrap06 |
0007 | IntG32 | 0008:8013D068 | DPL=0 | P | _KiTrap07 |
0008 | TaskG | 0050:000013D8 | DPL=0 | P | |
0009 | IntG32 | 0008:8013D3A8 | DPL=0 | P | _KiTrap09 |
000A | IntG32 | 0008:8013D4A8 | DPL=0 | P | _KiTrap0A |
000B | IntG32 | 0008:8013D5CC | DPL=0 | P | _KiTrap0B |
000C | IntG32 | 0008:8013D8BC | DPL=0 | P | _KiTrap0C |
000D | IntG32 | 0008:8013DABC | DPL=0 | P | _KiTrap0D |
000E | IntG32 | 0008:8013E468 | DPL=0 | P | _KiTrap0E |
000F | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0010 | IntG32 | 0008:8013E8D4 | DPL=0 | P | _KiTrap10 |
0011 | IntG32 | 0008:8013E9E8 | DPL=0 | P | _KiTrap11 |
0012 | TaskG | 00A0:8013E7D4 | DPL=0 | P | |
0013 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0014 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0015 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0016 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0017 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0018 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0019 | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001A | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001B | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001C | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001D | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001E | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
001F | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
0020 | Reserved | 0008:00000000 | DPL=0 | NP | |
0021 | TrapG16 | 00C7:00000696 | DPL=3 | P | |
0022 | Reserved | 0008:00000000 | DPL=0 | NP | |
0023 | Reserved | 0008:00000000 | DPL=0 | NP | |
0024 | Reserved | 0008:00000000 | DPL=0 | NP | |
0025 | Reserved | 0008:00000000 | DPL=0 | NP | |
0026 | Reserved | 0008:00000000 | DPL=0 | NP | |
0027 | Reserved | 0008:00000000 | DPL=0 | NP | |
0028 | Reserved | 0008:00000000 | DPL=0 | NP | |
0029 | Reserved | 0008:00000000 | DPL=0 | NP | |
002A | IntG32 | 0008:8013B8A6 | DPL=3 | P | _KiGetTickCount |
002B | IntG32 | 0008:8013B990 | DPL=3 | P | _KiCallbackReturn |
002C | IntG32 | 0008:8013BAA0 | DPL=3 | P | _KiSetLowWaitHighThread |
002D | IntG32 | 0008:8013C65C | DPL=3 | P | _KiDebugService |
002E | IntG32 | 0008:8013B440 | DPL=3 | P | _KiSystemService |
002F | IntG32 | 0008:8013E7D4 | DPL=0 | P | _KiTrap0F |
The first thing to notice about the Windows NT IDT contents is that all of the exception handler addresses are above 0x80000000. Addresses above 0x80000000 are reserved by Windows NT for supervisor-level (Ring 0) access. Although it’s not obvious from the figure, nearly every exception handler address is in NTOSKRNL.EXE, which is the Ring 0 kernel component of Windows NT. Because I had previously loaded the symbols from NTOSKRNL’s DBG file, SoftIce looked up the exception handler addresses and emitted the symbolic names for most of the exception handlers. The first 0x20 exceptions are handled by a series of routines named _KiTrap00, _KiTrap01, and so on. The “Ki” stands for Kernel Interrupt.
Something else worth noting in the IDT entries is the Descriptor Privilege Level (DPL) field. Again, without getting into a morass of details, the DPL field specifies the least privileged execution level that is allowed to invoke that particular software interrupt. For example, INT 2EH can be called by any privilege level from Ring 3 (the lowest privilege) to Ring 0 (the highest). Likewise, INT 3H, which is designated for breakpoints, can also be invoked from Ring 3 and higher privilege-level code.
Exceptions 0x2A though 0x2E are handled by other routines in NTOSKRNL.EXE. For example, in my August 1996 article “Poking Around Under the Hood: A Programmer’s View of Windows NT 4.0,” I described how an INT 2EH call was the mechanism by which Ring 3 application code transfers control to the Ring 0 system code to perform special operations such as creating a new process. INT 2EH is invoked from Ring 3 by system DLLs such as NTDLL.DLL, USER32.DLL, and GDI32.DLL. Looking at IDT entry 0x2E, you’ll see that its address points to the _KiSystemService function in NTOSRKNL. _KiSystemService, in turn, forwards control to the appropriate code.
After INT 2EH, the next most commonly used interrupt from the previous figure is INT 2BH. The name of the IDT entry for this interrupt is _KiCallbackReturn, which gives a good hint about its meaning. When a Ring 3 callback function is invoked from Ring 0, a method to get back to the Ring 0 caller is required. INT 2BH fills this need. A typical example of this is a window hook callback, such as you would install by calling SetWindowsHookEx. The real meat of the USER functionality is in the Ring 0 WIN32K.SYS driver, and it’s this code that makes the hook callbacks into Ring 3 code. When the callback is finished, the system executes an INT 2BH to return to Ring 0.
Enough about interrupts. What about exceptions, in particular those nasty ones like access violations? The two most common exceptions that typically arise at the processor level are exception 0xD (GPF) and exception 0xE (page fault). Somehow, between the time the CPU generates these exceptions and the time your application gets a chance to handle them, the operating system changes the exception code to something more generic and to its liking.
Let’s pretend that you try to run the following buggy program that attempts to write the value 2 to memory offset 0:
int main()
{
*(int *) 0 = 2;
}
As you’d expect, offset 0 isn’t a usable program address. Under Windows NT, for instance, the first 4KB page of memory is marked “not present” to prevent problems with programs that use NULL pointers. The attempt to write to this address causes a page fault (exception 0xE). Looking at the IDT figure, you’ll see that this exception is handled by the _KiTrap0E code in NTOSKRNL.EXE.
While I’ve stepped through the _KiTrap0E code many times in a debugger, this code is extremely nasty and worthy of an entire article in its own right. For now, it’s enough to say that the Ring 0 _KiTrap0E code checks for a variety of special circumstances. The buggy sample program isn’t anything special, though. Therefore, KiTrap0E uses an IRETD instruction to transfer control to Ring 3 at the start of NTDLL’s KiUserExceptionDispatcher function. I won’t describe KiUserExceptionDispatcher here, since I already covered it in gory detail in my article “A Crash Course on the Depths of Win32 Structured Exception Handling,” (MSJ, January 1997). The important thing to know is that KiUserExceptionDispatcher is told that the exception code is 0xC0000005 (STATUS_ACCESS_VIOLATION), not good old exception 0xE, which is what the CPU generated.
Where does Win32 come up with exception codes like 0xC0000005? The answer can be found in the WINERROR.H header file from the Win32 SDK or your C++ compiler. Near the very top, you’ll see a comment that says:
// Values are 32 bit values layed out as follows:
Reading further in the comments, you’ll see that the top two bits (31 and 30) represent a severity code. The next lower bit (29) is the customer code. Bit 28 is reserved, while the remaining 12 bits in the upper WORD are the facility code. The bottom WORD (bits 0–15) is used for whatever types of subcodes the upper WORD wants to use it for.
It’s interesting to note that, under Win32, the method by which bitfields are used to categorize information is also the same mechanism used for the Win32 last error codes. Thus, you can see where error codes like 0x80010002 (RPC_
E_CALL_CANCELED) come from. Incidentally, the use of severity, customer, and facility bitfields didn’t originate with Windows NT. IBM’s OS/2 uses a similar scheme, which is a by-product of the joint operating system work done by Microsoft and IBM in the late 1980s.
Turning back to exceptions, take a look at the severity bits, bits 31 and 30. A value of 0 indicates success, 1 is informational, 2 means warning, while 3 (both bits on) means error. A fatal exception certainly qualifies as an error, so any 32-bit fatal exception code will have the top two bits set. The next two lower bits, customer and reserved, are typically both set to zero since they aren’t usually used.
From just this limited knowledge of how exception codes are constructed, you can infer that any fatal exception code will start out with the value 0xC as its first nibble. Thus, you end up with exception codes like 0xC0000005 (STATUS_
ACCESS_VIOLATION) and 0xC000001D (STATUS_ILLEGAL_INSTRUCTION). Less severe exceptions—that is, warnings—have a severity level of 2, so you get exception codes like 0x80000003 (STATUS_BREAKPOINT) and 0x80000004 (STATUS_SINGLE_STEP). A reasonably complete list of possible exception codes under Win32 can be found in WINNT.H by searching for STATUS_. When you look at this list, it’s important to remember that not every Win32 exception code can be generated by every processor that supports Win32.
While writing this column, I became intrigued by how many possible Win32 exception codes I could generate. I was also curious to see what exception codes the operating system would assign to various evil things that I could intentionally try to do. To help figure these things out, I wrote a framework for generating processor faults in various manners and reporting what Win32 exception codes they mapped to. The end result was my GenException program (see Figure 5).
Figure 5: GenException.CPP
//==========================================
// Matt Pietrek
// Microsoft Systems Journal, October 1997
// FILE: GenException.CPP
// To compile: CL GenException.CPP
//==========================================
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <float.h>
#include <assert.h>
typedef void (* PFNGENERATEEXCEPTION)(void);
void GenerateSTATUS_BREAKPOINT( void )
{
__asm int 3 // A regular, old breakpoint instruction
}
void GenerateSTATUS_SINGLE_STEP( void )
{
// This is easier than using the hardware
// breakpoint register to create an int 1
__asm int 1
}
void GenerateSTATUS_ACCESS_VIOLATION( void )
{
// Create a page fault (exception 0xE) by reading
// from memory above 2GB.
int i = *(int *)0xFFFFFFF0;
}
void GenerateSTATUS_ILLEGAL_INSTRUCTION( void )
{
__asm _emit 0x0F // Garbage instruction causes an exception 0xD
__asm _emit 0xFF
}
void GenerateSTATUS_ARRAY_BOUNDS_EXCEEDED( void )
{
DWORD arrayBounds[2] = { 10, 48 };
__asm mov eax, 12
__asm bound eax, arrayBounds // This BOUND instruction works
__asm mov eax, 7
__asm bound eax, arrayBounds // This makes an exception 5
}
void UnmaskFPExceptionBits( void )
{
unsigned short cw;
__asm fninit // Initialize the coprocessor
__asm fstcw [cw]
cw &= 0xFFE0; // Turn off the most of the exception bits (except the
// the precision exception)
__asm fldcw [cw]
}
void GenerateSTATUS_FLOAT_DIVIDE_BY_ZERO( void )
{
double a = 0;
a = 1 / a;
__asm fwait;
}
void GenerateSTATUS_FLOAT_OVERFLOW( void )
{
double a = DBL_MAX;
a *= a;
__asm fwait;
}
void GenerateSTATUS_FLOAT_STACK_CHECK( void )
{
unsigned a;
__asm fistp [a]
__asm fwait;
}
void GenerateSTATUS_FLOAT_UNDERFLOW( void )
{
double a = DBL_MIN;
a /= 10;
__asm fwait;
}
void GenerateSTATUS_INTEGER_DIVIDE_BY_ZERO( void )
{
// Divide by zero to cause an exception 0
int i = 0;
i = 2 / i;
}
void GenerateSTATUS_INTEGER_OVERFLOW( void )
{
__asm mov eax, 07FFFFFFFh // Signed max value
__asm add eax, 2 // result = 0x80000001 -> overflow!
__asm into // Makes an exception 4
}
void GenerateSTATUS_PRIVILEGED_INSTRUCTION( void )
{
// The HLT instruction can only be executed at ring 0
__asm hlt
}
void GenerateSTATUS_STACK_OVERFLOW( void )
{
DWORD myArray[512];
// Recurse "infinitely" to overflow the stack
GenerateSTATUS_STACK_OVERFLOW();
}
DWORD GetExceptionNumber( PFNGENERATEEXCEPTION pfn )
{
DWORD exceptionCode = 0;
__try
{
pfn();
}
__except( exceptionCode = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER )
{
}
return exceptionCode;
}
#define SHOW_EXCEPTION( x ) \
dwExceptionNumber = GetExceptionNumber( Generate##x ); \
printf( "%X %s\n", dwExceptionNumber, #x ); \
assert( dwExceptionNumber == x );
int main(int argc, char *argv[])
{
DWORD dwExceptionNumber;
SHOW_EXCEPTION( STATUS_BREAKPOINT )
SHOW_EXCEPTION( STATUS_SINGLE_STEP )
SHOW_EXCEPTION( STATUS_ACCESS_VIOLATION )
SHOW_EXCEPTION( STATUS_ILLEGAL_INSTRUCTION )
SHOW_EXCEPTION( STATUS_ARRAY_BOUNDS_EXCEEDED )
UnmaskFPExceptionBits();
SHOW_EXCEPTION( STATUS_FLOAT_DIVIDE_BY_ZERO )
UnmaskFPExceptionBits();
SHOW_EXCEPTION( STATUS_FLOAT_OVERFLOW )
UnmaskFPExceptionBits();
SHOW_EXCEPTION( STATUS_FLOAT_STACK_CHECK )
UnmaskFPExceptionBits();
SHOW_EXCEPTION( STATUS_FLOAT_UNDERFLOW )
SHOW_EXCEPTION( STATUS_INTEGER_DIVIDE_BY_ZERO )
SHOW_EXCEPTION( STATUS_INTEGER_OVERFLOW )
SHOW_EXCEPTION( STATUS_PRIVILEGED_INSTRUCTION )
SHOW_EXCEPTION( STATUS_STACK_OVERFLOW );
return 0;
}
The GenException code is divided into three parts. The first part is a series of functions with names that start with Generate, followed by the name of the Win32 exception they produce. For example, GenerateSTATUS_ILLEGAL_INSTRUCTION causes an illegal instruction exception. The second part of GenException is the GetExceptionNumber function. It uses Win32 structured exception handling to determine the Win32 exception code caused by the various GenerateXXX functions and returns that value to its caller. GetExceptionNumber takes one parameter, a function pointer to the GenerateXXX function it should call.
The final portion of GenException.CPP is the main function. It consists of a series of calls to a C++ preprocessor macro that I named SHOW_EXCEPTION. Each call to SHOW_EXCEPTION corresponds to a particular Win32 exception code to be generated. The SHOW_EXCEPTION macro takes a predefined exception name (for example, STATUS_ACCESS_VIOLATION), and synthesizes a call to the corresponding GenerateXXX function. I used the SHOW_EXCEPTION macro to hide a lot of boilerplate code that differs only by the actual exception code involved. By using the preprocessor token pasting and stringizing macros, the line
SHOW_EXCEPTION( STATUS_BREAKPOINT )
expands to:
dwExceptionNumber = GetExceptionNumber( GenerateSTATUS_BREAKPOINT) );
printf( "%X %s\n", dwExceptionNumber, "STATUS_BREAKPOINT" );
assert( dwExceptionNumber == STATUS_BREAKPOINT );
In writing GenException, some exceptions were extremely easy to create, such as STATUS_ACCESS_VIOLATION. It wasn’t trivial to create exceptions other than the common ones, such as STATUS_ILLEGAL_INSTRUCTION. In many cases I had to resort to inline assembly language. Two good examples of this are CPU exceptions 4 and 5, which are generated by the INTO and BOUND instructions, respectively. I won’t detail how I generated the various exceptions; the GenException.CPP code contains comments for that purpose.
Creating the floating point exceptions was somewhat tricky, as Win32 initializes the floating point unit to not generate exceptions. I had to explicitly turn off certain bits in the coprocessor’s control word to get the floating point exceptions, like STATUS_FLOAT_DIVIDE_BY_ZERO. If you’re curious, the UnmaskFPExceptionBits function contains the relevant bit-twiddling code. I also learned the hard way that floating point instructions aren’t raised until the next floating point instruction after the actual bad instruction. I used the __asm fwait instruction to force exceptions to be raised immediately after the intentionally bad instruction.
While GenException probably isn’t the most exciting or useful program you’ll ever run, I believe you’ll find it enlightening to see how various Win32 exceptions can be caused. In most cases, the CPU generates an exception 0xD, which the Win32 exception handler then analyzes to construct a more meaningful and specific exception code. My purpose in describing these mechanisms is to explain exceptions at both the hardware and operating system levels, and to show how they relate to one another.
To obtain complete source code listings, see the MSJ web site at http://www.microsoft.com/msj/
Have a question about programming in Windows? Send it to Matt at mpietrek@tiac.com or http://www.tiac.com/users/mpietrek