[windows内核]中断与异常处理

详细解释在手册中的:

CHAPTER 6
INTERRUPT AND EXCEPTION HANDLING

也可以参考

《软件调试第二版:卷1硬件基础》 中的第3章 中断和异常
《WINDOWS内核原理与实现》中的5.2章中断与异常

一、中断

描述:

  1. 中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU“有事情需要处理”,因此又叫中断请求(Interrupt Request)

  2. 中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(中断处理程序在哪有IDT表决定)

  3. 80x86有两条中断请求线:

    1. 可屏蔽中断线,称为INTR(Interrupt Require)
    2. 非屏蔽中断线,称为NMI(NonMaskable Interrupt)
  4. 假设没有中断这种机制,当一个的程序的代码为死循环时,其他的程序就没有机会执行了。

中断的本质:改变CPU的执行路线

在这里插入图片描述
在这里插入图片描述

可屏蔽中断

描述:

  1. 在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器
  2. 它负责分配中断资源和管理各个中断源发出的中断请求
  3. 为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request ) 后面加上数字来表示不同的中断,比如:在Windows中,时钟中断的IRQ编号为0,也就是IRQ0
时钟中断

描述:

  1. 大多数操作系统时钟中断在10-100MS之间,Windows系列为10-20MS
  2. Windows时钟中断每隔10~20MS会向CPU发送一个请求,当CPU收到请求时,操作系统就会接管CPU,指定CPU去执行一段代码,操作系统在这段代码里便有机会进行线程的切换。这样,即便一个程序进入死循环,操作系统依然有机会进行线程切换
  3. 当然,操作系统主要并不是通过时钟中断来进行线程切换,而只是有机会进行线程切换,这里只是举个例子。
    在这里插入图片描述
可屏蔽中断的处理

描述:

  1. 时钟中断的IRQ编号为0,所在位置为IDT[0x30]
  2. IRQ1~IRQ15分别对应IDT[0x31]~IDT[0x35]

在这里插入图片描述
注意:

  1. 如果自己的程序执行时不希望CPU去处理这些中断,可以:
    1. 用CLI指令清空EFLAG寄存器中的IF位
    2. 用STI指令设置EFLAG寄存器中的IF位
  2. 硬件中断与IDT表中的对应关系并非固定不变的,

参见:APIC(高级可编程中断控制器)在手册第三卷中
CHAPTER 10
ADVANCED PROGRAMMABLE
INTERRUPT CONTROLLER (APIC)

在这里插入图片描述

非可屏蔽中断

在这里插入图片描述
注意:

  1. 当非可屏蔽中断产生时,CPU在执行完当前指令后会里面进入中断处理程序
  2. 非可屏蔽中断不受EFLAG寄存器中IF位的影响,一旦发生,CPU必须处理
  3. 非可屏蔽中断处理程序位于IDT表中的2号位置nt!KiNmiInterruptShadow
二、异常

描述:

异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页面等。

在这里插入图片描述

中断与异常的区别:

  1. 中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的
  2. 异常来自于CPU本身,是CPU主动产生的
  3. INT N虽然被称为“软件中断”,但其本质是异常
  4. EFLAGIF位对INT N无效。
异常处理

描述:

无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表

在这里插入图片描述
常见的异常处理程序:
在这里插入图片描述
页错误:当我们访问一个线性地址,而这个线性地址指向的物理页无效的,便会触发CPU异常,该异常位于E号门(IDT[0xE])
段错误:一旦段的运算发生异常时(如权限检查),便会走D号门(IDT[0xD])
除0错误:当除数为0时,会触发异常,这时走0号门(IDT[0x0])
双重错误:假设执行一个异常(如页错误)时又产生了一个错误,那么便会触发双重错误,这时走8号门(IDT[0x8])

无处不在的缺页异常

缺页异常是操作系统中最常见的异常了
缺页异常的产生(例举两种):

  1. PDE/PTEP=0时会发生缺页异常
  2. PDE/PTE的属性为只读但程序试图写入时会发生缺页异常

