1. 程式人生 > >3.保護模式5----特權級概述(轉)

3.保護模式5----特權級概述(轉)

3、DPL,RPL,CPL 之間的聯絡和區別是什麼?RPL和CPL是必須相同嗎?如果相同,為什麼要釆用兩個而不改用一個呢?



答:特權級是保護模式下一個重要的概念,CPL,RPL和DPL是其中的核心概念,查閱資料無數,總結如下:
  簡單解釋:

--------------------------------------------------------------------------------

  CPL是當前程序的許可權級別(Current Privilege Level),是當前正在執行的程式碼所在的段的特權級,存在於cs暫存器的低兩位。
  RPL說明的是程序對段訪問的請求許可權(Request Privilege Level),是對於段選擇子而言的,每個段選擇子有自己的RPL,它說明的是程序對段訪問的請求許可權,有點像函式引數。而且RPL對每個段來說不是固定的,兩次訪問同一段時的RPL可以不同。RPL可能會削弱CPL的作用,例如當前CPL=0的程序要訪問一個數據段,它把段選擇符中的RPL設為3,這樣雖然它對該段仍然只有特權為3的訪問許可權。
DPL儲存在段描述符中,規定訪問該段的許可權級別(Descriptor Privilege Level),每個段的DPL固定。
當程序訪問一個段時,需要程序特權級檢查,一般要求DPL >= max {CPL, RPL}
下面打一個比方,中國官員分為6級國家主席1、總理2、省長3、市長4、縣長5、鄉長6,假設我是當前程序,級別總理(CPL=2),我去聊城市(DPL=4)考察(呵呵),我用省長的級別(RPL=3 這樣也能嚇死他們:-))去訪問,可以吧,如果我用縣長的級別,人家就不理咱了(你看看電視上的微服私訪,呵呵),明白了吧!為什麼採用RPL,是考慮到安全的問題,就好像你明明對一個檔案用有寫許可權,為什麼用只讀開啟它呢,還不是為了安全!


  全面解釋:
