http://www.haogongju.net/art/896140

《Orange‘s 一個操作系統的實現》3.保護模式5----特權級概述(轉)

作者:Aoysme | 出處:博客園 | 2011/11/20 15:41:41 | 閱讀22
學習過程中遇到一個對保護模式總結很好的Blog,轉來分享一下。

先說下特權級的概念,在保護模式下,系統依靠特權級來實施代碼和數據的保護,相當於權限啦。特權級共有4個級別,0,1,2,3,數字越小表示權限越高。如圖:

保護模式 對CPL,RPL,DPL的總結 - xuejianxinokok - xuejianxinokok的博客
  
較爲核心的代碼和數據放在較高(靠內)的層級中,處理器用此來防止較低特權的任務在不被允許的情況下訪問處於高特權級的段。爲了防止概念混淆,我們不用特權級大小來說明,改爲內層(高),外層(低)來講。

特權級有3種CPL,DPLRPL,每個都是有4個等級。
我對他們的關係理解是這樣:一般來說,CPL代表當前代碼段的權限,如果它想要去訪問一個段或門,首先要看看對方的權限如何,也就是檢查對方的DPL,如果滿足當前的權限比要訪問的權限高,則有可能允許去訪問,有些情況我們還要檢查
選擇子的權限,即RPL,因爲我們通過選擇子:偏移量的方式去訪問一個段,這算是一個訪問請求動作,因此
稱爲請求訪問權限RPL(Requst Privilege Level)。當請求權限也滿足條件,那麼訪問就被允許了。


CPL(Current Privilege Level)
CPL是當前執行的任務的特權等級,它存儲在CS和SS的第0位和第1位上。(兩位表示0~3四個等級)
通常情況下,CPL等於代碼所在段的特權等級,當程序轉移到不同的代碼段時,處理器將改變CPL
注意:在遇到一致代碼段時,情況特殊,一致代碼段的特點是:可以被等級相同或者更低特權級的代碼訪問,當處理器訪問一個與當前代碼段CPL特權級不同的一致代碼段時,CPL不會改變。

DPL(Descriptor Privilege Level)
表示門或者段的特權級,存儲在門或者段的描述符的DPL字段中。正如上面說的那樣,噹噹前代碼段試圖訪問一個段或者門時,其DPL將會和當前特權級CPL以及段或門的選擇子比較,根據段或者門的類型不同,DPL的含義不同:
    1.數據段的DPL:規定了訪問此段的最低權限。比如一個數據段的DPL是1,那麼只有運行在CPL爲0或1的程序纔可能訪問它。爲什麼說可能呢?因爲還有一個比較的因素是RPL。訪問數據段要滿足有效特權級別(上述)
高於數據段的DPL.
    2.非一致代碼段的DPL(不使用調用門的情況):DPL規定訪問此段的特權,只有CPL與之相等纔有可能訪問。
    3.調用門的DPL,規定了程序或任務訪問該門的最低權限。與數據段同。
    4.一致代碼段和通過調用門訪問的非一致代碼段,DPL規定訪問此段的最高權限。
     比如一個段的DPL爲2,那麼CPL爲0或者1的程序都無法訪問。
   5. TSS的DPL,同數據段。

RPL(Rquest Privilege Level)

