Simple scenario
- Application uses
writefunction fromlibcand links to shared library. - Shared library defines
writefunction. - Original
writefunction fromlibcwill be overloaded by its version from the shared library.
Nested scenario
- Application uses
writefunction fromlibc. - Shared library 1 does not defines its own
writebut depends on shared library 2. - Shared library 2 defines
writefunction. - No.
writefunction will NOT be replaced with a version from the second shared library.
I do want to understand Why so ? How to make it work with nested shared library dependencies.
Here is exact code examples:
main.c
#include <unistd.h>
int main() {
write(1,"Hello\n",6);
return 0;
}
shared-lib-1.c
#include <unistd.h>
__attribute__((constructor))
void shared_lib_1_constructor(void) {
char str[] = "shared-lib-1-constructor\n";
write(1, str, sizeof(str));
}
shared-lib-2.c
#include <stdlib.h>
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count) {
exit(1);
return 0;
}
__attribute__((constructor))
void shared_lib_2_constructor(void) {
char str[] = "shared-lib-2-constructor\n";
write(1, str, sizeof(str));
}
build.sh
#!/usr/bin/env sh
set -v
set -e
gcc -g -fPIC -shared shared-lib-2.c -o libshared-lib-2.so
gcc -g -fPIC -shared -Wl,-rpath . -L`pwd` -lshared-lib-2 shared-lib-1.c -o libshared-lib-1.so
gcc -g -L`pwd` -Wl,-rpath . -lshared-lib-1 main.c
Execution of the a.out gives:
[smirnov@localhost tmp]$ ./a.out
shared-lib-2-constructor
shared-lib-1-constructor
Hello
write was not overwritten by shared-lib-2. It was completely ignored even from shared-lib-2 code.
How does it work with nested libraries ? Moving write definition to shared-lib-1 does work, it overloads glibc version and exits from application.
Running application with LD_DEBUG=all shows order of resolving:
36441: Initial object scopes
36441: object=./a.out [0]
36441: scope 0: ./a.out ./libshared-lib-1.so /lib64/libc.so.6 ./libshared-lib-2.so /lib64/ld-linux-x86-64.so.2
...
36441: calling init: ./libshared-lib-2.so
36441:
36441: symbol=write; lookup in file=./a.out [0]
36441: symbol=write; lookup in file=./libshared-lib-1.so [0]
36441: symbol=write; lookup in file=/lib64/libc.so.6 [0]
36441: binding file ./libshared-lib-2.so [0] to /lib64/libc.so.6 [0]: normal symbol `write'
Why libc placed in between shared-lib-1 and shared-lib-2 ?
It seems that ld resolves in the following order:
- all dependencies of a.out, but not recursivelly, only first level
- all subdependencies, but not recursivelly
- so on...
The only solution I've found is to use
LD_DYNAMIC_WEAK=1 ./a.out
Is there a way to fix behavior in advance ?