一旦发生缺页异常,CPU会执行IDT表中的0xE号中断处理程序,由操作系统来接管
在这里插入图片描述
例1:

  1. 若一个物理页是有效的,那么PDE/PTEP位一定为1
  2. 当其他进程的物理页发生紧缺(不够用)时,但当前线性地址是有效的(指向了这个物理页),那么操作系统就会把这个物理页的内容保存到文件里,再将这个物理页挂到别人的PDE/PTE中供其使用,最后将当前进程指向这个物理页的线性地址的PDE/PTEP位改为0
  3. 再次访问这个线性地址时,操作系统发现P位为0,便会进入0xE号中断处理程序(IDT[0xE]
  4. 进入IDT[0xE]后,若操作系统发现P=0转移位=0原型位=0,而其他位都是有值的时候,就说明当前物理页被存储到了页面文件里页面文件的位置(编号)保存在PFN
  5. 操作系统找到页面文件后,把里面的内容从文件中再次读到物理页,并将P位改回1

说明:
整个过程对于用户来说是完全不可见的,用户并不知道发生了一个异常,只知道程序能够对地址进行正确的读写,但其实这个过程中可能有大量异常在发生
操作系统通过这种异常的方式节省大量物理页,当我们的程序在执行时,这种缺页异常时时刻刻在发生

例2:

  1. 当一个线性地址的PDE/PTE属性为只读,但试图往里
  2. CPU检测到了这个异常,但CPU没有权利处理,便进入0xE号中断处理程序(IDT[0xE]),由操作系统来接管
  3. 操作系统检测出用户的操作确实是没有权限的,便会返回一个错误(0xC0000005,内存访问异常)
分析IDT表中0x2号中断的执行流程

打开IDA,下载好对应的符号文件
我这里静态分析的是ntkrnlpa.exe不过他们都是加载的同一个符号文件ntkrpamp.pdb,至于不同的内核文件的区别简单来说,是同一套源代码根据编译选项的不同而编译出四个可执行文件,分别用于:

ntoskrnl - 单处理器,不支持PAE
ntkrnlpa - 单处理器,支持PAE
ntkrnlmp - 多处理器,不支持PAE
ntkrpamp - 多处理器,支持PAE

先看IDA中的反汇编

.text:82847240 _KiTrap02       proc near               ; CODE XREF: _KiTrap02+1A0↓j
.text:82847240                                         ; DATA XREF: KiSystemStartup(x)+170↓o ...
.text:82847240
.text:82847240 var_8           = dword ptr -8
.text:82847240 var_4           = dword ptr -4
.text:82847240
.text:82847240                 cli
.text:82847241                 mov     eax, large fs:40h
.text:82847247                 mov     ecx, large fs:124h
.text:8284724E                 mov     edi, [ecx+50h]
.text:82847251                 mov     ecx, [edi+18h]
.text:82847254                 mov     [eax+1Ch], ecx
.text:82847257                 mov     cx, [edi+6Eh]
.text:8284725B                 mov     [eax+66h], cx
.text:8284725F                 mov     ecx, [edi+1Ch]
.text:82847262                 test    ecx, ecx
.text:82847264                 jz      short loc_8284726A
.text:82847266                 mov     cx, 48h
.text:8284726A
.text:8284726A loc_8284726A:                           ; CODE XREF: _KiTrap02+24↑j
.text:8284726A                 mov     [eax+60h], cx
.text:8284726E                 push    large dword ptr fs:40h
.text:82847275                 mov     eax, large fs:3Ch
.text:8284727B                 mov     ch, [eax+5Fh]
.text:8284727E                 mov     cl, [eax+5Ch]
.text:82847281                 shl     ecx, 10h
.text:82847284                 mov     cx, [eax+5Ah]
.text:82847288                 mov     large fs:40h, ecx
.text:8284728F                 pushf
.text:82847290                 and     [esp+8+var_8], 0FFFFBFFFh
.text:82847297                 popf
.text:82847298                 mov     ecx, large fs:3Ch
.text:8284729F                 lea     eax, [ecx+58h]
.text:828472A2                 mov     byte ptr [eax+5], 89h
.text:828472A6                 mov     eax, [esp+4+var_4]
.text:828472A9                 push    0
.text:828472AB                 push    0
.text:828472AD                 push    0
.text:828472AF                 push    0
.text:828472B1                 push    dword ptr [eax+50h]
.text:828472B4                 push    dword ptr [eax+38h]
.text:828472B7                 push    dword ptr [eax+24h]
.text:828472BA                 push    dword ptr [eax+4Ch]
.text:828472BD                 push    dword ptr [eax+20h]
.text:828472C0                 push    0
.text:828472C2                 push    dword ptr [eax+3Ch]
.text:828472C5                 push    dword ptr [eax+34h]
.text:828472C8                 push    dword ptr [eax+40h]
.text:828472CB                 push    dword ptr [eax+44h]
.text:828472CE                 push    dword ptr [eax+58h]
.text:828472D1                 push    large dword ptr fs:0
.text:828472D8                 push    0FFFFFFFFh
.text:828472DA                 push    dword ptr [eax+28h]
.text:828472DD                 push    dword ptr [eax+2Ch]
.text:828472E0                 push    dword ptr [eax+30h]
.text:828472E3                 push    dword ptr [eax+54h]
.text:828472E6                 push    dword ptr [eax+48h]
.text:828472E9                 push    dword ptr [eax+5Ch]
.text:828472EC                 push    0
.text:828472EE                 push    0
.text:828472F0                 push    0
.text:828472F2                 push    0
.text:828472F4                 push    0
.text:828472F6                 push    0
.text:828472F8                 push    0
.text:828472FA                 push    0
.text:828472FC                 push    0
.text:828472FE                 push    0
.text:82847300                 push    dword ptr [eax+20h]
.text:82847303                 push    dword ptr [eax+3Ch]
.text:82847306                 mov     ebp, esp
.text:82847308                 push    0
.text:8284730A                 push    ebp
.text:8284730B                 call    _KiSaveProcessorState@8 ; KiSaveProcessorState(x,x)
.text:82847310                 call    _KiHandleNmi@0  ; KiHandleNmi()
.text:82847315                 or      al, al
.text:82847317                 jnz     loc_828473AF
.text:8284731D                 xor     ebx, ebx
.text:8284731F                 mov     bl, large fs:51h
.text:82847326                 cmp     ds:dword_829310DC, ebx
.text:8284732C                 jz      short loc_82847342
.text:8284732E                 lea     eax, unk_829310D8
.text:82847334                 push    eax
.text:82847335                 push    0
.text:82847337                 mov     ecx, esp
.text:82847339                 mov     edx, ebp
.text:8284733B                 call    @KiAcquireQueuedSpinLockCheckForFreeze@8 ; KiAcquireQueuedSpinLockCheckForFreeze(x,x)
.text:82847340                 jmp     short loc_82847366
.text:82847342 ; ---------------------------------------------------------------------------
.text:82847342
.text:82847342 loc_82847342:                           ; CODE XREF: _KiTrap02+EC↑j
.text:82847342                 cmp     ds:dword_829310E0, 8
.text:82847349                 jb      short loc_82847366
.text:8284734B                 jnz     short loc_82847364
.text:8284734D                 cmp     byte ptr ds:_KdDebuggerNotPresent, 0
.text:82847354                 jnz     short loc_82847364
.text:82847356                 cmp     ds:_KdDebuggerEnabled, 0
.text:8284735D                 jz      short loc_82847364
.text:8284735F                 call    _KeEnterKernelDebugger@0 ; KeEnterKernelDebugger()
.text:82847364
.text:82847364 loc_82847364:                           ; CODE XREF: _KiTrap02+10B↑j
.text:82847364                                         ; _KiTrap02+114↑j ...
.text:82847364                 jmp     short loc_82847364
.text:82847366 ; ---------------------------------------------------------------------------
.text:82847366
.text:82847366 loc_82847366:                           ; CODE XREF: _KiTrap02+100↑j
.text:82847366                                         ; _KiTrap02+109↑j
.text:82847366                 mov     ds:dword_829310DC, ebx
.text:8284736C                 inc     ds:dword_829310E0
.text:82847372                 push    large dword ptr fs:24h
.text:82847379                 mov     large dword ptr fs:24h, 1Fh
.text:82847384                 push    0
.text:82847386                 call    ds:__imp__HalHandleNMI@4 ; HalHandleNMI(x)
.text:8284738C                 pop     large dword ptr fs:24h
.text:82847393                 dec     ds:dword_829310E0
.text:82847399                 jnz     short loc_828473E5
.text:8284739B                 mov     ds:dword_829310DC, 0FFFFFFFFh
.text:828473A5                 mov     ecx, esp
.text:828473A7                 call    @KeReleaseQueuedSpinLockFromDpcLevel@4 ; KeReleaseQueuedSpinLockFromDpcLevel(x)
.text:828473AC                 add     esp, 8
.text:828473AF
.text:828473AF loc_828473AF:                           ; CODE XREF: _KiTrap02+D7↑j
.text:828473AF                 mov     eax, large fs:40h
.text:828473B5                 cmp     word ptr [eax], 58h
.text:828473B9                 jz      short loc_828473E5
.text:828473BB                 add     esp, 8Ch
.text:828473C1                 pop     large dword ptr fs:40h
.text:828473C8                 mov     ecx, large fs:3Ch
.text:828473CF                 lea     eax, [ecx+28h]
.text:828473D2                 mov     byte ptr [eax+5], 8Bh
.text:828473D6                 pushf
.text:828473D7                 or      [esp+4+var_4], 4000h
.text:828473DE                 popf
.text:828473DF                 iret
.text:828473E0 ; ---------------------------------------------------------------------------
.text:828473E0                 jmp     _KiTrap02
.text:828473E5 ; ---------------------------------------------------------------------------
.text:828473E5
.text:828473E5 loc_828473E5:                           ; CODE XREF: _KiTrap02+159↑j
.text:828473E5                                         ; _KiTrap02+179↑j
.text:828473E5                 mov     eax, 2
.text:828473EA                 jmp     _KiSystemFatalException
.text:828473EA _KiTrap02       endp ; sp-analysis failed

WRK-v1.2源码对比
异常处理函数在

\base\ntos\ke\i386\trap.asm

 public  _KiTrap02
_KiTrap02       proc
.FPO (1, 0, 0, 0, 0, 2)
        cli

; 
; Set CR3, the I/O map base address, and LDT segment selector
; in the old TSS since they are not set on context
; switches. These values will be needed to return to the
; interrupted task.

        mov     eax, PCR[PcTss]                      ; get old TSS address
        mov     ecx, PCR[PcPrcbData+PbCurrentThread] ; get thread address
        mov     edi, [ecx].ThApcState.AsProcess      ; get process address
        mov     ecx, [edi]+PrDirectoryTableBase      ; get directory base
        mov     [eax]+TssCR3, ecx                    ; set previous cr3

        mov     cx, [edi]+PrIopmOffset               ; get IOPM offset
        mov     [eax]+TssIoMapBase, cx               ; set IOPM offset

        mov     ecx, [edi]+PrLdtDescriptor           ; get LDT descriptor
        test    ecx, ecx                             ; does task use LDT?
        jz      @f
        mov     cx, KGDT_LDT

@@:     mov     [eax]+TssLDT, cx                     ; set LDT into old TSS

;
; Update the TSS pointer in the PCR to point to the NMI TSS
; (which is what we're running on, or else we wouldn't be here)
;
        push    dword ptr PCR[PcTss]

        mov     eax, PCR[PcGdt]
        mov     ch, [eax+KGDT_NMI_TSS+KgdtBaseHi]
        mov     cl, [eax+KGDT_NMI_TSS+KgdtBaseMid]
        shl     ecx, 16
        mov     cx, [eax+KGDT_NMI_TSS+KgdtBaseLow]
        mov     PCR[PcTss], ecx

;
; Clear Nested Task bit in EFLAGS
;
        pushfd
        and     [esp], not 04000h
        popfd

;
; Clear the busy bit in the TSS selector
;
        mov     ecx, PCR[PcGdt]
        lea     eax, [ecx] + KGDT_NMI_TSS
        mov     byte ptr [eax+5], 089h  ; 32bit, dpl=0, present, TSS32, not busy

;
; Allow only one processor at a time to enter the NMI path.  While
; waiting, spin on a LOCAL data structure (to avoid cache thrashing
; during a crashdump which can cause the dump to hang), and poll for
; Freeze IPI requests so that the correct state for this processor
; appears in the crashdump.
;
;
; (1) make a trap frame,
; (2) acquire lock
; (3) while not lock owner
; (4)     if (IPI_FREEZE)
; (5)        KeFreezeExecutionTarget(&TrapFrame, NULL)
; (6) let the HAL have it
; (7) release lock to next in line
;
; Build trap frame from the data in the previous TSS.
;

        mov     eax, [esp]              ; get saved TSS address
        push    0                       ; build trap frame, starting with
        push    0                       ; faked V86Gs thru V86Es
        push    0
        push    0
        push    [eax].TssSs             ; copy fields from TSS to
        push    [eax].TssEsp            ; trap frame.
        push    [eax].TssEflags
        push    [eax].TssCs
        push    [eax].TssEip
        push    0
        push    [eax].TssEbp
        push    [eax].TssEbx
        push    [eax].TssEsi
        push    [eax].TssEdi
        push    [eax].TssFs
        push    PCR[PcExceptionList]
        push    -1                      ; previous mode
        push    [eax].TssEax
        push    [eax].TssEcx
        push    [eax].TssEdx
        push    [eax].TssDs
        push    [eax].TssEs
        push    [eax].TssGs
        push    0                       ; fake out the debug registers
        push    0
        push    0
        push    0
        push    0
        push    0
        push    0                       ; temp ESP
        push    0                       ; temp CS
        push    0
        push    0
        push    [eax].TssEip
        push    [eax].TssEbp
        mov     ebp, esp                ; ebp -> TrapFrame
        stdCall _KiSaveProcessorState, <ebp, 0> ; save processor state

.FPO ( 0, 0, 0, 0, 0, FPO_TRAPFRAME )

        stdCall _KiHandleNmi            ; Rundown the list of NMI handlers
        or      al, al                  ; did someone handle it?
        jne     short Kt02100           ; jif handled

;
; In the UP case, jump to the recursive NMI processing code if this is a
; nested NMI.
;
; In the MP case, attempt to acquire the NMI lock, checking for freeze
; execution in case another processor is trying to dump memory.  We need
; a special check here to jump to the recursive NMI processing code if
; we're already the owner of the NMI lock.
;

ifdef NT_UP

        cmp     ds:KiNMIRecursionCount, 0
        jne     short Kt0260

else

;
; Check if we recursed out of the HAL and back into the NMI handler.
;

        xor     ebx, ebx
        mov     bl, PCR[PcNumber]       ; extend processor number to 32 bits
        cmp     ds:KiNMIOwner, ebx      ; check against the NMI lock owner
        je      short Kt0260            ; go handle nested NMI if needed

;
; We aren't dealing with a recursive NMI out of the HAL, so try to acquire the
; NMI lock.
;

        lea     eax, KiLockNMI          ; build in stack queued spin lock.
        push    eax
        push    0
        mov     ecx, esp
        mov     edx, ebp
        fstCall KiAcquireQueuedSpinLockCheckForFreeze

endif

        jmp     short Kt0280

;
; The processor will hold off nested NMIs until an iret instruction is
; executed or until we enter and leave SMM mode.  There is exposure in this
; second case because the processor doesn't save the "NMI disabled" indication
; along with the rest of the processor state in the SMRAM save state map.  As
; a result, it unconditionally enables NMIs when leaving SMM mode.
;
; This means that we're exposed to nested NMIs in the following cases.
;
; 1) When the HAL issues an iret to enter the int 10 BIOS code needed to print
;    an error message to the screen.
;
; 2) When the NMI handler is preempted by a BIOS SMI.
;
; 3) When we take some kind of exception from the context of the NMI handler
;    (e.g. due to a debugger breakpoint) and automatically run an iret when
;    exiting the exception handler.
;
; Case (3) isn't of concern since the NMI handler shouldn't be raising
; exceptions except when being debugged.
;
; For (2), NMIs can become "unmasked" while in SMM mode if the SMM code
; actually issues an iret instruction, meaning they may actually occur
; before the SMI handler exits.  We assume that these NMIs will be handled by
; the SMM code.  After the SMI handler exits, NMIs will be "unmasked" and
; we'll continue in the NMI handler, running on the NMI TSS.  If a nested NMI
; comes in at this point, it will be dispatched using the following sequence.
;
;    a) The NMI sends us through a task gate, causing a task switch from the
;       NMI TSS back onto itself.
;
;    b) The processor saves the current execution context in the old TSS.
;       This is the NMI TSS in our case.
;
;    c) The processor sets up the exception handler context by loading the
;       contents of the new TSS.  Since this is also the NMI TSS, we'll just
;       reload our interrupted context and keep executing as if nothing had
;       happened.
;
; The only side effect of this "invisible" NMI is that the backlink field of
; the NMI TSS will be stomped, meaning the link to the TSS containing our
; interrupted context is broken.
;
; In case (1), the NMI isn't invisible since the HAL will "borrow" the
; KGDT_TSS before issuing the iret.  It does this when it finds that the NMI
; TSS doesn't contain space for an IOPM.  Due to "borrowing" the TSS, the
; nested NMI will put us back at the top of this NMI handler.  We've again
; lost touch with the original interrupted context since the NMI TSS backlink
; now points to a TSS containing the state of the interrupted HAL code.
;
; To increase the chances of displaying a blue screen in the presence of
; hardware that erroneously generates multiple NMIs, we reenter the HAL NMI
; handler in response to the first few NMIs fitting into case (1).  Once the
; number of nested NMIs exceeds an arbitrarily chosen threshold value, we
; decide that we're seeing an "NMI storm" of sorts and go into a tight loop.
; We can't bugcheck the system directly since this will invoke the same HAL
; int 10 code that allowed us to keep looping through this routine in the
; first place.
;
; The only other case where this handler needs to explicitly account for
; nested NMIs is to avoid exiting the handler when our original interrupted
; context has been lost as will happen in cases (2) and (3).
;

