how to get Ada exception message when catching from C++ handler?
Asked Answered
D

2

13

Using GNAT Ada and Gnu C++, I'm interfacing an Ada piece of code with a c++ wrapper and I'd like to catch Ada exceptions properly when running this (stupid) code:

with ada.text_io;

package body ada_throw is

   procedure ada_throw is
   begin
      ada.text_io.put_line ("hello");
      raise program_error;
   end ada_throw;       

end ada_throw;

relevant spec code is:

package ada_throw is

   procedure ada_throw;
   pragma export (convention => C, entity => ada_throw, external_name => "ada_throw");

end ada_throw;

Whe doing this on the C++ side:

#include <iostream>

extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();
  ada_throw();
  std::cout << "end of program" << std::endl;
  return 0;
}

I'm getting this:

hello

raised PROGRAM_ERROR : ada_throw.adb:8 explicit raise

So the exception mechanism works, I don't get the last print of my C++ program and the return code is non-zero.

Now I want to catch the exception. If I use catch(...) it works, but I can't get the explicit error message anymore, so I tried this:

#include <iostream>
#include <cxxabi.h>
extern "C"
{
  void ada_throw();
  void adainit();
}

int main()
{
  adainit();

  try
  {
    ada_throw();
  }
  catch (abi::__foreign_exception const &e)
  {
    std::cout << "exception" << std::endl;        
  }

  std::cout << "end of program" << std::endl;
  return 0;
}

it works properly, I get:

hello
exception
end of program

The only catch is that abi::__foreign_exception doesn't have a what() method, so I cannot get hold of the meaningful error message.

And debugging the program to try to hack into e is also a dead end since it's just a null pointer with the proper type:

(gdb) p &e
$2 = (const __cxxabiv1::__foreign_exception *) 0x0

Is there a way to get it from the C++ ?

Droll answered 21/5, 2018 at 11:58 Comment(11)
This really depends on how your respective ada and c++ compilers implement exceptions. Both languages specify things differently, and it takes deliberate design choices - in both the compilers code-generation, and in use of library features - to ensure compatibility or, at least, a consistent mapping across the boundary between languages. Any solution will be specific to particular sets of compilers.Topgallant
Yes, this question is tagged gnat as well. So it's using g++/gnat, I'll editEntirety
The world of ABI is only very slowly evolving beyond a C interface. Exceptions are a compiler specific thing, although there might be a platform convention to which they adhere.Generatrix
Ada exception definition is given in a-except.ads. The file contains a line that I believed could be a clue :Machine_Occurrence : System.Address; -- The underlying machine occurrence. For GCC, this corresponds to the _Unwind_Exception structure address. But since you have no valid pointer ...Overissue
Also found this : function Get_Exception_Machine_Occurrence (X : Exception_Occurrence) return System.Address; pragma Export (Ada, Get_Exception_Machine_Occurrence,"__gnat_get_exception_machine_occurrence"); -- Get the machine occurrence corresponding to an exception occurrence. It is Null_Address if there is no machine occurrence (in runtimes tha doesn't use GCC mechanism) or if it has been lost (Save_Occurrence doesn't save the machine occurrence).Overissue
hmmm let me try our most recent Ada compiler to see if the pointer is still nullEntirety
For ABI documentation, some clues could be found here: #18672691 (points to C++ Itanium exception handling itanium-cxx-abi.github.io/cxx-abi/abi-eh.html). Here is some clues for Ada exceptions www2.adacore.com/gap-static/GNAT_Book/html/node25.htm Figure 18.2 looks interesting.Overissue
Another clue : gcc.gnu.org/onlinedocs/gnat_ugn/Exception-Handling-Control.html states The other approach is called ’zero cost’ exception handling. With this method, the compiler builds static tables to describe the exception ranges. No dynamic code is required when entering a frame containing an exception handler.[...] Note that in this mode and in the context of mixed Ada and C/C++ programming, to propagate an exception through a C/C++ code, the C/C++ code must be compiled with the -funwind-tables GCC’s option."Overissue
I guess that -funwind_tables is set by default since the exception propagates to the C++ layer all right. It's just that the e variable is a null-reference (with latest GNAT 17.2 as well)Entirety
The rule of thumb is to never allow exceptions propagate across language boundaries. Throwing ada exception through C (which does not have exceptions at all) and trying to catch in C++ (which is not aware of ada exceptions existance) seems like a bizarre idea. Even if this accidentally works due to somewhat compatible underlaying exception implementation that would be a straight shot in the leg. The only way is to convert exception into the appropriate error form when it passes each language boundary.Stale
I was talking with AdaCore GNAT maintainers and they have a lead for that though: catch the exception, then invoke some GNAT.Last_Exception (don't remember exactly) to get the last thrown exception. Not perfect in multithreaded environment, but would work most of the time (unfortunately the feature is broken, and being fixed at the current time, only for beta releases of the GNATPro edition, so not available to the public / GPL until 2019)Entirety
P
3

In Ada you can get information about an exception occurrence using the functions Ada.Exceptions.Exception_Name and Ada.Exceptions.Exception_Message, which are a part of the standard library. One option is to provide a thin binding to these two functions, and call them, when you need information about the exception. (Exactly how to map abi::__foreign_exception to Ada.Exceptions.Exception_ID is left as an exercise for the reader.)

Another option is to make it explicit to the Ada compiler that you are writing C++ on the other side, and use CPlusPlus instead of C as your export convention. That may make the compiler provide exceptions, which C++ understands.

This is only a partial answer, as I'm very much out of practice with C++, but I hope it can help you in the right direction.

Pimbley answered 21/5, 2018 at 12:13 Comment(2)
CPlusPlus doesn't exist, but CPP does :) trying that now. ouch, the Ada compiler doesn't seem to be able to generate a proper C++ name. Still the same _ada_throw symbol...Entirety
Upvoted for your first option, but I suspect your alternative isn't going to work, and that this answer would be improved by removing it.Stetson
A
3

Depending on what you want to do with the exception in the C++ world, it might be easier/cleaner to write a wrapper in Ada, which calls ada_throw and handles any exception. Then simply call this wrapper from C++.

If the C++ code must see the exception and know what's going on, the wrapper could provide a what() method lookalike, and re-raise either the same or a new (custom) exception.

Armillas answered 21/5, 2018 at 12:32 Comment(3)
The GNAT.CPP_Exceptions seems to be the perfect thing to perform the wrapping. Quoting its .ads file -- This package provides an interface for raising and handling C++ exceptions. I'm giving a look at adacore.com/gems/gem-114-ada-and-c-exceptionsOverissue
Unfortunatly, the Ada Gem shows how to pass exceptions across languages, but no info given on how to get the exception informations in the C++ part.Overissue
that's a good idea. But I have quite a few entrypoints, so I'd have to write a wrapper for each entrypoint.Entirety

© 2022 - 2024 — McMap. All rights reserved.