RPL是通過選擇子的低兩位來表現出來的(這麼說來,CS和SS也是存放選擇子的,同時CPL存放在CS和SS的低兩位上,那麼對CS和SS來說,選擇子的RPL=當前段的CPL)。處理器通過檢查RPLCPL來確認一個訪問是否合法。即提出訪問的段除了有足夠的特權級CPL,如果RPL不夠也是不行的(有些情況會忽略RPL檢查)。
爲什麼要有RPL
操作系統往往通過設置RPL的方法來避免低特權級的應用程序訪問高特權級的內層數據。
例子情景:調用者調用操作系統的某過程去訪問一個段。
當操作系統(被調用過程)從應用程序(調用者)接受一個選擇子時,會把選擇子的RPL設置稱調用者的權限等級,於是操作系統用這個選擇子去訪問相應的段時(這時CPL爲操作系統的等級,因爲正在運行操作系統的代碼),處理器會使用調用者的特權級去進行特權級檢查,而不是正在實施訪問動作的操作系統的特權級(CPL),這樣操作系統就不用以自己的身份去訪問(就防止了應用去訪問需要高權限的內層數據,除非應用程序本身的權限就足夠高)。
那麼RPL的作用就比較明顯了:因爲同一時刻只能有一個CPL,而當低權限的應用去調用擁有至高權限的操作系統的功能來訪問一個目標段時,進入操作系統代碼段時CPL變成了操作系統的CPL,如果沒有RPL,那麼權限檢查的時候就會用CPL,而這個CPL權限比應用程序高,也就可能去訪問需要高權限才能訪問的數據,這就不安全了。所以引入RPL,讓它去代表訪問權限,因此在檢查CPL的同時,也會檢查RPL.一般來說如果RPL的數字比CPL大(權限比CPL的低),那麼RPL會起決定性作用。
說這麼多不明白都不行啦~真累
下面是引用的一個超棒的關於權限控制的總結:
引用地址
還有一篇文章在此

■ 數據訪問時的權限check

一、 訪問data segment時(ds、es、fs 及gs)
1、 程序指令要訪問數據時,data segment selector 被加載進 data segment register(ds、es、fs 和 gs)前,處理器會進行一系列的權限檢查,通過了才能被加載進入segment register。處理器分爲兩步進行檢查:
CPL(當前程序運行的權限級別)與RPL(位於selector中的RPL)作比較,並設置有效權限級別爲低權限的一個。
★ 得出的有效權限級別與DPL(segment descriptor 中的DPL)作比較,有效權限級別高於DPL,那麼就通過。低於就不允許訪問。
2、舉個例子:
如果:CPL = 3、RPL = 2、DPL = 2。那麼
EPL = CPL >RPL ?CPL : RPL;
if (EPL <= DPL) {
/* 允許訪問 */
} else {
/* 失敗,#GP 異常生產,執行異常 */
}
或者:
if ((CPL <=DPL) && (RPL <=DPL)) {
/* 允許訪問 */
} else {
/* 失敗,#GP 異常生產,執行異常 */
}

   也就是要訪問目標data segment,那麼必須要有足夠的權限,這個足夠的權限就是:當前運行的權限級別及選擇子的請求權限級別要高於等於目標data segment的權限級別。

二、 訪問stack segment時
1、 該問stack 時的權限檢查更嚴格,CPLRPLDPL三者必須相等才能通過該問請求。

2、 舉個例子:

if (CPL ==RPL &&RPL == DPL &&CPL ==DPL) {
      /* 允許訪問 */
} else {
     /* 失敗,#GP 異常生產,執行異常 */
}

也就是說每個權限級別有相對應的statck segment。不能越權訪問,即使高權限訪問低權限也是被拒絕的

■ 控制權的轉移及權限檢查。

   權限檢查的4個要素:

CPL:處理器當前運行的級別,也就是:當前 CS 的級別,在 CS 的 BIT0 ~ Bit1

DPL:訪問目標代碼段所需的級別。定義在 segment descriptor 的DPL 域中

RPL: 通過 selector 進行訪問時,selector 內定義的級別。

★ conforming/nonconforming:目標代碼屬於 nonconforming 還是 conforming 定義在segment descritptor 的 C 標誌位中

x86 的各方面檢查依賴於目標代碼段是 nonconforming(不一致) 還是 conforming(一致) 類型


一、 直接轉移(far call 及 far jmp)
    1、 直接轉移定義爲不帶gate selector或 taskselector的遠調用。當執行一條 call cs:eip 或 jmp cs:eip 指令時,cs 是目標代碼段的selector,處理器在加載指令操作數中的cs進cs register前,要進行一系列的權限檢查,控制權的轉移權限分兩部分,根據目標代碼段descriptor定義的兩種情況:
