1
votes

I've stumbled onto an interesting phenomenon while exploring shared library constructors. If I have a program which links against multiple shared libraries, and each library has its own constructor, if all the constructors have the same function name, only one library's constructor will be called multiple times, rather than each library's constructor being called once.

Here's a minimal example:

foo.h

#pragma once
void foo();

foo.c

#include <stdio.h>
#include "foo.h"

void foo() {};

void __attribute__((constructor)) init()
{
    printf("foo init\n");
}

bar.h

#pragma once
void bar();

bar.c

#include <stdio.h>
#include "bar.h"

void bar() {};

void __attribute__((constructor)) init()
{
    printf("bar init\n");
}

main.c

#include <stdio.h>
#include "foo.h"
#include "bar.h"

int main()
{
    foo();
    bar();
    return 0;
}

Compile:

gcc -o libfoo.so foo.c -fPIC -shared
gcc -o libbar.so bar.c -fPIC -shared
gcc -o main main.c -lfoo -lbar -L.

Now if I run main I get:

foo init
foo init

I expect to see one foo init and one bar init as each library's constructor is being called, but instead foo's constructor is called twice. What's also interesting is that if I reverse the order of the libraries in the last compile step to

gcc -o main main.c -lbar -lfoo -L.

The output I get is

bar init
bar init

I can fix this by changing one of the constructor function names to make them unique. I should also note this happens regardless of the function name, as long as they're named the same in both libraries.

My question is: why does this happen in the first place? I assume this is because there is a name conflict but I don't understand exactly what's happening.

I tested using gcc 4.8.4 on Ubuntu 14.04.5 LTS, as well as gcc 7.4.0 on Ubuntu 18.04.2 LTS in WSL.

1
This GNU doc maybe helpful. - Mihir Luthra

1 Answers

4
votes

This is normal and expected. In the C language, each external identifier can have at most one definition; if there are more, the behavior is undefined. ELF dynamic linking is (rather painstakingly) designed to yield behavior as close as possible to static linking, where despite more than one library defining an external identifier with the same name, the behavior may very well be well-defined due to only one definition getting used (static libraries are simply archives of object files, where only the object files needed to provide definitions for undefined symbol references get pulled into the link). This means the first definition (in link order) gets used whenever definitions exist in more than one linked shared library.

That's the explanation for what you're seeing, but the solution is much simpler. There is no reason for constructors to have external linkage to begin with. Make them all static and it doesn't matter what their names are. And for external identifiers in your libraries that do need to be external, make sure they're named according to some pattern where they won't clash.