Linking a shared library in executable vs. another shared lib

1.3k views Asked by At

tl;dr

Are there any differences in the way linux loads and links a shared library if the library is linked by an executable or by another shared library?

Background

Suppose I have a shared library (e.g. libA.so) containing a class with a static std::map and a set of singleton classes. Each singleton class has access to the map and statically adds an instance of itself to the map.

There are two scenarios:

  1. I use the shared library (libA.so) in an executable to read all the registered classes from the global map.
  2. I use the shared library (libA.so) in another shared library (libB.so) and use this new one in an executable. In this case libB.so uses the map from libA.so to provide some functionality to the executable (like a facade).

Problem

If I use (i.e. link) this shared library in an executable (scenario 1), the aforementioned map contains the list of singleton classes, however, if I use this library in another shared library and then use the new one in an executable (scenario 2), the map seems to be empty.

I cannot seem to understand how the linker handles the shared libraries in either of cases.

Update

As it turned out libB.so does not link to libA.so correctly even as explicitly instructed using -lA flag of g++. Even though I cannot see libA.so linked by libB.so using ldd, pmap or objdump, I get no runtime errors when using classes of libA.so. If I run the same command with clang++ I can see that all required libraries are listed.

2

There are 2 answers

3
jxh On BEST ANSWER

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.

2
Basile Starynkevitch On

Drepper's paper How To Write a Shared Library is a good reference on the subject and should answer your question.

You should be sure that in the second scenario, the library is linked only once (e.g. using the exact same path). See ld-linux(8) and use e.g. LD_DEBUG etc...

You might also strace your execution to understand what is going on.

And you should check (with pmap or using cat /proc/$ThePid/maps) that the library is loaded only once.