KI_NMI_RECURSION_LIMIT equ 8

Kt0260:

        cmp     ds:KiNMIRecursionCount, KI_NMI_RECURSION_LIMIT
        jb      short Kt0280

;
; When we first hit the recursion limit, take a shot at entering the debugger.
; Go into a tight loop if this somehow causes additional recursion.
;

        jne     short Kt0270

        cmp     _KdDebuggerNotPresent, 0
        jne     short Kt0270

        cmp     _KdDebuggerEnabled, 0
        je      short Kt0270

        stdCall _KeEnterKernelDebugger

Kt0270:
        jmp     short Kt0270

;
; This processor now owns the NMI lock.   See if the HAL will handle the NMI.
; If the HAL does not handle it, it will NOT return, so if we get back here,
; it's handled.
;
; Before handing off to the HAL, set the NMI owner field and increment the NMI
; recursion count so we'll be able to properly handle cases where we recurse
; out of the HAL back into the NMI handler.
;

Kt0280:

ifndef NT_UP
        mov     ds:KiNMIOwner, ebx
endif
        inc     ds:KiNMIRecursionCount
        stdCall _HalHandleNMI,<0>

;
; We're back, therefore the Hal has dealt with the NMI.  Clear the NMI owner
; flag, decrement the NMI recursion count, and release the NMI lock.
;
; As described above, we can't safely resume execution if we're running in the
; context of a nested NMI.  Bugcheck the machine if the NMI recursion counter
; indicates that this is the case.
;

        dec     ds:KiNMIRecursionCount
        jnz     Kt02200

