Interrupts and exceptions under Win32

Matt Pietrek This article is published in the October 1997 date of MSJ magazine Under The Hood column. Interrupt and exception in the DOS era is the soul of the whole system, but Windows has its hidden depths to the system. Matt Pietrek detailed analysis of the problem and calls between interrupt and exception handling mechanism and kernel mode and user mode code for Windows is. The authors also provide a more interesting experimental program.

You may feel all right. But when you write some new code and run it only when you know the feeling of being cheated! He appeared worrisome access violation (Access Violation). You may also see that code 0xC0000005 scary, is STATUS_ACCESS_VIOLATION. 0xC0000005 is how to represent "just wrong", and Win32® is how to support different types of exceptions, which are not as many people know. In this month's column, I want to dig abnormal under Win32 and how they are associated with abnormalities associated hardware. In discussing the hardware it is aimed primarily at Intel x86 platform.
If you've ever written a program or written a MS-DOS® extender as Windows® 3.x, you will come across 0xD exception (General Protection Fault, referred to as GPF). You may have seen other errors, such as illegal instruction error (Error 6). These codes are not given artificial. On any Intel manuals have said, these are the exception code to notify the CPU with a variety of problems or events. In Win32 you can not see the code, because the Windows® NT, the Win32 operating system family's flagship product, is designed to run on multiple platforms. It does not simply let the Alpha or MIPS versions of Windows NT using an Intel CPU exception code.
Instead, Win32 uses its own set of code system to represent a variety of abnormalities. On any given Win32 platform, the system CPU exception code corresponding mapped onto one or more general purpose Win32 exception code. For example, the exception code 0xD Intel CPU may become STATUS_ACCESS_VIOLATION (0xC0000005). Similarly, exception code 0xD may become a Win32 STATUS_PRIVILEGED_INSTRUCTION (0xC0000096) exception. The underlying hardware exception decided that it should be mapped to which Win32 exception.
Let's start from the CPU interrupt abnormal and begin our journey Win32 exception. Exception (Exception) and interrupt (Interrupt) is a means, when the CPU is executing code switch to a totally different code path through which to deal with some external stimuli or conditions. Interrupt is usually caused by an external stimulus, such as pressing a key. The code is abnormal or conditions result data generated by the processor. CPU does not attempt to read the address of a physical memory mapped to produce abnormal, this is a classic example of the most unusual.
Intel CPU retains 32 interrupt / exception number to handle a variety of situations. FIG 1 is some common code. Many of them the meaning is clear, but there are many (at least before this column is running sample program) you have not encountered before. Veteran on MS-DOS may wonder INT 5H not even listed in the Print screen, INT 8H not a timer interrupt. Why is that? Intel described in Figure 1 is the definition of exceptions and interrupts. Unfortunately, before Intel rapid development of MS-DOS has put some of the interrupt number used for other purposes. The result when a programmer even accidentally when using the BOUND instruction to get the output contents of the screen!
Figure 1: Intel-defined exception and interrupt
code defines the
00 division was wrong
01 debug exception (single-step debugging and hardware)
02 can not be shielded interrupt (NMI)
03 breakpoint interrupt
04 overflow interrupt (INTO)
05 cross-border interrupt
06 illegal instruction
07 Association The processor is not available
08 nested exception
0A illegal task state segment (the TSS)
0B field is not present
Stack Fault 0C
0D general protection fault
0E page fault

sake of simplicity, this column I will use the following abnormal to represent an exception or interrupt. Like I said earlier, interrupts and exceptions are technically different. Further, a fault exception may be further divided (Fault), trap (the Trap) and termination (Abort). I do not want them here for a detailed description, you can simply think they are the same.

When an exception occurs, CPU suspends the current execution path, the control to the exception handler. The CPU flag register (EFLAGS), the code segment register (CS), the instruction pointer register (EIP) pushed onto the stack in order to protect the current execution state. Then, according to the exception code to find pre-designed program to deal with this address exceptions and transfers control to it. In fact, the exception code is the interrupt descriptor table (Interrupt Descriptor Talbe, IDT) of the index, while the interrupt descriptor table who pointed out an exception should be referred to treatment.

IDT is the basic data structure used by Intel CPU, which consists of up to 256 interrupt descriptor, each length of 8 bytes. Interrupt descriptor table is created and maintained by the operating system, so although is understood to be a data structure of the CPU, but it is controlled by the operating system. If the operating system the IDT made a mistake, and that the whole system crashes immediately.

