为什么 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,仅打开本应为 dlsym
d.
的符号
我正在尝试为应用程序编写一个简单的插件系统,并希望防止插件相互踩踏符号,但是 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,仅打开本应为 dlsym
d.