楔子

以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++ 异常调用栈简析的更多相关文章

  1. cocos2d-x安卓应用启动调用过程简析

    调用org.cocos2dx.cpp.AppActivity AppActivity是位于proj.android/src下的开发者类(即开发者自定义的类),它继承自org.cocos2dx.lib. ...

  2. Windows下获取Dump文件以及进程下各线程调用栈的方法总结(转)

    1. Dump文件的用途 Dump文件, 主要用于诊断一个进程的运行状态,尤其是碰到崩溃(Crash)或者挂起(hang)不响应时,需要分析它的工作状态.  除了平时常见的attach到这个进程, 分 ...

  3. 编写高质量代码改善C#程序的157个建议——建议70:避免在调用栈较低的位置记录异常

    建议70:避免在调用栈较低的位置记录异常 并不是所有的异常都要被记录到日志,一类情况是异常发生的场景需要被记录,还有一类就是未被捕获的异常.未被捕获的异常通常被视为一个Bug,所以,对于它的记录,应该 ...

  4. 简析 .NET Core 构成体系

    简析 .NET Core 构成体系 Roslyn 编译器 RyuJIT 编译器 CoreCLR & CoreRT CoreFX(.NET Core Libraries) .NET Core 代 ...

  5. [转载] Thrift原理简析(JAVA)

    转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...

  6. JDK框架简析--java.lang包中的基础类库、基础数据类型

    题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含 ...

  7. 简析.NET Core 以及与 .NET Framework的关系

    简析.NET Core 以及与 .NET Framework的关系 一 .NET 的 Framework 们 二 .NET Core的到来 1. Runtime 2. Unified BCL 3. W ...

  8. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  9. SpringMVC源码情操陶冶-DispatcherServlet简析(二)

    承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...

随机推荐

  1. C#里如何简单的校验时间格式

    前言: 晚上打算睡觉的时候,群里反馈订单接收失败,开工排查问题,日志显示验签失败,发现一个蛮有意思的BUG,总算有了一个写作的素材 场景描述 本次的场景属于比较常见的收单API,对第三方的订单进行签名 ...

  2. JS 字符串转 GBK 编码超精简实现

    前言 JS 中 GBK 编码转字符串是非常简单的,直接调用 TextDecoder 即可: const gbkBuf = new Uint8Array([196, 227, 186, 195, 49, ...

  3. 2. 组复制技术架构 | 深入浅出MGR

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1. 传统主从复制技术架构 传统主从复制的方式是在master节点上执行数据更新事务,而后记录这些事务到binlog中,再 ...

  4. 万答#19,MySQL可以禁用MyISAM引擎吗?

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. MyISAM的适用场景越来越少了. 随着MySQL 8.0的推出,系统表已经全面采用InnoDB引擎,不再需要MyISAM ...

  5. eclipse mave无法下载jar包

    解决方法: 先查看本地配置是否正确: 确保此处没有勾选 此处和自己配置的maven一致,没有则修改此处设置.然后项目上右键–>Maven–>Update Project 即可 . 如果还不 ...

  6. 开源深度思考 - In Community We Trust

    作者 | 黄东旭,PingCAP 联合创始人&CTO 出品 | CSDN(ID:CSDNnews) 业界一直流传着黄东旭的传说:小学三年级开始写代码,四五年级学习C语言,初中毕业时,已经能够用 ...

  7. 使用dotnet-monitor分析在Kubernetes的应用程序:Sidecar模式

    dotnet-monitor可以在Kubernetes中作为Sidecar运行,Sidecar是一个容器,它与应用程序在同一个Pod中运行,利用Sidecar模式使我们可以诊断及监控应用程序. 如下图 ...

  8. 在vue项目中使用UEditor--plus

    1:UEditor-plus富文本编辑器如何在vue项目中使用 备注:UEditor是由百度web前端研发部开发的所见即所得的开源富文本编辑器,由于该项目不在维护:程序员自发对其进行了维护,详见 ht ...

  9. bfile 类型数据的存取

    KingbaseES 支持 bfile 数据类型.对于bfile ,实际数据是存储在操作系统上,数据库存储的只是指向文件的指针. 具体例子如下: test=# create directory BFI ...

  10. Grafana Mimir:支持乱序的指标采集

    Grafana Mimir:支持乱序的指标采集 译自:New in Grafana Mimir: Introducing out-of-order sample ingestion 很早之前在使用th ...