cdllconstructorshared-libraries

Why 'constructor' attributed function with the same name is called 3 times from library X instead of 1 time from libraries X, Y, Z


Scenario:

$ cat lib.c
#include <stdio.h>
#define STR_(x) #x
#define STR(x) STR_(x)
#define CAT_(x,y) x##y
#define CAT(x,y) CAT_(x,y)

__attribute__((constructor))
void CAT(foo,)(void) { printf("foo" STR(N) " %p\n", CAT(foo,)); }
void CAT(bar,N)(void){ puts("bar" STR(N)); }

$ cat main.c
void barx(void);
void bary(void);
void barz(void);

int main(void)
{
    barx();
    bary();
    barz();
}

$ cat build_run.sh
gcc lib.c -DN=x -c -fPIC -o libx.o && gcc libx.o -o libx.so -shared &&
gcc lib.c -DN=y -c -fPIC -o liby.o && gcc liby.o -o liby.so -shared &&
gcc lib.c -DN=z -c -fPIC -o libz.o && gcc libz.o -o libz.so -shared &&
gcc -L. -o main main.c -lx -ly -lz &&
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./main

$ bash build_run.sh
foox 0x7f0bf002e139
foox 0x7f0bf002e139
foox 0x7f0bf002e139
barx
bary
barz

Here we see that:

  1. All .so libraries have constructor attributed function with the same name foo.
  2. Function foo is called 3 times from library X (which may be unexpected behavior) instead of 1 time from libraries X, Y, Z (which may be expected behavior).

As I understand, addresses of constructor attributed functions foo are placed (directly or indirectly) in .init_array section. Hence, function names are expected to be irrelevant.

The core question: why function foo is called 3 times from library X instead of 1 time from libraries X, Y, Z?


Extra observations:

  1. If we change in lib.c from CAT(foo,) to CAT(foo,N) and rerun the build_run.sh, then we will see:
$ bash build_run.sh
fooz 0x7fc121dcc139
fooy 0x7fc121dd1139
foox 0x7fc121dd6139
barx
bary
barz

which may be expected behavior.

  1. Running the original (i.e. with CAT(foo,)) example on Cygwin leads to function foo is called 1 time from libraries X, Y, Z (which may be expected behavior).

System and software info:

$ uname -a
Linux xxx 5.15.0-79-generic #86-Ubuntu SMP Mon Jul 10 16:07:21 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

UPD. 1) I've tried to add -Bsymbolic to gcc libx.o -o libx.so -shared (to each library), which is "When creating a shared library, bind references to global symbols to the definition within the shared library, if any." However, I didn't help. 2) By some reason I cannot make constructor attributed functions static.


Solution

  • I think it is an example of symbol interposition.

    When loading shared libraries, the dynamic linker (Linux) resolves symbols (function names, variables) globally by default. If two or more shared libraries define the same symbol (e.g., foo), the first-loaded version of the symbol is used for all subsequent references.

    Potential fixes:

    Cygwin uses DLL libraries instead and Windows has a different mechanism.In Windows, each DLL has a distinct address space for its functions and variables, meaning symbol names are resolved independently within each DLL rather than globally across all loaded libraries.