是否可以链接共享库(来自另一个共享库),而不必使其符号在全局范围内可见?

时间:2019-05-06 06:13:45

标签: c shared-libraries elf

假设我完全控制的libA取决于libC.so.2。同时,与我的libB可以共存的第三方libA取决于libC.so.1

正常的动态链接不起作用,因为libAlibB都将为libC中的符号接收错误的实现。如何使libAlibB一起使用,而对libA的构建管道进行最少的修改?

2 个答案:

答案 0 :(得分:0)

我已经搜索并测试了一下...

首先,变化中的libC

// libC1.c => libC.so.1
int c(void) { return 21; }
// libC2.c => libC.so.2
int c(void) { return 42; }

然后libAlibB

// 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)。现在libAlibB都指向正确的libC,但是有一个问题:两个libC都导出符号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中的一个,符号c(在 both libAlibB中均未定义) )(对于libAlibB都解析为已加载的那个)。

dlopen似乎是唯一的方法

有两种方法:

使用libAlibB修改应用程序

使用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的调用替换为: / p>

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

Allegedly -Bsymbolic might help,但我无法使其正常工作


如果运行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:     

问题在于,对于libAlibB而言,初始范围都依次包含库libC.so.1libC.so.2 。因此,当在clibA的每个符号中解析符号libB时,它首先会查看libC.so.1,找到符号并对其进行处理。

现在缺少的“所有”是一种更改此“初始对象范围”的方法。

答案 1 :(得分:0)

如果您可以更改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标头中的函数名称也将需要更新(通常在vim中是简单的s///)。

Implib.so的工作原理是为有问题的库中的每个符号生成一堆包装器(在这种情况下为libC.so.2),然后在内部(通过dlsym)将调用转发到其实际实现。