On most operating systems, including Win32-based systems, IDT is on the high privilege level memory, low-privileged application can not access it. This real-mode MS-DOS programs are very different, where applications often replace the interrupt vector table (a version of the IDT in real mode). Since a plurality of programs based on MS-DOS, the driver, the TSR (Terminate and Stay Resident programs) lack of coordination, resulting in MS-DOS system and 16-bit Windows system is particularly labile. In the latest 32-bit operating system, CPU strictly limit access to the IDT, a corresponding increase in stability. However Win32 Device Driver (high privilege level) can access the IDT, and it can modify the corresponding entry in the IDT.

Now let us return to the case when an exception occurs. The CPU 8 acquires the abnormal number as an index byte descriptor. Including the various domains in the descriptor. Figure 2 shows a simplified form of interrupt descriptor. Note that for each exception, it has a corresponding exception handler address (CS: EIP), control is to go to this address. Figure 3 shows the GPF in (exception 0xD) the order of occurrence of events.
Figure 2: Interrupt Descriptor
  
Figure 3: The sequence of events when an exception occurs
  
if in normal times, here I will write a test program can display the contents of IDT. Unfortunately (at least for me), the application can not access the IDT. This is because under Win32, applications run in Ring 3, which is the lowest privilege level. Win32 operating system kernel to run in Ring 0 (kernel mode or management), which is the highest privilege level. Meanwhile, the critical operating system data structures, such as the IDT, the code can only be accessed by Ring 0. (Ring 1 and 2 is not used in Win32. 80286 from the beginning they exist, but as far as I know, no one has to use these privilege level.)
Since I can not write a program can read the IDT, it would take some of other information bar. FIG 4 is SoftICE / NT command obtained before an IDT 30 interrupt descriptor table entry. SoftICE as a driver running at Ring 0, so it IDT have read / write access.
FIG 4: SoftICE result of the command output IDT
Int Sel the Type: the Attributes the Symbol Offset / Owner
IDTbase = 80.0364 million Limit = 07FF
0000 IntG32 0008: 8013C354 the 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 0008 IntG32: 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: 00,000,696 DPL = 3 P       
0022 0008 Reserved: 00000000 DPL = 0 NP       
0023 0008 Reserved: 00000000 DPL = 0 NP       
0024 0008 Reserved: 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
IntG32 0008 002E: 8013B440 DPL = 3 P _KiSystemService
002F IntG32 0008: 8013E7D4 DPL = 0 P _KiTrap0F

thing you see is all exception handler address IDT Windows NT in both the above 0x80000000. Above address 0x80000000 is reserved for Windows NT privilege level (Ring 0) access. Although may not be evident from the chart, but it does almost all exception handler address in NTOSKRNL.EXE, which is running on Windows NT kernel Ring 0 components. Since I had previously loaded the debug symbols from NTOSKRNL the DBG file, so look for SoftICE exception handler address and found most of the name of the exception handler. 0x20 is a series of exceptions before named _KiTrap00, _KiTrap01 and other routine treatment. "Ki" represents the kernel interrupt (Kernel Interrupt).

It should also be noted that IDT a descriptor privilege level (Descriptor Privilege Level, DPL) domain. It allows the caller to specify a specific software interrupt the lowest privilege level. For example, INT 2EH may be from Ring 3 (lowest privilege level) to Ring 0 call at any level (the highest privilege level) in. Likewise, INT 3H breakpoints for, and Ring 3 may be higher privilege level code calls.

0x2A 0x2E from the exception to be treated NTOSKRNL.EXE in other routines. For example, in my August 1996 article "Poking Around Under the Hood: A Programmer's View of Windows NT 4.0", I talked about the application code passes control to Ring level 3 to Ring 0 level of the system code to complete such as creating a mechanism for special operations like the new process, and that is to call INT 2E. INT 2E are system DLL, e.g. NTDLL.DLL, USER32.DLL GDI32.DLL and called from Ring 3. IDT look 0x2E this one, you'll see that it's addressed to NTOSKRNL in _KiSystemService function. It is this function to transfer control to the appropriate code.

After the INT 2EH, the next most frequently used in the preceding table was undoubtedly interrupt INT 2BH. This interruption in the IDT name of an item called _KiCallbackReturn, the name suggests it does. When the callback Ring 3 Ring code calls are 0, 0 caller Ring method in a need for a back. INT 2BH being used for this purpose. A typical example of this is to call SetWindowsHookEx to install a Windows hook callback function. The real part of the user function in the Ring WIN32K.SYS driver 0's, exactly what it calls the hook callback function in the Ring 3. When the callback is finished executing, the system performs a return to INT 2BH Ring 0.

About interrupt talking about had enough. Abnormal how to do that, especially as an access violation of unpleasant abnormal? The processor level two most frequent abnormality is abnormal 0xD (GPF) and 0xE (page fault). These anomalies generated from the CPU to your application gets a chance to handle them within this period of time, the operating system error code into a more general code it likes.

