X86之操作系統之DPL,RPL,CPL

1、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,是考慮到安全的問題,
就好像你明明對一個文件用有寫權限,爲什麼用只讀打開它呢,還不是爲了安全!
1). CPL要通過門(中斷門,陷阱門,任務門,調用門)訪問一個GDT中的描述符,必須有如下關係:
CPL <= DPL (門): 當前運行級不能低於門,如果是外部中斷或CPU異常會免去這一判斷
CPL >= DPL (描述符):門只能是用於保持或提升運行級別,因此GDT的描述符中的DPL均爲0。如果有提升,則需要進行堆棧切換,如下:
  運行級別不變的堆棧值:
  eflag
  cs
  eip
  error code
  運行級別改變的堆棧值:
  ss
  esp
  eflag
  cs
  eip
  error code
2). 各個門的DPL解釋:
中斷門: 用於硬件中斷,DPL爲0,不允許用戶態直接使用int指令訪問,硬件中斷免去這一判斷,因此可以在用戶態響應中斷,見set_intr_gate。
DPL0陷阱門: 用於CPU異常,DPL爲0,不允許用戶態直接使用int指令訪問,硬件中斷免去這一判斷,因此可以在用戶產生CPU異常,見set_trap_gate。
DPL3陷阱門: 用於系統調用,DPL爲3,允許用戶態直接使用int指令訪問,這樣才能通過int80訪問系統調用,只有80號向量屬於此門,見set_system_gate。
調用門: DPL爲3,允許用戶態訪問,和LDT一起使用,用於特殊場景,見set_call_gate。
 
全面解釋:
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.
注:CS.RPL代表CS積存器中的Register Privilege Level,並不是一般所說的Request Privilege Level. 
   
而DPL是段描述符中的特權級, 它的本意是用來代表它所描述的段的特權級. 一個程序可以使用很多段(Data,Code,Stack)也可以只用一個code段等.在正常的情況下當程序的環
境建立好後,段描述符都不需要改變-----當然DPL也不需要改變.
一、對數據段和堆棧段訪問時的特權級控制:
要求訪問數據段或堆棧段的程序的CPL≤待訪問的數據段或堆棧段的DPL,同時選擇子的RPL≤待訪問的數據段或堆棧段的DPL,即程序訪問數據段或堆棧段要遵循一個準則:只有相
同或更高特權級的代碼才能訪問相應的數據段 (這樣才能保護數據不被隨意更改,注意這裏僅僅是數據段和堆棧段!). 這裏,RPL可能會削弱CPL的作用,訪問數據段或堆棧段時,
默認用CPU和RPL中的最小特權級去訪問數據段,所以max {CPL, RPL} ≤ DPL,否則訪問失敗。
二、對代碼段訪問的特權級控制(代碼執行權的特權轉移):
讓我們先來記一些“定律”:
所有的程序轉跳,CPU都不會把段選擇子的RPL賦給轉跳後程序的CS.RPL. (段選擇子不過是一個給CPU的"引導"作用,具體講解請各位看官瀏覽下我前面的保護模式下尋址 , 段選擇
子在完成了引導作用後的工作就是進程跟新的段的瓜葛了,CPU與代碼如何如何那跟段選擇子一點兒都沒有關係,所以段選擇子的RPL不會給CPL,記住,段選擇子僅僅一個引導而已
!)
轉跳後程序的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不會變了),此時,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的變化,即引起代碼執行特權級的躍遷,這是目前得知的改變執行特權級的唯一辦法,如果各位讀者還知道其他方法請留言告訴我。
    以上解釋參考了OldLinux BBS的帖子http://www.oldlinux.org/cgi-bin/LB5000XP/topic.cgi?forum=18&topic=73&show=0
 