--------------------------------------------------------------------------------

  RPL是段選擇子裡面的bit 0和bit 1位組合所得的值,但這裡要首先搞清楚什麼是段選擇子,根據Intel 的檔案(IA-32 IntelR Architecture Software Developer's Manual, Volume 3System Programming Guide)它是一個16Bit identifier (原文:A segment selector is a 16-bit identifier for a segment). 但 identifier 又是什麼. identifier 可以是一個變數的名字( An identifier is a name for variables), 簡單的說它可以就是一般意義的變數. 這裡 16-bit identifier for a segment 可以就是一個一般意義的16bit變數但同時要求對它的值解釋的時候必須跟據Intel定下的規則---也就是bit 0和bit 1位的組合值就是RPL等等… 因此在程式裡如果有需要的話你可以宣告一個或者多個變數來代表這些段選擇子,這樣的話你的程式在某一時刻就可以有很多段選擇子,當然有那麼多段選擇子就有那麼多RPL.可以這樣說程式有多少個是RPL是你怎樣看待你自己宣告的變數. |
  程式的CPL(CS.RPL)是CS register 裡bit 0和bit 1 位組合所得的值.在某一時刻就只有這個值唯一的代表程式的CPL. 

  而DPL是段描述符中的特權級, 它的本意是用來代表它所描述的段的特權級. 一個程式可以使用很多段(Data,Code,Stack)也可以只用一個code段等.在正常的情況下當程式的環境建立好後,段描述符都不需要改變-----當然DPL也不需要改變. 


  一、對資料段和堆疊段訪問時的特權級控制:

要求訪問資料段或堆疊段的程式的CPL≤待訪問的資料段或堆疊段的DPL,同時選擇子的RPL≤待訪問的資料段或堆疊段的DPL,即程式訪問資料段或堆疊段要遵循一個準則:只有相同或更高特權級的程式碼才能訪問相應的資料段。這裡,RPL可能會削弱CPL的作用,訪問資料段或堆疊段時,預設用CPU和RPL中的最小特權級去訪問資料段,所以max {CPL, RPL} ≤ DPL,否則訪問失敗。


  二、對程式碼段訪問的特權級控制(程式碼執行權的特權轉移):

  讓我們先來記一些“定律”:
所有的程式轉跳,CPU都不會把段選擇子的RPL賦給轉跳後程序的CS.RPL. .

  轉跳後程序的CPL(CS.RPL)只會有下面的倆種可能
  轉跳後程序的CPL(CS.RPL) = 轉跳前程式的CPL(CS.RPL) 
  或
  轉跳後程序的CPL(CS.RPL) = 轉跳後程序的CodeDescriptor.DPL

  以 Call 為例(只能跳到等於當前特權級或比當前特權級更高的段):
  怎樣決定這兩種選擇,這就要首先知道轉跳後程序的段是一致程式碼段還是非一致程式碼段.其實也很簡單,規則如下:
  如果能成功轉跳到一致程式碼段, 轉跳後程序的CPL(CS.RPL) = 轉跳前程式的CPL(CS.RPL),(轉跳後程序的CPL繼承了轉跳前程式的CPL)
  如果能成功轉跳到非一致程式碼段, 轉跳後程序的CPL(CS.RPL) =轉跳後程序的Descriptor.DPL。(轉跳後程序的CPL變成了該程式碼段的特權級.我在前面提到DPL是段描述符中的特權級, 它的本意是用來代表它所描述的段的特權級)怎樣才能成功轉跳啦?

  這裡有四個重要的概念:

  1).段的保護觀念是高特權級不找低特權級辦事,低特權級找高特權級幫忙,相同的一定沒問題.(這樣想邏輯是沒錯,事實對不對就不知道.)也就是縣長不找鄉長,鄉長不求農民,反過來農民求鄉長,鄉長找縣長.這個概念是最重要的。
  2) 一致程式碼段的意義: 讓客人很方便的利用主人(一致程式碼段)的東西為自己辦事.但客人這身份沒有改變NewCS.RPL=OldCS.RPL所以只能幫自己辦事。比方說鄉長有一頭牛,農民可以借來幫自己種田,但不能種別人的田.但是如果你是鄉長當然可以種鄉里所有的田。
  3) 非一致程式碼段的意義:主人(非一致程式碼段)可以幫客人但一定是用自己的身份NewCS.RPL= DestinationDescriptorCode.DPL這裡可能有安全的問題, 搞不好很容易農民變縣長。主人太頑固了一定要堅持自己的身份,有什麼方法變通一下,來個妥協好不好。好的,它就是RPL的用處。
  4) RPL: 它讓程式有需要的時候可以表示一個特權級更低的身份Max(RPL,CPL)而不會失去本身的特權級CPL(CS.RPL),有需要的時候是指要檢查身份的時候。事實上RPL跟段本身的特權級DPL和當前特權級CPL沒有什麼關係,因為RPL的值在成功轉跳後並不賦給轉跳後的CS.RPL。
  還是要問怎樣才能成功轉跳啦?這裡分兩種情況:

  普通轉跳(沒有經過Gate 這東西):即JMP或Call後跟著48位全指標(16位段選擇子+32位地址偏移),且其中的段選擇子指向程式碼段描述符,這樣的跳轉稱為直接(普通)跳轉。普通跳轉不能使特權級發生躍遷,即不會引起CPL的變化,看下面的詳細描述:


目標是一致程式碼段:
  要求:CPL(CS.RPL)>=DestinationDescriptorCode.DPL ,其他RPL是不檢查的。
  轉跳後程序的CPL(NewCS.RPL) = 轉跳前程式的CPL( OldCS.RPL)
  上面的安排就是概念1,2的意思,此時,CPL沒有發生變化,縱使它執行了特權級(DPL)較高的程式碼。若訪問時不滿足要求,則發生異常。
