详细解释在手册中的:
CHAPTER 6
INTERRUPT AND EXCEPTION HANDLING
也可以参考
《软件调试第二版:卷1硬件基础》 中的第3章 中断和异常
《WINDOWS内核原理与实现》中的5.2章中断与异常
一、中断
描述:
-
中断通常是由CPU外部的输入输出设备(硬件)所触发的,供外部设备通知CPU“有事情需要处理”,因此又叫中断请求(Interrupt Request)
-
中断请求的目的是希望CPU暂时停止执行当前正在执行的程序,转去执行中断请求所对应的中断处理例程(中断处理程序在哪有IDT表决定)
-
80x86有两条中断请求线:
- 可屏蔽中断线,称为INTR(Interrupt Require)
- 非屏蔽中断线,称为NMI(NonMaskable Interrupt)
-
假设没有中断这种机制,当一个的程序的代码为死循环时,其他的程序就没有机会执行了。
中断的本质:改变CPU的执行路线
可屏蔽中断
描述:
- 在硬件级,可屏蔽中断是由一块专门的芯片来管理的,通常称为中断控制器
- 它负责分配中断资源和管理各个中断源发出的中断请求
- 为了便于标识各个中断请求,中断管理器通常用IRQ(Interrupt Request ) 后面加上数字来表示不同的中断,比如:在Windows中,时钟中断的IRQ编号为0,也就是IRQ0
时钟中断
描述:
- 大多数操作系统时钟中断在10-100MS之间,Windows系列为10-20MS
- Windows时钟中断每隔10~20MS会向CPU发送一个请求,当CPU收到请求时,操作系统就会接管CPU,指定CPU去执行一段代码,操作系统在这段代码里便有机会进行线程的切换。这样,即便一个程序进入死循环,操作系统依然有机会进行线程切换
- 当然,操作系统主要并不是通过时钟中断来进行线程切换,而只是有机会进行线程切换,这里只是举个例子。
可屏蔽中断的处理
描述:
- 时钟中断的
IRQ
编号为0,所在位置为IDT[0x30]
IRQ1
~IRQ15
分别对应IDT[0x31]
~IDT[0x35]
注意:
- 如果自己的程序执行时不希望CPU去处理这些中断,可以:
- 用CLI指令清空EFLAG寄存器中的IF位
- 用STI指令设置EFLAG寄存器中的IF位
- 硬件中断与IDT表中的对应关系并非固定不变的,
参见:APIC(高级可编程中断控制器)在手册第三卷中
CHAPTER 10
ADVANCED PROGRAMMABLE
INTERRUPT CONTROLLER (APIC)
非可屏蔽中断
注意:
- 当非可屏蔽中断产生时,CPU在执行完当前指令后会里面进入中断处理程序
- 非可屏蔽中断不受
EFLAG
寄存器中IF
位的影响,一旦发生,CPU必须处理 - 非可屏蔽中断处理程序位于IDT表中的2号位置
nt!KiNmiInterruptShadow
二、异常
描述:
异常通常是CPU在执行指令时检测到的某些错误,比如除0、访问无效页面等。
中断与异常的区别:
- 中断来自于外部设备,是中断源(比如键盘)发起的,CPU是被动的
- 异常来自于CPU本身,是CPU主动产生的
INT N
虽然被称为“软件中断”,但其本质是异常EFLAG
的IF
位对INT N
无效。
异常处理
描述:
无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表
常见的异常处理程序:
页错误
:当我们访问一个线性地址,而这个线性地址指向的物理页是无效的,便会触发CPU异常,该异常位于E号门(IDT[0xE])
段错误
:一旦段的运算发生异常时(如权限检查),便会走D号门(IDT[0xD])
除0错误
:当除数为0时,会触发异常,这时走0号门(IDT[0x0])
双重错误
:假设执行一个异常(如页错误)时又产生了一个错误,那么便会触发双重错误,这时走8号门(IDT[0x8])
无处不在的缺页异常
缺页异常是操作系统中最常见的异常了
缺页异常的产生(例举两种):
- 当
PDE/PTE
的P=0
时会发生缺页异常 - 当
PDE/PTE
的属性为只读但程序试图写入时会发生缺页异常
一旦发生缺页异常,CPU会执行IDT
表中的0xE
号中断处理程序,由操作系统来接管
例1:
- 若一个物理页是有效的,那么
PDE/PTE
的P
位一定为1 - 当其他进程的物理页发生紧缺(不够用)时,但当前线性地址是有效的(指向了这个物理页),那么操作系统就会把这个物理页的内容保存到文件里,再将这个物理页挂到别人的
PDE/PTE
中供其使用,最后将当前进程指向这个物理页的线性地址的PDE/PTE
的P
位改为0 - 再次访问这个线性地址时,操作系统发现
P
位为0,便会进入0xE
号中断处理程序(IDT[0xE]
) - 进入
IDT[0xE]
后,若操作系统发现P=0
、转移位=0
、原型位=0
,而其他位都是有值的时候,就说明当前物理页被存储到了页面文件里,页面文件的位置(编号)保存在PFN
中 - 操作系统找到页面文件后,把里面的内容从文件中再次读到物理页,并将
P
位改回1
说明:
整个过程对于用户来说是完全不可见的,用户并不知道发生了一个异常,只知道程序能够对地址进行正确的读写,但其实这个过程中可能有大量异常在发生
操作系统通过这种异常的方式节省大量物理页,当我们的程序在执行时,这种缺页异常时时刻刻在发生
例2:
- 当一个线性地址的
PDE/PTE
属性为只读,但试图往里写时 - CPU检测到了这个异常,但CPU没有权利处理,便进入
0xE
号中断处理程序(IDT[0xE]
),由操作系统来接管 - 操作系统检测出用户的操作确实是没有权限的,便会返回一个错误(
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,可能根高版本反汇编出来的更清晰些。