{"id":111368,"date":"2025-07-11T07:00:00","date_gmt":"2025-07-11T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111368"},"modified":"2025-07-11T11:26:24","modified_gmt":"2025-07-11T18:26:24","slug":"20250711-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250711-00\/?p=111368","title":{"rendered":"Detecting and reporting all unhandled C++ exceptions as well as all unhandled structured exceptions"},"content":{"rendered":"<p>Last time, <a title=\"Our first attempt to detect and report all unhandled C++ exceptions as well as all unhandled structured exceptions\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250710-00\/?p=111366\"> attempted to intercept the ways that a C++ program could exit due to an unhandled exception<\/a> by installing a custom unhandled structured exception filter and letting it classify the nature of the unhandled exception. But I also noted that there are other ways a C++ program can exit on an unhandled exception that aren&#8217;t covered by this. How can that be?<\/p>\n<p>If you mark a function as <code>noexcept<\/code>, then the compiler will terminate execution if an unhandled exception exits that function. In that case, the unhandled exception never reaches the custom filter because the Microsoft Visual C++ runtime never lets the exception get that far.<\/p>\n<p>Marking a function as <code>noexcept<\/code> is similar to manually wrapping the function body in a <code>try<\/code>\/<code>catch<\/code> that &#8220;handles&#8221; the exception by terminating the program.\u00b9<\/p>\n<pre>void MyFunction() <span style=\"border: solid 1px currentcolor;\">try<\/span>\r\n{\r\n    \u27e6 existing body \u27e7\r\n}\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">catch (...)          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                    <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    std::terminate();<\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">}                    <\/span>\r\n<\/pre>\n<p>From the operating system&#8217;s point of view, this exception was handled, which means that the unhandled exception filter is never called. Mind you, the exception was &#8220;handled&#8221; by explicitly terminating the program, but the operating system doesn&#8217;t know or care about what the handler does.<\/p>\n<p>Another source of unhandled exceptions is in coroutines. When an exception happens in a coroutine, the coroutine promise&#8217;s <code>unhandled_<wbr \/>exception()<\/code> is called, and each promise can decide how it wants to treat unhandled exceptions. One possible response is to <a title=\"winrt::fire_and_forget was too forgetful\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190320-00\/?p=102345\"> call <code>std::<wbr \/>terminate<\/code> directly<\/a>.<\/p>\n<p>More generally, when C++ decides that something horrifically bad has happened, such as throwing an exception during exception unwinding, its traditional response is to call <code>std::<wbr \/>terminate<\/code>.<\/p>\n<p>So our code to intercept all unhandled exceptions should still install a terminate handler so it can observe these &#8220;handled by terminating&#8221; exceptions.<\/p>\n<pre>#define MSVC_EXCEPTION_CODE 0xE06D7363U\r\n\r\nstd::terminate_handler previousTerminate;\r\n\r\nLONG CALLBACK MyUnhandledExceptionFilter(\r\n    EXCEPTION_POINTERS *ExceptionInfo)\r\n{\r\n    if (ExceptionInfo-&gt;ExceptionRecord.ExceptionCode ==\r\n            MSVC_EXCEPTION_CODE) {\r\n        CaptureDump(ExceptionInfo,\r\n                    UnhandledException::Structured);\r\n    } else {\r\n        CaptureDump(ExceptionInfo,\r\n                    UnhandledException::Cpp);\r\n    }\r\n    return EXCEPTION_EXECUTE_HANDLER;\r\n}\r\n\r\nvoid __cdecl my_terminate()\r\n{\r\n    CaptureDump(nullptr,\r\n                UnhandledException::Cpp);\r\n    if (previousTerminate) {\r\n        previousTerminate();\r\n    } else {\r\n        std::abort();\r\n    }\r\n}\r\n\r\nvoid InstallCustomUnhandledExceptionFilters()\r\n{\r\n    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);\r\n\r\n    <span style=\"border: solid 1px currentcolor;\">previousTerminate = std::set_terminate(my_terminate);<\/span>\r\n}\r\n<\/pre>\n<p>Now, when <code>std::<wbr \/>terminate<\/code> is called due to mishandling of a thrown exception, the exception is considered still active and can be retrieved by <code>std::<wbr \/>current_<wbr \/>exception<\/code>. You may want to use that information when capturing the dump to extract additional information about the exception (such as its <code>what()<\/code>). In that case, it seems best to let all unhandled C++ exceptions go to the custom terminate handler, so it can do that work.<\/p>\n<pre>#define MSVC_EXCEPTION_CODE 0xE06D7363U\r\n\r\nLPTOP_LEVEL_EXCEPTION_FILTER previousFilter;\r\nstd::terminate_handler previousTerminate;\r\n\r\nLONG CALLBACK MyUnhandledExceptionFilter(\r\n    EXCEPTION_POINTERS *ExceptionInfo)\r\n{\r\n    if (ExceptionInfo-&gt;ExceptionRecord.ExceptionCode ==\r\n            MSVC_EXCEPTION_CODE) {\r\n        <span style=\"border: solid 1px currentcolor;\">return previousFilter(ExceptionInfo);<\/span>\r\n    }\r\n    CaptureDump(ExceptionInfo,\r\n                UnhandledException::Structured);\r\n    return EXCEPTION_EXECUTE_HANDLER;\r\n}\r\n\r\nvoid __cdecl my_terminate()\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">const char* what = nullptr;             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">auto ptr = std::current_exception();    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">if (ptr) {                              <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    try {                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        std::rethrow_exception(ptr);    <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    } catch (std::exception const&amp; ex) {<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        what = ex.what();               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    } catch (...) {                     <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                   <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                       <\/span>\r\n    CaptureDump(nullptr,\r\n                UnhandledException::Cpp, <span style=\"border: solid 1px currentcolor;\">what<\/span>);\r\n    if (previousTerminate) {\r\n        previousTerminate();\r\n    } else {\r\n        std::abort();\r\n    }\r\n}\r\n\r\nvoid InstallCustomUnhandledExceptionFilters()\r\n{\r\n    previousFilter = SetUnhandledExceptionFilter(\r\n                        MyUnhandledExceptionFilter);\r\n\r\n    previousTerminate = std::set_terminate(my_terminate);\r\n}\r\n<\/pre>\n<p>But wait, we&#8217;re not done yet.<\/p>\n<p>Some libraries let you customize how they report critical failures rather than their default behavior of failing fast. You may want to use those customization points to funnel these reports through your infrastructure. You&#8217;ll have to handle these on a case-by-case basis since there is no standard for these customization points.<\/p>\n<p>\u00b9 I say &#8220;similar to&#8221; because it&#8217;s not exactly the same. The explicitly caught version forces the stack to unwind before calling <code>std::terminate<\/code>, but the <code>noexcept<\/code> version is permitted to call <code>std::terminate<\/code> without unwinding the stack, which is good because that lets you inspect the function&#8217;s local variables in the crash dump. <a title=\"If you want to terminate on an unexpected exception, then don\u2019t sniff at every exception; just let the process terminate\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191024-00\/?p=103022\"> We learned about this some time ago<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Closing another exit point.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-111368","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Closing another exit point.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111368","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111368"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111368\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111368"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111368"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111368"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}