This patch introduces __gnu_cxx::__gnat_exception, a class derived
from abi::__foreign_exception, that can be used to catch GNAT
exceptions and obtain information from them, such as the exception
name and message:

  try {
    try {
      raise_gnat_exception (1);
    } catch (__gnu_cxx::__gnat_exception& e) {
      __builtin_printf("caught gnat %p\n", &e);
      __builtin_printf("what: %s\n", e.what ());
      auto msg = e.__msg ();
      __builtin_printf("msg: %*s\n", msg.__len, msg.__ptr);
      throw;
    }
  } catch (abi::__foreign_exception& f) {
    __builtin_printf("caught foreign %p\n", &f);
  }

prints:

  caught gnat 0x<_Unwind_Exception*>
  what: <GNAT Exception name>
  msg: <GNAT Exception message>
  caught foreign 0x<_Unwind_Exception*>

I'm not entirely happy with the C++ API WRT how internal Ada strings
are exposed as {len,ptr}, but something like std::string is probably
undersirable in libsupc++, more so because it would require a hosted
libstdc++.

It would be cool to expose GNAT exceptions' backtraces as well, but
System.Params.Default_Exception_Msg_Max_Length might conceivably vary,
and that would offset the backtrace data, and we presumably don't want
libstdc++ to depend on libgnat to get runtime access to that param and
behave accordingly, and a #define that might go out of sync feels
risky.

Regstrapped on x86_64-linux-gnu; also tested on arm-eabi with gcc-15.  I
wasn't sure whether to bump the CXXABI version; 1.3.17 is present in the
GCC 16 branch, but adding symbols might be permissible.  I wasn't sure
whether to add the new symbols to gnu-versioned-namespace.ver, but I
did, and removed a duplicate pair of symbols there while at that.  Ok to
install?


for  libstdc++-v3/ChangeLog

        * config/abi/pre/gnu-versioned-namespace.ver (CXXABI_2.0):
        Drop duplicates.  Add __gnat_exception symbols.
        * config/abi/pre/gnu.ver (CXXABI_1.3.18): New.  Add
        __gnat_exception symbols.
        * testsuite/util/testsuite_abi.cc: Update known_versions and
        latestp.
        * libsupc++/Makefile.am (sources): Add gnat_exception.cc.
        * libsupc++/Makefile.in: Rebuild.
        * libsupc++/cxxabi.h (__gnu_cxx::__gnat_exception): New.
        (__cxxabiv1::__foreign_exception): Befriend it.
        * libsupc++/eh_personality.cc (PERSONALITY_FUNCTION): Select
        __gnat_exception for GNAT exceptions.
        * libsupc++/eh_arm.cc (__cxa_type_match): Likewise.
        * libsupc++/eh_catch.cc (__cxxabiv1::__cxa_begin_catch):
        Return unwindHeader for GNAT exceptions.
        * libsupc++/gnat_exception.cc: New.
        * libsupc++/unwind-cxx.h (__is_gnat_exception_class): New, for
        __ARM_EABI_UNWINDER__ and generic.
        (__gnat_exception_class) [!__ARM_EABI_UNWINDER__]: New.
---
 .../config/abi/pre/gnu-versioned-namespace.ver     |    7 +-
 libstdc++-v3/config/abi/pre/gnu.ver                |   10 ++
 libstdc++-v3/libsupc++/Makefile.am                 |    1 
 libstdc++-v3/libsupc++/Makefile.in                 |    5 +
 libstdc++-v3/libsupc++/cxxabi.h                    |   16 ++++
 libstdc++-v3/libsupc++/eh_arm.cc                   |    6 +
 libstdc++-v3/libsupc++/eh_catch.cc                 |    5 +
 libstdc++-v3/libsupc++/eh_personality.cc           |    2 
 libstdc++-v3/libsupc++/gnat_exception.cc           |   91 ++++++++++++++++++++
 libstdc++-v3/libsupc++/unwind-cxx.h                |   31 +++++++
 libstdc++-v3/testsuite/util/testsuite_abi.cc       |    3 -
 11 files changed, 171 insertions(+), 6 deletions(-)
 create mode 100644 libstdc++-v3/libsupc++/gnat_exception.cc

