跳轉到

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 不修?#

學生常問:改掉這些指令不就好了?答案是三重鎖鏈:

  1. 相容性鎖鏈:數千萬台 PC、數十億行二進制程式碼依賴這些行為。任何一條指令行為的改變都可能破壞現有軟體——x86 的帝國就是靠「永不背叛舊程式」建立起來的。
  2. 市場鎖鏈:1990 年代的 Intel 正忙著和 AMD 打主頻戰爭、對付 Pentium FDIV 醜聞(1994)、推動 MMX/SSE 多媒體指令。虛擬化?那是一個年出貨量趨近於零的學術議題。
  3. 認知鎖鏈:PC 的使用模型就是「一人一機一 OS」。Intel 的架構師沒有理由為一個不存在的市場,去承擔動搖相容性的風險。

歷史關聯:這正是第 2 章結尾的翻版——1970 年代硬體設計者「先想好特權模型」,1990 年代硬體設計者「先想好相容性」。制度的慣性(相容即正義)壓倒了理論的清晰(敏感⊆特權)。而打破這條鎖鏈的,不是 Intel,而是一群無視警告的史丹佛人。