Linking a Shared Library with Another Shared Lib in Linux

Linking a shared library with another shared lib in linux

Suppose that libabc.so is obtained from posiition independent object code files abc1.pic.o and abc2.pic.o ; then you have built them with e.g.

 gcc -Wall -fPIC -O -g abc1.c -c -o abc1.pic.o
gcc -Wall -fPIC -O -g abc2.c -c -o abc2.pic.o

and you build libabc.so with

gcc -shared  abc1.pic.o  abc2.pic.o -L/usr/local/lib -l123 -o libabc.so

I added -L/usr/local/lib before -l123 because I am assuming you have a /usr/local/lib/lib123.so shared library.

Read also the Program Library HowTo.

As you see, you may link a shared library lib123.so into your own shared library libabc.so

Then check with ldd libabc.so

You may want to set up some rpath in your libabc.so by adding -Wl,-rpath and -Wl,$RPATHDIR to the linking command.

For much more details, read Drepper's paper How to write shared libraries

PS. Don't use a static library for lib123.a (it should be PIC). If you link non-PIC code into a shared object, you lose most of the advantages of shared objects, and the dynamic linker ld.so has to do zillions of relocations.

Using a shared library in another shared library

There is more than one way in which multiple shared libraries may be added to
the linkage of a program, if you are building all the libraries, and the program,
yourself.

The elementary way is simply to explicitly add all of the libraries to the
the linkage of the program, and this is the usual way if you are building only the
program and linking libraries built by some other party.

If an object file foo.o in your linkage depends on a library libA.so, then
foo.o should precede libA.so in the linkage sequence. Likewise if libA.so
depends on libB.so then libA.so should precede libB.so. Here's an illustration.

We'll make a shared library libsquare.so from the files:

square.h

#ifndef SQUARE_H
#define SQUARE_H

double square(double d);

#endif

and

square.cpp

#include <square.h>
#include <cmath>

double square(double d)
{
return pow(d,2);
}

Notice that the function square calls pow, which is declared in the
Standard header <cmath> and defined in the math library, libm.

Compile the source file square.cpp to a position-independent object file
square.o:

$ g++ -Wall -fPIC -I. -c square.cpp

Then link square.o into a shared library libsquare.so:

$ g++ -shared -o libsquare.so square.o

Next we'll make another shared library libcube.so from these files:

cube.h

#ifndef CUBE_H
#define CUBE_H

double cube(double d);

#endif

and

cube.cpp

#include <cube.h>
#include <square.h>

double cube(double d)
{
return square(d) * d;
}

See that the function cube calls square, so libcube.so is going to
depend on libsquare.so. Build the library as before:

$ g++ -Wall -fPIC -I. -c cube.cpp
$ g++ -shared -o libcube.so cube.o

We haven't bothered to link libsquare with libcube, even though libcube
depends on libsquare, and even though we could have, since we're building libcube.
For that matter, we didn't bother to link libm with libsquare. By default the
linker will let us link a shared library containing undefined references, and it
is perfectly normal. It won't let us link a program with undefined references.

Finally let's make a program, using these libraries, from this file:

main.cpp

#include <cube.h>
#include <iostream>

int main()
{
std::cout << cube(3) << std::endl;
return 0;
}

First, compile that source file to main.o:

$ g++ -Wall -I. -c main.cpp

Then link main.o with all three required libraries, making sure to list
the linker inputs in dependency order: main.o, libcube.so, libsquare.so, libm.so:

$ g++ -o prog main.o -L. -lcube -lsquare -lm

libm is a system library so there's no need to tell the linker where to look for
it. But libcube and libsquare aren't, so we need to tell the linker to look for
them in the current directory (.), because that's where they are. -L. does that.

We've successfully linked ./prog, but:

$ ./prog
./prog: error while loading shared libraries: libcube.so: cannot open shared object file: No such file or directory

It doesn't run. That's because the runtime loader doesn't know where to find libcube.so (or libsquare.so, though it didn't get that far).

Normally, when we build shared libraries we then install them in one of the loader's default
search directories (the same ones as the linker's default search directories), where they're available to any program, so this wouldn't happen. But I'm not
going to install these toy libraries on my system, so as a workaround I'll prompt the loader where to look
for them by setting the LD_LIBRARY_PATH in my shell.

$ export LD_LIBRARY_PATH=.
$ ./prog
27

Good. 3 cubed = 27.

Another and better way to link a program with shared libraries that aren't located
in standard system library directories is to link the program using the linker's
-rpath=DIR option. This will write some information into the executable to tell
the loader that it should search for required shared libraries in DIR before it tries
the default places.

Let's relink ./prog that way (first deleting the LD_LIBRARY_PATH from the shell so that it's not effective any more):

$ unset LD_LIBRARY_PATH
$ g++ -o prog main.o -L. -lcube -lsquare -lm -Wl,-rpath=.

And rerun:

$ ./prog
27

To use -rpath with g++, prefix it with -Wl, because it's an option for linker, ld,
that the g++ frontend doesn't recognise: -Wl tells g++ just to pass the
option straight through to ld.

How to link shared library to another shared library

Let's decode your symbol:

$ c++filt _ZN5vatps6PosAPI8sendDataB5cxx11Ev
vatps::PosAPI::sendData[abi:cxx11]()

So, your code expects sendData with std::string with C++11 ABI, whereas libPosAPI.so provides sendData with pre-C++11 ABI std::string.

abi:cxx11 hints to GCC5 and the C++11 ABI:

Users that depend on third-party libraries or plugin interfaces that still use the old ABI can build their code with -D_GLIBCXX_USE_CXX11_ABI=0 and everything should work fine. In most cases, it will be obvious when this flag is needed because of errors from the linker complaining about unresolved symbols involving __cxx11.

