透過 WinDbg 的 !idt 指令,我們可以看到 IDT 的內容:
0: kd> !idt
Dumping IDT:
37: 806e6864 hal!PicSpuriousService37
3d: 806e7e2c hal!HalpApcInterrupt
41: 806e7c88 hal!HalpDispatchInterrupt
50: 806e693c hal!HalpApicRebootService
62: 81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63: 8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73: 81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)
82: 81bd4bec atapi!IdePortInterrupt (KINTERRUPT 81bd4bb0)
83: 81be1ae4 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 81be1aa8)
93: 81965204 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 819651c8)
a3: 81964044 i8042prt!I8042MouseInterruptService (KINTERRUPT 81964008)
b1: 81be423c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 81be4200)
b2: 81795bec serial!SerialCIsrSw (KINTERRUPT 81795bb0)
b4: 818765d4 NDIS!ndisMIsr (KINTERRUPT 81876598)
c1: 806e6ac0 hal!HalpBroadcastCallService
d1: 806e5e54 hal!HalpClockInterrupt
e1: 806e7048 hal!HalpIpiHandler
e3: 806e6dac hal!HalpLocalApicErrorService
fd: 806e75a8 hal!HalpProfileInterrupt
fe: 806e7748 hal!HalpPerfInterrupt
單純的 !idt 只會輸出 ntoskrnl.exe 之外的 interrupt handler ,若想看到全部 IDT 項目,則須加上 -a:
!idt -a
More
0: kd> !idt
Dumping IDT:
37: 806e6864 hal!PicSpuriousService37
3d: 806e7e2c hal!HalpApcInterrupt
41: 806e7c88 hal!HalpDispatchInterrupt
50: 806e693c hal!HalpApicRebootService
62: 81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63: 8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73: 81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)
一般的情況下,!idt 已經給予足夠的資訊了,以上面的 IDE 介面來說:
- 它的 interrupt number 是 62 、
- ISR 是位於 atapi module 的 IdePortInterrupt
不過好奇心驅使,可以發現:當去查詢 atapi!IdePortInterrupt 的位置時,卻不是 0x81bbe6f4 、也不是 0x81bbe6b8。
0: kd> x atapi!IdePortInterrupt
f980467e atapi!IdePortInterrupt =
而是 0xf980467e ,加上一個 IDT entry 竟然有這麼多 addresses ,真是詭異。所以我們可以懷疑一下,!idt 這個指令的輸出是經過加工的。不過必須找到一些更基礎的資訊來支持這個懷疑。在 x86 處理器的 protected mode 中, IDT 是由 IDTR (Interrupt Descriptor Table Register)所紀錄,所以來看看 registers 的狀況如何:
0: kd> r
eax=00000001 ebx=ffdff980 ecx=80554780 edx=000003f8 esi=00000003 edi=19d73a7c
eip=8052c5ec esp=805523b0 ebp=805523c0 iopl=0 nv up ei pl nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202
nt!RtlpBreakWithStatusInstruction:
8052c5ec cc int 3
什麼?沒有 IDTR ,原來要打 r idtr:
0: kd> r idtr
idtr=8003f400
知道 IDT 的位置後,必須算出適當的位移量來找到 atapi!IdePortInterrupt ,參考一下 Intel 的文件:Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programming Guide 5.10 可以知道 IDT entry —— interrupt gate 的格式:
所以,atapi!IdePortInterrupt 等於:
0: kd> dd 8003f400+0x8*0x62
8003f710 0008e6f4 81bb8e00 0008a974 81918e00
8003f720 00081d28 80548e00 00081d32 80548e00
8003f730 00081d3c 80548e00 00081d46 80548e00
跟 IDT Entry 第一個 address 是符合的,所以可以確定它是 interrupt handler 的 entry point:
62: 81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
那麼該怎麼從這 0x81bbe6f4 entry point 到 atapi!IdePortInterrupt 去執行呢?線索就在 KINTERRUPT 81bbe6b8 上, Windows 透過 interrupt object 來管理系統上的 interrupts ,driver programmers 必須向 interrrupt object 註冊自己的 ISR ,那麼 interrupt handling flow 才會有機會執行到這個 ISR 。Windows Internals chapter 3 有針對 interrupt 作詳細的說明。透過 dt 指令可以看到存放在 0x81bbe6b8 的 kinterrupt 變數。
0: kd> dt nt!_kinterrupt 81bbe6b8
+0x000 Type : 22
+0x002 Size : 484
+0x004 InterruptListEntry : _LIST_ENTRY [ 0x81bbe6bc - 0x81bbe6bc ]
+0x00c ServiceRoutine : 0xf980467e unsigned char atapi!IdePortInterrupt+0
+0x010 ServiceContext : 0x81bbd030
+0x014 SpinLock : 0
+0x018 TickCount : 0xffffffff
+0x01c ActualLock : 0x81bbe91c -> 0
+0x020 DispatchAddress : 0x80546660 void nt!KiInterruptDispatch+0
+0x024 Vector : 0x162
+0x028 Irql : 0x5 ''
+0x029 SynchronizeIrql : 0x5 ''
+0x02a FloatingSave : 0 ''
+0x02b Connected : 0x1 ''
+0x02c Number : 0 ''
+0x02d ShareVector : 0 ''
+0x030 Mode : 1 ( Latched )
+0x034 ServiceCount : 0
+0x038 DispatchCount : 0xffffffff
+0x03c DispatchCode : [106] 0x56535554
注意到沒有?放在 nt!_kinterrupt+0x03c 位置的 DispatchCode 其實就是 IDT Eetry。
0: kd> .formats 0x81bbe6b8+0x03c
Evaluate expression:
Hex: 81bbe6f4
Decimal: -2118392076
Octal: 20156763364
Binary: 10000001 10111011 11100110 11110100
Chars: ....
Time: ***** Invalid
Float: low -6.90244e-038 high -1.#QNAN
Double: -1.#QNAN
既然確定了 0x56535554 就是 entry point ,那麼可以反組譯一下 DispatchCode 來確定是否會執行到 atapi!IdePortInterrupt :
0: kd> u 81bbe6f4 81bbe6f4+0x106*0x4
; Setup Trap Frame
81bbe6f4 54 push esp
81bbe6f5 55 push ebp
81bbe6f6 53 push ebx
81bbe6f7 56 push esi
81bbe6f8 57 push edi
81bbe6f9 83ec54 sub esp,54h
81bbe6fc 8bec mov ebp,esp
81bbe6fe 89442444 mov dword ptr [esp+44h],eax
81bbe702 894c2440 mov dword ptr [esp+40h],ecx
81bbe706 8954243c mov dword ptr [esp+3Ch],edx
81bbe70a f744247000000200 test dword ptr [esp+70h],20000h
81bbe712 0f852b010000 jne 81bbe843
81bbe718 66837c246c08 cmp word ptr [esp+6Ch],8
81bbe71e 7423 je 81bbe743
81bbe720 8c642450 mov word ptr [esp+50h],fs
81bbe724 8c5c2438 mov word ptr [esp+38h],ds
81bbe728 8c442434 mov word ptr [esp+34h],es
81bbe72c 8c6c2430 mov word ptr [esp+30h],gs
81bbe730 bb30000000 mov ebx,30h
81bbe735 b823000000 mov eax,23h
81bbe73a 668ee3 mov fs,bx
81bbe73d 668ed8 mov ds,ax
81bbe740 668ec0 mov es,ax
81bbe743 648b1d00000000 mov ebx,dword ptr fs:[0]
81bbe74a 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh
81bbe755 895c244c mov dword ptr [esp+4Ch],ebx
81bbe759 81fc00000100 cmp esp,10000h
81bbe75f 0f82b6000000 jb 81bbe81b
81bbe765 c744246400000000 mov dword ptr [esp+64h],0
81bbe76d fc cld
81bbe76e 8b5d60 mov ebx,dword ptr [ebp+60h]
81bbe771 8b7d68 mov edi,dword ptr [ebp+68h]
81bbe774 89550c mov dword ptr [ebp+0Ch],edx
81bbe777 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h
81bbe77e 895d00 mov dword ptr [ebp],ebx
81bbe781 897d04 mov dword ptr [ebp+4],edi
81bbe784 64f60550000000ff test byte ptr fs:[50h],0FFh
81bbe78c 750d jne 81bbe79b
; Pass KInterrupt object to KiInterruptDispatch
81bbe78e bfb8e6bb81 mov edi,81BBE6B8h ; 0x81bbe6b8 是我們的 interrupt object
81bbe793 e9c87e98fe jmp nt!KiInterruptDispatch (80546660); 0x80546660 interrupt object 中的 DispatchAddress 一欄
如果我們再追進 nt!KiInterruptDispatch :
0: kd> u nt!KiInterruptDispatch L50
nt!KiInterruptDispatch:
80546660 64ff05c4050000 inc dword ptr fs:[5C4h]
80546667 8bec mov ebp,esp
80546669 8b4724 mov eax,dword ptr [edi+24h]
8054666c 8b4f29 mov ecx,dword ptr [edi+29h]
8054666f 50 push eax
80546670 83ec04 sub esp,4
80546673 54 push esp
80546674 50 push eax
80546675 51 push ecx
80546676 ff1590904d80 call dword ptr [nt!_imp__HalBeginSystemInterrupt (804d9090)]
8054667c 0bc0 or eax,eax
8054667e 7440 je nt!KiInterruptDispatch+0x60 (805466c0)
80546680 83ec0c sub esp,0Ch
80546683 833d6c56568000 cmp dword ptr [nt!PPerfGlobalGroupMask (8056566c)],0
8054668a c745f400000000 mov dword ptr [ebp-0Ch],0
80546691 7541 jne nt!KiInterruptDispatch+0x74 (805466d4)
80546693 8b771c mov esi,dword ptr [edi+1Ch]
80546696 f00fba2e00 lock bts dword ptr [esi],0
8054669b 722b jb nt!KiInterruptDispatch+0x68 (805466c8)
8054669d 8b4710 mov eax,dword ptr [edi+10h]
805466a0 50 push eax
805466a1 57 push edi
; 注意到了嗎?di 是 interrupt ojbect; edi+0Ch 是欄位 ServiceRoutine 也就是 0xf980467e atapi!IdePortInterrupt+0
805466a2 ff570c call dword ptr [edi+0Ch]
終於,水落石出了!最後用 interrupt object 做一個總結:
- 當 interrupt 發生時,CPU 根據 interrupt number 到 IDT 描述找到對應的 DispatchCode;
- DispatchCode 產生 trap frame ,然後呼叫對應的 kernel interrupt handler (nt!KiInterruptDispatch),並將 interrupt object 當作參數傳入;
- Kernel interrupt handler 轉而呼叫對應的 ISR 。
那 !idt 輸出的格式就是:
Interrupt Number: IDT Entry Code Kernel/User Defined ISR (Corresponding interrupt object address)
是吧!再看一次:
0: kd> !idt
Dumping IDT:
37: 806e6864 hal!PicSpuriousService37
3d: 806e7e2c hal!HalpApcInterrupt
41: 806e7c88 hal!HalpDispatchInterrupt
50: 806e693c hal!HalpApicRebootService
62: 81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63: 8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73: 81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)
82: 81bd4bec atapi!IdePortInterrupt (KINTERRUPT 81bd4bb0)
83: 81be1ae4 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 81be1aa8)
93: 81965204 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 819651c8)
a3: 81964044 i8042prt!I8042MouseInterruptService (KINTERRUPT 81964008)
b1: 81be423c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 81be4200)
b2: 81795bec serial!SerialCIsrSw (KINTERRUPT 81795bb0)
b4: 818765d4 NDIS!ndisMIsr (KINTERRUPT 81876598)
c1: 806e6ac0 hal!HalpBroadcastCallService
d1: 806e5e54 hal!HalpClockInterrupt
e1: 806e7048 hal!HalpIpiHandler
e3: 806e6dac hal!HalpLocalApicErrorService
fd: 806e75a8 hal!HalpProfileInterrupt
fe: 806e7748 hal!HalpPerfInterrupt
: )
相關閱讀
- Windows Debugging – Kernel Debugging with WinDbg and VMWare
Reference
- Windows Internals chapter 3
- Intel® 64 and IA-32 Architectures Software Developer's Manuals: http://www.intel.com/products/processor/manuals/