ifndef NT_UP

        mov     ds:KiNMIOwner, KI_NMI_UNOWNED

        mov     ecx, esp                ; release queued spinlock.
        fstCall KeReleaseQueuedSpinLockFromDpcLevel
        add     esp, 8                  ; free queued spinlock context

endif

Kt02100:

;
; As described in the comments above, we can experience "invisible" nested
; NMIs whose only effect will be pointing the backlink field of the NMI TSS
; to itself.  Our interrupted context is gone in these cases so we can't leave
; this exception handler if we find a self referencing backlink.  The backlink
; field is found in the first 2 bytes of the TSS structure.
;
; N.B. This still leaves a window where an invisible NMI can cause us to iret
;      back onto the NMI TSS.  The busy bit of the NMI TSS will be cleared on
;      the first iret, which will lead to a general protection fault when we
;      try to iret back into the NMI TSS (iret expects the targeted task to be
;      marked "busy").
;

        mov     eax, PCR[PcTss]
        cmp     word ptr [eax], KGDT_NMI_TSS    ; check for a self reference
        je      Kt02200

        add     esp, KTRAP_FRAME_LENGTH ; free trap frame
        pop     dword ptr PCR[PcTss]    ; restore PcTss

        mov     ecx, PCR[PcGdt]
        lea     eax, [ecx] + KGDT_TSS
        mov     byte ptr [eax+5], 08bh  ; 32bit, dpl=0, present, TSS32, *busy*

        pushfd                          ; Set Nested Task bit in EFLAGS
        or      [esp], 04000h           ; so iretd will do a task switch
        popfd

        iretd                           ; Return from NMI
        jmp     _KiTrap02               ; in case we NMI again