目標是非一致程式碼段:
  要求:CPL(CS.RPL)=DestinationDescriptorCode.DPL AND RPL≤CPL(CS.RPL)
  轉跳後程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
  上面的安排就是概念3的意思和部分1的意思----主人(一致程式碼段)只幫相同特權級的幫客人做事。因為前提是CPL=DPL,所以轉跳後程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL不會改變CPL的值,特權級(CPL)也沒有發生變化。如果訪問時不滿足前提CPL=DPL,則引發異常。


  通過呼叫門的跳轉:當段間轉移指令JMP和段間轉移指令CALL後跟著的目標段選擇子指向一個呼叫門描述符時,該跳轉就是利用呼叫門的跳轉。這時如果選擇子後跟著32位的地址偏移,也不會被cpu使用,因為呼叫門描述符已經記錄了目的碼的偏移。使用調門進行的跳轉比普通跳轉多一個步驟,即在訪問呼叫門描述符時要將描述符當作一個數據段來檢查訪問許可權,要求指示呼叫門的選擇子的RPL≤門描述符DPL,同時當前程式碼段CPL≤門描述符DPL,就如同訪問資料段一樣,要求訪問資料段的程式的CPL≤待訪問的資料段的DPL,同時選擇子的RPL≤待訪問的資料段或堆疊段的DPL。只有滿足了以上條件,CPU才會進一步從呼叫門描述符中讀取目的碼段的選擇子和地址偏移,進行下一步的操作。
  從呼叫門中讀取到目的碼的段選擇子和地址偏移後,我們當前掌握的資訊又回到了先前,和普通跳轉站在了同一條起跑線上(普通跳轉一開始就得到了目的碼的段選擇子和地址偏移),有所不同的是,此時,CPU會將讀到的目的碼段選擇子中的RPL清0,即忽略了呼叫門中程式碼段選擇子的RPL的作用。完成這一步後,CPU開始對當前程式的CPL,目的碼段選擇子的RPL(事實上它被清0後總能滿足要求)以及由目的碼選擇子指示的目的碼段描述符中的DPL進行特權級檢查,並根據情況進行跳轉,具體情況如下:


目標是一致程式碼段:
  要求:CPL(CS.RPL)≥DestinationDescriptorCode.DPL ,RPL不檢查,因為RPL被清0,所以事實上永遠滿足RPL≤DPL,這一點與普通跳轉一致,適用於JMP和CALL。
  轉跳後程序的CPL(NewCS.RPL) = 轉跳前程式的CPL( OldCS.RPL),因此特權級沒有發生躍遷。
  
目標是非一致程式碼段:
當用JMP指令跳轉時:
  要求:CPL(CS.RPL)=DestinationDescriptorCode.DPL AND RPL<= CPL(CS.RPL)(事實上因為RPL被清0,所以RPL≤CPL總能滿足,因此RPL與CPL的關係在此不檢查)。若不滿足要求則程式引起異常。
  轉跳後程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
  因為前提是CPL=DPL,所以轉跳後程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL不會改變CPL的值,特權級也沒有發生變化。如果訪問時不滿足前提CPL=DPL,則引發異常。
當用CALL指令跳轉時:
要求:CPL(CS.RPL)≥DestinationDescriptorCode.DPL(RPL被清0,不檢查),若不滿足要求則程式引起異常。
轉跳後程序的CPL(NewCS.RPL) = DestinationDescriptorCode.DPL
當條件CPL=DPL時,程式跳轉後CPL=DPL,特權級不發生躍遷;當CPL>DPL時,程式跳轉後CPL=DPL,特權級發生躍遷,這是我們當目前位置唯一見到的使程式當前執行憂先級(CPL)發生變化的跳轉方法,即用CALL指令+呼叫門方式跳轉,且目的碼段是非一致程式碼段。


  總結:以上介紹了兩種情況的跳轉,分別是普通跳轉和使用呼叫門的跳轉,其中又可細分為JMP跳轉和CALL跳轉,跳轉成功已否是由CPL,RPL和DPL綜合決定的。所有跳轉都是從低特權級程式碼向同級或更高特權級(DPL)跳轉,但保持當前執行特權級(CPL)不變,這裡有點難於區別為什麼說向高特權級跳轉,又說特權級沒變,這裡“高特權級”是指目的碼段描述符的DPL,它規定了可以跳轉到該段程式碼的最高特權級;而後面的CPL不變才真正說明了特權級未發生躍遷。我們可以看到,只有用CALL指令+呼叫門方式跳轉,且目的碼段是非一致程式碼段時,才會引起CPL的變化,即引起程式碼執行特權級的躍遷,這是目前得知的改變執行特權級的唯一辦法,如果各位讀者還知道其他方法請留言告訴我。