楔子以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指令调到了如下
文章插图
而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)这段的原型是:
文章插图
文章插图
总结下:堆栈的调用如下:
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
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Java Style的C++容器流式处理类
- C++ 右值引用与一级指针
- C++和Java多维数组声明和初始化时的区别与常见问题
- C++栈和典型迷宫问题
- C++ 标准文档
- 手把手教你玩转 Gitea|在 Windows 系统上安装 Gitea
- Jupyter,Matplotlib,Pandas 【机器学习】利用 Python 进行数据分析的环境配置 Windows
- C++ 右值引用与 const 关键字
- 从 C# 崩溃异常 中研究页堆布局
- 关于windows-server-下MySQL Community版本的的安装与配置