Option -Bsymbolic 会导致严重副作用

本文是对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时工作,但是你还是会遇到麻烦。

Case 1: 在静态可执行文件和DSO中,一个变量被定义了2编

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;	
}
  • 没用用-Bsymbolic编译,在main中打印出的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
  • 用-Bsymbolic编译,在main中打印出来的是0,即使main调用了init 函数,main中的MyGlobalVar依然没有被初始化。
$  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

Case 2 异常处理

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();
}
  • 当不用-Bsymbolic编译编译时,main会捕获异常
$ 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
$
  • 当用-Bsymbolic编译编译时,main不会捕获异常
$ 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

Case 3: dynamic cast

类型表引起的问题也会出现在 dymanic cast中,如果 DSO 试图转换一个指向在静态可执行文件中创建的对象的指针时,由于type table 不同,转化会失败

Case4: VTables

Case5: Duplicated I/O Buffers

替代-Bsymbolic的安全方法

使用连接器脚本强制非公开的符号为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*;};


你可能感兴趣的:(linux)