windows C++ 異常調用棧簡析

楔子以win11 + vs2022運行VC++ 編譯觀察的結果 。如果安裝了Visual Studio 2022,比如安裝在D盤,則路徑:
D:\Visual Studio\IDE\VC\Tools\MSVC\14.33.31629下面包含了vcruntime.dll的源碼,主要VC編譯器和ntdll.dll 以及KernelBase.dll交互 。注:本篇不敘述正常的windows用戶態和內核態異常處理,僅看用戶態下偏角的運作方式 。
代碼void main(){ char* pStr = NULL; try {throw pStr; } catch (char* s) {printf("Hello S"); } getchar();}try里面拋出一個異常,異常調用堆棧如下
分析紅色箭頭,throw拋出異常之后,調用了_CxxThrowException函數 , 這個函數剛好在vcruntime.dll里面 。

windows C++ 異常調用棧簡析

文章插圖
_CxxThrowException函數源碼在VS路徑:
D:\Visual Studio\IDE\VC\Tools\MSVC\14.33.31629\crt\src\vcruntime\throw.cppextern "C" __declspec(noreturn) void __stdcall _CxxThrowException(void *pExceptionObject, // The object thrown_ThrowInfo *pThrowInfo// Everything we need to know about it) {//為了方便觀看,此處省略一萬字RaiseException(EH_EXCEPTION_NUMBER, EXCEPTION_NONCONTINUABLE, _countof(parameters), parameters);}_CxxThrowException又調用了RaiseException函數 。RaiseException函數會進入到內核里面分別調用如下:
ntdll.dll!KiUserExceptionDispatch-》ntdll.dll!RtlDispatchException-》ntdll.dll!RtlpExecuteHandlerForException-》windows異常分為內核態和用戶態處理過程,RtlpExecuteHandlerForException則剛好是用戶態處理過程 。這些過程過于復雜,此處為了避免無端枝節,不贅述 。
RtlpExecuteHandlerForException是調用異常處理的函數,通俗點就是跳轉到catch地址,然后執行catch后面的代碼 。
在VS2022里面,異常處理函數是__CxxFrameHandler4(此函數在vcruntime.dll里面)源碼在路徑:
D:\Visual Studio\IDE\VC\Tools\MSVC\14.33.31629\crt\src\vcruntime\risctrnsctrl.cpp__CxxFrameHandler4后面的調用函數是:
__CxxFrameHandler4-》vcruntime140_1d.dll!__InternalCxxFrameHandler-》vcruntime140_1d.dll!FindHandler-》vcruntime140_1d.dll!CatchIt-》vcruntime140_1d.dll!__FrameHandler4::UnwindNestedFrames-》ntdll.dll!RtlUnwindEx-》ntdll.dll!RtlGuardRestoreContext-》ntdll.dll!RtlRestoreContext-》ntdll.dll!RtlpExecuteHandlerForUnwind-》vcruntime140_1d.dll!__CxxFrameHandler4-》到了這里實際上已經接近完成了,但是實際上還遠不止如此 。如果再繼續調用,會直接跳到函數
ntdll.dll!RcConsolidateFrames -》vcruntime140_1d.dll!__FrameHandler4::CxxCallCatchBlock從__CxxFrameHandler4到RcConsolidateFrames經歷什么?會發現跟上面的對不上 。堆棧也沒有顯示 。為此,還需要繼續跟蹤
匯編為了能看到從__CxxFrameHandler4到RcConsolidateFrames經歷什么,我們跟蹤下匯編
windows C++ 異常調用棧簡析

文章插圖
__CxxFrameHandler4調用了RtlGuardRestoreContext,繼續單步F11,RtlGuardRestoreContext里面調用了函數RtlGuardRestoreContext
windows C++ 異常調用棧簡析

文章插圖
RtlGuardRestoreContext里面有個跳轉指令jmp rdx 。看下圖:
windows C++ 異常調用棧簡析

文章插圖
jmp指令調到了如下
windows C++ 異常調用棧簡析

文章插圖
而callrax的rax就是CxxCallCatchBlock函數的地址 。因為RcConsolidateFrames函數是在ntdll.dll里面沒有被開源,所以兩次跳轉(jmp 和 call 應該是這個函數里面所做的動作)如此一來就對上上面的那個函數調用順序(從上到下) , 但是還有一個問題,這個try里面拋出了異常,那么catch是何時被執行的呢?
Catch理順了RcConsolidateFrames函數調用順序,RcConsolidateFrames自己則調用了函數CxxCallCatchBlock 。這個函數里面調用了catch處理異常 。CxxCallCatchBlock函數源碼地址:
D:\Visual Studio\IDE\VC\Tools\MSVC\14.33.31629\crt\src\vcruntime\frame.cpp(1344行)源碼:
void * RENAME_EH_EXTERN(__FrameHandler4)::CxxCallCatchBlock(EXCEPTION_RECORD *pExcept){//為了方便觀看,此處省略一萬行continuationAddress = RENAME_EH_EXTERN(_CallSettingFrame_LookupContinuationIndex)}RENAME_EH_EXTERN(_CallSettingFrame_LookupContinuationIndex)這段的原型是:
windows C++ 異常調用棧簡析

文章插圖

windows C++ 異常調用棧簡析

文章插圖
總結下:堆棧的調用如下:
vcruntime140_1d.dll!__FrameHandler4::CxxCallCatchBlock (jmp rdx)ntdll.dll!RcConsolidateFrames ntdll.dll!RtlRestoreContext ntdll.dll!RtlGuardRestoreContextntdll.dll!RtlUnwindExvcruntime140_1d.dll!__FrameHandler4::UnwindNestedFramesvcruntime140_1d.dll!CatchItvcruntime140_1d.dll!FindHandlervcruntime140_1d.dll!__InternalCxxFrameHandlervcruntime140_1d.dll!__CxxFrameHandler4ntdll.dll!RtlpExecuteHandlerForException()ntdll.dll!RtlDispatchExceptionntdll.dll!KiUserExceptionDispatch()KernelBase.dll!RaiseException()vcruntime140d.dll!_CxxThrowExceptionConsoleApplication2.exe!main

推薦閱讀