- x86/x64體系探索及編程
- 鄧志著
- 638字
- 2019-03-01 11:49:38
第5章 了解Flags
在這一章里,我們來好好地了解eflags寄存器,它看似簡單,其實還真的不像想象中那樣簡單。
Flags?Eflags?Rflags?它到底是多少位的?
在x86/x64上,除非使用Pentium 4和Athlon 64之前的處理器,否則Flags都應該被擴展為64位,因為從AMD的Athlon64處理器和Intel后期的Pentium 4處理器開始,都支持x86-64技術,后來慢慢將x86-64體系稱為x64以對應于x86。
pushf ; 壓入16位的flags(低16位) pushfd ; 壓入32位的eflags(低32位) pushfq ; 壓入64位的rflags
因此在x64機器統稱為rflags似乎更合適,可是在legacy mode里flags還是被稱為eflags。在上面的使用中,PUSHF是壓入低16位,PUSHFD是壓入低32位,而PUSHFQ是壓入全部的64位。
它們的Mnemonic形式不同,可是opcode碼是一樣的,壓入多少取決于operandsize(操作數的大小),在16位的operand size下,壓入16位,在32位的operand size下,壓入32位,在64位的operand size下,壓入的是64位。與PUSHF/D/Q指令相對應的是POPF/D/Q指令,它們在助記符上有著同樣的意義。
上面是32位下的eflags寄存器,在64位下的rflags寄存器高32位為保留位。按Intel的分類,在eflags寄存器里可以分為status flags(狀態標志位)、control flags(控制標志位)和system flags(系統標志位)。
控制標志位
control flags只有一個DF(Direction Flags)標志位(bit10),它使用在LODSx,STOSx,MOVSx,SCASx,OUTSx,以及INSx這類串指令,指示串指令的指針方向。
DF標志所表達的意思是(以movsb指令為例)在一個循環里:
if (eflags.DF == 0) { buffer[edi++]=source[esi++]; /* 指針 edi 和 esi 都是遞增 */ } else if (eflags.DF == 1) { buffer[edi--]=source[esi--]; /* 指針 edi 和 esi 都是遞減 */ }
當DF=0時,串指令中的edi和esi寄存器加1遞增,DF=1時,edi和esi寄存器減1遞減。在遞增的情況下,源串和目標串指針應該指向起始點;在遞減的情況下,源串和目標串指針應該指向終止點,這是軟件設計者的職責。
5.1 Eflags中的狀態標志位
status flags包括:OF(溢出標志),SF(符號位標志),ZF(零標志),AF(調整位標志),CF(進位標志),以及PF(奇偶位標志)。這些標志位反映了指令執行結果的狀態值。
PF標志位
指令判斷結果值的最低字節(byte 0),而設置相應的PF標志位,如下所示。
當最低字節(byte 0)中位為1值的數量是偶數PF標志被置位,否則被清0。
AF標志位
當運算時bit 3發生向上進位或借位時,AF標志被置位。AF標志位使用在BCD碼的運算指令上,如下面使用AF標志位的例子。
mov al,8 ; al=0000 1000B mov bl,9 ; bl=0000 1001B add al,bl ; al=0001 0001B,AF標志為1 aaa ; 使用 AF 標志進行調整,AX的結果是:00000001 00000111B
在上面的8+9式子里,bit 3向bit 4進1位,AF標志為1。AAA指令根據AF標志進行調整后,AX的值變成0107H(BCD碼形式)。
5.1.1 signed數的運算
status flags標志位中有一部分用于表達signed(符號)數運算結果的狀態,一部分用于表達unsigned(無符號)數運算結果的狀態。而ZF標志位可以使用在signed和unsigned數上。
signed數運算中使用的標志位有:OF(溢出)標志和SF(符號)標志。
5.1.1.1 溢出位和符號位的產生
對于signed(符號數)的溢出,有兩種情況。
① overflow(向上溢出):當結果值超出signed數的最大值時產生overflow。
② underflow(向下溢出):當結果值超出signed數的最小值時產生underflow。
當結果產生overflow或underflow時會對OF標志位置位。
overflow的產生
我們看看下面的2個正數相加的式子,為了計算方便,以4位的signed數為例。
- 式子1:7+6。
- 式子2:3+4。
如上面所示:式子2的運算是正確的。而在式子1中的+7與+6相加里,結果值卻是-2,顯然這是錯誤的。因為這個4位符號數的結果值超出了正數最大值7,而產生了overflow。因此,在這個計算結果中eflags.OF=1(溢出標志被置位),eflags.SF=1(符號標志位被置位)。
記錄下來:兩個正數相加,結果為負數時,產生了overflow。
underflow的產生
同樣以4位數為例,再看看2個負數相加的式子。
- 式子1:(-4)+(-8)
- 式子2:(-4)+(-1)
在式子1中:(-4)+(-8)=(+4)兩個負數相加結果為正數,顯然是錯誤的。4位數的負數最小值是-8,而-4加上-8的值應為-12,它也超出了4位符號數的最小值,產生了underflow,這時eflags.OF=1,eflags.SF=0。
式子2中:(-4)+(-1)=(-5)這個值是正確的,這時eflags.OF=0,eflags.SF=1。值得注意的是,在這兩個式子中都產生了進位。因此這兩個式子中,CF標志位也被置位。
記錄下來:兩個負數相加,結果為正數時,產生了underflow溢出。
那么,當正數和負數相加時,情況又如何呢?
上面的2個正數與負數相加的式子中,它們的值都是正確的,OF標志都為0(沒有溢出)。式子1中SF標志為0,式子2中的SF標志為1。
記錄下來:正數和負數相加,不會產生溢出。
OF標志和SF標志也將影響到條件指令的執行,在x86上有下面幾類條件指令族:Jcc指令家族,SETcc指令家族,LOOPcc指令家族,以及CMOVcc指令家族。這些指令助記符中cc代表一個條件碼助記符。
5.1.1.2 signed數的比較操作
上面的OF、SF及ZF標志都用于signed數的比較。在執行cmp指令比較時,是對兩個數進行相減操作,將比較的結果反映在標志位上。
-1>-2?4>-6?這兩個比較式子如何反映在標志位上?
計算(-1)-(-2)和(4)-(-6)的結果,從eflags標志位上獲得比較結果,如下所示。
在式子1中,-1減-2的結果是SF、OF以及ZF標志位都是0;式子2中,+4減-6的結果產生了overflow,因此OF標志與SF標志都為1。
對于這兩個比較式子,我們知道前面的數都大于后面的數,因此得到的結論如下。
記錄下來:當OF==SF時,比較結果是大于。
再看看-1>2和-3>6這兩個比較式子,我們知道前面的數都小于后面的數,那么標志位上是什么呢?
在式子2的計算中,由于負數減正數結果值為正數而產生了underflow,因此OF標志被置位。可以看出,這兩個式子中,ZF為0,SF與OF標志位都不相等。我們得到的結論是:
記錄下來:當OF<>SF時,比較結果是小于。
signed數的條件碼
基于SF標志、OF標志,以及ZF標志位,下面是用于signed數的條件碼。
G (greater) :OF == SF 并且 ZF=0 L (less) :OF <> SF GE (greater or euqal) :OF == SF LE (less or equal) :OF <> SF或者ZF=1
在GE(大于等于)的情況下只需要判斷OF是否等于SF標志,無論ZF是否為零都滿足條件。而在L(小于)的情況下只需要判斷OF不等于SF標志就可以了,也不需要判斷ZF標志。
5.1.2 unsigned數的運算
ZF標志和CF標志被用在與unsigned數相關的運算里,在unsigned數的相關比較中不會使用OF和SF這兩個標志位。
在x86上,盡管對于數的運算,指令會同時依據unsigned和signed數的結果對OF、SF、CF,以及ZF、AF和PF做相應的設置。可是,在unsigned與singed數與條件相關的指令中會做出相應的區分。
5.1.2.1 進位標志的產生
在相加運算中,由于向前進位而使用CF標志置位。在相減運算中,由于向前借位也會使CF標志置位。
-4加-8產生了進位,+4減-6產生了借位,這兩個計算結果都會使CF標志置位。
5.1.2.2 unsigned數的比較及條件碼
當unsigned數相減時,如果不夠減則會產生借位(eflags.CF=1),表明是小于關系。下面是用于unsigned數的條件碼。
A (Above) :CF=0并且ZF=0 B (below) :CF=1 AE (Above or euqal) :CF=0 BE (below or equal) :CF=1或者ZF=1
這與signed數的情形類似,AE(高于等于)和B(低于)的比較中都無需判斷ZF標志。
5.2 IOPL標志位
eflags有兩個位來表示IOPL(I/O Privilege Level)標志位,指示訪問I/O地址空間所需要的權限,這個值僅在CPL=0權限下可以修改。IOPL標志還將影響到IF標志位,IF標志在具有IOPL所規定的權限內能被修改。
只有當CPL=0時,可以改變IOPL的值,當CPL<=IOPL時,可以改變IF標志位。
改變IOPL值可以使用popfd指令和iret指令,IF標志位還有專門的指令開/關中斷:sti和cli指令。當使用popfd指令進行修改時,沒有足夠的權限時不能修改其值,但是并不會產生異常。
實驗5-1:嘗試改變IOPL和IF標志位
由于需要權限的改變,在我們的實例中,需要開啟保護模式才能完成實驗,因此,我們在setup.asm模塊(common\setup.asm)里開啟了保護模式,并沒有使用分頁機制。并在protected.asm模塊里(topic05\ex5-1\protected.asm)進行這些實驗。
代碼清單5-1(topic05\ex5-1\protected.asm):
pushfd ; get eflags or DWORD [esp],0x3000 ; 將 IOPL=3 popfd ; modify the IOPL ... ... ; 進入 ring 3 完成實驗 push user_data32_sel | 0x3 push esp push user_code32_sel | 0x3 push DWORD user_entry retf ... ... pushfd ; get eflags and DWORD [esp],0xffffcfff or DWORD [esp],0x0200 ; 嘗試將 IOPL 改為 0,IF 改為 1 popfd ; 修改 eflags
在ring 3里嘗試同時改變IOPL和IF的值,完整的源碼在topic05\ex5-1\目錄下。
上面這是在VMware里的測試結果(在真機下情況一樣),結果顯示:在CPL=0下將IOPL的權限改為3級,在CPL=3下,IF標志可以改為1(CPL<=IOPL),而IOPL的值不變(需要0級權限)。
I/O Bitmap
IOPL控制著程序的I/O地址空間訪問權,只有在足夠的權限下才能訪問I/O地址,否則會產生#GP異常。其實這話說得不太完整,還與I/O位圖相關。
如果當前CPL>IOPL(值大于),在TSS段中的I/O Bitmap有最后的決定權!
是的!即使當前運行的權限低于IOPL所規定的權限,也可以在TSS中的I/O Bitmap對某些port進行設置,達到可以訪問I/O地址空間。當CPL>IOPL時,對port的I/O訪問處理器將檢查I/O Bitmap中相應的port位以決定這個I/O訪問是否違例,當CPL<=IOPL時則無須檢查I/O Bitmap。
I/O Bitmap中的每個bit對應于一個port,當這個bit被置位時(設為1),程序對port無訪問權。當這個bit被清0時,port是可以訪問的。
實驗5-2:利用I/O Bitmap的設置來決定I/O空間訪問權
為了完成這個實驗,我們在TSS段中加入了I/O Bitmap(I/O位圖),對common\setup.asm源碼進行了相應的改動!原來是沒有I/O Bitmap的。
同時,我們在protected.asm文件里增加了一個函數,用來設置I/O Bitmap的值,下面是這個函數的源碼。
代碼清單5-2(topic05\ex5-2\protected.asm):
;-------------------------------------------------------- ; set_IO_bitmap(int port,int value):設置 IOBITMAP 中的值 ; input: ; esi - port(端口值),edi - value 設置的值 ;--------------------------------------------------------- set_IO_bitmap: jmp do_set_IO_bitmap GDT_POINTER dw 0 dd 0 do_set_IO_bitmap: push ebx push ecx str eax ; 得到 TSS selector sgdt [GDT_POINTER] ; 得到 GDT base and eax,0xfffffff8 add eax,[GDT_POINTER+2] mov ebx,[eax+4] and ebx,0x00ff shl ebx,16 mov ecx,[eax+4] and ecx,0xff000000 or ebx,ecx mov eax,[eax] ; 得到 TSS descriptor shr eax,16 or eax,ebx movzx ebx,WORD [eax+102] add eax,ebx ; 得到 IOBITMAP mov ebx,esi shr ebx,3 and esi,7 bt edi,0 jc set_bitmap btr DWORD [eax+ebx],esi ; 清位 jmp do_set_IO_bitmap_done set_bitmap: bts DWORD [eax+ebx],esi ; 置位 do_set_IO_bitmap_done: pop ecx pop ebx ret
我們做實驗的代碼如下。
代碼清單5-3(topic05\ex5-2\protected.asm):
;; 測試1:讀 port 0x21 in al,MASTER_OCW1_PORT ; 嘗試讀 port 0x21 mov esi,msg6 call puts ;; 測試2:寫 port 0x21 mov al,0x0f out MASTER_OCW1_PORT,al ; 嘗試寫 port 0x21
在topic05\ex5-2\目錄下有全部的源代碼,下面是測試的結果:
實驗結果表明:在第1次讀的時候,進入了#GP異常處理程序,這里的IOPL值是0,而我們的CPL權限是3,并且在開始的時候我們在I/O Bitmap中設置了port 0x21對應的位為1值,指示port為不可訪問,所以產生了異常。
;; 現在重新開啟I/O可訪問權限 mov esi,MASTER_OCW1_PORT mov edi,0 ; set port 0x21 IOBITMAP to 0 call set_IO_bitmap iret
而在后來的寫操作是成功的!因為,我們在#GP處理程序返回前重新開啟了port為可訪問(即:在I/O Bitmap中將port 0x21對應的bit清0),這時候對port 0x21的訪問是成功的。
5.3 TF標志與RF標志
顯然eflags.RF標志與eflags.TF標志是配合一起使用的,當TF標志被置位時,就代表開啟了single-debug(單步調試)功能,處理器將進入single-debug狀態。
什么時候開始進入single-debug?
答案是:當TF標志被置位,執行完下一條指令后,處理器進入#DB處理。這是因為single-debug屬于trap類型的#DB異常。看看下面的指令序列。
; 開啟 single debug功能 pushfd bts dword [esp],8 ; eflags.TF=1 popfd ; 更新 eflags 寄存器 mov eax,1 ;test 1 mov eax,2 ;test 2 mov eax,3 ;test 3 mov eax,4 ;test 4 mov eax,5 ;test 5
popfd指令執行完后,將更新TF標志為1,那么應該是在test 1之前還是之后呢?答案是test 1之后(test 2之前),是在TF被置位后的下一條指令執行完后產生#DB異常。
處理器在進入#DB異常處理程序之前,會將TF標志清0以防止在中斷處理程序內發生single-deubg,這是顯而易見的事情,RF標志也會被清0。在進入中斷處理程序前,NT和VM標志都會得到清0。
那么,在壓入單步調試#DB處理程序stack內的eflags寄存器中TF是原來的值(即為1),RF標志被清0。
看看上圖:當popf指令執行完畢后,TF被置1,第1條mov指令執行完畢,產生#DB異常,CPU進入#DB處理程序后清TF和RF標志,而在#DB處理程序里,在iret指令返回前,應該將stack中的RF標志置為1,以確保返回到被中斷的指令時能順利執行。iret指令將pop回stack中原來的eflags值。
當第2條mov指令執行完畢后,CPU將清RF標志為0,處理器重新轉入到#DB處理程序中執行。除非TF標志被清0,否則重復上一流程。
由于引發#DB異常的不止single-debug(單步調試)這一途徑。#DB異常可以是Fault類型或是Trap類型,因此,在#DB異常處理程序中有責任去確保返回被中斷的執行能夠得到正常執行。通過設置stack中的eflags映像中的RF為1,讓iret返回前更新eflags寄存器。
處理器會在每一條指令執行完畢后將RF標志清0,RF標志的值基本上恒為0。
實驗5-3:實現一個single-debug例子
正如前面所列代碼,我們是這樣開啟和測試TF、RF以及#DB處理程序的。
代碼清單5-4(topic05\ex5-3\protected.asm):
; 開啟 single debug 功能 pushfd bts dword [esp],8 ; eflags.TF=1 popfd ; 更新 eflags 寄存器 mov eax,1 ; test 1 mov eax,2 ; test 2 mov eax,3 ; test 3 mov eax,4 ; test 4 mov eax,5 ; test 5
我們測試了這5條mov指令產生single-debug的情況,為了方便觀察演示,這5條mov指令只是給eax賦一個序號。我們需要實現自己的#DB處理程序,完整的源代碼在topic05\ex5-3\protected.asm里。
上面是實驗5-3的運行結果,每進入#DB處理程序一次就打印相關的信息。請留意畫線標注的eax寄存器的值,這正是我們給eax寄存器進行mov操作后的值。說明在每執行一條mov指令后產生了#DB異常,這就是單步調試的結果。而eip是這些mov指令執行狀態的當前值。其他的寄存器值都是不變的。
實際上,在這個例子里,RF標志置不置位對結果沒影響,那是因為由single-debug引起的#DB屬于trap類型的異常!它會返回到被中斷指令的下一條指令。
然而不要以這個例子為準而認為不需要將RF置位,由其他中斷源產生的#DB異常可能是fault類型的,fault類型的異常會返回中斷指令再嘗試重新執行。
5.4 NT標志
這個NT標志也牽扯著其他復雜的信息,NT標志被使用于處理器提供的task switch(任務切換)場景中,它是Nested Task(嵌套任務)標志位,當NT=1時,表示當前執行的任務被嵌套在另一個任務里(這是從任務的術語上來講),當NT=0時,當前執行的任務沒有被嵌套。NT標志一般由處理器自動維護,但是可以在任何權限下被軟件修改。
什么時候NT標志被置為1?
在使用call指令進行task switch,以及發生interrupt/exception時的task switch,處理器從new task的TSS加載完eflags寄存器后,會將NT置1。
這個情景中的task switch是指:call調用一個TSS selector或者taskgate,以及interrupt/exception發生時,vector指向IDT中的task-gate。
當然,使用jmp一個TSS selector或task-gate也會產生任務切換,iret指令也可以產生任務切換,但它們不在上述將NT置為1的情景中。
在上述的task switch情景中,處理器會同時將舊任務的TSS selector寫入新任務TSS段中的previous-link域中,以便可以切換回到舊任務。
什么時候NT標志被清為0?
其中一個情景是:當使用iret指令從被嵌套的任務(new)返回到原來的(old)任務時,處理器從stack中pop出eflags寄存器后會清NT為0(實際上是,先將stack中eflags寄存器image中的NT位清0,然后pop的時候,NT標志就為0)。
當執行iret指令時,處理器會檢查當前的eflags.NT標志是否為1,為1時表示處于nested狀態,執行完后NT被清為0。
這個情景中的返回是指:使用iret指令從interrupt/exception處理程序中返回時。注意:使用ret指令從一個過程的返回并不在其中。
當執行ret指令時,并不會清NT標志位(不改變stack中eflags寄存器image中的NT標志位,pop的時候NT標志為0),它并不需要去檢查NT標志位是否為1值。
上述是Intel關于NT清0這一點的描述,可是AMD的描述似乎沒有提及在stack中的eflags寄存器的image中的NT是否有被清0,似乎是pop出eflags寄存器后再將NT清0,但不管怎樣,執行結果是完全一致的。
另一個情景是:使用jmp進行task切換時,處理器從新任務的TSS加載eflags完后,會將NT標志清為0,表示JMP指令執行的并不是嵌套任務。
在軟件中可以由程序員自己手工去修改NT標志的值,通過修改在stack中eflags寄存器image的NT標志位,然后使用popf指令進行更新。
在long mode下的64位模式下并不支持TSS的task switch機制,因此,在64位模式下NT標志位是無效的。
5.5 AC標志
eflags中的AC標志是指Alignment Check(地址中的對齊檢查),只有當同時開啟CR0.AM和eflags.AC標志位時處理器才支持這項功能。
我們從下面的表格來看,什么情況下才是有效的alignment粒度。
當對上述的某一類數據類型的訪問違反了它的粒度時,會產生#AC異常。
mov ax,WORD [0x10003] ; 跨 WORD 類型邊界 mov eax,DWORD [0x10005] ; 跨 DWORD 類型邊界 mov rax,QWORD [0x10007] ; 跨 QWORD 類型邊界 mov esi,0x10006 lodsd ; 跨 bit string類型邊界
上面幾種情況下,都屬于non-alignmnet行為。只有在權限級別3下的non-alignmnet行為才會產生#AC異常,在0、1及2級下不會產生#AC異常。
實驗5-4:測試Alignment Check功能
#AC異常屬于fault類型,#AC處理程序會返回到發生錯誤的指令繼續執行,因此在我們的#AC處理程序中必須要修正這個錯誤:
代碼清單5-5(topic05\ex5-4\protected.asm):
;----------------------------------------------- ; AC_handler():#AC handler ;----------------------------------------------- AC_handler: jmp do_AC_handler ac_msg1 db '---> Now,enter the #AC exception handler <---',10 ac_msg2 db 'exception location at 0x' ac_location dq 0,0 do_AC_handler: pusha mov esi,[esp+4+4*8] mov edi,ac_location call get_dword_hex_string mov esi,ac_msg1 call puts call println ;; 現在 disable AC 功能 btr DWORD [esp+12+4*8],18 ; 清elfags image中的AC標志 popa add esp,4 ; 忽略 error code iret
#AC處理程序里在iret返回前,將stack中的eflags.AC清為0,iret執行完后,eflags.AC被清0。在ring0代碼里先將CR0.AM置位,在ring3代碼里,再將eflags.AC置位。
代碼清單5-6(topic05\ex5-4\protected.asm):
; 開啟 eflags.AC 標志 pushf bts DWORD [esp],18 mov ebx,[esp] popf ; test 1 mov ax,WORD [0x10003] ; 跨 WORD 類型邊界 push ebx popf ; test 2 mov eax,DWORD [0x10005] ; 跨 DWORD 類型邊界 push ebx popf ; teset 3 mov esi,0x10006 ; 跨 string 類型邊界 lodsd
由于在#AC處理程序會將AC標志清0,因此,每做一次測試前,再重新開啟AC標志位。這個實驗的完整源代碼在topic05\ex5-4\目錄下,運行的結果如下。
結果顯示,共產生了3次#AC異常,就是我們所做的3個測試。
5.6 VM標志
eflags中的VM標志指示著處理器進入和離開virtual-8086模式,當VM=1時進入virtual-8086模式,VM=0時離開virtual-8086模式,VM標志不能被popfd指令修改,只有兩種途徑可以置eflags.VM標志位。
① 執行一個task switch(任務切換)時,包括:使用call/jmp指令執行一個TSS selector或task-gate,在TSS段中的eflags寄存器Figure-中VM被置1,處理器加載eflags時,VM為1,從而進入virtual-8086模式;當執行iret指令時,stack中的eflags.NT=1表示將進行任務切換,如果TSS段中的eflags.VM為1,也指示處理器進入virtual-8086模式。
② 當執行iret指令時,stack中的eflags映像的VM為1,也將指示處理器進入virtual-8086模式。
只有執行iret指令,stack中的eflags映像的VM標志為0時,才會離開virtual-8086模式,執行call/jmp進行任務切換(TSS段中的eflags.VM為0)這種情況并不能離開virtual-8086模式。
在64位模式下,處理器不支持virtual-8086模式,VM標志位也被忽略。
5.7 eflags寄存器的其他事項
處理器初始化后,eflags的值為00000002H,Bit 1為1,其他位為0,Bit 1是保留位,其值固定為1。某些標志位可以使用專門指令進行設置,對于CF標志,可以使用STC指令置位,CLC指令清位,CMC指令對CF標志進行取反。對于DF標志,可以使用STD指令置位,CLD指令清位。
STI指令可以對IF標志進行置位,CLI指令對IF標志進行清位,它需要足夠的權限來修改IF標志位,這個足夠的權限是:CPL<=IOPL。
可是這和popfd指令修改IF標志位不同,當不夠權限時(CPL>IOPL),popfd指令修改IF標志不會產生#GP異常,而使用STI指令時會產生#GP異常。
實驗5-5:測試sti指令
分別使用popfd指令和sti指令在權限不足的情況下進行測試,下面是實驗的代碼(在3級權限下)。
代碼清單5-7(topic05\ex5-5\protected.asm):
;;測試 popfd 指令 pushfd bts DWORD [esp],9 popfd ;; 測試 sti 指令 sti mov esi,msg7 call puts ;打印成功信息 call println;
而在我們的#GP處理程序里,我們需要修正這個錯誤,要么將IOPL權限降低,要么不繼續執行引發錯誤的指令。
代碼清單5-8(topic05\ex5-5\protected.asm):
mov eax,[esp] cmp BYTE [eax],0xfb ; 檢查是否因為 sti 指令而產生 #GP 異常 jne do_GP_handler_done inc eax ; 如果是,跳過產生 #GP 異常的 sti 指令,執行下一條指令 mov [esp],eax mov esi,gp_msg3 call puts do_GP_handler_done: iret
對OS來說降低IOPL權限似乎是一個比較危險的行為,示例中的#AC處理程序如果檢測到發生#GP異常是由STI指令產生的(sti指令的opcode碼為FB),那么就跳過STI指令繼續向下執行。由于#GP異常屬于fault類型,因此必須修正這個錯誤,否則將一直在#GP處理程序里轉不出來。
實驗的結果表明:在STI指令里發生了#GP處理程序,當跳過STI指令后得以繼續往下執行。
當有下面的指令序列:
sti ; 在過程返回前開啟 IF 標志 ret
在某個過程里,sti緊接著ret指令,sti指令執行后,如果在ret指令返回前發生了外部中斷,這個外部中斷將被暫緩響應。ret指令返回結束后,外部中斷才被響應。
sti ; 第1次開啟IF標志 sti ; 第2次開啟IF標志 ret
Intel明確規定,在上面2次開啟IF標志的序列中,ret返回前外部中斷是可以被響應的。