當異常處理程序不在鏈表頭部時,系統需移除其前的所有幀,此過程稱「展開」。Visual C++ 透過 __global_unwind2 封裝 RtlUnwind (實際轉發至 NTDLL.dll) 完成。核心步驟:
FS:[4]、FS:[8] 取得堆疊邊界,驗證幀合法性。EXCEPTION_RECORD,ExceptionCode = STATUS_UNWIND,Flags = EXCEPTION_UNWINDING。RtlCaptureContext 捕獲當前上下文 (寄存器狀態)。EXCEPTION_REGISTRATION 鏈表,對每個幀呼叫 RtlpExecuteHandlerForUnwind 執行清理 (如呼叫解構函數),並透過 RtlpUnlinkHandler 移除幀。pRegistrationFrame),呼叫 NtContinue 恢復執行;若遍歷完仍未找到,拋出 STATUS_INVALID_UNWIND_TARGET。安全檢查包含:堆疊範圍、DWORD 對齊、幀順序。任何違規觸發 EXCEPTION_NONCONTINUABLE 終止進程。
當鏈表無人處理異常,BaseProcessStart 的 __except 區塊呼叫 UnhandledExceptionFilter。關鍵邏輯:
EXCEPTION_ACCESS_VIOLATION 若因寫入 .rsrc 區,自動修改頁面屬性允許寫入並重試。NtQueryInformationProcess 檢查 ProcessDebugPort,若被除錯返回 EXCEPTION_CONTINUE_SEARCH 喚醒除錯器。SetUnhandledExceptionFilter 設定回呼,優先執行。NtRaiseHardError 顯示「應用程式錯誤」對話框。若註冊表 AeDebug\Auto=1,直接啟動 JIT 除錯器 (CreateProcess + 事件同步)。點選「確定」→ 返回 EXCEPTION_EXECUTE_HANDLER → ExitProcess 終止進程;點選「取消」→ 附加除錯器 → 返回 EXCEPTION_CONTINUE_SEARCH。
用戶模式異常入口為 NTDLL!KiUserExceptionDispatcher (跳過核心模式 IDT 處理)。偽代碼核心:
if (RtlDispatchException(pExcptRec, pContext))
NtContinue(pContext, 0);
else
NtRaiseException(pExcptRec, pContext, 0);
RtlDispatchException 遍歷鏈表,嚴格驗證堆疊完整性,呼叫 RtlpExecuteHandlerForException (首次調度) 或 RtlpExecuteHandlerForUnwind (展開)。兩者皆為 ExecuteHandler 前端,差異在 EDX 傳遞不同處理常式指標。ExecuteHandler 以 SEH 保護自身呼叫用戶回呼,若回呼中再發生異常,返回 DISPOSITION_NESTED_EXCEPTION 或 DISPOSITION_COLLIDED_UNWIND 觸發嚴重錯誤處理。
KiUserExceptionDispatcher()
RtlDispatchException()
RtlpExecuteHandlerForException()
ExecuteHandler() → __except_handler3()
scopetable filter-expression()
__global_unwind2()
RtlUnwind()
RtlpExecuteHandlerForUnwind()
scopetable __except block()
x64 環境下,Prolog 負責保存非易失性寄存器、配置固定堆疊、建立幀指標;Epilog 則逆向恢復。展開代碼透過掃描 Epilog 模式 (add RSP, const 或 lea RSP, const[FPReg] + pop 序列 + ret/jmp) 實現可靠展開。__chkstk 輔助函數處理大於 4KB 的堆疊配置探測。