;
; We'll branch here if we need to bugcheck the machine directly.
;

Kt02200:

        mov     eax, EXCEPTION_NMI
        jmp     _KiSystemFatalException

_KiTrap02       endp

ReactOS 0.4.15源码

VOID __cdecl KiTrap02Handler	(VOID )	
{
     PKTSS Tss, NmiTss;
     PKTHREAD Thread;
     PKPROCESS Process;
     PKGDTENTRY TssGdt;
     KTRAP_FRAME TrapFrame;
     KIRQL OldIrql;
 
     /*
      * In some sort of strange recursion case, we might end up here with the IF
      * flag incorrectly on the interrupt frame -- during a normal NMI this would
      * normally already be set.
      *
      * For sanity's sake, make sure interrupts are disabled for sure.
      * NMIs will already be since the CPU does it for us.
      */
     _disable();
 
     /* Get the current TSS, thread, and process */
     Tss = KeGetPcr()->TSS;
     Thread = ((PKIPCR)KeGetPcr())->PrcbData.CurrentThread;
     Process = Thread->ApcState.Process;
 
     /* Save data usually not present in the TSS */
     Tss->CR3 = Process->DirectoryTableBase[0];
     Tss->IoMapBase = Process->IopmOffset;
     Tss->LDT = Process->LdtDescriptor.LimitLow ? KGDT_LDT : 0;
 
     /* Now get the base address of the NMI TSS */
     TssGdt = &((PKIPCR)KeGetPcr())->GDT[KGDT_NMI_TSS / sizeof(KGDTENTRY)];
     NmiTss = (PKTSS)(ULONG_PTR)(TssGdt->BaseLow |
                                 TssGdt->HighWord.Bytes.BaseMid << 16 |
                                 TssGdt->HighWord.Bytes.BaseHi << 24);
 
     /*
      * Switch to it and activate it, masking off the nested flag.
      *
      * Note that in reality, we are already on the NMI TSS -- we just
      * need to update the PCR to reflect this.
      */
     KeGetPcr()->TSS = NmiTss;
     __writeeflags(__readeflags() &~ EFLAGS_NESTED_TASK);
     TssGdt->HighWord.Bits.Dpl = 0;
     TssGdt->HighWord.Bits.Pres = 1;
     TssGdt->HighWord.Bits.Type = I386_TSS;
 
     /*
      * Now build the trap frame based on the original TSS.
      *
      * The CPU does a hardware "Context switch" / task switch of sorts
      * and so it takes care of saving our context in the normal TSS.
      *
      * We just have to go get the values...
      */
     RtlZeroMemory(&TrapFrame, sizeof(KTRAP_FRAME));
     TrapFrame.HardwareSegSs = Tss->Ss0;
     TrapFrame.HardwareEsp = Tss->Esp0;
     TrapFrame.EFlags = Tss->EFlags;
     TrapFrame.SegCs = Tss->Cs;
     TrapFrame.Eip = Tss->Eip;
     TrapFrame.Ebp = Tss->Ebp;
     TrapFrame.Ebx = Tss->Ebx;
     TrapFrame.Esi = Tss->Esi;
     TrapFrame.Edi = Tss->Edi;
     TrapFrame.SegFs = Tss->Fs;
     TrapFrame.ExceptionList = KeGetPcr()->NtTib.ExceptionList;
     TrapFrame.PreviousPreviousMode = (ULONG)-1;
     TrapFrame.Eax = Tss->Eax;
     TrapFrame.Ecx = Tss->Ecx;
     TrapFrame.Edx = Tss->Edx;
     TrapFrame.SegDs = Tss->Ds;
     TrapFrame.SegEs = Tss->Es;
     TrapFrame.SegGs = Tss->Gs;
     TrapFrame.DbgEip = Tss->Eip;
     TrapFrame.DbgEbp = Tss->Ebp;
 
     /* Store the trap frame in the KPRCB */
     KiSaveProcessorState(&TrapFrame, NULL);
 
     /* Call any registered NMI handlers and see if they handled it or not */
     if (!KiHandleNmi())
     {
         /*
          * They did not, so call the platform HAL routine to bugcheck the system
          *
          * Make sure the HAL believes it's running at HIGH IRQL... we can't use
          * the normal APIs here as playing with the IRQL could change the system
          * state.
          */
         OldIrql = KeGetPcr()->Irql;
         KeGetPcr()->Irql = HIGH_LEVEL;
         HalHandleNMI(NULL);
         KeGetPcr()->Irql = OldIrql;
     }
 
     /*
      * Although the CPU disabled NMIs, we just did a BIOS call, which could've
      * totally changed things.
      *
      * We have to make sure we're still in our original NMI -- a nested NMI
      * will point back to the NMI TSS, and in that case we're hosed.
      */
     if (KeGetPcr()->TSS->Backlink == KGDT_NMI_TSS)
     {
         /* Unhandled: crash the system */
         KiSystemFatalException(EXCEPTION_NMI, NULL);
     }
 
     /* Restore original TSS */
     KeGetPcr()->TSS = Tss;
 
     /* Set it back to busy */
     TssGdt->HighWord.Bits.Dpl = 0;
     TssGdt->HighWord.Bits.Pres = 1;
     TssGdt->HighWord.Bits.Type = I386_ACTIVE_TSS;
 
     /* Restore nested flag */
     __writeeflags(__readeflags() | EFLAGS_NESTED_TASK);
 
     /* Handled, return from interrupt */
 }

