From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 48) id 69F383857B9A; Thu, 11 Jan 2024 17:00:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 69F383857B9A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1704992418; bh=Lk34+Y9+oVFBs8zG7mDRbVe3c29/3GLMvngr8e8zV7k=; h=From:To:Subject:Date:From; b=KuaThX82V67o86oO9cOQfj3ujzL7ezCzN21oWAjK/EGojnQvAzL+pfRJtxecyUjZR bo9VU6bk/r9mIA4zfN8ZgbUTDPhVtVhkgQQtguaoA6TBeGHujK0xSFIJCV52znnFrk nITut1EeRqlzIbSd8oD8BGe6V7ILBJWzXdSQyX90= From: "matteo at mitalia dot net" To: gcc-bugs@gcc.gnu.org Subject: [Bug libgcc/113337] New: Rethrown uncaught exceptions don't invoke std::terminate if SEH-based unwinding is used Date: Thu, 11 Jan 2024 17:00:17 +0000 X-Bugzilla-Reason: CC X-Bugzilla-Type: new X-Bugzilla-Watch-Reason: None X-Bugzilla-Product: gcc X-Bugzilla-Component: libgcc X-Bugzilla-Version: 13.1.0 X-Bugzilla-Keywords: X-Bugzilla-Severity: normal X-Bugzilla-Who: matteo at mitalia dot net X-Bugzilla-Status: UNCONFIRMED X-Bugzilla-Resolution: X-Bugzilla-Priority: P3 X-Bugzilla-Assigned-To: unassigned at gcc dot gnu.org X-Bugzilla-Target-Milestone: --- X-Bugzilla-Flags: X-Bugzilla-Changed-Fields: bug_id short_desc product version bug_status bug_severity priority component assigned_to reporter target_milestone attachments.created Message-ID: Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Bugzilla-URL: http://gcc.gnu.org/bugzilla/ Auto-Submitted: auto-generated MIME-Version: 1.0 List-Id: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=3D113337 Bug ID: 113337 Summary: Rethrown uncaught exceptions don't invoke std::terminate if SEH-based unwinding is used Product: gcc Version: 13.1.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libgcc Assignee: unassigned at gcc dot gnu.org Reporter: matteo at mitalia dot net Target Milestone: --- Created attachment 57046 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=3D57046&action=3Dedit Test program to reproduce the bug Sample code: ``` #include #include #include #include static void custom_terminate_handler() { fprintf(stderr, "custom_terminate_handler invoked\n"); std::exit(1); } int main(int argc, char *argv[]) { std::set_terminate(&custom_terminate_handler); if (argc < 2) return 1; const char *mode =3D argv[1]; fprintf(stderr, "%s\n", mode); if (strcmp(mode, "throw") =3D=3D 0) { throw std::exception(); } else if (strcmp(mode, "rethrow") =3D=3D 0) { try { throw std::exception(); } catch (...) { throw; } } else { return 1; } return 0; } ``` Compiling and running this on e.g. Linux, it prints "custom_terminate_handl= er invoked" both when invoked as `./a.out throw` and `./a.out rethrow`. If ins= tead this is compiled with a 64 bit gcc+mingw64 build that uses SEH exceptions, = it behaves correctly in the "throw" case, but in the rethrow case it crashes in std::abort (so, you get an "abnormal program termination"/the JIT debugger = is invoked). Diving in the problem, I think I found the culprit: on rethrow, `__cxxabiv1::__cxa_rethrow` (libstdc++-v3/libsupc++/eh_throw.cc) invokes `_Unwind_Resume_or_Rethrow` (libgcc/unwind-seh.c), which goes like this: ``` _Unwind_Reason_Code _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *exc) { if (exc->private_[0] =3D=3D 0) _Unwind_RaiseException (exc); else _Unwind_ForcedUnwind_Phase2 (exc); abort (); } ``` The problem here is that unconditional abort(); I don't know exactly about = the else branch, but I think that the "regular", first branch case should read `return _Unwind_RaiseException (exc);` as, if no handler is found, `_Unwind_RaiseException` does return to allow the runtime to call `std::terminate`, as per the relevant comment ``` /* The exception handler installed in crt0 will continue any GCC exception that reaches there (and isn't marked non-continuable). Returning allows the C++ runtime to call std::terminate. */ return _URC_END_OF_STACK; ``` Indeed, patching the binary on the fly to change the `std::abort` call to a return fixes the problem, as `__cxa_rethrow` does call `std::terminate` if `_Unwind_Resume_or_Rethrow` returns. Comparing with the code from libgcc/unwind.inc (used in the SjLj and Dwarf2 case, from what I gather) ``` /* Resume propagation of an FORCE_UNWIND exception, or to rethrow a normal exception that was handled. */ _Unwind_Reason_Code LIBGCC2_UNWIND_ATTRIBUTE _Unwind_Resume_or_Rethrow (struct _Unwind_Exception *exc) { struct _Unwind_Context this_context, cur_context; _Unwind_Reason_Code code; unsigned long frames; /* Choose between continuing to process _Unwind_RaiseException or _Unwind_ForcedUnwind. */ if (exc->private_1 =3D=3D 0) return _Unwind_RaiseException (exc); uw_init_context (&this_context); cur_context =3D this_context; code =3D _Unwind_ForcedUnwind_Phase2 (exc, &cur_context, &frames); gcc_assert (code =3D=3D _URC_INSTALL_CONTEXT); uw_install_context (&this_context, &cur_context, frames); } ``` I see that the `_Unwind_RaiseException` case is indeed implemented forwardi= ng the error code back to the caller, while the `_Unwind_ForcedUnwind_Phase2` = case terminates either in a failed `gcc_assert`, or in a `uw_install_context` (w= hich is noreturn and boils down to a longjmp, at least in the sjlj case). So again, I'm not entirely sure about the `_UA_FORCE_UNWIND` case, but the "regular rethrown exception" branch should surely forward back the error co= de instead of crashing on `abort`.=