【问题标题】:Linking with multiple versions of a library链接库的多个版本
【发布时间】:2011-03-15 01:16:40
【问题描述】:

我有一个应用程序与第三方供应商 VENDOR1 的库 libfoo 的版本 X 静态链接。它还与来自不同第三方供应商 VENDOR2 的动态(共享)库 libbar 链接,该库静态链接来自 VENDOR1 的 libfoo 版本 Y。

所以 libbar.so 包含版本 Y 的 libfoo.a 而我的可执行文件包含版本 X 的 libfoo.a libbar 仅在内部使用 libfoo,并且没有 libfoo 对象从我的应用程序传递到 libbar。

在构建时没有错误,但在运行时应用程序段错误。原因似乎是版本 X 使用的结构与版本 Y 的大小不同,并且运行时链接器似乎混淆了哪个由哪个使用。

VENDOR1 和 VENDOR2 都是封闭源代码,所以我无法重建它们。

有没有办法构建/链接我的应用程序,使其始终解析为版本 X,而 libbar 始终解析为版本 Y,并且两者从不混合?

【问题讨论】:

  • 你能让你的应用动态链接到 VENDOR1 吗?
  • 绝不是语言中立的。这对于编译器链接器和操作系统来说是非常具体的,它们是如何一起工作的。最简单的方法是给两个供应商发电子邮件,看看他们是如何解决这个问题的。
  • 我们目前的想法是,至少在 Linux 上,使用带有 RTLD_DEEPBIND 标志的 libbar.so 上的 dlopen()。另一种可能性是将使用 libfoo.a 的应用程序分离到一个共享库 libbaz.so 中,它包装了 libfoo.a 的使用,然后让应用程序 dlopen libbaz.so 和 libbar.so 与我们认为可能保留所有的 RTLD_LOCAL内部的重复符号。这可能适用于 linux,但我们需要它,因此也适用于 Solaris、AIX 和 HPUX。
  • 哎呀哎呀。不幸的是,不,在 Linux 上没有“简单”的解决方法。

标签: c++ c linux unix linker


【解决方案1】:

感谢所有回复。我有一个似乎有效的解决方案。 下面用一个例子来详细说明问题。

在 main.c 我们有:

#include <stdio.h>

extern int foo();

int bar()
{
    printf("bar in main.c called\n");
    return 0;
}

int main()
{
    printf("result from foo is %d\n", foo());
    printf("result from bar is %d\n", bar());
}

在 foo.c 我们有:

extern int bar();

int foo()
{
    int x = bar();
    return x;
}

在 bar.c 中我们有:

#include <stdio.h>

int bar()
{
    printf("bar in bar.c called\n");
    return 2;
}

编译 bar.c 和 foo.c:

$ gcc -fPIC -c bar.c
$ gcc -fPIC -c foo.c

将 bar.o 添加到静态库:

$ ar r libbar.a bar.o

现在使用 foo.o 创建一个共享库并与静态 libbar.a 链接

$ gcc -shared -o libfoo.so foo.o -L. -lbar

编译 main.c 并链接共享库 libfoo.so

$ gcc -o main main.c -L. -lfoo

设置 LD_LIBRARY_PATH 找到 libfoo.so 并运行 main:

$ setenv LD_LIBRARY_PATH `pwd`
$ ./main
bar in main.c called
result from foo is 0
bar in main.c called
result from bar is 0

请注意,调用的是 main.c 中 bar 的版本,而不是链接到共享库中的版本。

在 main2.c 我们有:

#include <stdio.h>
#include <dlfcn.h>


int bar()
{
    printf("bar in main2.c called\n");
    return 0;
}

int main()
{
    int x;
    int (*foo)();
    void *handle = dlopen("libfoo.so", RTLD_GLOBAL|RTLD_LAZY);
    foo = dlsym(handle, "foo");
    printf("result from foo is %d\n", foo());
    printf("result from bar is %d\n", bar());
}

编译并运行 main2.c(注意我们不需要显式链接 libfoo.so):

$ gcc -o main2 main2.c -ldl
$ ./main2
bar in bar.c called
result from foo is 2
bar in main2.c called
result from bar is 0

现在共享库中的 foo 调用共享库中的 bar 和 main.c 中的 main 调用 bar

我不认为这种行为是直观的,使用 dlopen/dlsym 需要做更多的工作,但它确实解决了我的问题。

再次感谢 cmets。

【讨论】:

  • 非常感谢您的提示,但您能否解释一下为什么要使用 RTLD_GLOBAL,如果重点是为了将库彼此隔离?
【解决方案2】:

尝试部分链接,以便您拥有一个带有 libbar 和 libfoo-Y 的目标文件“partial.o”。使用带有“--localize-symbols”的 objcopy 来使 libfoo-Y 中的 partial.o 中的符号成为本地符号。您应该能够通过在 libfoo-Y 上运行 nm 并按摩输出来生成。然后获取修改后的 partial.o 并将其链接到您的应用。

我在 vxWorks 上使用 gcc 工具链做了类似的事情,其中​​动态库并不复杂,但需要将同一个库的两个版本干净地链接到一个单一的应用程序中。

【讨论】:

    【解决方案3】:

    对不起,没有。我对 Linux(可能还有大多数 *nixes)的理解是这是不可能的。对于您的问题,我能想到的唯一“解决方案”是,如果您创建一个代理应用程序,它会以 IPC 的形式从 libbar 公开您需要的内容。然后,您可以使用 LD_LIBRARY_PATH 或类似的东西使该代理加载正确的版本。

    【讨论】:

    • "可能还有大多数 *nixes" - AIX 除外。在 AIX 上这实际上是可能的,并且默认的链接器行为正是问题所要求的。
    • OS X 也能优雅地处理这个问题。每个 .so/.dylib 都有自己的链接表/引用。我说大部分正是因为我知道这还不是全部。无论如何,Linux 不会这样做,AFAIK。
    猜你喜欢
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-29
    • 1970-01-01
    相关资源
    最近更新 更多