分析IDT表中0x8号中断的执行流程

IDA反汇编

.text:82848357 _KiTrap08       proc near               ; DATA XREF: KiSystemStartup(x)+F8↓o
.text:82848357                                         ; INIT:82B861A4↓o
.text:82848357
.text:82848357 var_4           = dword ptr -4
.text:82848357
.text:82848357                 cli
.text:82848358                 mov     eax, large fs:40h
.text:8284835E                 mov     ecx, large fs:124h
.text:82848365                 mov     edi, [ecx+50h]
.text:82848368                 mov     ecx, [edi+18h]
.text:8284836B                 mov     [eax+1Ch], ecx
.text:8284836E                 mov     cx, [edi+6Eh]
.text:82848372                 mov     [eax+66h], cx
.text:82848376                 mov     ecx, [edi+1Ch]
.text:82848379                 test    ecx, ecx
.text:8284837B                 jz      short loc_82848381
.text:8284837D                 mov     cx, 48h
.text:82848381
.text:82848381 loc_82848381:                           ; CODE XREF: _KiTrap08+24↑j
.text:82848381                 mov     [eax+60h], cx
.text:82848385                 mov     ecx, large fs:3Ch
.text:8284838C                 lea     eax, [ecx+50h]
.text:8284838F                 mov     byte ptr [eax+5], 89h
.text:82848393                 pushf
.text:82848394                 and     [esp+4+var_4], 0FFFFBFFFh
.text:8284839B                 popf
.text:8284839C                 mov     eax, large fs:3Ch
.text:828483A2                 mov     ch, [eax+57h]
.text:828483A5                 mov     cl, [eax+54h]
.text:828483A8                 shl     ecx, 10h
.text:828483AB                 mov     cx, [eax+52h]
.text:828483AF                 mov     eax, large fs:40h
.text:828483B5                 mov     large fs:40h, ecx
.text:828483BC
.text:828483BC loc_828483BC:                           ; CODE XREF: .text:828483CC↓j
.text:828483BC                 push    0
.text:828483BE                 push    0
.text:828483C0                 push    0
.text:828483C2                 push    eax
.text:828483C3                 push    8
.text:828483C5                 push    7Fh
.text:828483C7                 call    _KeBugCheck2@24 ; KeBugCheck2(x,x,x,x,x,x)
.text:828483C7 _KiTrap08       endp