1)、nonconforming target code segment
★ 直接轉移後的權限級別是不能必改變的。因此,CPL 必須要等於目標代碼段的DPL
★ 要有足夠的請求權限進行訪問。因此,目標代碼段選擇子的RPL <=CPL

2)、conforming target code segment
★ conforming code segment 允許訪問高權限級別的代碼。這裏只需檢查 CPL >= DPL 即可,RPL 忽略不檢查。
★ 轉移後CPL不會改變。

   2、 以上兩步通過後,處理器加載目標代碼段的CS 進入CS register,但權限級別不改變,繼而RPL被忽略。

★ 處理器根據CS selector在相應的descriptor table 找到 code segment descriptor。CS 的Bit2(TI域) 指示在哪個descriptor table 表查找,CS.TI = 0 時在GDT查找,CS.TI = 1時在LDT 查找。
★ CS的Bit15~Bit3 是selector index 值,處理器基於GDT或LDT來查找segment descriptor。具體是:GDTR.base 或 LDTR.base + CS.SI × 8 得出code segment descritpro。
★ 處理器自動加載code segment descriptor 的 base address、segment limit及attribute 域進入 CS register的相應的隱藏域。
★ 轉到CS.base + eip 處執行指令

總結:用代碼形式來說明直接轉移 call cs:eip 這條指令
例: call 1a:804304c (即cs = 1a, eip = 804304c)

target_cs = 1a;
target_eip = 0x0804304c;
CPL = CS.RPL;            /* 當前執行的代碼段的權限級別就是CPL */
RPL = target_cs.RPL;     /* 目標段 selector 的低3位是RPL */
target_si = target_cs.SI;    /* 目標段 selector 的索引是Bit15~Bit3 */
target_ti = target_cs.TI;    /* 目標段selector的描述符表索引是Bit2 */
CODESEG_DESCRIPTOR target_descriptor;

if (target_ti == 0) { /* target_cs.TI爲0 就是參考到 GDT(全局描述符表) */
/* 以GDTR寄存器的base 爲基地址加上selector的索引乘以8即得出目標
       段描述符,目標描述符的DPL就是目標段所需的訪問權限 */
    target_descriptor = GDTR.base + target_si * 8

} else {               /* 否則就是參考 LDT (局部描述符表)*/
    /* 以 LDTR寄存器的base 爲基地址得出目標段描述符 */

target_descriptor = LDTR.base + target_si * 8;
}
DPL = target_descriptor.DPL;     /* 獲取DPL */
if (target_descriptor.type & 0x06) { /* conforming */
if (CPL >=DPL) {   /* 允許執行高權限代碼 */
     /* go ahead */
    } else {
     /* 引發 #GP 異常 */
    goto DO_GP_EXCEPTION;
}

} else {                  /* nonconforming */
    if (CPL ==DPL &&RPL <= CPL) {  
        /* go ahead */
    } else {
        /* 引發 #GP 異常 */
       goto DO_GP_EXCEPTION;
    }
  
}
/****** go ahead … …******/
CS = target_cs;           /* 加載目標段CS 進入 CS 寄存器 */
EIP = target_eip;        /* 加載目標指令EIP 進入 EIP 寄存器 */
                 /* 當前執行權限 CPL 不改變 */

goto target_descriptor.base + target_eip; /* 跳轉到目標地址執行 */

DO_GP_EXCEPTION:     /* 執行 #GP異常點 */
           … …

二、 使用call gate 進行控制權的轉移
使用call gate進行轉移控制,目的是建立一個利用gate進行向高權限代碼轉移的一種保護機制。gate符相當一個進入高權限代碼的一個通道。


對於指令 call cs:eip 來說:
★ 目標代碼的selector是一個門符的選擇子。用來獲取門描述符。
★ 門描述符含目標代碼段的selector及目標代碼的偏移量。
★ 目標代碼的ip值被忽略。因爲門符已經提供了目標代碼的偏移量。

