dynamic_cast fails when used with dlopen/dlsym
Asked Answered
A

4

13

Intro

Let me apologise upfront for the long question. It is as short as I could make it, which is, unfortunately, not very short.

Setup

I have defined two interfaces, A and B:

class A // An interface
{
public:
  virtual ~A() {}

  virtual void whatever_A()=0;
};

class B // Another interface
{
public:
  virtual ~B() {}

  virtual void whatever_B()=0;
};

Then, I have a shared library "testc" constructing objects of class C, implementing both A and B, and then passing out pointers to their A-interface:

class C: public A, public B
{
public:
  C();
  ~C();

  virtual void whatever_A();
  virtual void whatever_B();
};

A* create()
{
  return new C();
}

Finally, I have a second shared library "testd", which takes a A* as input, and tries to cast it to a B*, using dynamic_cast

void process(A* a)
{
  B* b = dynamic_cast<B*>(a);
  if(b)
    b->whatever_B();
  else
    printf("Failed!\n");
}

Finally, I have main application, passing A*'s between the libraries:

A* a = create();
process(a);

Question

If I build my main application, linking against the 'testc' and 'testd' libraries, everything works as expected. If, however, I modify the main application to not link against 'testc' and 'testd', but instead load them at runtime using dlopen/dlsym, then the dynamic_cast fails.

I do not understand why. Any clues?

Additional information

  • Tested with gcc 4.4.1, libc6 2.10.1 (Ubuntu 9.10)
  • Example code available
Antependium answered 28/2, 2010 at 17:5 Comment(1)
It's rather OS specific. But I imagine the problem is that the main module has no way to get access to the RTTI data that would be required to make the dynamic_cast work.Savil
A
15

I found the answer to my question here. As I understand it, I need to make the typeinfo available in 'testc' available to the library 'testd'. To do this when using dlopen(), two extra things need to be done:

  • When linking the library, pass the linker the -E option, to make sure it exports all symbols to the executable, not just the ones that are unresolved in it (because there are none)
  • When loading the library with dlopen(), add the RTLD_GLOBAL option, to make sure symbols exported by testc are also available to testd
Antependium answered 28/2, 2010 at 18:58 Comment(1)
It does. If you like to see for yourself, follow the "example code" link in the question, and make the modifications above.Antependium
B
5

In general, gcc does not support RTTI across dlopen boundaries. I have personal experience with this messing up try/catch, but your problem looks like more of the same. Sadly, I'm afraid that you need to stick to simple stuff across dlopen.

Barbiturate answered 28/2, 2010 at 17:20 Comment(1)
I haven't tried, but I guess following the instructions at gcc.gnu.org/faq.html#dso should fix your problem as well.Antependium
H
3

I have to add to this question since I encountered this problem as well.

Even when providing -Wl,-E and using RTLD_GLOBAL, the dynamic_casts still failed. However, passing -Wl,-E in the actual application's linkage as well and not only in the library seem to have fixed it.

Hower answered 10/3, 2010 at 15:56 Comment(1)
I had to do that too. Actually in my case the library didn't need -Wl,-E, just the main app.Britnibrito
B
2

If one have no control over the source of the main application, -Wl,-E is not applicable. Passing -Wl,-E to the linker while building own binaries (the host so and the plugins) do not help either. In my case the only working solution was to load and unload my host so from the _init function of the host so itself using RTLD_GLOBAL flag (See code below). This solution works in both cases:

  1. the main application links against the host so.
  2. the main application loads host so using dlopen (without RTLD_GLOBAL).

In both cases one has to follow the instructions stated by gcc visibility wiki.

If one makes the symbols of the plugin and the host so visible to each other (by using #pragma GCC visibility push/pop or corresponding attribute) and loads the plugins (from the host so) by using RTLD_GLOBAL 1. will work also without loading and unloading the own so (as mentioned by link given above). This solution makes 2. also work which has not been the case before.


// get the path to the module itself
static std::string get_module_path() {
    Dl_info info;
    int res = dladdr( (void*)&get_module_path, &info);
    assert(res != 0); //failure...

    std::string module_path(info.dli_fname);
    assert(!module_path.empty()); // no name? should not happen!
    return module_path;
}

void __attribute__ ((constructor)) init_module() {
    std::string module = get_module_path();

    // here the magic happens :)
    // without this 2. fails
    dlclose(dlopen(module.c_str(), RTLD_LAZY | RTLD_GLOBAL));
}
Bartholomew answered 20/4, 2011 at 9:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.