Я немного искал и проверял ...
Сначала меняется libC
:
// libC1.c => libC.so.1
int c(void) { return 21; }
// libC2.c => libC.so.2
int c(void) { return 42; }
Затем libA
и libB
:
// libA.c => libA.so | gcc -fPIC -shared -o libA.so libA.c -l:libC.so.1 -L.
extern int c(void);
int a(void) { return c(); }
// libB.c => libB.so | gcc -fPIC -shared -o libB.so libB.c -l:libC.so.2 -L.
extern int c(void);
int b(void) { return c(); }
Обратите внимание, что выше в обоих случаях я указываю правильный файл .so
напрямую (-l:libC.so.1
и -l:libC.so.2
).Теперь и libA
, и libB
относятся к правильному libC
, но есть проблема: Оба libC
s экспортируют символ c
!
Таким образом ...
extern int a(void);
extern int b(void);
#include <stdio.h>
int main() {
printf("a => %d, b => %d\n", a(), b());
}
... счастливо напечатает a => 21, b => 21
.Причина в том, что после того, как динамический компоновщик загружает один из libC
s, разрешается символ c
(который не определен в и libA
и libB
) (для обоих libA
и libB
) к загруженному.
dlopen
кажется единственным способом
Существует два подхода:
Изменить приложение с помощью libA
иlibB
Загрузка обеих библиотек по своему усмотрению с использованием dlopen
, RTLD_LOCAL
делает символы загруженными (таким образом, также символы зависимостей загруженной библиотеки) не видимыми для приложения (илипозже звонит на dlopen
).
#include <stdio.h>
#include <assert.h>
#include <dlfcn.h>
int (*a)(void);
int (*b)(void);
int main() {
void * const a_handle = dlopen("libA.so", RTLD_NOW | RTLD_LOCAL);
// you could dlopen("libC.so.2", RTLD_NOW | RTLD_GLOBAL) here to "select"
// the correct symbol `c` for the following, too.
void * const b_handle = dlopen("libB.so", RTLD_NOW | RTLD_LOCAL);
assert(a_handle); // real error handling here please!
assert(b_handle);
*(void **)(&a) = dlsym(a_handle, "a");
*(void **)(&b) = dlsym(b_handle, "b");
assert(a); // real error handling here please!
assert(b);
printf("a => %d, b => %d\n", a(), b());
}
Затем компиляция, компоновка и запуск выше (main2.c
) дает
# gcc main2.c -ldl
# LD_LIBRARY_PATH=. ./a.out
a => 21, b => 42
Изменение libA
В исходном коде вашего libA
где бы вы ни вызывали функцию funC
из libC
, вам нужно заменить этот вызов на funC_impl
на:
int (*funC_impl)(char *, double); // for a funC(char *, double) which returns an int
// and somewhere during initialization:
void * const c_handle = dlopen("libC.so.2", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
// check c_handle != NULL
*(void **)(&funC_impl) = dlsym(c_handle, "funC");
// check for errors! (dlerror)
И все это для каждой функции, конечно ...и, конечно, вы не можете контролировать libB
таким образом.
Если запустить LD_LIBRARY_PATH=. LD_DEBUG=all ./a.out 2>&1
(для версии вверху ответа), то это часть вывода:
10545: Initial object scopes
10545: object=./a.out [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=linux-vdso.so.1 [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545: scope 1: linux-vdso.so.1
10545:
10545: object=./libA.so [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=./libB.so [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=./libC.so.1 [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=./libC.so.2 [0]
10545: scope 0: ./a.out ./libA.so ./libB.so /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/libc.so.6 ./libC.so.1 ./libC.so.2 /nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2
10545:
10545: object=/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27/lib/ld-linux-x86-64.so.2 [0]
10545: no scope
10545:
Проблема в том, что для libA
и libB
Начальная область содержит библиотеки libC.so.1
и libC.so.2
в указанном порядке .Таким образом, при разрешении символа c
в каждом из libA
и libB
он сначала просматривает libC.so.1
, находит символ и завершает его.
Теперь "все", что отсутствует, этоспособ изменить эту «начальную область объекта».