diff --git a/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver 
b/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
index 7c5059944272e..6298a9ddeb289 100644
--- a/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
+++ b/libstdc++-v3/config/abi/pre/gnu-versioned-namespace.ver
@@ -286,8 +286,6 @@ CXXABI_2.0 {
     _ZTIN10__cxxabiv121__vmi_class_type_infoE;
     _ZTIN10__cxxabiv115__forced_unwindE;
     _ZTIN10__cxxabiv119__foreign_exceptionE;
-    _ZTIN10__cxxabiv115__forced_unwindE;
-    _ZTIN10__cxxabiv119__foreign_exceptionE;
 
     # typeinfo name
     _ZTS[a-z];
@@ -372,6 +370,11 @@ CXXABI_2.0 {
     _ZdaPvSt11align_val_t;
     _ZdaPvSt11align_val_tRKSt9nothrow_t;
     _ZdaPv[jmy]St11align_val_t;
+
+    # __gnu_cxx::__gnat_exception typeinfo and member functions
+    _ZNK9__gnu_cxx16__gnat_exception*;
+    _ZTIN9__gnu_cxx16__gnat_exceptionE;
+    _ZTSN9__gnu_cxx16__gnat_exceptionE;
 };
 
 # Symbols in the support library (libsupc++) supporting trans-mem.
diff --git a/libstdc++-v3/config/abi/pre/gnu.ver 
b/libstdc++-v3/config/abi/pre/gnu.ver
index 3a6afac830873..ab26420f9a67b 100644
--- a/libstdc++-v3/config/abi/pre/gnu.ver
+++ b/libstdc++-v3/config/abi/pre/gnu.ver
@@ -2999,6 +2999,16 @@ CXXABI_1.3.17 {
 } CXXABI_1.3.15;
 #endif
 
+CXXABI_1.3.18 {
+
+  global:
+    # __gnu_cxx::__gnat_exception typeinfo and member functions
+    _ZNK9__gnu_cxx16__gnat_exception*;
+    _ZTIN9__gnu_cxx16__gnat_exceptionE;
+    _ZTSN9__gnu_cxx16__gnat_exceptionE;
+
+} CXXABI_1.3.17;
+
 # Symbols in the support library (libsupc++) supporting transactional memory.
 CXXABI_TM_1 {
 
diff --git a/libstdc++-v3/libsupc++/Makefile.am 
b/libstdc++-v3/libsupc++/Makefile.am
index a3dc60f39458f..3523366fc13f7 100644
--- a/libstdc++-v3/libsupc++/Makefile.am
+++ b/libstdc++-v3/libsupc++/Makefile.am
@@ -80,6 +80,7 @@ sources = \
        enum_type_info.cc \
        function_type_info.cc \
        fundamental_type_info.cc \
+       gnat_exception.cc \
        guard.cc \
        guard_error.cc \
        hash_bytes.cc \
diff --git a/libstdc++-v3/libsupc++/Makefile.in 
b/libstdc++-v3/libsupc++/Makefile.in
index db5e83803d52e..ec4dd4af3d296 100644
--- a/libstdc++-v3/libsupc++/Makefile.in
+++ b/libstdc++-v3/libsupc++/Makefile.in
@@ -161,8 +161,8 @@ am__objects_1 = array_type_info.lo atexit_arm.lo 
atexit_thread.lo \
        eh_catch.lo eh_exception.lo eh_globals.lo eh_personality.lo \
        eh_ptr.lo eh_term_handler.lo eh_terminate.lo eh_tm.lo \
        eh_throw.lo eh_type.lo eh_unex_handler.lo enum_type_info.lo \
-       function_type_info.lo fundamental_type_info.lo guard.lo \
-       guard_error.lo hash_bytes.lo nested_exception.lo \
+       function_type_info.lo fundamental_type_info.lo gnat_exception.lo \
+       guard.lo guard_error.lo hash_bytes.lo nested_exception.lo \
        new_handler.lo new_op.lo new_opnt.lo new_opv.lo new_opvnt.lo \
        new_opa.lo new_opant.lo new_opva.lo new_opvant.lo del_opa.lo \
        del_opant.lo del_opsa.lo del_opva.lo del_opvant.lo \
@@ -546,6 +546,7 @@ sources = \
        enum_type_info.cc \
        function_type_info.cc \
        fundamental_type_info.cc \
+       gnat_exception.cc \
        guard.cc \
        guard_error.cc \
        hash_bytes.cc \
diff --git a/libstdc++-v3/libsupc++/cxxabi.h b/libstdc++-v3/libsupc++/cxxabi.h
index 244b21fc9db36..09e48bd2a866a 100644
--- a/libstdc++-v3/libsupc++/cxxabi.h
+++ b/libstdc++-v3/libsupc++/cxxabi.h
@@ -54,6 +54,10 @@
 #include <bits/cxxabi_init_exception.h>
 
 #ifdef __cplusplus
+namespace __gnu_cxx
+{
+  class __gnat_exception;
+}
 namespace __cxxabiv1
 {
   extern "C"
@@ -662,6 +666,7 @@ namespace __cxxabiv1
   {
     virtual ~__foreign_exception() throw();
     virtual void __pure_dummy() = 0; // prevent catch by value
+    friend class __gnu_cxx::__gnat_exception;
   };
 
 } // namespace __cxxabiv1
@@ -690,6 +695,17 @@ namespace abi = __cxxabiv1;
 
 namespace __gnu_cxx
 {
+  // A magic placeholder class that can be caught by reference
+  // to recognize GNAT exceptions.
+  class __gnat_exception : public abi::__foreign_exception
+  {
+    virtual ~__gnat_exception() throw();
+  public:
+    const char *what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW;
+    struct __ada_str { unsigned __len; const char *__ptr; };
+    __ada_str __msg() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW;
+  };
+
   /**
    *  @brief Exception thrown by __cxa_guard_acquire.
    *  @ingroup exceptions
diff --git a/libstdc++-v3/libsupc++/eh_arm.cc b/libstdc++-v3/libsupc++/eh_arm.cc
index 7008ce60f6df1..5d0ed1d789fe4 100644
--- a/libstdc++-v3/libsupc++/eh_arm.cc
+++ b/libstdc++-v3/libsupc++/eh_arm.cc
@@ -56,7 +56,11 @@ __cxa_type_match(_Unwind_Exception* ue_header,
   if (forced_unwind)
     throw_type = &typeid(abi::__forced_unwind);
   else if (foreign_exception)
-    throw_type = &typeid(abi::__foreign_exception);
+    {
+      throw_type = &typeid(abi::__foreign_exception);
+      if (__is_gnat_exception_class(ue_header->exception_class))
+       throw_type = &typeid(__gnu_cxx::__gnat_exception);
+    }
   else
     {
       if (dependent_exception)
diff --git a/libstdc++-v3/libsupc++/eh_catch.cc 
b/libstdc++-v3/libsupc++/eh_catch.cc
index 424dbc724b6a4..650c75e177d05 100644
--- a/libstdc++-v3/libsupc++/eh_catch.cc
+++ b/libstdc++-v3/libsupc++/eh_catch.cc
@@ -58,6 +58,11 @@ __cxxabiv1::__cxa_begin_catch (void *exc_obj_in) 
_GLIBCXX_NOTHROW
       // Remember for end_catch and rethrow.
       globals->caughtExceptions = header;
 
+      // For GNAT exceptions, return the address of the unwind header.
+      // __gnat_exception member functions rely on that.
+      if (__is_gnat_exception_class(header->unwindHeader.exception_class))
+       return &header->unwindHeader;
+
       // ??? No sensible value to return; we don't know what the
       // object is, much less where it is in relation to the header.
       return 0;
diff --git a/libstdc++-v3/libsupc++/eh_personality.cc 
b/libstdc++-v3/libsupc++/eh_personality.cc
index 10458318f88ef..ff242872d6587 100644
--- a/libstdc++-v3/libsupc++/eh_personality.cc
+++ b/libstdc++-v3/libsupc++/eh_personality.cc
@@ -571,6 +571,8 @@ PERSONALITY_FUNCTION (int version,
       else if (foreign_exception)
        {
          throw_type = &typeid(abi::__foreign_exception);
+         if (__is_gnat_exception_class(exception_class))
+           throw_type = &typeid(__gnu_cxx::__gnat_exception);
        }
       else
 #endif
diff --git a/libstdc++-v3/libsupc++/gnat_exception.cc 
b/libstdc++-v3/libsupc++/gnat_exception.cc
new file mode 100644
index 0000000000000..a1b6d1ee71fc3
--- /dev/null
+++ b/libstdc++-v3/libsupc++/gnat_exception.cc
@@ -0,0 +1,91 @@
+// -*- C++ -*- __gnu_cxx::__gnat_exception implementation.
+// Copyright (C) 2026 Free Software Foundation, Inc.
+//
+// This file is part of GCC.
+//
+// GCC is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// GCC is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+// <http://www.gnu.org/licenses/>.
+
+#include "exception"
+#include <cxxabi.h>
+#include "unwind.h"
+
+namespace {
+
+#if __ARM_EABI_UNWINDER__ && ! __USING_SJLJ_EXCEPTIONS__
+  typedef _Unwind_Control_Block GNAT_Unwind_Header;
+#else
+  typedef struct
+  {
+    _Unwind_Exception_Class exception_class;
+    _Unwind_Exception_Cleanup_Fn exception_cleanup;
+    _Unwind_Word private_[6];
+  } GNAT_Unwind_Header;
+#endif
+
+  struct GNAT_Exception_Data
+  {
+    bool not_handled_by_others;
+    char lang;
+    unsigned name_length;
+    const char *full_name; /* NULL terminated string.  */
+    /* ... */
+  };
+
+  struct GNAT_Exception_Occurrence
+  {
+    GNAT_Exception_Data *eid;
+    GNAT_Unwind_Header *header;
+    unsigned msg_length;
+    char msg[/*msg_[max_]length*/];
+    /* ... */
+  };
+
+  struct GNAT_Exception
+  {
+    GNAT_Unwind_Header header;
+    GNAT_Exception_Occurrence occurrence;
+  };
+}
+
+__gnu_cxx::__gnat_exception::~__gnat_exception() throw()
+{
+}
+
+const char*
+__gnu_cxx::__gnat_exception::what()
+  const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT
+{
+  GNAT_Exception const *ex = reinterpret_cast<GNAT_Exception const *>(this);
+  if (ex->occurrence.eid)
+    return ex->occurrence.eid->full_name;
+  return "GNAT exception";
+}
+
+struct __gnu_cxx::__gnat_exception::__ada_str
+__gnu_cxx::__gnat_exception::__msg()
+  const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_USE_NOEXCEPT
+{
+  GNAT_Exception const *ex = reinterpret_cast<GNAT_Exception const *>(this);
+  __ada_str str = {
+    ex->occurrence.msg_length,
+    ex->occurrence.msg
+  };
+  return str;
+}
diff --git a/libstdc++-v3/libsupc++/unwind-cxx.h 
b/libstdc++-v3/libsupc++/unwind-cxx.h
index b6030f0d19784..6e69c1ff9c5be 100644
--- a/libstdc++-v3/libsupc++/unwind-cxx.h
+++ b/libstdc++-v3/libsupc++/unwind-cxx.h
@@ -250,6 +250,20 @@ __is_gxx_exception_class(_Unwind_Exception_Class c)
         && (c[7] == '\0' || c[7] == '\x01');
 }
 