1、 權限的檢查
★ 首先,必須要有足夠的權限來訪問gate符,所以:CPL <= DPLg(門符的DPL)且:RPL <= DPLg
★ 低權限代碼向高權限代碼轉移,所以,對於conforming類型的代碼段來說,必須CPL >= DPLs(目標代碼段的DPL
★ 對於nonconforming類型代碼段來說,必須CPL = DPLs
★ call 指令改變當前權限,而jmp指令不改變當前權限。

總結:

if ((CPL <= DPLg) && (RPL <= DPLg)) { /* 足夠的權限訪問門符 */
if (target.C == CONFORMING) {   /* 目標代碼屬於 conforming類型 */
    /* 向高權限級別代碼轉移控制 */
    if (CPL >= DPLs) {
           /* 通過訪問 */
      } else {
            /* 失敗,#Gp異常發生 */
    }

    } else {               /* 目標代碼屬於 nonconforming 類型 */
       /* 平級轉移 */
    if (CPL == DPLs) {
            /* 通過訪問 */
        } else {
             /* 失敗,#GP 異常發生 */
        }
}

} else {   /* 沒有足夠權限訪問門符 */
/* #GP 異常發生 */
}

2、 控制權的轉移
指令:call 1a:804304c (其中1a是call gate selector)

gate_selector = 0x1a;               /* call gate selector */
RPL = gate_selector.RPL;            /* 門符選擇子RPL */
CPL = CS.RPL;                     /* 當前代碼段低3位是CPL*/
CALLGATE_DESCRIPTOR call_gate_descriptor;      /* 門符的描述符 */
CODESEG_DESCRIPTOR target_cs_descritpor;      /* 目標代碼段的描述符 */
call_gate_si = gate_selector.SI;      /* 門符 selector 的索引 */
call_gate_ti = gate_selector.TI;      /* 門符selector的描述符表索引 */
                                                                         
/* 獲取call gate descriptor */
if (call_gate_ti == 0) {      /* TI爲0 就是參考到 GDT(全局描述符表) */
/* 以GDTR寄存器的base 爲基地址加上selector的索引乘以8即得出門符的描述符,門符的DPL就是門符的訪問權限 */
    call_gate_descriptor = GDTR.base + call_gate_si * 8;
} else {               /* 否則就是參考 LDT (局部描述符表)*/
     /* 以 LDTR寄存器的base 爲基地址得出目標段描述符 */
call_gate_descriptor = LDTR.base + call_gate_si * 8;
}

/* 獲取 target code segment descriptor */
target_cs = call_gate_descriptor.selector;    /* 獲取門符的目標代碼段選擇子 */
target_cs_si = target_cs.SI;         /* 目標代碼段的索引 */
target_cs_ti = target_cs.TI;                  /* 目標代碼描述符表索引 */
if (target_cs_ti == 0)
    target_cs_descriptor = GDTR.base + target_cs_si * 8;
else
    target_cs_descriptor = LDTR.base + target_cs_si * 8;

DPLg = call_gate_descriptor.DPL;     /* 獲取門符的DPL */
DPLs = target_cs_descriptor.DPL;     /* 獲取目標代碼段的DPL */

if (CPL <= DPLg &&RPL <= DPLg) {   /* 有足夠權限訪問門符 */
     if (target_cs_descriptor.type & 0x06) { /* conforming */
          if (CPL >= DPLs) {
                 /* 允許訪問目標代碼段 */
          } else {
                 /* #GP 異常產生 */
          }
     } else if (CPL == DPLs) { /* nonconforming */
         /* 允許訪問目標代碼段 */
     } else {
          /* 拒絕訪問,#GP 異常發生 */
          goto DO_GP_EXCEPTION;
     }
} else { /* 無權限訪問門符 */
     /* 拒絕訪問, #GP異常發生 */
     goto DO_GP_EXCEPTION;
}
/* 允許訪問 */
current_CS = target_cs;       /* 加載目標代碼段進入CS 寄存器 */
current_CS.RPL = DPLs;        /* 改變當前執行段權限 */
current_EIP = call_gate_descriptor.offset;   /* 加載EIP */
/* 跳轉到目標代碼執行 */
/* goto current_CS:current_EIP */
goto target_cs_descriptor.base + call_gate_descriptor.offset;
return;
DO_GP_EXCEPTION:         /* 執行異常 */

三、 使用中斷門或陷井門進行轉移
   中斷門符及陷井門必須存放在IDT中,IDT表也可以存放call gate。

1、 中斷調用時的權限檢查
   用中斷門符進行轉移時,所作的權限檢查同call gate相同,區別在於intterrupt gate 轉移不需要檢查RPL,因爲,沒有RPL需要檢查。
★ 必須有足夠的權限訪問門符,CPL <= DPLg
★ 向同級權限代碼轉移時,CPL == DPLs,向高權限代碼轉移時,CPL > DPLs

總結

if (CPL <= DPLg) { /* 有足夠權限訪問門符 */
    if (CPL >= DPLs) {
        /* 允許訪問目標代碼頭 */
    } else {
         /* 失敗,#GP異常發生 */
    }

} else {
/* 失敗,#GP異常發生 */
}

2、 控制權的轉移
   發生異常或中斷調用時
★ 用中斷向量在中斷描述符表查找描述符:中斷向量×8,然後加上IDT表基址得出描述符表。
★ 從查找到的描述符中得到目標代碼段選擇子,並在相應的GDT或LDT中獲取目標代碼段描述符。
★ 目標代碼段描述符的基址加上門符中的offset,確定最終執行入口點。


中斷或陷井門符轉移的總結:
例: int 0x80 指令發生的情況

vector = 0x80;
INTGATE_DESCRIPTOR gate_descriptor = IDTR.base + vector * 8;
CODESEG_DESCRIPTOR target_descriptor;
TSS tss = TR.base;               /* 得到TSS 內存塊 */
DPLg = gate_descriptor.DPL;
target_cs = gate_descriptor.selector;
if (CPL <= DPLg) {            /* 允許訪問門符 */

if (target_cs.TI == 0) {   /* index on GDT */
    target_descriptor = GDTR.base + target_cs.SI * 8;
} else {              /* index on LDT */
target_descriptor = LDTR.base + target_cs.SI * 8;
    }

DPLs = target_descriptor.DPL;


if (CPL > DPLs) {     /* 向高權限代碼轉移 */

    /* 根據目標代碼段的DPL值來選取相應權限的stack結構 */
    switch (DPLs) {
    case 0 :     /* 假如目標代碼處理0級,則選0級的stack結構 */
             SS = tss.ss0;
             ESP = tss.esp0;
             break;
        case 1:
             SS = tss.ss1;
            ESP = tss.esp1;
             break;
        case 2:
             SS = tss.ss2;
             ESP = tss.esp2;
             break;
    }

       /* 以下必須保護舊的stack結構,以便返回 */
    *--esp = SS;          /* 將當前SS入棧保護 */
    *--esp = ESP;         /* 將當前ESP入棧保護 */

} else if (CPL == DPLs) {
     /* 同級轉移,繼續向下執行 */
} else {
    /* 失敗,#GP異常產生,轉去處理異常 */
}


*--esp = EFLAGS;          /* eflags 寄存器入棧 */

     /* 分別將 NT、NT、RF及VM標誌位清0 */
EFLAGS.TF = 0;           
EFLAGS.NT = 0;
EFLAGS.RF = 0;
EFLAGS.VM = 0;

if (gate_descriptor.type == I_GATE32) { /* 假如是中斷門符 */
EFLAGS.IF = 0;          /* 也將IF標誌位清0,屏蔽響應中斷 */
     }
            
     *--esp = CS;              /* 當前段選擇子入棧 */
     *--esp = EIP;             /* 當前EIP 入棧 */
CS = target_selector;      /* 加載目標代碼段 */
CS.RPL = DPLs;            /* 改變當前執行權限級別 */
EIP = gate_descriptor.offset; /* 加載進入EIP */

/* 執行中斷例程 */
goto target_descritptor.base + gate_descriptor.offset;

} else {
/* 失敗,#GP 異常產生,轉去處理異常 */
}

■ 堆棧的切換


控制權發生轉移後,處理器自動進行相應的堆棧切換。
1、 當轉向到同權限級別的代碼時,不會進行堆棧級別的調整,也就是不進行堆棧切換。
2、 當轉向高權限級別時,將發生相應級別的堆棧切換。從TSS塊獲取相應級別的stack結構。

例:假如當前運行級別CPL爲2時,發生了向0級代碼轉移時:

TSS tss = TR.base;         /* 從TR寄存器中獲取TSS 塊 */
CPL = 2;                 /* 當前運行級別爲2 級*/
DPL = 0;                 /* 目標代碼需要級別爲 0 級 */

/* 根據目標代碼需要的級別進行選取相應的權限級別的stack結構 */
switch (DPL) {
case 0:
     SS = tss.ss0;
     ESP = tss.esp0;
     break;
case 1:
     SS = tss.ss1;
     ESP = tss.esp1;
     break;
case 2:
     SS = tss.ss2;
     ESP = tss.esp2;
     break;
}
*--esp = SS;          /* 保存舊的stack結構 */
*--esp = ESP;
/* 然後再作相當的保存工作,如保存參數等 */
*--esp = CS;         /* 最後保存返回地址 */
*--esp = EIP;

■ 控制權的返回


當目標代碼執行完畢,需要返回控制權給原代碼時,將產生返回控制權行爲。返回控制權行爲,比轉移控制權行爲簡單得多。因爲,一切條件已經在交出控制權之前準備完畢,返回時僅需出棧就行了。

1、 near call 的返回
近調用情況下,段不改變,即CS不改變,權限級別不改變。僅需從棧中pop返回地址就可以了。


2、 直接控制權轉移的返回(far call或far jmp)
   直接控制權的轉移是一種不改變當前運行級別的行爲。只是發生跨段的轉移。這時,CS被從棧中pop出來的CS值加載進去,處理器會檢查CPL與這個pop出來的選擇子中的RPL進行檢查,相符則返回。不相符則發生 #GP異常。
  
總結:假如當前運行的目標代碼執行完畢後,將要返回。這時CPL爲2

CPL = 2;      /* 當前代碼運行級別爲 2 */
… …
EIP = *esp++;   /* pop出原EIP 值 */
CS = *esp++;    /* pop出原CS值 */
if (CPL == CS.RPL) {  
/* CS.RPL 代表是原來的運行級別,與CPL相符則返回 */
return ;
} else {
/* #GP異常產生,執行異常處理 */
}

3、 利用各種門符進行向高權限代碼轉移後的返回
從高權限代碼返回低權限代碼,須從stack中pop出原來的stack結構。這個stack結構屬於低權限代碼的stack結構。然後直接pop 出原返回地址就可以了。

總結:

CPL = 0;        /* 當前運行級別爲 0 級 */
… …
EIP = *esp++;    /* 恢復原地址 */
CS = *esp++;     /* 恢復原地址及運行級別 */

ESP = *esp ++;     /* 恢復原stack結構 */
SS = *esp++;     /* 恢復原stack 結構,同時恢復了原stack訪問級別 */
return ;       /* 返回 */
 
************************************************************************************
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的作用,訪問數據段或堆棧段時,默認用CPL和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的變化,即引起代碼執行特權級的躍遷,這是目前得知的改變執行特權級的唯一辦法,如果各位讀者還知道其他方法請留言告訴我。

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