Linking a shared library in executable vs. another shared lib

I will describe a scenario that could produce the behavior you are seeing.

  1. Global constructors of the singleton objects are called, and they get registered against the map.
  2. Global constructor of the map is called, causing the map to be initialized into an empty data structure.

Another scenario:

  • If you are reading the map from libB.so from a global constructor, it may be called before any object has had a chance to register itself.

Generally, there is no guaranteed order of execution of global constructors from different translation units, and certainly not from different shared libraries.

A solution to the first issue above would be to use a singleton style pattern for your map, so that it is initialized on use rather than through a global constructor.

TheMap & GlobalMap () {
static TheMap instance;
return instance;
}

A solution to the second issue above would be suppress accessing the global map from global constructors. That is, change all global constructors in libB.so to be initialize on use rather than initialize in a global constructor.

Is it possible to link a shared library (from another shared library), without making its symbols globally visible?

In case you are ok with changing names of symbols from libC.so.2 you can use Implib.so's renaming functionality. E.g. to change all libC.so.2 symbols to have MYPREFIX_ prefix:

$ cat mycallback.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C"
#endif
void *mycallback() {
void *h = dlmopen(LM_ID_NEWLM, "libxyz.so", RTLD_LAZY | RTLD_DEEPBIND);
if (h)
return h;
fprintf(stderr, "dlmopen failed: %s\n", dlerror());
exit(1);
}
$ implib-gen.py --dlopen-callback=mycallback --symbol_prefix=MYPREFIX_ libC.so.2
$ ... # Link your app with libC.so.2.tramp.S, libC.so.2.init.c and mycallback.c, keep libC.so.1 unchanged

Function names in libC.so.2's header will need to be updated as well (often that's a simple s/// in vim).

Implib.so works by generating a bunch of wrappers for each symbol in problematic library (in this case libC.so.2) and forwarding calls to their actual implementation internally (via dlsym).

Linking shared lib on Linux with duplicate yet modified class/struct causes segfault

I followed the recommendations found in the last answer and Is there symbol conflict when loading two shared libraries with a same symbol :

  • running 'nm Master' and 'nm libSlave.so' showed the same automatically generated constructor symbols:
...
000000000000612a W _ZN5DummyC1EOS_
00000000000056ae W _ZN5DummyC1ERKS_
0000000000004fe8 W _ZN5DummyC1Ev
...

So, the mangled function signatures match in both the master's binary and the slave.

When loading the library, the master's function is used instead of the library's version. To study this further, I created an even more minimalistic example like in the post referenced above:

master.cpp

#include <iostream>

#include <dlfcn.h> // shared library loading on Unix systems

// prototype for imported slave function
void hello();
typedef void F_hello();

void printHello() {
std::cout << "Hello world from master" << std::endl;
}

int main() {
printHello();

// now load dynamic library
void *soHandle = nullptr;
const char * const sharedLibPath = "libSlave.so";
// I tested different RTLD_xxx options, see text for explanations
soHandle = dlopen( sharedLibPath, RTLD_NOW | RTLD_DEEPBIND);
if (soHandle == nullptr)
return 1;

// now load shared lib function and execute it
F_hello * helloFn = reinterpret_cast<F_hello*>(dlsym( soHandle, "hello" ) );
helloFn();

return 0;
}

slave.h

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

void hello();

#ifdef __cplusplus
}
#endif

slave.cpp

#include "slave.h"
#include <iostream>

void printHello() {
std::cout << "Hello world from slave" << std::endl;
}

void hello() {
printHello(); // should call our own hello() function
}

You notice the same function printHello() exists both in the library and the master.

I compiled both manually this time (without CMake) and the following flags:

# build master
/usr/bin/c++ -fPIC -o tmp/master.o -c master.cpp
/usr/bin/c++ -rdynamic tmp/master.o -o Master -ldl

# build slave
/usr/bin/c++ -fPIC -o tmp/slave.o -c slave.cpp
/usr/bin/c++ -fPIC -shared -Wl,-soname,libSlave.so -o libSlave.so tmp/slave.o

Mind the use of -fPIC in both master and slave-library.

I now tried several combinations of RTLD_xx flags and compile flags:

1.

dlopen() flags: RTLD_NOW | RTLD_DEEPBIND
-fPIC for both libs

Hello world from master
Hello world from slave

-> result as expected (this is what I wanted to achieve)

2.

dlopen() flags: RTLD_NOW | RTLD_DEEPBIND
-fPIC for only the library

Hello world from master
Speicherzugriffsfehler (Speicherabzug geschrieben) ./Master

-> Here, a segfault happens in the line where the iostream libraries cout call is made; still, the printHello()s function in the library is called

3.

dlopen() flags: RTLD_NOW
-fPIC for only the library

Hello world from master
Hello world from master

-> This is my original behavior; so RTLD_DEEPBIND is definitely what I need, in conjunction with -fPIC in the master's binary;

Note: while CMake automatically adds -fPIC when building shared libraries, it does not generally do this for executables; here you need to manually add this flag when building with CMake

Note2: Using RTLD_NOW or RTLD_LAZY does not make a difference.

Using the combination of -fPIC on both executable and shared lib, with RTLD_DEEPBIND lets the original example with the different Dummy classes work without problems.

Link shared library with each other shared libraries c++

If your libB doesn't use anything in libA, the linker might discard linking to libA (depending on whether the linker flag --as-needed is the default in your toolchain.)

To force libB to link to libA, specify the flag -Wl,--no-as-needed to appear before -lA.



Related Topics



Leave a reply



Submit