使用 RTLD_DEEPBIND 动态加载共享库

Dynamic loading of shared library with RTLD_DEEPBIND

背景: 我有应用程序的一部分用作其他独立应用程序的库。他们 link 在 link 时间内到那个图书馆(比方说 lib.so)。这种方法的问题是我们必须使用相同的外部库,如 boost、ace 等,否则我们将有重复的符号,最终会导致崩溃。我们想解决这个问题。

我知道两种技术 - 一种是隐藏所有符号(不确定共享库的作用域顺序 global/local),另一种是使用动态 linking。我们选择了第二个选项(动态 linking),因为它让客户有机会使用存根 lib.so 进行简单测试。我们有非常简单的 api.

我在下面写了一个应用程序的小例子,它加载了示例共享库并且它崩溃了(我想了解它崩溃的原因以及它应该如何编写)。 崩溃发生在 dlopen 中,恰好在分配给 std::string(Aclass 类型的构造函数)时全局变量的初始化中。从我们的测试来看,在库的持续初始化过程中对 std 库的任何访问都会导致崩溃。

我们通过向 EXECUTABLE 添加 -fPIC 标志设法消除了崩溃(为什么这解决了我们的问题,我认为它应该为共享库设置,谁能更准确地解释我)?对我的理解来说不必要的这个标志是有问题的,因为它会减慢应用程序的速度,在我的情况下(低延迟应用程序)它是非常有问题的。

总结一下: 1.为什么会出现这个crash? 2. 为什么 -fPIC 标志足以解决此崩溃问题? 3. 为什么将-fPIC 标志设置为可执行文件就足够了? 4. 是否有可能以其他方式解决我的问题,以便共享库和客户端应用程序可以使用不同版本的库(如 boost、ace 等、编译器、linux 版本和标准库保证相同)? 5. 删除标志 RTLD_DEEPBIND 也将修复崩溃,但从 gcc man 看来我应该使用此标志,因为它将更改共享库的符号范围顺序 - 首先它将在本地范围内搜索符号,然后在全局范围内搜索符号 - 看起来像对我来说必须有,因为共享库将使用与可执行文件不同的外部库(并且动态加载将通过污染其符号范围来保护可执行文件)。为什么在这种简单的情况下删除此标志修复崩溃?

共享库dynLib.cpp:

#include <string>
class Aclass
{
    std::string s;
    s = "123";
}

Aclass a;

可执行文件main.cpp:

#include <stdlib.h>
#include <dlfcn.h>
#include <string>
#include <unistd.h>
#include <iostream>

int main()
{
    std::string dummyCrasher;
    dlerror();
    void* handle = dlopen("./libdynLib.so", RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
    if(!handle)
    {
        std::cout << "handle is null" << dlerror();
    }

    usleep(1000 * 1000 * 10);
}  

生成文件:生成文件

CXXFLAGS=-m32 -march=x86-64 -Wl,v -g -O3 -Wformat -Werror=format -c
CLINKFLAGS=-Wl,-Bstatic -Wl,Bdynamic -ldl -m32 -march=x86-64

all: dynLib.so dynamiclinking

dynLib.so: dynLib.o
    g++44 $(CLINKFLAGS) -shared -o libdynLib.so dynLib.o

dynLib.o: dynLib.cpp
    g++44 $(CXXFLAGS) dynLib.cpp

dynamiclinking: main.o
    g++44 $(CLINKFLAGS) -o dynamiclinking main.o -ldl

main.o: main.cpp
    g++44 (CXXFLAGS) main.cpp

.PHONY: clean
clean:
    rm dynLib.o main.o dynamiclinking libdynLib.so

PS。我手写代码(可能会出现一些拼写错误) PS 2. 使用 -fPIC 标志它会起作用:

main.o: main.cpp
    g++44 (CXXFLAGS) main.cpp -fPIC

更新 可以通过 libstdc++ 的 static linkage 来解决这个问题。但是我的问题仍然没有得到解答:(也许有人有时间看一下?

UPDATE2 GCC 4.4.6 和 4.8.1 出现同样的问题。

我认为您遇到的问题与 中的问题相同,其中可执行文件获取全局变量的副本:

well that's a wonderful feature of you building the main application without the -fPIC option. [...] This means that when the symbol is found in libdep.so, it gets copied into the initial data segment of the main executable at that address. Then the reference to duplicate in libdep.so is looked up and it points to the copy of the symbol that's in the main executable.

由于 RTLD_DEEPBIND,dynLib.so 在初始化 std::string 时从原始 libstdc++ 中看到错误的全局变量集,因此崩溃。

至于为什么链接器有这样的行为,this article有详细的解释(强调我的):

Recall that the program/executable is not relocatable, and thus its data addresses have to bound at link time. Therefore, the linker has to create a copy of the variable in the program's address space, and the dynamic loader will use that as the relocation address. This is similar to the discussion in the previous section - in a sense, myglob in the main program overrides the one in the shared library, and according to the global symbol lookup rules, it's being used instead.

最后一点:此行为是 platform-specific,至少在 PowerPC 上,主可执行文件中没有这样的全局变量副本。