The issue is somewhat similar to this question but the accepted answer does not really propose a solution or workaround.
In our project, we have a dylib and the main executalble. The dylib is compiled with -fno-rtti
, while the executable does use RTTI. The problem happens when an exception (e.g. std::bad_alloc
) is thrown from the dylib and is caught in the exe.
(Before you yell "exceptions need RTTI so you must have it on!", please note that the RTTI necessary for exceptions is always generated regardless of the -frtti
or -fno-rtti
setting. This is actually documented in the -fno-rtti
flag description. The issue on OS X is that it's not generated in the same way)
After some investigation, the following was discovered:
- In the dylib (
-fno-rtti
), there is a local copy of the exception's RTTI structures; in particular, the__ZTISt9bad_alloc
symbol (typeinfo for std::bad_alloc
). - The exe (
-frtti
) imports the typeinfo symbol fromlibstdc++.6.dylib
and does not have a local copy
Since the exception handling code relies on comparing typeinfo pointers to determine exception match, the matching fails, and only the catch(...)
succeeds.
So far I see the following options:
1) compile everything, or at least the files that throw and catch exceptions, with -frtti
. This is doable but I don't like the idea of generating RTTI for everything even if we don't use it; and the list of files which work with exceptions is prone to get stale.
2) when linking the dylib, somehow make the linker throw away the weak exception definition from the object file and use the one from libstdc++.6.dylib
. So far I was not successful.
3) ???
I made a small test illustrating the problem.
--- throw.cpp ---
#include <iostream>
#if defined(__GNUC__)
#define EXPORT __attribute__((visibility("default")))
#else
#define EXPORT __declspec(dllexport)
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw std::bad_alloc();
}
--- main.cpp ---
#include <stdio.h>
#include <iostream>
#if defined(__GNUC__)
#define IMPORT extern
#else
#define IMPORT __declspec(dllimport)
#endif
IMPORT void dothrow ();
int main (void) {
try {
std::cout << "trying lib->main exception" << std::endl;
dothrow ();
}
catch ( const std::bad_alloc& )
{
std::cout << "caught bad_alloc in main - good." << std::endl;
}
catch (...)
{
std::cout << "caught ... in main - bad!" << std::endl;
}
}
--- makefile ---
# for main exe
CFLAGS_RTTI=-m32 -frtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc -funwind-tables
# for dylib
CFLAGS_NORTTI=-m32 -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc
# for linking; some switches which don't help
CLFLAGS=-Wl,-why_live,-warn_commons,-weak_reference_mismatches,error,-commons,error
all: test
test: libThrow.dylib main.o
g++ $(CFLAGS_RTTI) -o test main.o -lthrow -L./ $(CLFLAGS)
main.o: main.cpp
g++ $(CFLAGS_RTTI) -c -o main.o main.cpp
throw.o: throw.cpp
g++ $(CFLAGS_NORTTI) -c -o throw.o throw.cpp
libThrow.dylib: throw.o
g++ $(CFLAGS_NORTTI) -dynamiclib -o libThrow.dylib throw.o
clean:
rm test main.o throw.o
Running:
$ ./test
trying lib->main exception
before throw
caught ... in main - bad!
Symbols of the files involved:
$ nm -m throw.o | grep bad_alloc
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
00000300 (__TEXT,__eh_frame) weak private external __ZNSt9bad_allocC1Ev.eh
(undefined) external __ZNSt9bad_allocD1Ev
00000290 (__DATA,__const_coal) weak external __ZTISt9bad_alloc
000002a4 (__TEXT,__const_coal) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc
$ nm -m libThrow.dylib | grep bad_alloc
00000ce6 (__TEXT,__text) non-external __ZNSt9bad_allocC1Ev
(undefined) external __ZNSt9bad_allocD1Ev (from libstdc++)
00001050 (__DATA,__const) weak external __ZTISt9bad_alloc
00000e05 (__TEXT,__const) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc (from libstdc++)
$ nm -m main.o | grep bad_alloc
(undefined) external __ZTISt9bad_alloc
$ nm -m test | grep bad_alloc
(undefined) external __ZTISt9bad_alloc (from libstdc++)
Note: similar compilation options on Linux and Windows works fine. I can throw exceptions from a shared object/dll and catch them in the main exe, even if they're compiled with different -frtti
/-fno-rtti
options.
EDIT: here's how I ended up solving it for the specific case of bad_alloc
:
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
#define throw_nomem std::__throw_bad_alloc
#else
#define throw_nomem throw std::bad_alloc
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw_nomem();
}
The function __throw_bad_alloc
is imported from libstdc++.6.dylib
and so always throws a correct type.
-fvisibility
flags causing such behaviour? – Maxim Egorushkinthrow
statement with-fno-rtti
? My GCC (admittedly not OSX, but still) doesn't let me do that at all (immediately bails out with an error), much to my grief. Also, it adds RTTI for every darn type in any translation unit whether the type may be thrown or not. – Damonthrow
would seemingly use the weak__ZTISt9bad_alloc
symbol inlibThrow.dylib
when there's a strong version of it to use (albeit indirect fromlibstdc++abi
) inlibstdc++.dylib
which is probably being used by thecatch
. This seems like a "bug" (or at least a "quirk") withld
to me. Is that what seems to be going on? – Turix