WRK-v1.2源码对比
异常处理函数在

\base\ntos\ke\i386\trap.asm

public  _KiTrap08
_KiTrap08       proc
.FPO (0, 0, 0, 0, 0, 2)

        cli


; 
; Set CR3, the I/O map base address, and LDT segment selector
; in the old TSS since they are not set on context
; switches. These values will be needed to return to the
; interrupted task.

        mov     eax, PCR[PcTss]                      ; get old TSS address
        mov     ecx, PCR[PcPrcbData+PbCurrentThread] ; get thread address
        mov     edi, [ecx].ThApcState.AsProcess      ; get process address
        mov     ecx, [edi]+PrDirectoryTableBase      ; get directory base
        mov     [eax]+TssCR3, ecx                    ; set previous cr3

        mov     cx, [edi]+PrIopmOffset               ; get IOPM offset
        mov     [eax]+TssIoMapBase, cx               ; set IOPM offset

        mov     ecx, [edi]+PrLdtDescriptor           ; get LDT descriptor
        test    ecx, ecx                             ; does task use LDT?
        jz      @f
        mov     cx, KGDT_LDT

@@:     mov     [eax]+TssLDT, cx                     ; set LDT into old TSS

;
; Clear the busy bit in the TSS selector
;

        mov     ecx, PCR[PcGdt]
        lea     eax, [ecx] + KGDT_DF_TSS
        mov     byte ptr [eax+5], 089h  ; 32bit, dpl=0, present, TSS32, not busy

