Strange exception handling behaviour with dlopen()

Nemeth, Laszlo02 laszlo.2.nemeth@continental-corporation.com
Wed Nov 18 11:54:08 GMT 2020


> On Tue Nov 17 2020 at 20:44, Florian Weimer via Gcc-help <gcc-help@gcc.gnu.org> wrote:
>
>* Jonathan Wakely via Gcc-help:
>
>> On Tue, 17 Nov 2020 at 18:58, David Hagood via Gcc-help
>> <gcc-help@gcc.gnu.org> wrote:
>>>
>>> You are squarely into "undefined behavior" territory here:
>>>
>>>
>>> > static int throw314()
>>> > {
>>> >      std::cout << "throw314() called\n" << std::flush;
>>> >      throw 3.14f;
>>> > }
>>> >
>>> > static int throwDuringInitialization = throw314();
>>>
>>> You are throwing from a constructor. That's STRONGLY discouraged - it
>>
>> No it isn't.
>>
>>> leads to undefined behavior,
>>
>> No it doesn't.

Thanks Jonathan for stepping in and clarifying this :)
David, of course Jonathan is right, throwing exception is basically the only error handling
solution in ctors (beside logging). The standard clearly supports and describes this.

This wasn't the issue here.

>
>The code throws from an *ELF* constructor (although it uses a C++
>constructor to achieve this).  This is currently not supported by the
>glibc implementation.
>

Can you please Florian elaborate what this "not supported" means?
In some cases, which I presented, it works. Why?

My examples are only a bare-bone version of a larger error handling solution (attempt),
which tries to handle hardware exceptions by "translating" signals into C++ exception.

Others came up with similar solutions before, it's not my idea, see e.g.:
https://www.deadalnix.me/2012/03/24/get-an-exception-from-a-segfault-on-linux-x86-and-x86_64-using-some-black-magic/

One of those points when such a signal can be triggered is in the static initialization phase.
E.g. division by zero or seg. fault may occur in the static initialization code of a plugin library.

This example draft is closer what I actually do:

// Plugin library example of an erroneous code, compile with:
// g++ -shared -fPIC raise_SIGFPE_in_static_init.cpp -o raise_SIGFPE_in_static_init.so
#include <iostream>

static struct DivisionByZeroInCtor
{
  DivisionByZeroInCtor()
  {
    std::cout << "DivisionByZeroInCtor() called\n" << std::flush;
    int five = 5, zero = 0;
    int crash = five / zero;
  }
} divisionByZeroDuringInitialization;

// Main program, compile with:
// g++ -fnon-call-exceptions -Wl,-rpath,$PWD signal_translator_OK_in_place_load_w_signal_guard.cpp -ldl
// Works as expected.

#include <stdexcept>
#include <iostream>
#include <signal.h>
#include <dlfcn.h>

// Translate a signal into an exception
void signalTranslator(int sig)
{
    std::cout << "signalTranslator() called with signal=" << sig << "\n" << std::flush;
    throw std::runtime_error("Exception thrown from Signal Translator");
}

class SignalGuard
{
public:
    SignalGuard() {
        struct sigaction recovery_action;
        recovery_action.sa_handler = &signalTranslator;
        sigemptyset(&recovery_action.sa_mask);
        recovery_action.sa_flags = SA_NODEFER;
        sigaction(SIGFPE, &recovery_action, NULL);
    }
};

int main() {

    try {
        SignalGuard g;

        void* handle = dlopen("raise_SIGFPE_in_static_init.so", RTLD_LAZY);
        std::cout << "Lib loading: " << (handle ? "successfull" : "failed") << "\n";

    } catch (std::exception &e) {
        std::cout << "Exception caught in main function: " << e.what() << std::endl;
    }

    std::cout << "main() returns\n";
    return 0;
}
// This code actually works as expected.

This "signal translator" schema basically works in every other case for us, except in this
problematic case of dlopen() loading a plugin library, which raises a signal during static
init phase.

The crucial point of this solution is that it assumes that any C function can throw: since any
code can raise a signal, which is then translated into a C++ exception. And so I assume that the
compiler generates proper code for this situation.

We have an equivalent properly working solution on Windows (not with Posix signals). This is
possible (partly) because MSVC supports assuming C-code may throw.
See https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-160#arguments

It would be good to have a working solution with Linux/gcc, too...

>It's possible to correct this.  A really clean solution would require
>that we move the unwinder into glibc.  Maybe it is possible to get the
>desired effect by using the unwinder that was just loaded to catch the
>exception, but that seems rather tricky.

Florian, can you please tell what that means?

Is there any reliable way to resolve this with the current gcc and glibc ?

Am I right that if gcc can assume that certain (or any) C function can throw, then it had been able to generate correct unwinder code ?

Regards
--
László
ADAS Software Developer, Budapest, Hungary, +36-20-2230357
Less code - less problem, no code - no problem

(Sorry for being out-of-sync with the first mail-thread, I subscribed to the mail-list too late...)



More information about the Gcc-help mailing list