Suppose you want to run the following errors program that attempts to write to memory offset 0 2:
int main ()
{
    * (int *) 2 = 0;
}
As you might expect, the offset address 0 is not a program is available. For example, in Windows NT, the first 4KB page of memory is marked as "not present" to prevent the use of procedural issues NULL pointer. Trying to write this address will lead to a page fault (abnormal 0xE). IDT look at the above chart, you will see this exception is NTOSKRNL.EXE the _KiTrap0E process.

I have to track multiple times in the debugger to the code _KiTrap0E in, but this code is quite complicated, would like to get another job with a comprehensive description of article. For now, just know that the code checks 0 Ring _KiTrap0E variety of special conditions is sufficient. Therefore, KiTrap0E called IRETD instructions to control the spread of the beginning NTDLL Ring 3 KiUserExceptionDispatcher function. I do not speak here KiUserExceptionDispatcher, because I have "Crash Course A on the Depths of Win32 Structured Exception Handling" in my article (MSJ, January 1997) talked about in detail in this function. The key is to know KiUserExceptionDispatcher was told exception code is 0xC0000005 (STATUS_ACCESS_VIOLATION), not the exception code 0xE generated by the CPU.

Like Win32 exception code 0xC0000005 like where they come from? The answer can be found in the header file WINERROR.H Win32 SDK or your C ++ compiler in. Almost at the top, you'll see a comment:
// Values are 32 bit values layed out OUT AS Follows:
Continue reading this comment, you will know, the highest two (31 and 30 bit) on behalf of severity. The next bit (29) represents the definer. Bit 28 is reserved. The remaining high word is device code 12. Low word (bits 0-15) is an abnormal code.

The more interesting point is that, Win32's Last Error Code is classified information by using bit fields. So, would you like to know where the error code 0x80010002 (RPC_E_CALL_CANCELED) and the like from. By the way, use severity, defined by bit field and equipment did not originate in Windows NT. IBM's OS / 2 uses the same mechanism, it is a by-product of the work of the post-merger were completed by Microsoft and IBM operating systems in the 1980s.

Back exception, look at the severity bit, 31-bit and 30. A value 0 for success, 1 represents information for warning 2, 3 (two bits are set) indicating an error. A fatal exception corresponds to an error, so any 32 fatal exception code top two are set in. The next two bits, and reserved bits are defined, it is usually set to 0, since they are rarely used.

Just know that those with limited knowledge of aspects of the construction exception code above, you will be able to infer fatal exception codes are beginning to 0xC of. Therefore, someone like 0xC0000005 (STATUS_ACCESS_VIOLATION) 0xC000001D (STATUS_ILLEGAL_INSTRUCTION) exception code and the like, you know they belong to this category. Some lower than this serious procedural anomaly, that is a warning, it's a serious factor is 2, so you see something like 0x80000003 (STATUS_BREAKPOINT) and 0x80000004 (STATUS_SINGLE_STEP) like the code, you know that they belong to this category. Search STATUS_ can find a fairly complete list of possible anomalies in the code WINNT.H. When you look at this list to remember not support Win32 Each processor can generate all Win32 exception code.

