《80X86匯編語言程序設計教程》十九 操作系統類指令與輸入輸出保護
1、 通常只在操作系統代碼中使用,80386支持4個特權等級,操作系統指令也可分3種:實模式和任何特權級下可執行指令、實模式及特權級0下可執行的指令和僅在保護模式下執行的指令。
1) 實模式和任何特權級下可執行的指令
a)存儲全局和中斷描述符表寄存器指令
GDT與IDT整個系統各只有一張,它們的定位信息分別保存在GDTR與IDTR中,這兩個寄存器的值可以被保存。須註意,LDT表示任務私有,存儲LDTR值的指令不屬於這一類。
i)存儲全局描述符表寄存器指令:SGDT DST
DST是48位(6字節)存儲器操作數,執行後GDTR的16位界限值存入DST低字,而GDTR中32位基地址存入DST高雙字。對標誌位無影響。
ii)存儲中斷描述符表寄存器指令:SIDT DST
與“SGDT DST”類似。
b)存儲機器狀態字指令:SMSW DST
DST可以是16位通用寄存器或存儲單元。對標誌位無影響。
說明:為了兼容80286指令集(386的CR0低字節等同於286機器狀態字),在386中,存儲機器字應該使用存儲CR0寄存器指令。
2) 實模式及特權級0下可執行的指令
關鍵寄存器的設置指令等,在保護模式下CPL不為0的指令執行它們將引發錯誤碼為0的通用保護故障。虛擬8086模式下(CPL = 3)同樣如此。
a)清任務切換標誌指令:CLTS
任務切換時,CR0的TS位(任務切換標誌位)自動被置1(參考“《80X86匯編語言程序設計教程》十二 任務狀態段、控制門和控制轉移”),該指令功能是把TS標誌位清0。不影響其它標誌位。
b)暫停指令:HLT
該指令使處理器暫停執行,只有在接受一個已經啟用的中斷,或者讓系統復位,才重新啟動。對標誌位沒有影響。
c)裝載全局描述符表寄存器指令:LGDT SRC
SRC是48位(6字節)存儲器操作數,執行後偽描述符SRC(PDESC結構)的低字送GDTR低字(段界限),高雙字送GDTR高雙字(段基址)。對標誌位無影響。
d)裝載中斷描述符表寄存器指令:LIDT SRC
與“LGDTR”類似。
e)裝載機器狀態字指令:LMSW SRC
SRC可以是16位通用寄存器或存儲單元。該指令將SRC裝入機器狀態字(CR0的低16位),不影響標誌位。同樣,是為了兼容286,386不應該用它。
f)控制寄存器數據傳送指令:MOV DST,SRC
實現386控制寄存器和32位通用寄存器之間的數據傳送。所以,DST和SRC可以是3個控制寄存器(CR0、CR2、CR3---參考“《80X86匯編語言程序設計教程》八 80386程序設計基礎”)和任一32位通用寄存器,但不能同時為控制寄存器。對標誌位門影響。
g)調試寄存器數據傳送指令
規則同上。調試寄存器為DR0~DR7(參考“《80X86匯編語言程序設計教程》八 80386程序設計基礎”)。
h)測試寄存器數據傳送指令
規則同上。測試寄存器為TR6和TR7(參考“《80X86匯編語言程序設計教程》八 80386程序設計基礎”)。
3) 只能在保護模式下執行的指令
只能在保護模式下執行,在實模式下執行將引起非法操作碼故障(向量號6)。
a)裝載局部描述符表寄存器指令:LLDT SRC
SRC可以為16位通用寄存器或存儲單元(代表的選擇子必須指示GDT中類型為LDT的描述符,為0則表示空選擇子,及不使用LDT),不影響標誌位。CPL不為0時(錯誤碼為0)、選擇子不指示GDT中描述符或描述符類型不是LDT時( 錯誤碼由該選擇子構成),執行它將產生通用保護故障。
b)存儲局部描述符表寄存器指令:SLDT DST
規則同上。
c)裝載和存儲任務寄存器指令
任務寄存器TR指示當前任務狀態段TSS(參考“《80X86匯編語言程序設計教程》十二 任務狀態段、控制門和控制轉移”)。隨著任務的切換而切換,如果任務嵌套,那麽TR原值作為鏈接字保存在新任務TSS中。
i) 裝載任務寄存器指令:LTR SRC
SRC為16位通用寄存器或存儲單元。SRC指示的選擇子不能為空,必須索引位於GDT中的描述符,且類型為TSS。該指令不影響標誌位。CPL不為0時(錯誤碼為0)、選擇子不指示GDT中描述符或描述符類型不是LDT時(錯誤碼由該選擇子構成),執行它將產生通用保護故障。
ii)存儲任務寄存器指令:STR DST
DST規則同上,不影響標誌位。
d)調整申請特權級指令:ARPL OPRD1,OPRD2
OPRD1為16位通用寄存器或存儲單元,OPRD2為16位通用寄存器。該指令用選擇子OPRD2的申請特權級(RPL)去檢查選擇子OPRD1的RPL,如果OPRD1.RPL < OPRD2.RPL,那麽ZF = 1,OPRD1.RPL = OPRD2.RPL;否則ZF = 0。二者都可為空,不影響其它標誌位。
e)裝載存取權指令:LAR OPRD1,OPRD2
兩操作數都可以為16位或32位通用寄存器,OPRD2還可以是存儲單元,但它們尺寸必須一樣。如果選擇子OPRD2(32位則使用低16位)所指示描述符滿足下述條件,那麽ZF = 1,並把描述符屬性字段裝入OPRD1;否則ZF = 0,OPRD1不變。
i)在描述符表範圍內
ii)為存儲段描述符或系統段描述符,或任務門、調用門描述符
iii)CPL和OPRD2.RPL都不大於OPRD2.DPL
描述符參考“《80X86匯編語言程序設計教程》九 分段管理機制及純DOS環境搭建”與“《80X86匯編語言程序設計教程》十二 任務狀態段、控制門和控制轉移”。結果是高4字節與00FFXFF00相與的結果,X表示未定義,如果OPRD1為16位,那麽只取結果低2字節(無G位、AVL位)。除ZF不影響其它標誌位。
f)裝載段界限指令:LSL OPRD1,OPRD2
規則同上,區別在於轉載的是段界限字段且條件第二點限制更加嚴格,不能為門描述符。在滿足條件下,裝載到OPRD1的OPRD2所指示描述符的界限字段值以字節為單位。如果描述符中界限字段以4K為單位(G = 1),那麽裝入到OPRD1時左移12位,空出的位全部填1。如果使用16位操作數,只有段界限低16位被裝載到OPRD1。除ZF不影響其它標誌位。
g)讀寫檢驗指令
檢查在當前特權級上指定的段是否能讀寫,從而避免不必要的異常。
i)讀檢驗指令:VERR OPRD
OPRD可以是16位或32位通用寄存器和存儲單元。如果為32位則使用低16位,功能是判斷OPRD選擇子指示的段在當前CPL是否可讀,如果選擇子合法,且在當前CPL可讀,那麽ZF置1,否則ZF清0。除ZF不影響其它標誌位。
ii)寫檢驗指令:VERW OPRD
規則同上,只不過檢查的屬性是是否可寫。
4) 特權指令
保護模式下只有CPL = 0才能執行的指令,否則引發通用保護異常。特權指令在構造完善保護機制上起重要作用。總結如下:
指令 |
功能 |
指令 |
功能 |
CLTS |
清除CR0的TS位 |
LTR |
裝入TR |
HLT |
停機 |
MOV CRn,reg |
裝入控制寄存器 |
LGDT |
裝入GDTR |
MOV reg,CRn |
保存控制寄存器 |
LIDT |
裝入IDTR |
MOV DRn,reg |
裝入測試寄存器 |
LLDT |
裝入LDTR |
MOV reg,DRn |
保存測試寄存器 |
LMSW |
裝入MSW(CR0低16位) |
|
|
可見,設置GDTR、IDTR和LDTR是特權指令,而存儲它們不是。而設置和存儲控制和測試寄存器都是特權指令。
2、 輸入/輸出保護
1) 輸入/輸出保護
a)I/O敏感指令
輸入/輸出特權級(I/O Privilege Level)規定可執行I/O有關指令和訪問I/O空間地址的最外層特權級(在標誌寄存器EFLAGS中,參考“《80X86匯編語言程序設計教程》八 80386程序設計基礎”)。I/O許可位圖(在TSS中)規定I/O空間哪些地址可以在任何特權級執行的代碼訪問。I/O敏感指令如下:
指令 |
功能 |
保護模式下執行條件 |
CLI |
清除EFLAGS中IF位 |
CPL <= IOPL |
STI |
設置EFLAGS中IF位 |
CPL <= IOPL |
IN |
從I/O地址讀出數據 |
CPL <= IOPL或I/O位圖允許 |
INS |
從I/O地址讀出字符串 |
CPL <= IOPL或I/O位圖允許 |
OUT |
從I/O地址寫入數據 |
CPL <= IOPL或I/O位圖允許 |
OUTS |
從I/O地址寫入字符串 |
CPL <= IOPL或I/O位圖允許 |
如果權限不夠,則引發通用保護異常。每個任務有自己的EFLAGS與TSS,所以各個任務IOPL可以有不同,並且可定義不同的I/O許可位圖。註意:實模式下全部可執行。
b)I/O許可位圖
由二進制位串組成,每一位對應一個I/O地址,如果m位為0,那麽I/O地址m可由本任何特權等級代碼的程序訪問,否則只能在IOPL特權級或更內層特權級執行的程序訪問,不然引發通用保護異常。 一條I/O指令最多涉及4個I/O地址(如IN EAX,71H),只有涉及到的全部I/O許可位為0才能順利訪問。386支持I/O地址空間大小64K,所以I/O許可位有效部分最大為8KB。當前任務使用的I/O許可位圖在TSS低端64K(用16位偏移,最大為64K)字節內,位串以字節為存儲單位,所以要存儲整個I/O許可位圖時,偏移盡量保證低於64K – 8K = 56K。
c)I/O訪問許可檢查細節
步驟如下:
i)CPL <= IOPL是否成立,成立則直接跳轉到第8步直接進行I/O訪問
ii)取得位圖開始偏移(TSS的I/O許可位圖I偏移字段---TSS內偏移66H字節單元)
iii)計算字節偏移(I/O地址值右移3位---即除以8)
iv)計算位偏移以形成屏蔽碼值(I/O地址右移出來的3位放字單元)
v)字節是否越界,是則引發通用保護異常(位圖偏移+字節偏移+1<=段界限)
vi)從位圖中讀出兩個字節(讀寫最快)
vii)進行位檢查,不通過則引發通用保護異常
ix)進行I/O訪問
在讀取時,總是讀取兩個字節,由於I/O訪問最多同時訪問4個連續端口,最多也是分布在連續2個字節之內,所以在判斷是否越界時要也僅要加1處理,為了在判斷I/O許可位圖最高字節上述流程同樣適用,必須在I/O許可位圖最後添加一個全1字節0ffh。I/O許可位圖開始偏移加上8K所得值與TSS界限值二者較小者決定I/O許可位圖有效末端偏移。當許可位圖開始偏移大於56K時,將有部分位越過位圖界限,從而無法因無法訪問而觸發通用保護異常(那部分位被認為是全1)。利用這個特點,可以大大減小I/O許可位圖占用的存儲單元,從而大大減小TSS。如:
1 ;演示任務任務狀態段(TSS) 2 DemoTSSSeg segment para use16 3 DTSS TASKSS<> ;TSS低端部分 4 db 100h/8 dup(0ffh) ;對應I/O端口00H~0FFH 5 db 100h/8 dup(0) ;對應I/O端口100H~1FFH 6 db 0ffh ;IO許可位結束標誌 7 DemoTSSLen = $ - DemoTSSSeg 8 DemoTSSSeg
上面的TSS只含有許可位圖最初的200H位,對應端口0~1FFH,而其它位都被認為是1。之前實例中使用的格式,高度區只帶IO許可位結束標誌表示所有的位都為1。
2) 重要標誌保護
386對EFLAGS標誌中的IOPL、IF和VM這3個字段的處理比較特殊,只有較高權限等級的代碼才能通過IRET、POPF、CLI和STI等指令來改變它們。下面是不同特權等級下對3個字段的處理情況:
特權等級 |
標誌字段 |
||
VM |
IOPL |
IF |
|
CPL = 0 |
可變(除POPF指令外) |
可變 |
可變 |
0 < CPL <= IOPL |
不變 |
不變 |
可變 |
CPL > IOPL |
不變 |
不變 |
不變 |
可見,只有CPL = 0的情況下才能修改VM和IOPL,必須在IOPL同級或者更內層才能修改IF。須註意,在特權級不滿足的條件下,其中IRET和POPF指令試圖更改這3個字段並不引發異常,只是試圖的修改操作並不會被成功執行。此外,POPF總是不能改變VM位,而PUSHF指令總是把0壓入到VM位。
《80X86匯編語言程序設計教程》十九 操作系統類指令與輸入輸出保護