2.關於實例三的說明
有些步驟的實現方法已在前面的實例中做過介紹,下面就任務內無特權級變換的轉移和使用局部描述符LDT等作些說明:
(1)實模式下初始化LDT
演示任務使用了局部描述符表LDT,本實例中該LDT在實模式下初始化(當然,也可以在使用LDT前的保護模式初始化)。為了簡便,LDT中各描述符的界限和屬性值在定義時預置,利用一個子程序設置各段的段基地址。為方便起見,在定義時把各段的段值安排在相應描述符的段基地址低16位字段中。由於實例中各段在實模式下定位(這是因為程序是從實模式下啟動執行的),所以把段值乘以16就是對應的段基地址。
(2)裝載LDTR寄存器
在使用LDT之前,還要裝載局部描述符表寄存器LDTR。本實例中的如下兩條指令用於裝載LDTR:
mov ax,LDT_SEL lldt ax
LLDT指令是專門用於裝載LDTR的指令。該指令的操作數是對應LDT段描述符的選擇子。根據該選擇子,處理器從GDT中取出相應的LDT段描述符,在進行合法性等檢查後,LDT段描述符的基地址和界限等信息被裝入LDTR的高速緩沖寄存器中。由於要引用GDT,所以不能在實模式下裝載LDTR。在“操作系統類指令”一文中將對LLDT指令作詳細說明。
(3)利用段間轉移指令JMP實現任務內無特權級的轉移
在本實例中進入保護方式後,特權級是0。通過如下段間直接轉移指令實現從代碼段K到代碼段L的轉移:
JUMP16 CodeL_Sel,Virtual2
其中,選擇子CodeL_Sel是對應代碼段L的描述符的選擇子。該描述符在LDT中,所以選擇子中的描述符表指示位TI為1。描述符特權級是0,表示對應代碼段的特權級是0,選擇子中的請求特權級RPL也是0。目標代碼段不是一致代碼段,所以在CPL=DPL,RPL<=DPL的情況下,順利進行相同特權級的轉移:目標代碼段的選擇子CodeL_Sel被裝入CS,對應描述符中的信息被裝入高速緩沖寄存器中,偏移量Virtual2被裝入指令指針寄存器。由於是16位代碼段,所以偏移用16位表示。
類似地,通過如下段間直接轉移指令實現從代碼段L轉移到代碼段K:
JUMP16 CodeK_Sel,Virtual3
其中,選擇子CodeK_Sel是對應代碼段K的描述符選擇子。由於描述符在GDT中,所以選擇子中的TI位是0。
(4)利用段間調用指令CALL實現任務內無特權級變換的轉移
在代碼段L中,通過段間直接調用指令CALL調用了在代碼段C中的兩個子程序,這些調用都是無特權級變換的轉移。例如,利用如下指令調用了顯示字符串子程序DispMsg:
CALL16 CodeC_Sel,DispMsg
其中,CodeC_Sel是代碼段C的選擇子,DispMsg表示子程序的入口。描述代碼段C的描述符在LDT中,描述符特權級DPL是0,所以使用的選擇子CodeC_Sel的請求特權級RPL是0,描述符表指示位TI為1。目標代碼段C不是一致代碼段,所以在CPL=DPL,RPL<=DPL的情況下,順利進行相同特權級的轉移:當前CS和IP壓入堆棧,目標代碼段的選擇子CodeC_Sel被裝入CS,對應描述符中的信息被裝入高速緩沖寄存器中,16位偏移DispMsg被裝入指令指針IP。由於是16位段,所以偏移用16位表示,壓入堆棧的是字而不是雙字。
(5)段間返回指令RET實現任務內無特權級變換的轉移
段間返回指令RET從堆棧的棧頂彈出返回地址(由選擇子和偏移)構成。彈出選擇子內的RPL=CPL,並且對應DPL=CPL,RPL<=DPL是當然的,所以能順利進行相同特權級的轉移。
3.別名技術
在上述實例三中,使用了兩個描述符來描述演示任務的LDT段。段描述符LDTable被安排在GDT中,它是系統段描述符,把段LDTSeg描述成演示任務的局部描述符表LDT。描述符ToLDT被安排在LDT中,它是數據段描述符,把段LDTSeg描述成一個普通數據段。描述符LDTable被裝載到LDTR,描述符ToLDT被裝載到某個數據段寄存器。為什麼要這樣處理呢?根據實例三的功能要求,需要訪問演示任務的局部描述符表LDT段,以取得代碼段L的段界限值,這需要通過某個段寄存器進行,但不能把系統段描述符的選擇子裝載到段寄存器,所以采用兩個描述符來描述段LDTSeg。
這種為了滿足對同一個段實施不同方式操作的需要,而用多個描述符加以描述的技術稱為別名技術。在保護方式程序設計中,常常要采用別名技術。例如:用兩個具有不同類型值的描述符來描述同一個段。再如,用兩個具有不同DPL的描述符來描述同一個段。
<三>任務內不同特權級的變換
在一個任務內可以存在四種特權級,所以常常會發生不同特權級之間的變換。例如,外層的應用程序調用內層操作系統的例程,以獲得必要的諸如存儲器分配等系統服務。內層操作系統的例程完成後,返回到外層應用程序。
在同一任務內,實現特權級從外層到內層變換的普通途徑是使用段間調用指令CALL,通過調用門進行轉移;實現特權級從內層向外層變換的普通途徑是使用段間返回指令RET。注意,不能用JMP指令實現任務內不同特權級的變換。
1.通過調用門的轉移
當段間轉移指令JMP和段間調用指令CALL所含指針的選擇子指示調用門描述符時,就可以實現通過調用門的轉移。但只有CALL指令能變換到內層的特權級,JMP指令只能轉移到同級的代碼。
調用門描述符轉移的入口點包含目標地址的段及偏移量的48位全指針。在執行通過任務門的段間轉移指令JMP或段間調用指令CALL時,指令所含指針內的選擇子用於確定調用門,而偏移被丟棄;把調用門內的48位全指針作為目標地址指針進行轉移。
處理器采用與訪問數據段相同的特權級規則控制對門描述符的訪問。調用門描述符的DPL規定了訪問該門的最外層特權級,在取出調用門內的48位全指針,把它作為目標地址指針向目標代碼段轉移之前,要進行特權級檢查。只有在相同級或者更內層特權級的程序才可訪問調用門,即CPL<=調用門的DPL。同時,還要求指示門的選擇子的RPL必須滿足RPL<=調用門的DPL的條件。檢測通過後,才開始向目標代碼段轉移的步驟。其中還要檢測目標描述符是否為代碼段描述符,調用門內的選擇子指示的描述符必須是代碼段描述符。此外,在裝載代碼段描述符高速緩沖寄存器之前調整代碼段選擇子的RPL=0,也即調用門中代碼段選擇子的RPL被忽略。
對於使用調用門的段間轉移指令JMP,檢測條件與段間直接轉移相同。由於已置RPL=0,所以可認為 RPL<=DPL的條件總能滿足。所以,對於普通的非一致代碼段,當CPL=DPL時,發生無特權級變換的轉移;對於一致代碼段,在滿足CPL>=DPL時也發生無特權級變換的轉移;其它情形就引起異常。
對於使用調用門的段間調用指令CALL,情形就不同了。由於已置RPL=0,所以可認為RPL<=DPL的條件總能滿足。對於一致代碼段,在滿足CPL>=DPL時發生無特權級變換的轉移。對於非一致代碼段,當CPL=DPL時,仍發生無特權級變換的轉移;當CPL>DPL時,就發生向內層特權級變換的轉移,將調用門中的選擇子和偏移裝入CS和指令指針EIP中,並使CPL保持等於DPL,同時切換到內層堆棧。
綜上所述,使用段間調用指令CALL,通過調用門可以實現從外層程序調用進入內層程序(JMP指令只能實現無特權級變換的轉移);通過調用門也可實現無特權級變換的轉移。需要注意的是,JMP指令和CALL指令都不能實現向外層特權級的轉移否則會引起異常。
當然,CALL指令在最後把目標代碼段的指針裝入CS和EIP之前,要把原CS和EIP,即返回地址保存到堆棧。如無特權級變換,堆棧保持不變,返回地址就保存在原堆棧中;否則,返回地址保存在內層堆棧中。
2.堆棧切換
在使用CALL指令通過調用門向內層轉移時,不僅特權級發生變換,控制轉移到一個新的代碼段,而且也切換到內層的堆棧段。從本教程第五篇的任務狀態段TSS的格式可見,TSS中包含有指向0級、1級和2級堆棧的指針。在特權級發生向內層變換時,根據變換到的特權級使用TSS中相應的堆棧指針對SS及ESP寄存器進行初始化,建立起一個空棧。
在建立起內層堆棧後,處理器先把外層堆棧的指針SS及ESP寄存器的值壓入內層堆棧,以使得相應的向外層返回可恢復原來的外層堆棧。然後,從外層堆棧復制以雙字為單位的調用參數到內層堆棧中,調用門中的DCOUNT字段值決定了復制參數的數量。這些被復制的參數是主程序通過堆棧傳遞給子程序的實參,在調用之前被壓入外層堆棧。通過復制棧中的參數,使內層的子程序不需要考慮堆棧的切換,而容易地訪問主程序傳遞過來的實參。最後,調用的返回地址被壓入堆棧,以便在調用結束時返回。下圖為在向內層變換時,建立內層堆棧,並從外層堆棧復制2個雙字參數到內層堆棧的示意圖。圖中每項是雙字,可見的段寄存器內的選擇子被擴展成32為存入堆棧,高16位為0。對於16位的使用調用門的段也是如此。
需要注意的是,無論是否通過調用門,只要不發生特權級變換,就不會切換堆棧。
3.向外層返回
與使用CALL指令通過調用門向內層變換相反,使用RET指令實現向外層返回。段間返回指令RET從堆棧中彈出返回地址,並且可以采用調整ESP的方法,跳過相應的在調用之前壓入堆棧的參數。返回地址的選擇子指示要返回的代碼段的描述符,從而確定返回的代碼段。選擇子的RPL確定返回後的特權級,而不是對應描述符的DPL,這是因為,段間返回指令RET可能使控制返回到一致代碼段,而一致代碼段可以在DPL規定的特權級以外的特權級執行。需要注意的是,RET指令所使用的返回地址的選擇子只能使用代碼段描述符,而不能使用任何系統段描述符或門描述符,當然,更不能使用數據段描述符,否則會引起異常。與CALL指令相對應,RET指令也不能向內層返回。
段間返回指令完成返回的步驟如下:
(1)RET指令先從堆棧中彈出返回地址。如果彈出地址的選擇子的RPL規定相對於CPL更外層的特權級,那麼就引起向外層返回。
(2)為向外層返回,跳過內層堆棧中的參數,再從內層棧中彈出指向外層堆棧的指針,並裝入SS及 ESP,以恢復外層堆棧。
(3)調整ESP,跳過在相應的調用之前壓入到外層堆棧的參數。即返回指令不但彈出內層棧的參數,而且也彈出外層棧的參數。
(4)然後,檢查數據段寄存器DS、ES、FS及GS,以保證尋址的段在外層是可訪問的,如果段寄存器尋址的段在外層是不可訪問的,那麼裝入一個空選擇子,以避免在返回時發生保護空洞。
(5)返回外層繼續執行。
上述五步是對帶立即數的段間返回指令而言的,立即數規定了堆棧中要跳過的參數的字節數。對於無立即數的段間返回指令缺少第二步和第三步。若RET指令不需要向外層返回,那麼就只有(1)和(5)兩步。對於有通過堆棧傳遞參數的子程序,必須使用帶立即數的返回指令返回,否則返回時會裝載錯誤的外層棧指針。
若不使用帶立即數的返回指令,可以在返回前把外層棧的棧指針存入內層棧中的用於保存返回地址上方兩個雙字的區域中,由外層返回的過程可知,這可正確恢復外層棧的指針,但在外層程序中,必須人為調整外層棧指針,以便廢除在外層棧中壓入的參數。在使用C調用約定的程序中可使用此方法。但這會增加代碼的長度和處理時間,使代碼效率變低。正因為如此,在Windows 9X下,新增加了一種STDCALL的調用約定,它正是為了適應Intel系列處理器的體系結構而產生的。
<四>演示任務內特權級變換的實例(實例四)
下面給出一個演示任務內特權級變換的實例。該實例演示在任務內通過調用門從外層特權級變換到內層特權級;也演示通過段間返回指令從內層特權級變換到外層特權級;還演示通過調用門的無特權級變換的轉移。實例使用了任務狀態段TSS,這是因為任務內特權級變換時要使用的內層堆棧指針存放在TSS中。
1.實現步驟
該實例的實現步驟為:
(1)實方式下初始化;
(2)切換到保護模式;
(3)設置TR和LDTR。由於在任務內發生特權級變換時要切換堆棧,而內層堆棧的指針存放在當前任務的TSS中,所以在進入保護模式後設置任務狀態段寄存器TR。由於演示任務使用了局部描述符表,所以設置LDTR;
(4)經調用門進入32位過渡代碼段;
(5)建立返回3級演示代碼段的環境;
(6)利用RET指令轉移到3級的演示代碼段。為了演示外層程序通過調用門調用內層程序,要使CPL>0。實例先通過段間返回指令RET從特權級0變換到特權級3的演示代碼段。在特權級3下,通過調用門調用1級的子程序。隨著執行段間返回指令RET,又回到3級的演示代碼段;
(7)在3級的演示代碼段中,經調用門轉移到0級的32位過渡代碼段;
(8)直接轉0級的臨時代碼段;
(9)准備返回實模式;
(10)切換回實模式;
(11)實模式下的恢復工作。
2.源程序組織和清單
實例四由如下部分組成:
(1)全局描述符表GDT。GDT含有演示任務的TSS段描述符和LDT段描述符,此外還含有臨時代碼段的描述符、規范數據段描述符和視頻緩沖區段描述符。
(2)演示任務的LDT段。它含有除臨時代碼段外的其它代碼段的描述符和演示任務各級堆棧段描述符,還含有3個調用門。
(3)演示任務的TSS段。
(4)演示任務的0級、1級和3級堆棧段。
(5)顯示子程序段。32位代碼段,特權級1。
(6)演示代碼段。32位代碼段,特權級3。
(7)過渡代碼段。32位段,特權級0。
(8)臨時代碼段。16位段,特權級0。
(9)實模式下的數據和代碼段。
該實例的邏輯功能是顯示演示代碼段執行時的當前特權級CPL。源程序清單如下:
mov GISter>ax,LDT_SEL
lldt ax
LLDT指令是專門用於裝載LDTR的指令。該指令的操作數是對應LDT段描述符的選擇子。根據該選擇子,處理器從GDT中取出相應的LDT段描述符,在進行合法性等檢查後,LDT段描述符的基地址和界限等信息被裝入LDTR的高速緩沖寄存器中。由於要引用GDT,所以不能在實模式下裝載LDTR。在“操作系統類指令”一文中將對LLDT指令作詳細說明。
(3)利用段間轉移指令JMP實現任務內無特權級的轉移
在本實例中進入保護方式後,特權級是0。通過如下段間直接轉移指令實現從代碼段K到代碼段L的轉移:
JUMP16 CodeL_Sel,Virtual2
其中,選擇子CodeL_Sel是對應代碼段L的描述符的選擇子。該描述符在LDT中,所以選擇子中的描述符表指示位TI為1。描述符特權級是0,表示對應代碼段的特權級是0,選擇子中的請求特權級RPL也是0。目標代碼段不是一致代碼段,所以在CPL=DPL,RPL<=DPL的情況下,順利進行相同特權級的轉移:目標代碼段的選擇子CodeL_Sel被裝入CS,對應描述符中的信息被裝入高速緩沖寄存器中,偏移量Virtual2被裝入指令指針寄存器。由於是16位代碼段,所以偏移用16位表示。(本文來自編程入門網:www.bianceng.cn)
類似地,通過如下段間直接轉移指令實現從代碼段L轉移到代碼段K:
JUMP16 CodeK_Sel,Virtual3
其中,選擇子CodeK_Sel是對應代碼段K的描述符選擇子。由於描述符在GDT中,所以選擇子中的TI位是0。
(4)利用段間調用指令CALL實現任務內無特權級變換的轉移
在代碼段L中,通過段間直接調用指令CALL調用了在代碼段C中的兩個子程序,這些調用都是無特權級變換的轉移。例如,利用如下指令調用了顯示字符串子程序DispMsg:
CALL16 CodeC_Sel,DispMsg
其中,CodeC_Sel是代碼段C的選擇子,DispMsg表示子程序的入口。描述代碼段C的描述符在LDT中,描述符特權級DPL是0,所以使用的選擇子CodeC_Sel的請求特權級RPL是0,描述符表指示位TI為1。目標代碼段C不是一致代碼段,所以在CPL=DPL,RPL<=DPL的情況下,順利進行相同特權級的轉移:當前CS和IP壓入堆棧,目標代碼段的選擇子CodeC_Sel被裝入CS,對應描述符中的信息被裝入高速緩沖寄存器中,16位偏移DispMsg被裝入指令指針IP。由於是16位段,所以偏移用16位表示,壓入堆棧的是字而不是雙字。
(5)段間返回指令RET實現任務內無特權級變換的轉移
段間返回指令RET從堆棧的棧頂彈出返回地址(由選擇子和偏移)構成。彈出選擇子內的RPL=CPL,並且對應DPL=CPL,RPL<=DPL是當然的,所以能順利進行相同特權級的轉移。
3.別名技術
在上述實例三中,使用了兩個描述符來描述演示任務的LDT段。段描述符LDTable被安排在GDT中,它是系統段描述符,把段LDTSeg描述成演示任務的局部描述符表LDT。描述符ToLDT被安排在LDT中,它是數據段描述符,把段LDTSeg描述成一個普通數據段。描述符LDTable被裝載到LDTR,描述符ToLDT被裝載到某個數據段寄存器。為什麼要這樣處理呢?根據實例三的功能要求,需要訪問演示任務的局部描述符表LDT段,以取得代碼段L的段界限值,這需要通過某個段寄存器進行,但不能把系統段描述符的選擇子裝載到段寄存器,所以采用兩個描述符來描述段LDTSeg。
這種為了滿足對同一個段實施不同方式操作的需要,而用多個描述符加以描述的技術稱為別名技術。在保護方式程序設計中,常常要采用別名技術。例如:用兩個具有不同類型值的描述符來描述同一個段。再如,用兩個具有不同DPL的描述符來描述同一個段。
<三>任務內不同特權級的變換
在一個任務內可以存在四種特權級,所以常常會發生不同特權級之間的變換。例如,外層的應用程序調用內層操作系統的例程,以獲得必要的諸如存儲器分配等系統服務。內層操作系統的例程完成後,返回到外層應用程序。
在同一任務內,實現特權級從外層到內層變換的普通途徑是使用段間調用指令CALL,通過調用門進行轉移;實現特權級從內層向外層變換的普通途徑是使用段間返回指令RET。注意,不能用JMP指令實現任務內不同特權級的變換。
1.通過調用門的轉移
當段間轉移指令JMP和段間調用指令CALL所含指針的選擇子指示調用門描述符時,就可以實現通過調用門的轉移。但只有CALL指令能變換到內層的特權級,JMP指令只能轉移到同級的代碼。
調用門描述符轉移的入口點包含目標地址的段及偏移量的48位全指針。在執行通過任務門的段間轉移指令JMP或段間調用指令CALL時,指令所含指針內的選擇子用於確定調用門,而偏移被丟棄;把調用門內的48位全指針作為目標地址指針進行轉移。
處理器采用與訪問數據段相同的特權級規則控制對門描述符的訪問。調用門描述符的DPL規定了訪問該門的最外層特權級,在取出調用門內的48位全指針,把它作為目標地址指針向目標代碼段轉移之前,要進行特權級檢查。只有在相同級或者更內層特權級的程序才可訪問調用門,即CPL<=調用門的DPL。同時,還要求指示門的選擇子的RPL必須滿足RPL<=調用門的DPL的條件。檢測通過後,才開始向目標代碼段轉移的步驟。其中還要檢測目標描述符是否為代碼段描述符,調用門內的選擇子指示的描述符必須是代碼段描述符。此外,在裝載代碼段描述符高速緩沖寄存器之前調整代碼段選擇子的RPL=0,也即調用門中代碼段選擇子的RPL被忽略。
在裝載CS高速緩沖寄存器時,還要對目標代碼段描述符進行保護檢測。檢測過程中的DPL不再是調用門的DPL,而是調用門內選擇子所指示的目標代碼段描述符的DPL。段間調用指令CALL和段間轉移指令JMP所做的檢測不一樣。
對於使用調用門的段間轉移指令JMP,檢測條件與段間直接轉移相同。由於已置RPL=0,所以可認為RPL<=DPL的條件總能滿足。所以,對於普通的非一致代碼段,當CPL=DPL時,發生無特權級變換的轉移;對於一致代碼段,在滿足CPL>=DPL時也發生無特權級變換的轉移;其它情形就引起異常。
對於使用調用門的段間調用指令CALL,情形就不同了。由於已置RPL=0,所以可認為RPL<=DPL的條件總能滿足。對於一致代碼段,在滿足CPL>=DPL時發生無特權級變換的轉移。對於非一致代碼段,當CPL=DPL時,仍發生無特權級變換的轉移;當CPL>DPL時,就發生向內層特權級變換的轉移,將調用門中的選擇子和偏移裝入CS和指令指針EIP中,並使CPL保持等於DPL,同時切換到內層堆棧。
綜上所述,使用段間調用指令CALL,通過調用門可以實現從外層程序調用進入內層程序(JMP指令只能實現無特權級變換的轉移);通過調用門也可實現無特權級變換的轉移。需要注意的是,JMP指令和CALL指令都不能實現向外層特權級的轉移否則會引起異常。
當然,CALL指令在最後把目標代碼段的指針裝入CS和EIP之前,要把原CS和EIP,即返回地址保存到堆棧。如無特權級變換,堆棧保持不變,返回地址就保存在原堆棧中;否則,返回地址保存在內層堆棧中。
2.堆棧切換
在使用CALL指令通過調用門向內層轉移時,不僅特權級發生變換,控制轉移到一個新的代碼段,而且也切換到內層的堆棧段。從本教程第五篇的任務狀態段TSS的格式可見,TSS中包含有指向0級、1級和2級堆棧的指針。在特權級發生向內層變換時,根據變換到的特權級使用TSS中相應的堆棧指針對SS及ESP寄存器進行初始化,建立起一個空棧。
在建立起內層堆棧後,處理器先把外層堆棧的指針SS及ESP寄存器的值壓入內層堆棧,以使得相應的向外層返回可恢復原來的外層堆棧。然後,從外層堆棧復制以雙字為單位的調用參數到內層堆棧中,調用門中的DCOUNT字段值決定了復制參數的數量。這些被復制的參數是主程序通過堆棧傳遞給子程序的實參,在調用之前被壓入外層堆棧。通過復制棧中的參數,使內層的子程序不需要考慮堆棧的切換,而容易地訪問主程序傳遞過來的實參。最後,調用的返回地址被壓入堆棧,以便在調用結束時返回。下圖為在向內層變換時,建立內層堆棧,並從外層堆棧復制2個雙字參數到內層堆棧的示意圖。圖中每項是雙字,可見的段寄存器內的選擇子被擴展成32為存入堆棧,高16位為0。對於16位的使用調用門的段也是如此。
需要注意的是,無論是否通過調用門,只要不發生特權級變換,就不會切換堆棧。
3.向外層返回
與使用CALL指令通過調用門向內層變換相反,使用RET指令實現向外層返回。段間返回指令RET從堆棧中彈出返回地址,並且可以采用調整ESP的方法,跳過相應的在調用之前壓入堆棧的參數。返回地址的選擇子指示要返回的代碼段的描述符,從而確定返回的代碼段。選擇子的RPL確定返回後的特權級,而不是對應描述符的DPL,這是因為,段間返回指令RET可能使控制返回到一致代碼段,而一致代碼段可以在DPL規定的特權級以外的特權級執行。需要注意的是,RET指令所使用的返回地址的選擇子只能使用代碼段描述符,而不能使用任何系統段描述符或門描述符,當然,更不能使用數據段描述符,否則會引起異常。與CALL指令相對應,RET指令也不能向內層返回。
段間返回指令完成返回的步驟如下:
(1)RET指令先從堆棧中彈出返回地址。如果彈出地址的選擇子的RPL規定相對於CPL更外層的特權級,那麼就引起向外層返回。
(2)為向外層返回,跳過內層堆棧中的參數,再從內層棧中彈出指向外層堆棧的指針,並裝入SS及ESP,以恢復外層堆棧。
(3)調整ESP,跳過在相應的調用之前壓入到外層堆棧的參數。即返回指令不但彈出內層棧的參數,而且也彈出外層棧的參數。
(4)然後,檢查數據段寄存器DS、ES、FS及GS,以保證尋址的段在外層是可訪問的,如果段寄存器尋址的段在外層是不可訪問的,那麼裝入一個空選擇子,以避免在返回時發生保護空洞。
(5)返回外層繼續執行。
上述五步是對帶立即數的段間返回指令而言的,立即數規定了堆棧中要跳過的參數的字節數。對於無立即數的段間返回指令缺少第二步和第三步。若RET指令不需要向外層返回,那麼就只有(1)和(5)兩步。對於有通過堆棧傳遞參數的子程序,必須使用帶立即數的返回指令返回,否則返回時會裝載錯誤的外層棧指針。
若不使用帶立即數的返回指令,可以在返回前把外層棧的棧指針存入內層棧中的用於保存返回地址上方兩個雙字的區域中,由外層返回的過程可知,這可正確恢復外層棧的指針,但在外層程序中,必須人為調整外層棧指針,以便廢除在外層棧中壓入的參數。在使用C調用約定的程序中可使用此方法。但這會增加代碼的長度和處理時間,使代碼效率變低。正因為如此,在 Windows 9X下,新增加了一種STDCALL的調用約定,它正是為了適應Intel系列處理器的體系結構而產生的。
<四>演示任務內特權級變換的實例(實例四)
下面給出一個演示任務內特權級變換的實例。該實例演示在任務內通過調用門從外層特權級變換到內層特權級;也演示通過段間返回指令從內層特權級變換到外層特權級;還演示通過調用門的無特權級變換的轉移。實例使用了任務狀態段TSS,這是因為任務內特權級變換時要使用的內層堆棧指針存放在TSS中。
1.實現步驟
該實例的實現步驟為:
(1)實方式下初始化;
(2)切換到保護模式;
(3)設置TR和LDTR。由於在任務內發生特權級變換時要切換堆棧,而內層堆棧的指針存放在當前任務的TSS中,所以在進入保護模式後設置任務狀態段寄存器TR。由於演示任務使用了局部描述符表,所以設置LDTR;
(4)經調用門進入32位過渡代碼段;
(5)建立返回3級演示代碼段的環境;
(6)利用RET指令轉移到3級的演示代碼段。為了演示外層程序通過調用門調用內層程序,要使CPL>0。實例先通過段間返回指令RET從特權級0變換到特權級3的演示代碼段。在特權級3下,通過調用門調用1級的子程序。隨著執行段間返回指令RET,又回到3級的演示代碼段;
(7)在3級的演示代碼段中,經調用門轉移到0級的32位過渡代碼段;
(8)直接轉0級的臨時代碼段;
(9)准備返回實模式;
(10)切換回實模式;
(11)實模式下的恢復工作。
2.源程序組織和清單
實例四由如下部分組成:
(1)全局描述符表GDT。GDT含有演示任務的TSS段描述符和LDT段描述符,此外還含有臨時代碼段的描述符、規范數據段描述符和視頻緩沖區段描述符。
(2)演示任務的LDT段。它含有除臨時代碼段外的其它代碼段的描述符和演示任務各級堆棧段描述符,還含有3個調用門。
(3)演示任務的TSS段。
(4)演示任務的0級、1級和3級堆棧段。
(5)顯示子程序段。32位代碼段,特權級1。
(6)演示代碼段。32位代碼段,特權級3。
(7)過渡代碼段。32位段,特權級0。
(8)臨時代碼段。16位段,特權級0。
(9)實模式下的數據和代碼段。
該實例的邏輯功能是顯示演示代碼段執行時的當前特權級CPL。源程序清單如下:
;名稱:ASM4.ASM
;功能:演示任務內有特權級變換的轉移
;編譯:TASM ASM4.ASM
;連接:TLINK /32 ASM4.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表數據段(16位)
;----------------------------------------------------------------------------
;全局描述符表
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;規范段描述符
Normal Desc <0ffffh,,,ATDW,,>
;視頻緩沖區段描述符(DPL=3)
VideoBuf Desc <07fffh,8000h,0bh,ATDW+DPL3,,>
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;任務狀態段TSS描述符
DemoTSS Desc <DemoTssLen-1,DemoTSSSeg,,AT386TSS,,>
;局部描述符表段的描述符
DemoLDTD Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
;臨時代碼段描述符
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表長度
GDNum = ($-EFFGDT)/(SIZE Desc) ;需特殊處理的描述符數
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;規范段描述符選擇子
Video_Sel = VideoBuf-GDT ;視頻緩沖區段描述符選擇子
;----------------------------------------------------------------------------
DemoTSS_Sel = DemoTSS-GDT ;任務狀態段描述符選擇子
DemoLDT_Sel = DemoLDTD-GDT ;局部描述符表段的選擇子
TempCode_Sel = TempCode-GDT ;臨時代碼段的選擇子
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定義結束
;----------------------------------------------------------------------------
DemoLDTSeg SEGMENT PARA USE16 ;局部描述符表數據段(16位)
;----------------------------------------------------------------------------
DemoLDT LABEL BYTE ;局部描述符表
;0級堆棧段描述符(32位段)
DemoStack0 Desc <DemoStack0Len-1,DemoStack0Seg,,ATDW+DPL0,D32,>
;1級堆棧段描述符(32位段)
DemoStack1 Desc <DemoStack1Len-1,DemoStack1Seg,,ATDW+DPL1,D32,>
;3級堆棧段描述符(16位段)
DemoStack3 Desc <DemoStack3Len-1,DemoStack3Seg,,ATDW+DPL3,,>
;代碼段描述符(32位段,DPL=3)
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE+DPL3,D32,>
;過渡代碼段描述符(32位段)
T32Code Desc <T32CodeLen-1,T32CodeSeg,,ATCE,D32,>
;顯示子程序代碼段描述符(32位段,DPL=1)
EchoSubR Desc <EchoSubRLen-1,EchoSubRSeg,,ATCER+DPL1,D32,>
;----------------------------------------------------------------------------
DemoLDNum = ($-DemoLDT)/(SIZE Desc)
;----------------------------------------------------------------------------
;0級堆棧描述符選擇子(RPL=0)
DemoStack0_Sel = DemoStack0-DemoLDT+TIL+RPL0
;1級堆棧描述符選擇子(RPL=1)
DemoStack1_Sel = DemoStack1-DemoLDT+TIL+RPL1
;3級堆棧描述符選擇子(RPL=3)
DemoStack3_Sel = DemoStack3-DemoLDT+TIL+RPL3
;代碼段描述符選擇子(RPL=3)
DemoCode_Sel = DemoCode-DemoLDT+TIL+RPL3
;過渡代碼段描述符選擇子
T32Code_Sel = T32Code-DemoLDT+TIL
;顯示子程序代碼段描述符選擇子(RPL=1)
Echo_Sel1 = EchoSubR-DemoLDT+TIL+RPL1
;顯示子程序代碼段描述符選擇子(RPL=3)
Echo_Sel3 = EchoSubR-DemoLDT+TIL+RPL3
;----------------------------------------------------------------------------
;指向過渡代碼段內T32Begin點的調用門(DPL=0)
ToT32GateA Gate <T32Begin,T32Code_Sel,,AT386CGate,>
;指向過渡代碼段內T32End點的調用門(DPL=3)
ToT32GateB Gate <T32End,T32Code_Sel,,AT386CGate+DPL3,>
;指向顯示子程序代碼段的調用門(DPL=3)
ToEchoGate Gate <EchoSub,Echo_Sel3,,AT386CGate+DPL3,>
;----------------------------------------------------------------------------
DemoLDTLen = $-DemoLDT
;----------------------------------------------------------------------------
;指向過渡代碼段內T32Begin點的調用門的選擇子
ToT32A_Sel = ToT32GateA-DemoLDT+TIL
;指向過渡代碼段內T32End點的調用門的選擇子
ToT32B_Sel = ToT32GateB-DemoLDT+TIL
;顯示子程序調用門的選擇子
ToEcho_Sel = ToEchoGate-DemoLDT+TIL
;----------------------------------------------------------------------------
DemoLDTSeg ENDS ;局部描述符表段定義結束
;----------------------------------------------------------------------------
DemoTSSSeg SEGMENT PARA USE16 ;任務狀態段TSS
;----------------------------------------------------------------------------
DD 0 ;Back
DD DemoStack0Len ;0級堆棧指針
DD DemoStack0_Sel ;初始化
DD DemoStack1Len ;1級堆棧指針
DD DemoStack1_Sel ;初始化
DD 0 ;2級堆棧指針
DD 0 ;未初始化
DD 0 ;CR3
DD 0 ;EIP
DD 0 ;EFLAGS
DD 0 ;EAX
DD 0 ;ECX
DD 0 ;EDX
DD 0 ;EBX
DD 0 ;ESP
DD 0 ;EBP
DD 0 ;ESI
DD 0 ;EDI
DD 0 ;ES
DD 0 ;CS
DD 0 ;SS
DD 0 ;DS
DD 0 ;FS
DD 0 ;GS
DD DemoLDT_Sel ;LDT
DW 0 ;調試陷阱標志
DW $+2 ;指向I/O許可位圖
DW 0ffffh ;I/O許可位圖結束標志
;----------------------------------------------------------------------------
DemoTSSLen = $
;----------------------------------------------------------------------------
DemoTSSSeg ENDS ;任務狀態段TSS結束
;----------------------------------------------------------------------------
DemoStack0Seg SEGMENT DWORD STACK USE32 ;0級堆棧段(32位段)
DemoStack0Len = 512
DB DemoStack0Len DUP(?)
DemoStack0Seg ENDS ;0級堆棧段結束
;----------------------------------------------------------------------------
DemoStack1Seg SEGMENT DWORD STACK USE32 ;1級堆棧段(32位段)
DemoStack1Len = 512
DB DemoStack1Len DUP(?)
DemoStack1Seg ENDS ;1級堆棧段結束
;----------------------------------------------------------------------------
DemoStack3Seg SEGMENT DWORD STACK USE16 ;3級堆棧段(16位段)
DemoStack3Len = 512
DB DemoStack3Len DUP(?)
DemoStack3Seg ENDS ;3級堆棧段結束
;----------------------------------------------------------------------------
EchoSubRSeg SEGMENT PARA USE32 ;顯示子程序代碼段(32位,1級)
ASSUME CS:EchoSubRSeg
;----------------------------------------------------------------------------
Message DB 'CPL=',0 ;顯示信息(該代碼段可讀)
;----------------------------------------------------------------------------
EchoSub PROC FAR
cld
push ebp
mov ebp,esp
mov ax,Echo_Sel1 ;該代碼段是可讀段
mov ds,ax ;采用RPL=1的選擇子
mov ax,Video_Sel
mov es,ax
mov edi,1996
mov esi,OFFSET Message
mov ah,4eh ;置顯示屬性(紅底黃字)
EchoSub1: lodsb
or al,al
jz EchoSub2
stosw
jmp EchoSub1
EchoSub2: mov eax,[ebp+8] ;從堆棧中取調用程序的選擇子
and al,3 ;調用程序的CPL在CS的RPL字段
add al,'0'
mov ah,4eh ;置顯示屬性(紅底黃字)
stosw
pop ebp
retf
EchoSub ENDP
;----------------------------------------------------------------------------
EchoSubRLen = $
;----------------------------------------------------------------------------
EchoSubRSeg ENDS ;顯示子程序代碼段結束
;----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE32 ;32位代碼段(3級)
ASSUME CS:DemoCodeSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
CALL32 ToEcho_Sel,0 ;顯示當前特權級(變換到1級)
CALL32 ToT32B_Sel,0 ;轉到過渡代碼段(變換到0級)
DemoBegin ENDP
DemoCodeLen = $
;----------------------------------------------------------------------------
DemoCodeSeg ENDS ;32位代碼段結束
;----------------------------------------------------------------------------
T32CodeSeg SEGMENT PARA USE32 ;32位過渡代碼段(0級)
ASSUME CS:T32CodeSeg
;----------------------------------------------------------------------------
T32Begin PROC FAR
mov ax,DemoStack0_Sel ;建立0級堆棧
mov ss,ax
mov esp,DemoStack0Len
push DWORD PTR DemoStack3_Sel ;壓入3級堆棧指針
push DemoStack3Len
push DWORD PTR DemoCode_SEL ;壓入入口點
push OFFSET DemoBegin
retf ;利用RET實現轉3級的演示代碼
T32Begin ENDP
;----------------------------------------------------------------------------
T32End PROC FAR
JUMP32 TempCode_Sel,<OFFSET ToReal>
T32End ENDP
T32CodeLen = $
;----------------------------------------------------------------------------
T32CodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;16位臨時代碼段(0級)
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
mov ax,DemoTSS_Sel ;裝載TR
ltr ax
mov ax,DemoLDT_Sel ;裝載LDTR
lldt ax
JUMP16 ToT32A_Sel,0 ;通過調用門轉過渡段
ToReal: mov ax,Normal_Sel ;准備切換回實模式
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
TempCodeLen = $
;----------------------------------------------------------------------------
TempCodeSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;實方式數據段
VGDTR PDesc <GDTLen-1,> ;GDT偽描述符
SPVar DW ? ;用於保存實方式下的SP
SSVar DW ? ;用於保存實方式下的SS
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
CALL InitGDT ;初始化全局描述符表GDT
mov ax,DemoLDTSeg
mov fs,ax
mov si,OFFSET DemoLDT
mov cx,DemoLDNum
CALL InitLDT ;初始化局部描述符表LDT
mov SSVar,ss
mov SPVar,sp
lgdt QWORD PTR VGDTR ;裝載GDTR並切換到保護方式
cli
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar ;又回到實方式
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
;----------------------------------------------------------------------------
;入口參數:FS:SI=第一個要初始化的描述符,CX=要初始化的描述符數
;----------------------------------------------------------------------------
InitLDT PROC
ILDT: mov ax,WORD PTR FS:[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR fs:[si].BaseL,ax
mov BYTE PTR fs:[si].BaseM,dl
mov BYTE PTR fs:[si].BaseH,dh
add si,SIZE Desc
loop ILDT
ret
InitLDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start