Search code examples
c++rttilibstdc++dlopenvtable

Virtual exception class causes dynamic linker error


In the small reproducer the symbol lookup of the typeinfo/vtable of the exception class with vtable fails. Why is it going wrong? Is it possible to make RTTI work correctly for classes with vtable loaded with dlopen? The purpose of the indirect load is runtime binding based on cpu.

lib.h:

#include <exception>
class myexception : public std::exception {
    virtual void info();
};
void f();

lib.cc:

#include "lib.h"
void myexception::info() {};
void f() { throw myexception(); }

main.cc:

#include "lib.h"
int main() {
    try { f(); }
    catch(myexception) {}
}

stub.cc:

#include <dlfcn.h>
#include <stdlib.h>
__attribute__((constructor)) void init() {
    dlopen("libreal.so", RTLD_NOW | RTLD_GLOBAL);
}

build.sh:

g++ lib.cc -Wall -Wextra -shared -o libf.so -fPIC -g
g++ main.cc -Wall -Wextra libf.so -fPIE -g
mv libf.so libreal.so
g++ stub.cc -Wall -Wextra -shared -o libf.so -fPIC -ldl -g

With GCC or clang+libstdc++:

./a.out |& c++filt 
./a.out: symbol lookup error: ./a.out: undefined symbol: typeinfo for myexception

, and with clang+libc (or GCC with -fPIC rather than -fPIE):

./a.out |& c++filt 
./a.out: symbol lookup error: ./a.out: undefined symbol: vtable for myexception

EDIT: Originally the question stated that the binary compiled with GCC segfaults. This is only the case if the binary is compiled without fPIC/fpic/fPIE/fpie. (Clang doesn't require the flag and the question wasn't updated in respect to the clang behavior). To simplify the question I edited the question to only ask about the runtime linker issue rather than the segfault.


Solution

  • From the man page for dlopen:

    RTLD_LAZY

    Perform lazy binding. Only resolve symbols as the code that references them is executed. If the symbol is never referenced, then it is never resolved. (Lazy binding is only performed for function references; references to variables are always immediately bound when the library is loaded.)

    This is crucial. GNU ld implements lazy binding by default. This means your late binding method may work for functions, but not for data. (Vtables and RTTI info are data). If you link the executable with -z now (analogous to RTLD_NOW flag for dlopen), the method will stop working for functions too.

    There are two basic ways to resolve the situation.

    1. Do not use the runtime data (vtable and type info) of myexception outside of the library. This means you are very restricted in what you can do with myexception directly. You can wrap all operations that reference the runtime data in non-virtual functions exported from the library.
    2. Move the runtime data of myexception into the stub library. For most C++ compilers this means defining the first (in the order of declaration) non-inline virtual function there. You can declare a dummy virtual function at the top of the class and implement it in stub.cc. The rest may be implemented in lib.cc.