有4個層次的特權級,從高到低依次是:0級、1級、2級、3級,切換特權級是指從0級轉移到1級、或從1級轉移到3級,總之,是指從一個特權級轉移到了另外一個不同的特權級,
學習特權級切換,關鍵知識點是:
- 兩個指令
call和iret, - 一個CPU特性:特權級變換時會將一個特權級的堆疊復制到另外一個特權級堆疊,
從低到高
只有使用呼叫門才能從低特權級轉移到高特權級,更具體地說,是使用陳述句call 門選擇子,
門描述符
門選擇子的結構和段選擇子一致,只不過它指向的是門描述符而不是段描述符,
門描述符和段描述符占用的記憶體空間相同,都是8個位元組,64個bit,可后者包含的元素是:
- 目標代碼段選擇子,
- 在目標代碼段中的偏移量,
- ParamCount,這是什么?后面再解釋,
- 門描述符的屬性,
代碼
使用呼叫門從低特權級轉移到高特權級的代碼是:
push ax
push bx
;SelectorGate 指向一個高特權級的目標代碼段
call SelectorGate:0
ParamCount
入堆疊了2個元素,2就是上文提到的ParamCount,可以把它理解成函式的引數,在門描述符中,ParamCount占用5個bit位,能表示的最大值是2的5次方-1,即31,這意味著,使用一個呼叫門,最多能入堆疊31個元素,
堆疊復制
假定,上面的代碼的能成功從低特權級代碼段L轉移到高特權級代碼段H,L和H是不同的代碼段,各自的堆疊也不同,這不是必須的,L和H共用一個堆疊,也不是不行,這樣的話,在H中操作堆疊可能會破壞L中的堆疊(總之,存在這種可能),所以,代碼段擁有獨立的堆疊更好,
那么,問題來了,入堆疊操作發生在L中,被入堆疊元素存在于L的堆疊LS,在H中怎么從LS中獲取資料呢?也許存在方法,但一定很繁瑣,現實中,CPU會自動把LS中的元素復制到H的堆疊中,并非復制LS中的全部元素,而是從LS的堆疊頂開始,復制ParamCount個元素,這個復制操作發生在call執行的時候,
示意圖
短呼叫是段內部的呼叫,長呼叫是段之間的呼叫,長呼叫才能從低特權級轉移到高特權級,
短呼叫和長呼叫示意圖之間的差別,僅僅在于后者把cs入堆疊了,cs是呼叫者的代碼段選擇子,
示意圖中的堆疊,是H的堆疊HS,
短呼叫
| call執行前----> | 高地址 | |
|---|---|---|
| ss | ||
| esp | <-----------堆疊 | |
| 引數二(ax) | ||
| 引數一(bx) | ||
| call執行后----> | eip | |
長呼叫
| call執行前----> | 高地址 | |
|---|---|---|
| ss | ||
| esp | <-----------堆疊 | |
| 引數二(ax) | ||
| 引數一(bx) | ||
| cs | ||
| call執行后----> | eip | |
CPU作業流程--呼叫門
呼叫門的運行程序,涉及LS和HS兩個堆疊,上面的示意圖只畫出了HS的狀態,不足以闡述呼叫門的整個流程,本小節再用文字詳細說明呼叫門的運行程序,
TSS
先介紹一個新東西,TSS,任務狀態暫存器,一個暫存器,CPU在切換任務的時候,能用TSS給切換下來的任務建立一個快照,這個快照包含一個任務的所有暫存器資料,不過,大部分作業系統嫌棄這種切換方式太消耗時間,并沒有完全按照CPU廠商的意圖使用TSS,Linux系統也是如此,
在TSS中,包含ss0、esp0、ss1、esp1、ss2、esp2三組資料,正好對應0特權級、1特權級、2特權級三個層次的特權級,
特權級不是有四個層次嗎?為什么沒有對應3特權級的那一組資料呢?TSS的作用是為低特權級向高特權級轉移時提供高特權級的堆疊,3特權級是最低特權級,沒有更低的特權級向它轉移,
假如,從3特權級向0特權級轉移,CPU會從TSS中選擇ss0、esp0作為0特權級代碼段的堆疊,
流程
使用call SelectorGate:0實作低特權級向高特權級轉移的流程如下(不敘述CPL等特權級檢查流程,假設滿足這些條件):
- 執行
call陳述句時, - 把當前代碼段的堆疊LS的
ss_old、esp_old臨時保存起來, - 從門選擇子指向的目標代碼段中獲取DPL,根據DPL的值,在TSS中選擇
ss、esp,將堆疊指向的新堆疊,例如,DPL的值是0,選擇ss0、esp0, - 把
ss_old、esp_old中的入堆疊到新堆疊中, - 把LS中的堆疊元素復制到新堆疊中,復制規則是:從LS的堆疊頂開始,復制
ParamCount個元素, - 依次入堆疊
cs、eip,
小結
從低特權級轉移到高特權級的方法是,使用呼叫門,具體陳述句是call SelectorGate:0,
不能使用jmp,jmp只能在實作短呼叫,在同一個特權級轉移,因為jmp不會將下一條指令的地址存盤到新特權級的堆疊中,這意味著,jmp是一個有去無回的指令,從低特權級轉移到高特權級的場景,一般是用戶行程求助作業系統完成某種功能,需要再次回傳用戶行程,
從高到低
電腦開機后,CPU的特權級是0,這是從BIOS那里寄存下來的,這種知識似乎無用,懶得多說,
前文講了從低特權級切換到高特權級的方法,可CPU從開始作業的那一刻起,一直都是在0特權級,這樣說來,如果要動手實作從低特權級轉移到高特權級,應該先實作從高特權級轉移到高特權級,
怎么實作?
前文已經埋下了伏筆,call指令會將呼叫者(低特權級)的cs(選擇子)和eip(偏移量)入堆疊到被呼叫者(高特權級)的堆疊中,從堆疊中獲取呼叫者(低特權級)的cs(選擇子)和eip(偏移量),就能從高特權級轉移到低特權級,完成這項作業,只需一個指令而已,iretf,
代碼
;特權級是3
push ax
push bx
call SelectorGate:0
mov ax, 5
call SelectorGate:0呼叫下面的代碼,
;特權級是0
;呼叫門選擇子指向的目標代碼段,高特權級代碼段
;一些操作,示范,不必理會具體功能
mov al, 'A'
mov ah, 0Fh
mov [gs:(80*20+20)*2], ax
iretf
iretf執行后,CPU會繼續執行mov ax, 5,mov ax, 5就是被呼叫者堆疊中的cs:eip指向的指令,
示意圖
還是畫兩個和上面call指令類似的堆疊圖吧,
短呼叫回傳
| ret執行后 | 高地址 |
|---|---|
| 呼叫者ss | |
| 呼叫者esp | |
| 引數一 | |
| 引數二 | |
| ret執行前 | 呼叫者eip |
長呼叫回傳
| retf執行后 | 高地址 |
|---|---|
| 呼叫者ss | |
| 呼叫者esp | |
| 引數一 | |
| 引數二 | |
| 呼叫者cs | |
| retf執行前 | 呼叫者eip |
作業流程--iretf
長呼叫回傳使用iretf指令將上面的堆疊S中的元素出堆疊,具體流程如下:
- 從S中獲取呼叫者cs、呼叫eip,并加載到當前cs、eip中,
- 若
iretf含有引數,esp增加引數個數跳過這些引數, - 繼續出堆疊,把呼叫者esp、呼叫者ss加載到當前
esp、ss中,此時會切換到呼叫者堆疊, - 若
iretf含有引數,增加esp的值以跳過引數(在call前,引數也壓入了呼叫者堆疊中), - 最后執行
cs:eip代碼,
iretf
- RET:可能是近回傳,也可能是遠回傳,
- RETN:近回傳指令,
- RETF:遠回傳指令,
- RET6:子程式回傳后,(SP)←(SP) + 6,
沒有找到iretf的權威資料,上面的資料也沒有驗證,先擱置吧,
疑問
不使用呼叫門能不能轉移特權級?
使用jmp只能在同特權級跳轉,
一致代碼段,call只能轉移到比當前特權級高或相等的特權級,
非一致代碼段,call只能在相同特權級跳轉,
什么時候檢查特權級
沒有弄明白,好像不重要,
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/265255.html
標籤:其他
上一篇:SD卡命令詳解
下一篇:將Kali安裝到U盤內
