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里面。

_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是何时被执行的呢?
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 国际」许可协议进行许可。

windows C++ 异常调用栈简析的更多相关文章
- cocos2d-x安卓应用启动调用过程简析
调用org.cocos2dx.cpp.AppActivity AppActivity是位于proj.android/src下的开发者类(即开发者自定义的类),它继承自org.cocos2dx.lib. ...
- Windows下获取Dump文件以及进程下各线程调用栈的方法总结(转)
1. Dump文件的用途 Dump文件, 主要用于诊断一个进程的运行状态,尤其是碰到崩溃(Crash)或者挂起(hang)不响应时,需要分析它的工作状态. 除了平时常见的attach到这个进程, 分 ...
- 编写高质量代码改善C#程序的157个建议——建议70:避免在调用栈较低的位置记录异常
建议70:避免在调用栈较低的位置记录异常 并不是所有的异常都要被记录到日志,一类情况是异常发生的场景需要被记录,还有一类就是未被捕获的异常.未被捕获的异常通常被视为一个Bug,所以,对于它的记录,应该 ...
- 简析 .NET Core 构成体系
简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...
- [转载] Thrift原理简析(JAVA)
转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...
- JDK框架简析--java.lang包中的基础类库、基础数据类型
题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含 ...
- 简析.NET Core 以及与 .NET Framework的关系
简析.NET Core 以及与 .NET Framework的关系 一 .NET 的 Framework 们 二 .NET Core的到来 1. Runtime 2. Unified BCL 3. W ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
- SpringMVC源码情操陶冶-DispatcherServlet简析(二)
承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...
随机推荐
- 使用gulp助力前端自动化
前言 随着前端诸如webpack,rollup,vite的发展,gulp感觉似乎好像被取代了.其实并没有,只不过它从台前退居到了幕后.我们仍然可以在很多项目中看到它的身影,比如elementplus. ...
- 3.0.0 alpha 重磅发布!九大新功能、全新 UI 解锁调度系统新能力
2022 年 4 月 22 日,Apache DolphinScheduler 正式宣布 3.0.0 alpha 版本发布!此次版本升级迎来了自发版以来的最大变化,众多全新功能和特性为用户带来新的体验 ...
- Fiddler抓包工具下载安装及使用
一.Fiddler简介 简介: Fiddler是一款强大的Web调试工具,他能记录所有客户端和服务器的HTTP/HTTPS请求 工作原理: Fiddler是以代理web服务器的形式工作的,它使用代理地 ...
- Apache HttpClient 5 使用详细教程
点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. 超文本传输协议(HTTP)可能是当今 ...
- 【java】学习路径40-Buffer缓冲区输入流
@Testpublic void testBufferInputStream(){ BufferedInputStream bfis = null; try { bfis = new Buffered ...
- React报错之Property 'value' does not exist on type EventTarget
正文从这开始~ 总览 当event参数的类型不正确时,会产生"Property 'value' does not exist on type EventTarget"错误.为了解决 ...
- Session认证机制与JWT认证机制
一.什么是身份认证? 身份认证(Authentication)又称"身份验证"."鉴权",是指通过一定的手段,完成对用户身份的确认.日常生活中的身份认证随处可见 ...
- KingbaseES 时间类型数据和oracle时间类型的区别
Oracle日期时间类型有两类,一类是日期时间类型,包括Date, Timestamp with time zone, Timestamp with local time zone.另一类是Inter ...
- KingbaseES 开启事务提交跟踪
KingbaseESV8R6有个参数 track_commit_timestamp,用来开启跟踪事务提交的时间戳. 配置 编辑kingbase.conf,添加配置如下: track_commit_ti ...
- 记一次 Sedona(GeoSpark) 空间计算优化
项目需求需要空间计算能力,开始选型Sedona(GeoSpark)来完成, 需求需要每一条数据在满足某条件的情况下,去查找某张表进行空间匹配,找到离这个点(point)最近的一条道路(lineStri ...