翻译《The Old New Thing》- The case of the exception that a catch (…) didn’t catch

The case of the exception that a catch (...) didn't catch - The Old New Thing (microsoft.com)icon-default.png?t=N7T8https://devblogs.microsoft.com/oldnewthing/20240405-00/?p=109621

Raymond Chen 2024年04月05日


        一位客户认为他们修复了一个bug,但他们仍然因为这个bug而崩溃。

        根据!analyze的输出,问题来自于这个堆栈:

contoso!winrt::hresult_error::hresult_error+0x143
contoso!winrt::throw_hresult+0x132
contoso!winrt::impl::consume_LitWare_IIconProvider
    <winrt::LitWare::IIconProvider>::LoadIcon+0x3b
contoso!winrt::Contoso::implementation::IconDataModel::
    ReloadIcon$_ResumeCoro$1+0x214
contoso!winrt::impl::resume_background_callback+0x10
ntdll!TppSimplepExecuteCallback+0xa3
ntdll!TppWorkerThread+0x8f6
kernel32!BaseThreadInitThunk+0x1d
ntdll!RtlUserThreadStart+0x28

         这令人困惑,因为“我们已经修复了那个bug!”文件版本号和时间戳确认ReloadIcon的代码确实捕获了异常:

    try
    {
        icon = m_provider.LoadIcon(); // ⇐ blamed frame
    }
    catch(...)
    {
        // There was a problem getting the new icon.
        // Just stick with the old one.
        LOG_CAUGHT_EXCEPTION();
        co_return;
    }

        让我们看看崩溃时的堆栈:

KERNELBASE!RaiseFailFastException+0x152
combase!RoFailFastWithErrorContextInternal2+0x4d9
contoso!wil::details::FailfastWithContextCallback+0xc1
contoso!wil::details::WilFailFast+0x47
contoso!wil::details::ReportFailure_NoReturn<3>+0x2df
contoso!wil::details::ReportFailure_Base<3,0>+0x30
contoso!wil::details::ReportFailure_CaughtExceptionCommonNoReturnBase<3>+0xa7
contoso!wil::details::ReportFailure_CaughtExceptionCommon+0x22
contoso!wil::details::ReportFailure_CaughtException<3>+0x40
contoso!wil::details::in1diag3::FailFast_CaughtException+0x13
contoso!`<lambda_f370031fe3623a0b308de0bbdeb2db76>::operator()'::`1'::catch$2+0x22
ucrtbase!_CallSettingFrame_LookupContinuationIndex+0x20
ucrtbase!__FrameHandler4::CxxCallCatchBlock+0x115
ntdll!RcFrameConsolidation+0x6
contoso!<lambda_f370031fe3623a0b308de0bbdeb2db76>::operator()+0x1a
contoso!std::invoke+0x24
contoso!std::_Invoker_ret<void,1>::_Call+0x24
contoso!std::_Func_impl_no_alloc<<lambda_f370031fe3623a0b308de0bbdeb2db76>,
    void,Concurrency::task<void> >::_Do_call+0x28
contoso!std::_Func_class<void,Concurrency::task<void> >::operator()+0x31
contoso!Concurrency::details::_MakeTToUnitFunc::__l2::
    <lambda_64124396551846798083ef48cd389b4a>::operator()+0x46
contoso!std::invoke+0x66
contoso!std::_Invoker_ret<unsigned char,0>::_Call+0x66
contoso!std::_Func_impl_no_alloc<<lambda_64124396551846798083ef48cd389b4a>,
    unsigned char,Concurrency::task<void> >::_Do_call+0x72
contoso!std::_Func_class<unsigned char,Concurrency::task<void> >::
    operator()+0x32
contoso!Concurrency::task<void>::_ContinuationTaskHandle<void,
    void,std::function<void __cdecl(Concurrency::task<void>)>,
    std::integral_constant<bool,1>,Concurrency::details::_TypeSelectorNoAsync>::
    _LogWorkItemAndInvokeUserLambda<std::function<unsigned char __cdecl(
    Concurrency::task<void>)>,Concurrency::task<void> >+0x8b
contoso!Concurrency::task<void>::_ContinuationTaskHandle<void,
    void,std::function<void __cdecl(Concurrency::task<void>)>,
    std::integral_constant<bool,1>,Concurrency::details::_TypeSelectorNoAsync>::
    _Continue+0x8c
