Shared Library Symbol Conflicts and Static Linking (On Linux)

Shared library symbol conflicts and static linking (on Linux)

Is this a designed behavior?

Yes.

At the time of introduction of shared libraries on UNIX, the goal was to pretend that they work just as if the code was in a regular (archive) library.

Suppose you have foo() defined in both libfoo and libbar, and bar() in libbar calls foo().

The design goal was that cc main.c -lfoo -lbar works the same regardless of whether libfoo and libbar are archive or a shared libraries. The only way to achieve this is to have libbar.so use dynamic linking to resolve call from bar() to foo(), despite having a local version of foo().

This design makes it impossible to create a self-contained libbar.so -- its behavior (which functions it ends up calling) depends on what other functions are linked into the process. This is also the opposite of how Windows DLLs work.

Creating self-contained DSOs was not a consideration at the time, since UNIX was effectively open-source.

You can change the rules with special linker flags, such as -Bsymbolic. But the rules get complicated very quickly, and (since that isn't the default) you may encounter bugs in the linker or the runtime loader.

Is there symbol conflict when loading two shared libraries with a same symbol

My question is, when app loads liba.so and libb.so by dlopen(), will there be any symbol conflict for the two Hello() implementations?

There is none. These are addresses returned, and both dynamically loaded libraries will live in a separate address space.

Even the dlsym function cannot be confused since you pass the handle returned by the dlopen function, so it would not by any mean become ambiguous.

(This would not even be an issue with overloading within the same library, respectively)

Linking two shared libraries with some of the same symbols

There are several ways to solve this:

  • Pass -Bsymbolic or -Bsymbolic-functions to the linker. This has a global effect: every reference to a global symbol (of function type for -Bsymbolic-functions) that can be resolved to a symbol in the library is resolved to that symbol. With this you lose the ability to interpose internal library calls to those symbols using LD_PRELOAD. The symbols are still exported, so they can be referenced from outside the library.

  • Use a version script to mark symbols as local to the library, e.g. use something like: {local: bar;}; and pass --version-script=versionfile to the linker. The symbols are not exported.

  • Mark symbols with an approppiate visibility (GCC info page for visibility), which will be either hidden, internal, or protected. protected visibility symbols are exported as .protected, hidden symbols are not exported, and internal symbols are not exported and you compromise not to call them from outside the library, even indirectly through function pointers.

You can check which symbols are exported with objdump -T.

Should I hide symbols when linking a static library into a shared one?

In my opinion you should hide all symbols that are not part of your API. It seems also that you will need an option --exclude-libs,ALL to convert third party static library symbols to hidden ones. I don't know any cons of the solution, it speeds up dynamic linker, also don't see any drawbacks for further static linking with another library version.. because its static linking not dynamic linking (hot swapping or so).

Shared library with statically linked dependencies

Will the static version of library (lstatic) loaded by lshared.so and the dynamic version (lstatic.so) conflict.

Possibly.

Will they share any global state.

Possibly.

The answers depend on how exactly lshared.so is built (which symbols it exports), and how the main helloworld binary is linked.

The answers also tend to be somewhat complicated, and may depend on inlining and other optimizations, and possibly on compiler versions.

It is best to avoid doing that by using shared version of lstatic.so, where all the answers become simple.

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.

Shared library and multiple levels of link with static libraries

I can suggest two approaches:

  1. Stop linking libraries to each other. Instead, let your shared libraries have unresolved symbols which will be resolved when they are loaded by your executable which will link the static libraries.

  2. Do "whole-archive" linking of your libraries. Since you're using GCC, the options you need for this are something like:

    gcc -o libteam.a -Wl,--whole-archive -lvendor -Wl,--no-whole-archive ...

    This way you will link not just "some" symbols from libvendor.a, but all of them, so you never need to link libvendor.a into anything else that uses libteam.a.

How to deal with symbol collisions between statically linked libraries?

At least in the case of static libraries you can work around it quite conveniently.

Consider those headers of libraries foo and bar. For the sake of this tutorial I'll also give you the source files

examples/ex01/foo.h

int spam(void);
double eggs(void);

examples/ex01/foo.c (this may be opaque/not available)

int the_spams;
double the_eggs;

int spam()
{
return the_spams++;
}

double eggs()
{
return the_eggs--;
}

example/ex01/bar.h

int spam(int new_spams);
double eggs(double new_eggs);

examples/ex01/bar.c (this may be opaque/not available)

int the_spams;
double the_eggs;

int spam(int new_spams)
{
int old_spams = the_spams;
the_spams = new_spams;
return old_spams;
}

double eggs(double new_eggs)
{
double old_eggs = the_eggs;
the_eggs = new_eggs;
return old_eggs;
}

We want to use those in a program foobar

example/ex01/foobar.c

#include <stdio.h>

#include "foo.h"
#include "bar.h"

int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;

printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
spam(new_bar_spam), new_bar_spam,
eggs(new_bar_eggs), new_bar_eggs );

