C++: Undefined symbols when loading shared library with dlopen()

Denis Loh picture Denis Loh · Oct 6, 2012 · Viewed 14.4k times · Source

I have an issue when I try to use dlopen() to load a shared library into another shared library. I checked all tutorials on how to use dlopen() correctly. So here is the simplified code:

The main shared library contains a class with pure virtual functions which the sub-shared library (aka plug-in) must implement. Additionally, it has some other functions which are implemented with a default behavior. I created a macro which is added to every plug-in to have a symbol to load and create the class.

Main shared library

plugin.h:

Class A {
public:
  virtual int func1() = 0;
  virtual bool func2() const;
}

#define CREATE_CLASS(cls) extern "C" void *CreateClass(void) { return new cls; }

plugin.cpp:

bool A::func2() const {   return true; }

Build and link main.so

g++ -g -Wall -Woverloaded-virtual -Wno-parentheses -O2 -fPIC -c -o plugin.o plugin.cpp
g++ -g -Wall -Woverloaded-virtual -Wno-parentheses -O2 -fPIC  -rdynamic -shared plugin.o -ldl -o main-plugin.so

The sub-shared library does only implement the pure-virtual functions. The other function can be overridden though it is optional.

Sub shared library

plugin-impl.cpp

Class B : public A {
public:
  virtual int func1() { return 0; }
}

CREATE_CLASS(B)

These are the lines to build and link the sub-shared library.

Build and link sub.so

g++ -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses -fPIC -c -o subPlugin.o subPlugin.cpp
g++ -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses -fPIC  -shared subPlugin.o  -o subPlugin.so

This is the line to open the sub-shared library. I tried LAZY, NOW, NOW | GLOBAL and so on, without any effects.

dlopen() call somewhere in the main-shared library:

  handle = dlopen(file.c_str(), RTLD_LAZY);

Most of this is working very well. However, when I try to load the sub-shared library into the main shared library, dlopen complains about the undefined symbol of bool A::func2() const. The function does only exists in the main shared library, so I guess it must be exported anyway. Please help me out! I am very confused!

SOLUTION

Because I cannot change the executable, I have to link the main-shared library with the sub-shared librarys by adding the following options to g++:

-L$(PLUGINDIR) -Wl,-R$(PLUGINDIR) -lmain-shared

With this set, it is not required to set the LD_LIBRARY_PATH.

Answer

jpalecek picture jpalecek · Oct 6, 2012

Since your sub-library needs symbols from the main library, I think it you want it to be linked with it. Try linking it like this:

g++ -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses -fPIC  -shared 
    -lmain-plugin subPlugin.o  -o subPlugin.so

Probably you'll need to play with -L as well.

This is what I tried:

jirka@debian:/tmp$ cat executable.cpp 
#include <dlfcn.h>
#include <stdio.h>
int main()
{
  dlopen("./main-library.so", RTLD_NOW);
  void* handle=dlopen("./sub-library.so", RTLD_LAZY);
  printf("%x %s", dlsym(handle, "CreateClass"), dlerror());
}
jirka@debian:/tmp$ cat main-library.cpp 
class A {
public:
  virtual int func1() = 0;
  virtual bool func2() const;
};

#define CREATE_CLASS(cls) extern "C" void *CreateClass(void) { return new cls; }

bool A::func2() const {   return true; }
jirka@debian:/tmp$ cat sub-library.cpp 
class A {
public:
  virtual int func1() = 0;
  virtual bool func2() const;
};

#define CREATE_CLASS(cls) extern "C" void *CreateClass(void) { return new cls; }

class B : public A {
public:
  virtual int func1() { return 0; }
};

CREATE_CLASS(B)

jirka@debian:/tmp$ g++ -g -Wall -Woverloaded-virtual -Wno-parentheses -O2 -fPIC  -rdynamic -shared main-library.cpp -ldl -o main-library.so
jirka@debian:/tmp$ g++ -g -O3 -Wall -Werror=overloaded-virtual -Wno-parentheses -fPIC  -shared -l:main-library.so sub-library.cpp  -o sub-library.so
jirka@debian:/tmp$  g++ -ldl executable.cpp -o executable
jirka@debian:/tmp$ LD_LIBRARY_PATH=. ./executable 
b7713740 (null)

Another possibility is adding RTLD_GLOBAL when loading main-library:

jirka@debian:/tmp$ cat executable.cpp 
#include <dlfcn.h>
#include <stdio.h>
int main()
{
  dlopen("./main-library.so", RTLD_LAZY | RTLD_GLOBAL);
  void* handle=dlopen("./sub-library.so", RTLD_LAZY);
  printf("%x %s", dlsym(handle, "CreateClass"), dlerror());
}

That way, you needn't link anything with main-library.so.