本文是对Option -Bsymbolic can cause dangerous side effects的翻译。
-Bsymbolic 可能会解决很多问题。不幸地是,-Bsymbolic也是一个会导致一些副作用的选项。
正常情况下,在linux平台上(不使用-Bsymbolic),加载的目标文件中第一次出现的符号将在程序中一直被使用,不论是定义在静态可执行部分,还是在动态目标文件中。这是通过符号抢占(symbol preemption)
来实现的。动态加载器构建符号表,所有的动态符号根据该符号表被决议。所以正常情况下,如果一个符号实例出现在动态库(DSO)
中,但是已经在静态可执行文件或者之前加载的动态库中被定义,那么以前的定义也将被用于当前的动态库中。
-Bsymbolic 通过关闭DOS中的符号抢占来改变这种行为。因此,DSO将总是使用它自己的实例。这个可能会导致一些意想不到的行为。
接下来我们展示多种可控的方法来达到以上的目的,同时也避免-Bsymbolic带来的副作用
-Bsymbolic带来的问题是:意想不到的使用了一些目标文件中的多个实例。
在一个目标中有多个实例会导致C++ 程序不相容。ANSI C++ 标准中 3.2节写道:
每个程序应该只包含非内联函数或者程序中使用的对象的一个定义。定义可以显式的出现在程序中,它可以在标准库或者用户自定义库中被找到,或者它可以被隐式的定义。一个内联函数应该被定义在它被用到的每个翻译单元。
所以,一旦你打开了-Bsymbolic开关,你可能会遇到问题,但是就像下面展示的Case2,一些问题来自于意想不到的代码,即使你编程的时候很小心,你仍然会遇到问题。另一方面,就像Case1中展示的,即使你小心地设计代码,能让它在-Bsymbolic时工作,但是你还是会遇到麻烦。
cat main.c
int MyGlobalVar;
extern void init();
int main(){
init();
printf("MyGlobalVar=%d\n",MyGlobalVar);
}
cat so.c
int MyGlobalVar;
void init(){
MyGlobalVar = 1;
}
$gcc -fPIC -shared -Wl,-soname=so.so.1 -o so.so.1 so.c -lc
$ gcc main.c so.so.1 -Wl,-rpath=./
$ ./a.out
MyGlobalVar=1
$ gcc -shared -Wl,-soname=so.so.1 -o so.so.1 so.c -lc -Wl,-Bsymbolic
$ gcc main.c so.so.1 -Wl,-rpath=./ -Wl,-Bsymbolic
$ ./a.out
MyGlobalVar=0
cat main.cpp
class X{
int l;
public:
X():l(1){}
};
extern void thrower();
int main(){
try{
thrower(;
}catch(X x){
}
return 0;
}
cat so.cpp
class X{
int l;
public:
X():l(1){}
};
void thrower(){
throw X();
}
$ icpc -shared -Wl,-soname,so.so.1 -o so.so.1 -lc so.cpp
$ icpc main.cpp so.so.1 -Wl,-rpath,/home/user01/bsymbolic/except_example
./a.out
$
$ icpc -shared -Wl,-soname,so.so.1 -o so.so.1 -lc so.cpp -Bsymbolic
$ icpc main.cpp so.so.1 -Wl,-rpath,/home/user01/bsymbolic/except_example
-Bsymbolic
$ ./a.out
Aborted
类型表引起的问题也会出现在 dymanic cast中,如果 DSO 试图转换一个指向在静态可执行文件中创建的对象的指针时,由于type table 不同,转化会失败
使用连接器脚本强制非公开的符号为local。a.so的开发者显式的定义想要被DSO使用的每个符号或者对象。只要正确使用,它将保证其他开发者不会使用意想不到的接口。
详细的步骤:创建一个向下面的脚本,在此脚本命名为 version_script
VERS_1.1 {local: symbol_name;symbol_name;};
这里,symbol_name是符号名称,*
可以用于适配
之后当编译DSO时,在链接命令行中添加 -Xlinker --version-script -Xlinker version_script
。
例如:
$ icc my_func.c -o libmy_lib.so -i_static -shared -Xlinker --version-script -Xlinker version_script
$ icc main.c -L. -lmy_lib -o main -i_static
$ cat version_script
VERS_1.1 {local: foo*;};