以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裡面。
_CxxThrowException函數原始碼在VS路徑:
D:\Visual Studio\IDE\VC\Tools\MSVC\14.33.31629\crt\src\vcruntime\throw.cpp
extern "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經歷什麼,我們跟蹤下組合
__CxxFrameHandler4呼叫了RtlGuardRestoreContext,繼續單步F11,RtlGuardRestoreContext裡面呼叫了函數RtlGuardRestoreContext
RtlGuardRestoreContext裡面有個跳轉指令jmp rdx。看下圖:
jmp指令調到了如下
而call rax的rax就是CxxCallCatchBlock函數的地址。
因為RcConsolidateFrames函數是在ntdll.dll裡面沒有被開源,所以兩次跳轉(jmp 和 call 應該是這個函數裡面所做的動作)
如此一來就對上上面的那個函數呼叫順序(從上到下),但是還有一個問題,這個try裡面丟擲了異常,那麼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)
這段的原型是:
堆疊的呼叫如下:
vcruntime140_1d.dll!__FrameHandler4::CxxCallCatchBlock
(jmp rdx)ntdll.dll!RcConsolidateFrames
ntdll.dll!RtlRestoreContext
ntdll.dll!RtlGuardRestoreContext
ntdll.dll!RtlUnwindEx
vcruntime140_1d.dll!__FrameHandler4::UnwindNestedFrames
vcruntime140_1d.dll!CatchIt
vcruntime140_1d.dll!FindHandler
vcruntime140_1d.dll!__InternalCxxFrameHandler
vcruntime140_1d.dll!__CxxFrameHandler4
ntdll.dll!RtlpExecuteHandlerForException()
ntdll.dll!RtlDispatchException
ntdll.dll!KiUserExceptionDispatch()
KernelBase.dll!RaiseException()
vcruntime140d.dll!_CxxThrowException
ConsoleApplication2.exe!main
作者:江湖評談
版權:本作品採用「署名-非商業性使用-相同方式共用 4.0 國際」許可協定進行許可。