IDT,Interrupt Descriptor Table,中斷描述符表是CPU用來處理中斷和程序異常的。
一、有關IDT的基本知識
1、中斷時一種機制,用來處理硬件需要向CPU輸入信息的情況。 比如鼠標,鍵盤等。
2、中斷和異常的產生是隨機的,在CPU正常運行過程中隨時可能產生。CPU的中斷處理機制
3、中斷可以由硬件產生(稱爲外部中斷),也可以由軟件產生(稱爲內部中斷),在程序中寫入int n指令可以產生n號中斷和異常(n從0-ffh)。
4、同時CPU的中斷異常機制還是重要特性的支持原理,比如程序調試,程序運行過程中的異常處理(如零除數異常、內存分頁錯誤等)。
5、早期的操作系統甚至是通過中斷來進行內核調用的。int指令是一種c從ring3 進入ring 0的方法。比如windows在xp版本之前使用的int 2e。在x86 CPU提供了sysenter指令後,這種方式才被放棄。
6、每一種中斷對應一箇中斷號。CPU執行中斷指令時,會去IDT表中查找對應的中斷服務程序(interrupt service routine ,ISR)。ISR(爲了表述方便後面用ISR n表示n號中斷的處理程序),x86CPU最大可以支持256種中斷。
7、中斷是CPU的機制,不管運行的是什麼操作系統,只有是運行於x86架構,IDT結構式必然存在的。IDT表中的ISRs應該有操作系統提供。
8、異常分爲錯誤(Faults)、陷阱(Traps)和終止(Aborts)三種,其區別是“錯誤”允許產生錯誤的程序繼續允許,“陷阱”也可以,但是“錯誤”產生於指令執行後,而“陷阱”需要在產生異常的指令執行後,因此從“錯誤”中返回時繼續執行產生錯誤的指令,而從“陷阱返”回時應當從產生異常的指令後開始執行。終止產生時,處理程序不能得到精確的異常產生的代碼位置,程序不能繼續進行,硬件錯誤會產生“終止”。 例如page faults就是一個faults,mov [eax], ecx,產生了一個分頁錯誤,表面[eax]內存是無效,需要先進行分頁處理從新映射物理內存然後才能繼續進行mov操作;而斷點是一個Trap。
9、Intel指定或保留了前32箇中斷號的作用,操作系統可以指定其餘的中斷號的作用。
10、中斷的過程中可以產生新的中斷。中斷時有優先級的,高優先級的中斷可以“中斷”低優先級的中斷。有的ISR不能被中斷,可以使用STI (set interrupt-enable flag) and CLI(clear interrupt-enable flag)設置IF標誌來啓動和關閉中斷。
(更詳細的內容可以參考Intel® 64 and IA-32 Architectures Software Developer’s Manual)
二、IDT表的結構
中斷處理過程是有CPU直接調用的,CPU有專門的寄存器IDTR來保存IDT在內存中的位置,本文寫爲IDTR.base。IDTR有48爲,前32爲是IDT在內存中的位置(線性地址),後16爲是IDT的大小,本文寫爲IDT.limit。程序可以使用LIDT和SIDT指令來讀寫IDTR。
IDT是一個最大爲256項的表,每個表項爲8字節。稱爲中斷門。CPU通過IDT.base+n*8來尋找門。
根據中斷號對應的異常類型不同(Faults/Traps/Aborts)8個字節的意義也不同。
如上圖所示IDT門中的最後兩個byte是ISR在內存中的高16位,最開始前兩個字節是ISR在內存中地址的低16位。
三、使用WinDbg觀察、調試IDT
使用Vmware配置好內核調試環境[link]。
WinDbg有支持查看IDT的擴展命令!idt
!idt –a 命令可以看到所有中斷處理函數的地址。
r idtr 參看idtr寄存器中保存的idt表地址。
idt每個表項有8bytes,兩個dword大。
(注意.reload /i 加載符號文件)
kd> !idt -a
Dumping IDT:
00: 8053f19c nt!KiTrap00
01: 8053f314 nt!KiTrap01
02: Task Selector = 0x0058
03: 8053f6e4 nt!KiTrap03
04: 8053f864 nt!KiTrap04
05: 8053f9c0 nt!KiTrap05
06: 8053fb34 nt!KiTrap06
07: 8054019c nt!KiTrap07
08: Task Selector = 0x0050
09: 805405c0 nt!KiTrap09
0a: 805406e0 nt!KiTrap0A
0b: 80540820 nt!KiTrap0B
0c: 80540a7c nt!KiTrap0C
0d: 80540d60 nt!KiTrap0D
0e: 80541450 nt!KiTrap0E
… …
kd> r idtr
idtr=8003f400
kd> db idtr
8003f400 9c f1 08 00 00 8e 53 80-14 f3 08 00 00 8e 53 80 ......S.......S.
8003f410 3e 11 58 00 00 85 00 00-e4 f6 08 00 00 ee 53 80 >.X...........S.
8003f420 64 f8 08 00 00 ee 53 80-c0 f9 08 00 00 8e 53 80 d.....S.......S.
8003f430 34 fb 08 00 00 8e 53 80-9c 01 08 00 00 8e 54 80 4.....S.......T.
8003f440 98 11 50 00 00 85 00 00-c0 05 08 00 00 8e 54 80 ..P...........T.
8003f450 e0 06 08 00 00 8e 54 80-20 08 08 00 00 8e 54 80 ......T. .....T.
8003f460 7c 0a 08 00 00 8e 54 80-60 0d 08 00 00 8e 54 80 |.....T.`.....T.
8003f470 50 14 08 00 00 8e 54 80-80 17 08 00 00 8e 54 80 P.....T.......T.
可以看到,這個時候IDT的基地址是在803f400處。
注意觀察其中3號中斷門 e4 f6 08 00 00 ee 53 80 ISR 3
int 3是trap門,按照之前說的ISR地址構造方法,得到8053f6e4的中斷服務程序地址。
好,我們觀察了IDT表(知道了IDT表的結構,自己寫一個!idt WinDbg擴展也是很簡單的事情),但是,使用windbg調試IDT表式不可能的。WinDbg的工作就要依賴於debuggee系統的中斷向量,如果在ISR 3裏寫個int 3,那麼BSOD是絕對的。Windbg和Debuggee系統是通過com口連接的,當Debuggee系統中產生了int3中斷,系統首先進入到ISR 3中。在ISR 3中判斷存在內核調試器,然後通過com口和WinDbg通行。這個處理過程本身就依賴於Debuggee系統的ISR。如果在ISR 3中又存在一個int 3那麼就無限遞歸了。