+static inline bool
+__is_gnat_exception_class(_Unwind_Exception_Class c)
+{
+  // TODO: Take advantage of the fact that c will always be word aligned.
+  return c[0] == 'G'
+        && c[1] == 'N'
+        && c[2] == 'U'
+        && c[3] == '-'
+        && c[4] == 'A'
+        && c[5] == 'd'
+        && c[6] == 'a'
+        && c[7] == '\0';
+}
+
 // Only checks for primary or dependent, but not that it is a C++ exception at
 // all.
 static inline bool
@@ -346,6 +360,23 @@ __is_gxx_exception_class(_Unwind_Exception_Class c)
       || c == __gxx_dependent_exception_class;
 }
 
+// GNAT reports exceptions as "GNU-Ada\0"
+const _Unwind_Exception_Class __gnat_exception_class
+= ((((((((_Unwind_Exception_Class) 'G'
+        << 8 | (_Unwind_Exception_Class) 'N')
+       << 8 | (_Unwind_Exception_Class) 'U')
+       << 8 | (_Unwind_Exception_Class) '-')
+      << 8 | (_Unwind_Exception_Class) 'A')
+     << 8 | (_Unwind_Exception_Class) 'd')
+    << 8 | (_Unwind_Exception_Class) 'a')
+   << 8 | (_Unwind_Exception_Class) '\0');
+
+static inline bool
+__is_gnat_exception_class(_Unwind_Exception_Class c)
+{
+  return c == __gnat_exception_class;
+}
+
 // Only checks for primary or dependent, but not that it is a C++ exception at
 // all.
 static inline bool
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.cc 
b/libstdc++-v3/testsuite/util/testsuite_abi.cc
index 4e80c5f184a68..aaa8212b71cf2 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.cc
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.cc
@@ -243,6 +243,7 @@ check_version(symbol& test, bool added)
       known_versions.push_back("CXXABI_1.3.16");
 #endif
       known_versions.push_back("CXXABI_1.3.17");
+      known_versions.push_back("CXXABI_1.3.18");
       known_versions.push_back("CXXABI_IEEE128_1.3.13");
       known_versions.push_back("CXXABI_TM_1");
       known_versions.push_back("CXXABI_FLOAT128");
@@ -262,7 +263,7 @@ check_version(symbol& test, bool added)
 
       // Check that added symbols are added in the latest pre-release version.
       bool latestp = (test.version_name == "GLIBCXX_3.4.36"
-                    || test.version_name == "CXXABI_1.3.17"
+                    || test.version_name == "CXXABI_1.3.18"
                     || test.version_name == "CXXABI_FLOAT128"
                     || test.version_name == "CXXABI_TM_1");
       if (added && !latestp)


-- 
Alexandre Oliva, happy hacker            https://blog.lx.oliva.nom.br/
Free Software Activist     FSFLA co-founder     GNU Toolchain Engineer
More tolerance and less prejudice are key for inclusion and diversity.
Excluding neuro-others for not behaving ""normal"" is *not* inclusive!

Reply via email to