int 6 取代不了 ud2:trap 回報的是下一條指令
The Old New Thing(Raymond Chen) · 2026-10-19
想用手動執行 int 6 來模擬 x86 的「無效指令」例外,看起來和 ud2 沒差,但它取代不了 ud2。兩者進到同一個 interrupt vector 6,差別在於 int 6 是 trap,而真正的 #UD 是 fault,handler 拿到的指令位址不同。Raymond Chen 在 2026-10-19 的文章裡解釋了這個區別。
原本的問題
前一篇文章(2026-09-10)提到,ud2 是架構上保證會觸發「invalid opcode」例外的未定義指令,編譯器常拿來標記「不可達」的程式碼。例如標了 [[noreturn]] 的函式若意外返回,編譯器在 call 後面放一條 ud2,讓程式當場崩潰,而不是繼續執行下一個函式的內容。
有人會問:int 6 同樣是兩個位元組,interrupt vector 6 又正好是 invalid opcode vector,為什麼不直接用它?答案是 fault 和 trap 在回報位址上的語意相反。
核心差異:fault 與 trap
真正的 #UD(ud2) | 手動 int 6 | |
|---|---|---|
| 類型 | fault | trap |
| fault frame 內的位址 | 出錯指令本身 | 出錯指令的下一條 |
| 忽略後會怎樣 | 指令重新執行,再次 fault | 從下一條指令繼續 |
| user mode 能否發出 | 可以 | 作業系統一律拒絕 |
文章的說明是:fault 代表核心有機會修復並重試的錯誤,例如 page fault 可以把記憶體換入,access violation 可以改頁面保護。trap 則是回報「已經發生的操作」,讓你做完特殊處理後,從下一條指令繼續。
即使某些 fault 不太可能被修復,仍然以 fault 回報,所以想越過它就得多做一步。如果直接忽略,指令會重新執行,再次 fault。
為什麼模擬會露餡
如果手動發出 int 6 來假裝 #UD,handler 會以為這是 CPU 產生的 fault,預期 fault frame 裡的指令指標指向那條未定義指令。但 int 存進去的是下一條指令的位址。
; handler 看到的情況
fault frame IP -> xor eax, eax ; 合法指令,為什麼會 #UD?文章舉的情境是:handler 檢視該位址,看到一條完全合法的 xor eax, eax,不明白為什麼會有 invalid opcode fault。需要檢查出錯指令的場合之一,是在舊處理器上模擬缺少的指令,這時 handler 必須讀到真正出錯的那個位址。
權限問題:user mode 根本發不了
在 protected mode,發出 int 6 需要權限。interrupt descriptor table 對每個 interrupt 都描述了該做什麼,以及哪個 CPU privilege level 可以明確呼叫它。為了避免 user-mode 程式發出 int 6 而混淆 #UD handler,所有作業系統都不給 user mode 發出 int 6 的權限。
所以要得到 #UD fault,只有一條路:執行一條真正的未定義指令,這正是 ud2 存在的理由。
影響範圍
- 寫 inline asm 或手刻 trap 的人:想在 user-mode 程式裡製造「不可達即崩潰」,用
ud2,不要用int 6。後者在 user mode 會因權限被拒絕,即使成功也會讓 handler 讀到錯的位址。 - 寫 #UD handler 或模擬器的人:fault frame 的位址就是出錯指令,可以依此解碼並模擬;不要假設有人會用
int 6偽造,它不會被當成 fault。 - 看 crash dump 的人:在編譯器輸出裡看到
ud2,通常代表執行流到了編譯器認為不可達的地方。
文章未提供 ud2 的機器碼位元組或各作業系統 handler 的實作細節,以上僅依原文的架構層面說明。
原始來源:Why do we need ud2 when int 6 is right there?、Why is the x86 undefined instruction called ud2? Why 2?