return 0;
}

One problem becomes apparent immediately: C doesn't know overloading. So we have two times two functions with
identical name but of different signature. So we need some way to distinguish those. Anyway, lets see what a
compiler has to say about this:

example/ex01/ $ make
cc -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1

Okay, this was no surprise, it just told us, what we already knew, or at least suspected.

So can we somehow resolve that identifer collision without modifying the original libraries'
source code or headers? In fact we can.

First lets resolve the compile time issues. For this we surround the header includes with a
bunch of preprocessor #define directives that prefix all the symbols exported by the library.
Later we do this with some nice cozy wrapper-header, but just for the sake of demonstrating
what's going on were doing it verbatim in the foobar.c source file:

example/ex02/foobar.c

#include <stdio.h>

#define spam foo_spam
#define eggs foo_eggs
# include "foo.h"
#undef spam
#undef eggs

#define spam bar_spam
#define eggs bar_eggs
# include "bar.h"
#undef spam
#undef eggs

int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;

printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
bar_spam(new_bar_spam), new_bar_spam,
bar_eggs(new_bar_eggs), new_bar_eggs );

return 0;
}

Now if we compile this...

example/ex02/ $ make
cc -c -o foobar.o foobar.c
cc foobar.o foo.o bar.o -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1

... it first looks like things got worse. But look closely: Actually the compilation stage
went just fine. It's just the linker which is now complaining that there are symbols colliding
and it tells us the location (source file and line) where this happens. And as we can see
those symbols are unprefixed.

Let's take a look at the symbol tables with the nm utility:

example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

So now we're challenged with the exercise to prefix those symbols in some opaque binary. Yes, I know
in the course of this example we have the sources and could change this there. But for now, just assume
you have only those .o files, or a .a (which actually is just a bunch of .o).

objcopy to the rescue

There is one tool particularily interesting for us: objcopy

objcopy works on temporary files, so we can use it as if it were operating in-place. There is one
option/operation called --prefix-symbols and you have 3 guesses what it does.

So let's throw this fella onto our stubborn libraries:

example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o

nm shows us that this seemed to work:

example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams

example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams

Lets try linking this whole thing:

example/ex03/ $ make
cc foobar.o foo.o bar.o -o foobar

And indeed, it worked:

example/ex03/ $ ./foobar 
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000

Now I leave it as an exercise to the reader to implement a tool/script that automatically extracts the
symbols of a library using nm, writes a wrapper header file of the structure

/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */

and applies the symbol prefix to the static library's object files using objcopy.

What about shared libraries?

In principle the same could be done with shared libraries. However shared libraries, the name tells it,
are shared among multiple programs, so messing with a shared library in this way is not such a good idea.

You will not get around writing a trampoline wrapper. Even worse you cannot link against the shared library
on the object file level, but are forced to do dynamic loading. But this deserves its very own article.

Stay tuned, and happy coding.



Related Topics



Leave a reply



Submit