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:
.so
libraries have constructor
attributed function with the same name foo
.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:
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.
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
.
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:
-Bsymbolic
linker option when compiling each shared library but I never tested it myself.static
should also be effectiveCygwin 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.