Author : R. Koucha
Last update : 24-Mar-2019


How to make and manage shared objects in GNU/Linux environment




Introduction

1. Implicit loading/linking of shared objects
1.1. TEST 1 : Building of shared lib with "ld"
1.2. TEST 2 : Building of shared lib with "gcc"
2. Explicit loading/linking of shared objects
2.1. TEST 1 : Building of shared lib with "ld"
2.2. TEST 2 : Building of shared lib with "gcc"
Conclusion

Resources

About the author

Introduction

For scalability, evolutivity and debug purposes, it is interesting to make applications be based on add-ons. The shared object concept which comes along with the GNU/Linux environment can achieve it.

There are two ways to load and link with shared objects :

In both ways, there are some tricks and traps to know to make sure everything goes well. For example, thanks to this link, we realized that we must use "g++ (or gcc) -shared" instead of "ld -shared" to make the shared libraries.

For the following examples, we use a main program which source file is base.cc or base1.cc and a shared object which source file is base_shared.cc.

We chose very simple C++ written examples instead of C written programs because this is the most general case (C is a subset of C++) and the situation where most of the problems can appear when linking and loading shared objects.

The shared object defines a global C++ class which must be initialized at library loading time and deinitialized at library unloading time. This kind of initialization/deinitialization routines are called static constructors/destructors. Moreover, the shared object offers an entry point (base_initialize()) in C language (to avoid mangling problems) which is called by the main program.


1. Implicit loading/linking of shared objects

The implicit linking consists to start a main program referencing symbols located in some shared objects which are automatically loaded and linked to the main program at startup.

1.1. TEST 1 : Building of shared lib with "ld"

--> Location of the shared libs at program startup :

> export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`

--> Make the shared library :

> g++ -c base_shared.cc
> ld -shared base_shared.o -o libbase_shared.so

--> Make the main program

> g++ base.cc -L`pwd` -lbase_shared
/usr/bin/ld: a.out : hidden symbole « __dso_handle » in /usr/lib/gcc/i486-linux-gnu/4.1.2/crtbegin.o est référencé par DSO
/usr/bin/ld: édition de lien finale en échec: Section non-représentable pour la sortie
collect2: ld returned 1 exit status


We can see that the linking of the main program fails because of the referencing of a hidden symbol : __dso_handle.

1.2. TEST 2 : Building of shared lib with "gcc"

--> Location of the shared libs at program startup :

> export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`

--> Make the shared library :

> g++ -c base_shared.cc
> g++ -shared base_shared.o -o libbase_shared.so

--> Make the main program

> g++ base.cc -L`pwd` -lbase_shared

--> Run the program

> ./a.out
Constructor of toto
Main entry point
Shared lib entry point, toto's var = 18
Destructor of toto


We can see that the program is dynamically linked to the shared object. The static constructor and destructor of the shared library are run automatically at loading/unloading time.

2. Explicit loading/linking of shared objects

The explicit linking consists to start a main program referencing symbols located in some shared objects which are loaded and linked to the main program through a call to "dlopen()".

2.1. TEST 1 : Building of shared lib with "ld"

--> Make the shared library :

> g++ -c base_shared.cc
> ld -shared base_shared.o -o libbase_shared.so

--> Make the main program

> g++ base1.cc -ldl

--> Run the program

> ./a.out
Main entry point
./libbase_shared.so: undefined symbol: __dso_handle

We can see that the loading of the shared object fails because of an unresolved symbol : __dso_handle.

2.2. TEST 2 : Building of shared lib with "gcc"


--> Make the shared library :

> g++ -c base_shared.cc
> g++ -shared base_shared.o -o libbase_shared.so

--> Make the main program

> g++ base1.cc -l dl

--> Run the program

> ./a.out
Main entry point
Loading shared lib...
Constructor of toto
Shared lib entry point, toto's var = 18
Destructor of toto


We can see that the static constructor and destructor of the shared library are run automatically at loading/unloading time (i.e. During the call to "dlopen()").

Conclusion

Which ever the way we load and link the shared objects (implicitly or explicitly), it appears that the best way to make shared objects is to use "g++ -shared" and not "ld -shared". This makes the shared object prepared to run the static constructors at loading time and the static destructors at unloading time.


Resources

About the author

The author is an engineer in computer sciences located in France. He can be contacted here.

base.cc

#include <stdio.h>


extern "C" { void base_initialize(void); }


int main(void)
{

printf("Main entry point\n");

// Call a function in shared lib
base_initialize();

return 0;
} // main


base1.cc

#include <stdio.h>
#include <dlfcn.h>

extern "C" { void base_initialize(void); }


int main(void)
{
void *hdl;
void *sym;
char *err;
void (*call)(void);

printf("Main entry point\n");

// Check parameters
printf("Loading shared lib...\n");
hdl = dlopen("./libbase_shared.so", RTLD_LAZY | RTLD_GLOBAL);
if (NULL == hdl)
{
  fprintf(stderr, "%s\n", dlerror());
  return 1;
}

// Clear any pending error message
(void)dlerror();

// Look for symbol in the shared lib
sym = dlsym(hdl, "base_initialize");
if (NULL == sym)
{
  err = dlerror();
  if (err)
  {
    fprintf(stderr, "%s\n", err);
    return 1;
  }
  else
  {
    // The symbol has been found but it is NULL
    fprintf(stderr, "The symbol is NULL\n");
    return 1;
  }
}

// Call a function in shared lib
call = (void (*)(void))sym;
(*call)();

return 0;
} // main


base_shared.cc

#include <stdio.h>
#include <iostream>


using namespace std;



class toto
{
public:
toto() { cout << "Constructor of toto\n"; var = 18; };
~toto() { cout << "Destructor of toto\n"; var = 0; }
int var;
};


toto glob_class;


extern "C"
{

void base_initialize(void)
{

printf("Shared lib entry point, toto's var = %d\n", glob_class.var);

}

}


Compiler's version

> gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --enable-languages=c,c++,fortran,objc,obj-c++,treelang --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --program-suffix=-4.1 --enable-__cxa_atexit --enable-clocale=gnu --enable-libstdcxx-debug --enable-mpfr --enable-checking=release i486-linux-gnu
Thread model: posix
gcc version 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5)