2、問:爲什麼全局描述符表GDT的第0項總是一個空描述符,而局部描述符表卻不是這樣?
    答:首先讓我們先來熟悉一下概念。一個任務(Task )通常會涉及多個段,每個段需要一個描述符號來描述(當然不是絕對的一對一關係,一個段也可以由多個段描述符來對
應,視具體應用而定),爲了便於組織管理,80386把描述符組織成線性表。由描述符組成的線性表稱爲描述符表。在80386中有三種類型的描述符表(Descriptor Table),分別
是全局描述符表 GDT(Global Descriptor Table),局部描述符表(Local Descriptor Table)和中斷描述符表 IDT(Interrupt Descriptor Table)。在整個系統中,全局描述
符表GDT和中斷描述符表IDT只有一張,局部描述符表可以有若干張,每個任務可以有一張。
    在實模式下,邏輯地址是由段基址和段內偏移構成的。保護模式下,規則發生了很大變化,虛擬地址空間(相當於邏輯地址空間)中存儲單元的地址由段選擇子和段內偏移兩部分組成,與實模式相比,段選擇子代替了原來的段基址。從本質上來講,段選擇子最終還是要轉化成段基址,那麼選擇子是如何轉化爲段基址的呢?讓我們來看看選擇子的結構和用法:
 
    段選擇子長16位,其格式如上圖所示。從圖中可見,段選擇子的高13位是描述符的索引值。所謂描述符索引是指描述符在描述符表中的序號。由於描述符總是8個字節的,所以將描述符索引值邏輯左移3位即可得到對應描述符在描述符表中的偏移地址,再加上描述符表起始地址就可以確定描述符的位置,這算是一個小技巧。段段選擇子的第2位是引用描述符表指示位,標記爲TI(Table Indicator),TI=0表示該選擇子指示的是全局描述符表GDT中的描述符,TI=1表示該選擇子指示的是局部描述符表LDT中的描述符。第0和第1位稱
爲 RPL(Request Privilege Level請求特權級),用於特權級控制,在上一個問題中有詳細描述。通過段選擇子,我們可以從GDT或 LDT中找到需要的段描述符,段描述符中存儲着目標段的基址(起始地址),界限(段的範圍)以及其他一些控制信息,由此,我們完成了段選擇子到段基址的轉化。
 
    到這裏,我們似乎離開題目太遠了,請不要急,我們惟有將基本的概念陳述清楚,才能將問題回答透徹,那麼現在開始回答問題。
 
    如前所述,描述符的線性表構成了GDT,LDT,IDT,這些描述符並非都是用來描述數據段或代碼段的,有的可能用來指示一個任務,比如任務門描述符,用於任務切換;有的用來指示子程序,比如調用門;還有的用來指示中斷處理程序,如中斷門、陷阱門等,當然,中斷門和陷阱門只存在於IDT中。除此之外,由於CPU把局部描述符表LDT也當作數據段來管理,所以要求每一個LDT都必須有相應的描述符存在於GDT中,暫且稱之爲LDT描述符。由此可見,GDT、LDT和IDT是由各種不同種類的描述符排列構成的,他們的組成數據各不相同,作用也不同,唯一相同的是他們都是8字節的,都存於描述符表中,都用選擇子來定位(中斷門和陷阱門用INT 指令後的數字來做描述符索引)。
    前面說到GDT和IDT是整個系統一張,而LDT可以每個任務獨佔一長,用於存儲每個任務私有的段的信息,所以當任務發生切換時,LDT也要隨之切換,CPU中專門用一個16位的寄存器LDTR來存儲當前任務的LDT在GDT中的描述符的選擇子,以此來定位當前任務的LDT。同時也存在這麼一種情況,那就是一個任務使用的所有段都是系統全局的,它不需要用LDT來存儲私有段信息,因此,當系統切換到這種任務時,會將LDTR寄存器賦值成一個空(全局描述符)選擇子,選擇子的描述符索引值爲0,TI指示位爲0,RPL可以爲任意值,用這種
方式表明當前任務沒有 LDT。這裏的空選擇子因爲TI爲0,所以它實際上指向了GDT的第0項描述符,第0項的作用類似於C語言中NULL的用法,它雖然是一個描述符,但卻只起到到了標誌的作用,規定GDT的第0項描述符爲空描述符,其8個字節全爲0,就是這個原因。如果把前面的空描述符選擇子的TI位改爲1,使之指向LDT 中的0號描述符,這樣的選擇子就不是空選擇子,它指向的LDT中的0號描述符是可以正常使用的,也就是LDT中沒有空描述符一說。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章