官术网_书友最值得收藏!

第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返回前外部中斷是可以被響應的。

主站蜘蛛池模板: 临高县| 温州市| 洛阳市| 堆龙德庆县| 屏南县| 沂水县| 宜州市| 桐庐县| 邮箱| 镇康县| 那曲县| 扬中市| 松潘县| 安乡县| 静乐县| 邛崃市| 阿城市| 从江县| 赣州市| 河北省| 罗定市| 平乡县| 小金县| 乐山市| 宁都县| 汝阳县| 水城县| 留坝县| 辽宁省| 石台县| 涟源市| 墨脱县| 新和县| 新竹市| 化隆| 绥滨县| 宁武县| 松滋市| 沛县| 临武县| 邻水|