3.1 x86 的歷史包袱:夾在相容性與保護模式之間的不可虛擬化指令#
一個沒有計畫的王朝#
x86 的偉大與它的病,來自同一個源頭:它從來沒有機會被「設計」,只有機會被「繼承」。
1978 8086:16 位元,無特權等級,無記憶體保護
(設計者只想著:要能相容 8080 的八位元生態)
1982 80286:加上保護模式,但切回真實模式必須「重開機」
1985 80386:32 位元、分頁、Ring 0–3 四級特權
——第一次具備現代保護模型
1990s 每一代都必須能直接執行上一代的機器碼
1978 年的設計者面對的是 5 MHz、幾 KB 記憶體的晶片,想的是個人電腦——沒有人想過虛擬化。當 1985 年的 386 補上保護模式時,為了相容,那些「史前指令」全部原封不動地保留了下來。
體檢報告:17 條漏網之魚#
用普佩克–戈德堡標準檢驗 386 以後的 x86,問題指令集中在 16 位元真實模式與系統管理相關的區域。歷史上被反覆點名的「不可虛擬化指令」約有十餘條,以下是代表性的清單(數字為不同統計口徑,一般說法是「十餘條」):
| 指令 | 功能 | 為什麼致命 |
|---|---|---|
SGDT / SIDT | 讀取 GDT / IDT 基址 | 在 Ring 3 可執行且不報錯,直接泄露系統狀態 |
SMSW | 讀取 CR0(含 PE 保護模式位元) | Ring 3 可執行不報錯,guest 可得知真實 CPU 狀態 |
POPF | 從堆疊彈出旗標暫存器 | 在 Ring 3 執行時部分旗標會被靜默忽略(如 IF 中斷旗標),行為隨特權等級改變 |
PUSHF | 壓入旗標 | 可被 guest 用來偵測自身是否處於虛擬環境 |
LGDT / LIDT / LLDT | 載入描述表基址 | 敏感,但陷阱行為在特定模式下不可靠 |
CLI / STI(特定條件) | 開關中斷 | IOPL 位元造成的條件性行為 |
IN / OUT(IOPL 判定) | I/O 存取 | 特權判斷依 IOPL 而異,模式混雜 |
其中最有名的是 POP FS / POP GS 一族:在 Ring 3 載入區段暫存器時,某些版本的處理器會靜默忽略部分欄位而不觸發陷阱。
POPF:教科書上的經典反例#
POPF 是論文反覆引用的明星罪證。在 x86 上,它嘗試修改 IF(中斷允許旗標)——這是典型的敏感操作。但 386 之後的硬體規則是:
Ring 0 執行 POPF → 修改 IF 成功 (特權行為)
Ring 3 執行 POPF → 不觸發陷阱!
→ 其他旗標正常更新,
IF 卻被靜默忽略
災難在於:guest OS 在 Ring 1(若用環壓縮法跑 guest 核心)執行 POPF 關中斷時,CPU 不會報錯,卻什麼都沒做。中斷依然開著,VMM 一無所知,系統狀態悄然失衡。這不是「模擬得很慢」的問題,而是「幻覺出現裂縫」的正字標記。
為什麼 Intel 不修?#
學生常問:改掉這些指令不就好了?答案是三重鎖鏈:
- 相容性鎖鏈:數千萬台 PC、數十億行二進制程式碼依賴這些行為。任何一條指令行為的改變都可能破壞現有軟體——x86 的帝國就是靠「永不背叛舊程式」建立起來的。
- 市場鎖鏈:1990 年代的 Intel 正忙著和 AMD 打主頻戰爭、對付 Pentium FDIV 醜聞(1994)、推動 MMX/SSE 多媒體指令。虛擬化?那是一個年出貨量趨近於零的學術議題。
- 認知鎖鏈:PC 的使用模型就是「一人一機一 OS」。Intel 的架構師沒有理由為一個不存在的市場,去承擔動搖相容性的風險。
歷史關聯:這正是第 2 章結尾的翻版——1970 年代硬體設計者「先想好特權模型」,1990 年代硬體設計者「先想好相容性」。制度的慣性(相容即正義)壓倒了理論的清晰(敏感⊆特權)。而打破這條鎖鏈的,不是 Intel,而是一群無視警告的史丹佛人。