为什么 RTLD_DEEPBIND 和 RTLD_LOCAL 不能防止静态 class 成员符号的冲突

why are RTLD_DEEPBIND and RTLD_LOCAL not preventing collision of static class member symbol

我正在尝试为应用程序编写一个简单的插件系统,并希望防止插件相互踩踏符号,但是 RTLD_DEEPBIND 和 RTLD_LOCAL 似乎还不够当静态 class 成员在不同的插件中碰巧具有相同的名称时,它就会出现。

我写了一个精简的例子来说明我的意思。 我编译并 运行 它是这样的:

g++ -c dumb-plugin.cpp -std=c++17 -fPIC
gcc -shared dumb-plugin.o -o dumb1.plugin
cp dumb1.plugin dumb2.plugin
g++ main.cpp -ldl -o main
./main

第二个插件的输出文件内容表明它重用了第一个插件的class。

我怎样才能避免这种情况?

编辑:我用 clang 编译了插件(不仅仅是插件),尽管所有 RTLD_DEEPBIND 的东西都在 main.cpp 中,它仍然是用 g++ 编译的。当使用 gcc 10.3 或 11.1 编译插件时,即使我尝试了 -Bsymbolic,它也不起作用。这是一个错误吗?

如果我 运行 使用 clang 在 DSO compiled/linked 上读取,我会看到这两行:

21: 00000000000040b0     4 OBJECT  UNIQUE DEFAULT   26 _ZN9DumbClass7co[...]_ZN9DumbClass7co[...]
25: 00000000000040b0     4 OBJECT  UNIQUE DEFAULT   26 _ZN9DumbClass7co[...]

使用 gcc 我得到:

20: 00000000000040a8     4 OBJECT  WEAK   DEFAULT   24 _ZN9DumbClass7co[...]
27: 00000000000040a8     4 OBJECT  WEAK   DEFAULT   24 _ZN9DumbClass7co[...]

在 BIND 列下使用 WEAK 而不是 UNIQUE。

哑巴-plugin.cpp:

#include <dlfcn.h>
#include <cstdio>
#include <string>

int global_counter = 0;
static int static_global_counter = 0;

std::string replace_slashes(const char * str) {
    std::string s;
    for (const char* c = str; *c != '[=16=]'; c++)
        s += (*c == '/')?
            '#' : *c;
    return s;
}

void foo() {}

class DumbClass {
    public:
        static inline int counter = 0;
};

extern "C" void plugin_func() {
    static int static_local_counter = 0;

    Dl_info info;
    dladdr((void*)foo, &info);

    std::string path = "plugin_func() from: " + replace_slashes(info.dli_fname);
    auto fp = std::fopen(path.c_str(), "w");

    fprintf(fp, "static local counter: %d\n",  static_local_counter++);
    fprintf(fp, "DumbClass::counter: %d\n",    DumbClass::counter++);
    fprintf(fp, "global counter: %d\n",        global_counter++);
    fprintf(fp, "static global counter: %d\n", static_global_counter++);

    std::fclose(fp);
}

main.cpp:

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


int main () {
    char path1[512], path2[512];
    getcwd(path1, 512);
    strcat(path1, "/dumb1.plugin");
    getcwd(path2, 512);
    strcat(path2, "/dumb2.plugin");

    auto h1 = dlopen(path1, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
    auto h2 = dlopen(path2, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);

    auto func = (void(*)())  dlsym(h1, "plugin_func");
    func();
    func =      (void(*)())  dlsym(h2, "plugin_func");
    func();
}

gcc 作为 global unique symbols(ELF 格式的 GNU 扩展)。按照设计,每个进程只有一个具有给定名称的此类符号。

clang 实现诸如普通弱符号之类的东西。当使用 RTLD_LOCAL 和 RTLD_DEEPBIND 时,它们不会发生冲突。

有几种方法可以避免冲突,但所有方法都需要插件编写者采取行动。 IMO 的最佳方法是使用 hidden symbol visibility by default,仅打开本应为 dlsymd.

的符号