;
; Clear Nested Task bit in EFLAGS
;
        pushfd
        and     [esp], not 04000h
        popfd

;
; Get address of the double-fault TSS which we are now running on.
;
        mov     eax, PCR[PcGdt]
        mov     ch, [eax+KGDT_DF_TSS+KgdtBaseHi]
        mov     cl, [eax+KGDT_DF_TSS+KgdtBaseMid]
        shl     ecx, 16
        mov     cx, [eax+KGDT_DF_TSS+KgdtBaseLow]
;
; Update the TSS pointer in the PCR to point to the double-fault TSS
; (which is what we're running on, or else we wouldn't be here)
;
        mov     eax, PCR[PcTss]
        mov     PCR[PcTss], ecx

;
; The original machine context is in original task's TSS
;
@@:     stdCall _KeBugCheck2,<UNEXPECTED_KERNEL_MODE_TRAP,8,eax,0,0,0>
        jmp     short @b        ; do not remove - for debugger

_KiTrap08       endp

ReactOS 0.4.15源码

DECLSPEC_NORETURN VOID __cdecl KiTrap08Handler	(VOID )
{
     PKTSS Tss, DfTss;
     PKTHREAD Thread;
     PKPROCESS Process;
     PKGDTENTRY TssGdt;
 
     /* For sanity's sake, make sure interrupts are disabled */
     _disable();
 
     /* Get the current TSS, thread, and process */
     Tss = KeGetPcr()->TSS;
     Thread = ((PKIPCR)KeGetPcr())->PrcbData.CurrentThread;
     Process = Thread->ApcState.Process;
 
     /* Save data usually not present in the TSS */
     Tss->CR3 = Process->DirectoryTableBase[0];
     Tss->IoMapBase = Process->IopmOffset;
     Tss->LDT = Process->LdtDescriptor.LimitLow ? KGDT_LDT : 0;
 
     /* Now get the base address of the double-fault TSS */
     TssGdt = &((PKIPCR)KeGetPcr())->GDT[KGDT_DF_TSS / sizeof(KGDTENTRY)];
     DfTss  = (PKTSS)(ULONG_PTR)(TssGdt->BaseLow |
                                 TssGdt->HighWord.Bytes.BaseMid << 16 |
                                 TssGdt->HighWord.Bytes.BaseHi << 24);
 
     /*
      * Switch to it and activate it, masking off the nested flag.
      *
      * Note that in reality, we are already on the double-fault TSS
      * -- we just need to update the PCR to reflect this.
      */
     KeGetPcr()->TSS = DfTss;
     __writeeflags(__readeflags() &~ EFLAGS_NESTED_TASK);
     TssGdt->HighWord.Bits.Dpl = 0;
     TssGdt->HighWord.Bits.Pres = 1;
     // TssGdt->HighWord.Bits.Type &= ~0x2; /* I386_ACTIVE_TSS --> I386_TSS */
     TssGdt->HighWord.Bits.Type = I386_TSS; // Busy bit cleared in the TSS selector.
 
     /* Bugcheck the system */
     KeBugCheckWithTf(UNEXPECTED_KERNEL_MODE_TRAP,
                      EXCEPTION_DOUBLE_FAULT,
                      (ULONG_PTR)Tss,
                      0,
                      0,
                      NULL);
 }	

通过这几处对比,我们发现ReactOS 和IDA反汇编出来的还是有些不相同的,但是我们可以主要参考它的主要逻辑和设计思想,虽然ReactOS 自己说自己和windows没有半毛钱关系,但是大家都懂的。
而WRK中的源码和IDA反汇编中分析出来的基本相同,大家可以参考WRK中的详细注释和自己的分析来总结IDT表中0x2/0x8号中断的执行流程,这里由于本人比较菜就不写出自己的分析了,各位大佬自由发挥吧.

ReactOS源码查看网站

https://doxygen.reactos.org/index.html

WRK-v1.2很多地方都能找到,这里不贴出来了
WRK的参考可以去看前面说到的

《WINDOWS内核原理与实现》

IDA用的7.0,可能根高版本反汇编出来的更清晰些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值