contoso!Concurrency::task<void>::_ContinuationTaskHandle<void,
    void,std::function<void __cdecl(Concurrency::task<void>)>,
    std::integral_constant<bool,1>,Concurrency::details::_TypeSelectorNoAsync>::
    _Perform+0x8
contoso!Concurrency::details::_PPLTaskHandle<unsigned char,Concurrency::task<
    void>::_ContinuationTaskHandle<void,void,std::function<
    void __cdecl(Concurrency::task<void>)>,std::integral_constant<bool,1>,
    Concurrency::details::_TypeSelectorNoAsync>,
    Concurrency::details::_ContinuationTaskHandleBase>::invoke+0x37
contoso!Concurrency::details::_TaskProcHandle::_RunChoreBridge+0x25
contoso!Concurrency::details::_DefaultPPLTaskScheduler::_PPLTaskChore::
    _Callback+0x26
msvcp140!Concurrency::details::`anonymous namespace'::
    _Task_scheduler_callback+0x5d
ntdll!TppWorkpExecuteCallback+0x13a
ntdll!TppWorkerThread+0x8f6
kernel32!BaseThreadInitThunk+0x1d
ntdll!RtlUserThreadStart+0x28

        嘿,等等,这看起来一点也不像!analyze报告的堆栈!发生了什么?

        !analyze使用了第一次存储的异常堆栈。你可以使用!pde.dse命令转储所有存储的异常。

0:076> !pde.dse
Stowed Exception Array @ 0x000000002b1ef170

Stowed Exception #1 @ 0x000000001ce068e8
    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):
    E_ACCESSDENIED - General access denied error

    Stack    : 0x2b214de0
    contoso!winrt::hresult_error::hresult_error+0x143
    contoso!winrt::throw_hresult+0x132
    contoso!winrt::impl::consume_LitWare_IIconProvider
        <winrt::LitWare::IIconProvider>::LoadIcon+0x3b
    contoso!winrt::Contoso::implementation::IconDataModel::
        ReloadIcon$_ResumeCoro$1+0x214
    contoso!winrt::impl::resume_background_callback+0x10
    ntdll!TppSimplepExecuteCallback+0xa3
    ntdll!TppWorkerThread+0x8f6
    kernel32!BaseThreadInitThunk+0x1d
    ntdll!RtlUserThreadStart+0x28

Stowed Exception #2 @ 0x000000001ce02378
    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):
    E_ACCESSDENIED - General access denied error

    Stack    : 0x12cda890
    litware!winrt::hresult_error::hresult_error+0x12c
    litware!winrt::throw_hresult+0x83
    litware!winrt::LitWare::implementation::IconProvider::LoadIcon+0x90
    litware!winrt::impl::produce<winrt::LitWare::implementation::IconProvider,
        winrt::LitWare::IIconProvider>::LoadIcon+0x1b
    contoso!winrt::impl::consume_LitWare_IIconProvider
        <winrt::LitWare::IIconProvider>::LoadIcon+0x3b
    contoso!winrt::Contoso::implementation::IconDataModel::
        ReloadIcon$_ResumeCoro$1+0x214
    contoso!winrt::impl::resume_background_callback+0x10
    ntdll!TppSimplepExecuteCallback+0xa3
    ntdll!TppWorkerThread+0x8f6
    kernel32!BaseThreadInitThunk+0x1d
    ntdll!RtlUserThreadStart+0x28

Stowed Exception #3 @ 0x000000001ce04fa8
    0x80070005 (FACILITY_WIN32 - Win32 Undecorated Error Codes):
    E_ACCESSDENIED - General access denied error

    Stack    : 0x1d94b410
    combase!RoOriginateError+0x51
    contoso!wil::details::RaiseRoOriginateOnWilExceptions+0x137
    contoso!wil::details::ReportFailure_Return<1>+0x1b8
    contoso!wil::details::ReportFailure_Win32<1>+0x70
    contoso!wil::details::in1diag3::Return_Win32+0x18
    contoso!Internal::ContosoSettingsStorage::Save+0xdc729
    contoso!Internal::ContosoSettings::SaveToDefaultLocalStorage+0xf1
    contoso!Internal::ContosoSettings::Save+0x4ef
    contoso!Contoso::AppSettings::save+0x4ef
    contoso!std::_Func_impl_no_alloc<<lambda_f4300885c0b58e31cf789c4999ed9d7a>,
        void>::_Do_call+0x2b
    contoso!std::_Func_impl_no_alloc<<lambda_052e919cc0e5399df76dff3972c0cac1>,
        unsigned char>::_Do_call+0x28
    contoso!Concurrency::task<unsigned char>::_InitialTaskHandle<void,
        <lambda_f4300885c0b58e31cf789c4999ed9d7a>,
        Concurrency::details::_TypeSelectorNoAsync>::_Init+0xc3
    contoso!Concurrency::details::_PPLTaskHandle<unsigned char,
        Concurrency::task<unsigned char>::_InitialTaskHandle<void,
        <lambda_f4300885c0b58e31cf789c4999ed9d7a>,
        Concurrency::details::_TypeSelectorNoAsync>,
        Concurrency::details::_TaskProcHandle>::invoke+0x55
    contoso!Concurrency::details::_TaskProcHandle::_RunChoreBridge+0x25
    contoso!Concurrency::details::_DefaultPPLTaskScheduler::_PPLTaskChore::
        _Callback+0x26
    msvcp140!Concurrency::details::`anonymous namespace'::
        _Task_scheduler_callback+0x5d
    ntdll!TppWorkpExecuteCallback+0x13a
    ntdll!TppWorkerThread+0x686
    kernel32!BaseThreadInitThunk+0x10
    ntdll!RtlUserThreadStart+0x2b

        现在事情开始清晰起来了。

        抛出 Windows 运行时异常的经验法则是,在抛出异常或返回失败的HRESULT之前,你调用RoOriginateError来捕获堆栈和其他上下文。在处理 Windows 运行时常常用到的是异常被捕获并保存(“存储”),通常在IAsyncAction或类似的接口中,然后稍后,当调用者执行co_await或类似的操作时,异常被重新抛出。

        当异常被重新抛出时,原始堆栈已经被展开,所以堆栈上没有东西可以追踪。调用RoOriginateError在为时已晚之前捕获失败点的堆栈。然后这些信息可以用来“拼接”异常的生命周期,从抛出异常的代码开始,到尝试(并失败)捕获它的代码结束。

        系统通过在每个线程的数据中存储错误历史来完成这种堆栈拼接,允许组件捕获该历史并将其传输到另一个线程,当任务的错误状态在线程之间移动时,并寻找具有相同HRESULT的错误。如果有一个最近的捕获堆栈与未处理的异常的HRESULT匹配,那么系统会说,“我敢打赌这两个属于一起。”

        通常,所有这些堆栈拼接工作都很好,因为我们的 API 设计原则说,不应该为可恢复的错误抛出异常。这意味着通常没有大量的异常流量,所以误报的比率很低。

        但在这个案例中,我们有一个误报:IconDataModel调用了IconProvider::LoadIcon(),它以E_ACCESSDENIED失败。这个异常随后被捕获并处理。我们从前两个存储的异常中看到了这一点,使用我们刚才学到的关于拼接多个错误堆栈来获得导致失败的更完整的画面。

        在这种情况下,IconProvider::LoadIcon()明确地使用throw_hresult抛出了一个异常(存储的异常 #2),然后在 ABI 边界处将异常从 C++ 异常转换为HRESULT,然后在另一边,C++/WinRT 将HRESULT重新转换为异常并重新抛出(存储的异常 #1)。这个重新抛出的异常随后被catch(...)捕获,这就是那个异常的结束。

        但这并不是导致我们崩溃的原因。

         当前活动的堆栈显示我们从 lambda 表达式中引发了一个快速失败异常。调试器告诉我们是这个 lambda: 

void ViewPreferences::SaveChanges()
{
    m_settings.save_async()
    .then([](concurrency::task<void> precedingTask) {
        try
        {
            precedingTask.get();
        }
        CATCH_FAIL_FAST();
    });
}

        代码保存设置,并在操作失败时立即失败。

        我们在第三个堆栈中看到了这个失败,也就是ContosoSettingsStorage::Save那个。那个Save操作以E_ACCESSDENIED失败,并记录在了失败历史中。

        发生的事情是,大约在同一时间发生了两个E_ACCESSDENIED错误,!analyze试图弄清楚哪个堆栈属于哪个序列,并没有完全成功,它认为当前的失败与m_provider.LoadIcon()失败相匹配。但我们使用我们的人类大脑,看到m_provider.LoadIcon()异常被处理了,真正的罪魁祸首是存储的异常 #3。

        你可以调用函数RoTransformError如果你的代码接收一个错误代码并返回一个不同的错误代码。这告诉 COM 错误跟踪这两个错误序列应该拼接在一起形成一个大的错误序列。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/705821.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot3+Vue3宠物小程序宠物医院小程序的设计与实现

大家好&#xff0c;我是程序员小孟。 最近开发了一个宠物的小程序&#xff0c;含有详细的文档、源码、项目非常的不错&#xff01; 一&#xff0c;系统的技术栈 二&#xff0c;项目的部署教程 前端部署包&#xff1a;npm i 启动程序&#xff1a;npm run dev 注意事项&…

C++ 25 之 调用函数调用规则

c25调用函数调用规则.cpp #include<iostream> using namespace std;class Students04{ // 1.创建好类之后&#xff0c;编译器会默认提供三个函数&#xff1a;默认构造函数、构造函数、拷贝构造函数 // 2.自己写了有参构造函数&#xff0c;编译器就不会提供默认构造函数&…

JAVA云HIS医院管理系统源码 云HIS运维平台源码 SaaS模式支撑电子病历4级,HIS与电子病历系统均拥有自主知识产权

JAVA云HIS医院管理系统源码 云HIS运维平台源码 SaaS模式支撑电子病历4级&#xff0c;HIS与电子病历系统均拥有自主知识产权 系统简介&#xff1a; SaaS模式Java版云HIS系统&#xff0c;在公立二甲医院应用三年&#xff0c;经过多年持续优化和打磨&#xff0c;系统运行稳定、功…

EasyGBS下载、安装、登录WEB

下载 解压后有一个UserManual.pdf&#xff0c;可以参考。 安装 运行EasyGBS.exe 需要重启机器 打开WEB http://localhost:10000 登录WEB easygbs/easygbs

歌曲怎么转换格式?这几个方法帮你轻松搞定

把歌曲转换为mp3格式?mp3是一种数据压缩格式&#xff0c;因其具有文件尺寸小、音质好两大绝对优势&#xff0c;直到现在作为主流音频格式的地位仍难以被撼动&#xff0c;很多时候大家都需要将其他格式的音频文件转换为MP3进行使用&#xff0c;那么怎样把歌曲转换为mp3格式呢&a…

数据挖掘丨轻松应用RapidMiner机器学习内置数据分析案例模板详解(下篇)

RapidMiner 案例模板 RapidMiner 机器学习平台提供了一个可视化的操作界面&#xff0c;允许用户通过拖放的方式构建数据分析流程。RapidMiner目前内置了 13 种案例模板&#xff0c;这些模板是预定义的数据分析流程&#xff0c;可以帮助用户快速启动和执行常见的数据分析任务。 …

搜索是门艺术,大神都是这样找资源

以下所有资源均可在星云导航找到&#xff0c;网站地址&#xff1a;https://www.xygalaxy.com/ 浏览器搜索高级用法 1、排除干扰&#xff0c;指定关键词 1.1、排除指定关键字 格式&#xff1a;关键字1 -关键字2比如搜索&#xff1a;星云导航&#xff0c;不想要CSDN的内容 星…

长沙干洗服务,打造您的专属衣橱

长沙干洗服务&#xff0c;用心呵护您的每一件衣物&#xff01;致力于为您打造专属的衣橱&#xff0c;让您的每一件衣物都焕发出独特的魅力。 我们深知每一件衣物都承载着您的故事和情感&#xff0c;因此我们会以更加细心的态度对待每一件衣物。无论是您心爱的牛仔裤&#xff0c…

【最新鸿蒙应用开发】——总结ArkUI生命周期

鸿蒙ArkUI相关的生命周期都有哪些? 1. UIAbility生命周期 onCreate、onWindowStageCreate、onForeground、onBackground、onWindowStageDestroy、onDestroy。 onCreate&#xff1a;Create状态为在应用加载过程中&#xff0c;UIAbility实例创建完成时触发&#xff0c;系统会调…

C++ 19 之 封装

c19封装.cpp #include <iostream> #include <string.h> using namespace std;// 封装&#xff1a;将行为和属性作为一个整体来表现生活中的事物// 人&#xff1a; 行为&#xff1a; 吃饭 属性&#xff1a; 姓名、年龄 struct person {char name[20];int age;…

LeetCode435无重叠区间

题目描述 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 解析 由于要删除尽可能少的区间 &#xff0c;因此区间跨度大的一定是要先删除的&#xff0c;这样就有两种贪心思想了…

4090显卡 安装cuda 11.3 版本

文章目录 cuda 安装安装过程中会要求选择安装的内容更改cuda地址到你安装的地方 cuda 安装 cuda官网寻找cuda11.3 版本 https://developer.nvidia.com/cuda-11.3.0-download-archive?target_osLinux&target_archx86_64&DistributionUbuntu&target_version20.04&…

使用代理IP常见问题及解答

代理IP在互联网数据收集和业务开展中发挥着重要作用&#xff0c;它充当用户客户端和网站服务器之间的“屏障”&#xff0c;可以保护用户的真实IP地址&#xff0c;并允许用户通过不同的IP地址进行操作。然而&#xff0c;在使用代理IP的过程中&#xff0c;用户经常会遇到一些问题…

Spring 内置BeanPostProcessor 的子子孙孙

Spring 框架已经实现了很多BeanPostProcessor的类&#xff0c;如下是关于BeanPostProcessor 的类图&#xff0c;图片过大&#xff0c;可以下载资源包看。 要能说清楚这些类&#xff0c;挺难&#xff0c;我也不知道怎么写&#xff0c;这几个类都分布在不同的包中&#xff0c;我感…

3389端口修改工具,修改3389端口的操作

3389端口作为远程桌面协议&#xff08;RDP&#xff09;的默认端口&#xff0c;常常成为黑客攻击的目标。为了提高系统的安全性&#xff0c;修改3389端口成为一项重要的安全措施。本文将详细介绍如何使用3389端口修改工具进行专业操作&#xff0c;以确保系统的安全稳定。 一、备…

python 只有ListNode类的情况下,创建链表和遍历链表

class ListNode:def __init__(self, val0, nextNone):self.val valself.next nextif __name__ __main__: linklist dummy ListNode() for x in ([2,4,3]): linklist .next ListNode(x) linklist linklist .nextwhile dummy:print(dummy.val)dummy dummy.next 这里的…

ISO17025认证是什么?怎么做?

ISO17025认证是一种国际通用的实验室质量管理体系认证&#xff0c;其目标是确保实验室的技术能力、管理水平以及测试结果的可靠性和准确性达到国际认可的标准。该认证由国际标准化组织&#xff08;ISO&#xff09;和国际电工委员会&#xff08;IEC&#xff09;联合发布&#xf…

AMS深入浅出

目标&#xff1a; 1. 一、AMS启动流程 ActivityManagerService是 安卓10 以后&#xff0c;将AMS拆分出ActivityTaskManagerService。 1.1 启动入口 AMS是由SystemServer进程启动&#xff0c;在启动过程 startBootStripService&#xff0c;会启动AMS和ATMS服务。 SystemSe…

有向图的负权值边与建模

参见 dijkstra 算法为什么高效 昨天的文字最后提到 “经理办公桌上有一堆报表&#xff0c;让工人拟合一份最佳收支方案&#xff0c;工人用图论建模&#xff0c;就要使用 floyd&#xff0c;bellman-ford 算法。”&#xff0c;为什么工人的建模的熵减过程会出现负权重边&#xf…

下载elasticsearch-7.10.2教程

1、ES官网下载地址 Elasticsearch&#xff1a;官方分布式搜索和分析引擎 | Elastic 2、点击下载Elasticsearch 3、点击 View past releases&#xff0c;查看过去的版本 4、选择版本 Elasticsearch 7.10.2&#xff0c;点击 Download&#xff0c;进入下载详情 5、点击 LINUX X8…