In writing this column, I could in the end lead to the number of Win32 exception caught my interest. I can give a lot of mistakes in the end what I intend to lead the exception code to the operating system is full of curiosity. To help solve these problems, I wrote a program framework for a processor capable of generating error reports in various ways and Win32 exception codes that are mapped to the. This is my GenException program (see Figure 5).
GenException.CPP FIG. 5
// ==========================================
/ / Matt Pietrek
// in the Microsoft Systems Journal, October 1997
// FILE: GenException.CPP
// CL GenException.CPP using the command line compiler
// ==================== ======================
#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)
{
    the __asm int // Common breakpoint instruction. 3
}
GenerateSTATUS_SINGLE_STEP void (void)
{
    // int 1 it is easier to generate than a hardware breakpoint registers
    the __asm int 1
}
void GenerateSTATUS_ACCESS_VIOLATION (void)
{
// read addresses produced by the memory in the above 2GB
// a page fault (abnormality Code 0xE)
    int * I = (int *) 0xFFFFFFF0;
}
void GenerateSTATUS_ILLEGAL_INSTRUCTION (void)
{
    the __asm _emit 0x0F // invalid instruction causes an exception 0xD
    the __asm _emit 0xFF
}
void GenerateSTATUS_ARRAY_BOUNDS_EXCEEDED (void)
{
    DWORD arrayBounds [2] = {10, } 48;

    the __asm MOV EAX, 12 is
    the __asm bound EAX, this bOUND instruction arrayBounds // normal operation
    __asm mov eax, 7
    __asm bound eax, arrayBounds // This instruction causes abnormal BOUND 0x5
}
void UnmaskFPExceptionBits (void)
{
    unsigned Short CW;

    the __asm FNINIT numerical coprocessor // initialize
    the __asm FSTCW [CW]
    CW = & 0xFFE0; // Close Most exception bits (except precision exceptions)
    the __asm FLDCW [CW]

}
void GenerateSTATUS_FLOAT_DIVIDE_BY_ZERO (void)
{
    Double A = 0;
   
    A =. 1 / A;
    the __asm FWAIT;        
}
void GenerateSTATUS_FLOAT_OVERFLOW (void)
{
    Double A = DBL_MAX;

    A * = A;
    FWAIT the __asm;
        
}
void GenerateSTATUS_FLOAT_STACK_CHECK (void)
{
    unsigned A;

    FISTP the __asm [A]
    the __asm FWAIT;
        
}
void GenerateSTATUS_FLOAT_UNDERFLOW (void)
{
    Double DBL_MIN = A;
   
    A / = 10;
    the __asm FWAIT;
        
}
void GenerateSTATUS_INTEGER_DIVIDE_BY_ZERO (void)
{
    // division by zero causes an exception 0x0
    int I = 0;
    I = 2 / I;
}
void GenerateSTATUS_INTEGER_OVERFLOW (void)
{
    the __asm MOV EAX, the maximum number of signed 07FFFFFFFh //
    __asm add eax, 2 // result = 0x80000001 -> overflow!
    abnormal __asm 0x4 into //
}
void GenerateSTATUS_PRIVILEGED_INSTRUCTION (void)
{
    // the HLT instruction can only be executed in ring 0
    __asm   hlt
}
void GenerateSTATUS_STACK_OVERFLOW( void )
{
    DWORD myArray[512];
   
    // “无穷”递归导致堆栈溢出
    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;
}

codes GetException program is divided into three parts. The first part is a series of functions, their names begin with the Generate, followed Win32 their name to be generated abnormality. For example, GenerateSTATUS_ILLEGAL_INSTRUCTION cause an illegal instruction exception. The second part is GetExceptionNumber function. It uses the Win32 structured exception handling (seh) to determine the respective fault code GenerateXXX Win32 function caused, and the exception code returns to its caller. GetExceptionNumber function with an argument, the argument is a pointer to call it GenerateXXX function pointer.

GenException.CPP final part of the main function. It is a series of calls C ++ preprocessor macros, the macro is I named SHOW_EXCEPTION. The first call for SHOW_EXCEPTION each will generate a Win32 exception. Exception name (e.g. STATUS_ACCESS_VIOLATION) SHOW_EXCEPTION with a predefined, then it calls the synthesis of a corresponding GenerateXXX its function. I use SHOW_EXCEPTION macro to omit a large number of template code, the code of these modules differ only exception code actually call. By using the preprocessor symbol paste (preprocessor token pasting) and the string of (stringizing) macro, this line

SHOW_EXCEPTION (STATUS_BREAKPOINT)
are expanded into:
dwExceptionNumber = GetExceptionNumber (GenerateSTATUS_BREAKPOINT);
the printf ( "% X-% S \ n-", dwExceptionNumber , "STATUS_BREAKPOINT");
the Assert (dwExceptionNumber == STATUS_BREAKPOINT);

when writing getException, very easy to produce some anomalies, such STATUS_ACCESS_VIOLATION. Those who do not create a common abnormality is also important, for example STATUS_ILLEGAL_INSTRUCTION. In many cases, I had to resort to inline assembly. Two good examples are abnormal CPU 4 and 5, which are produced by INTO and BOUND instruction command. I do not talk in detail how the various exceptions generated, GenException.CPP code contains a number of related comments.

Generate floating-point exception requires some skill, because no exception Win32 initialization floating point unit. I had to clear off certain bit coprocessor control word to generate floating point exceptions, like STATUS_FLOAT_DIVIDE_BY_ZERO. If you're curious, you can see UnmaskFPExceptionBits function that contains the code to deal with those bits. Because when executing floating-point instructions, instructions only execute when an exception is thrown to the next instruction the actual error, so I use __asm fwait mandatory instruction throws an exception after a failed instruction intentionally.

GetException program may not have run you through the most exciting or the most useful program, but I'm sure you from how to generate a variety of Win32 exception inspired. In most cases, CPU generates an exception 0xD, Win32 exception handler then analyze the code and construct a more meaningful, more specific exception code. My aim is to describe these mechanisms to explain the level of hardware and operating system-level exception and show the connections between them to you.

Guess you like

Origin www.cnblogs.com/yilang/p/11851677.html