Можно ли связать разделяемую библиотеку (из другой разделяемой библиотеки), не делая ее символы глобально видимыми? - PullRequest
3 голосов
/ 06 мая 2019

Предположим, libA, который я полностью контролирую, зависит от libC.so.2. Между тем, третья сторона libB, с которой мои libA могут сосуществовать в одном и том же процессе, зависит от libC.so.1.

Обычное динамическое связывание не работает, потому что libA или libB получат неверную реализацию для символов в libC. Как я могу заставить libA работать с libB, с минимальной модификацией строительного конвейера libA?

Ответы [ 2 ]

0 голосов
/ 07 мая 2019

Если вы согласны с изменением имен символов с libC.so.2, вы можете использовать функцию переименования Implib.so . Например. изменить все символы libC.so.2 на префикс MYPREFIX_:

$ cat mycallback.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C"
#endif
void *mycallback() {
  void *h = dlmopen(LM_ID_NEWLM, "libxyz.so", RTLD_LAZY | RTLD_DEEPBIND);
  if (h)
    return h;
  fprintf(stderr, "dlmopen failed: %s\n", dlerror());
  exit(1);
}
$ implib-gen.py --dlopen-callback=mycallback --symbol_prefix=MYPREFIX_ libC.so.2
$ ... # Link your app with libC.so.2.tramp.S, libC.so.2.init.c and mycallback.c, keep libC.so.1 unchanged

Имена функций в заголовке libC.so.2 также должны быть обновлены (часто это просто s/// в vim).

Implib.so работает, генерируя связки для каждого символа в проблемной библиотеке (в данном случае libC.so.2) и перенаправляя вызовы их фактической реализации внутренне (через dlsym).

0 голосов
/ 06 мая 2019

Я немного искал и проверял ...

Сначала меняется 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 таким образом.

Якобы -Bsymbolic может помочь , но я не смог заставить его работать


Если запустить 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, находит символ и завершает его.

Теперь "все", что отсутствует, этоспособ изменить эту «начальную область объекта».

...