动态库连接-符号冲突-全局符号介入

参考牛人:http://blog.chinaunix.net/uid-26548237-id-3837099.html
符号介入讨论帖子:https://bbs.csdn.net/topics/391945247
强弱符号链接:https://blog.csdn.net/yihucha166/article/details/6041128

最重要的写在最前面:

动态符号符号连接时的符号冲突,另一种专业术语是共享对象的全局符号介入。
一个共享对象里面的全局符号被另一个共享对象的全局符号覆盖的现象又被称为共享对象的全局符号介入。

直接贴结论:

全局符号介入这个问题,实际上Linux下的动态链接器是这样处理的:它定义了一个规则,那就是当一个符号需要被加入到全局符号表时,如果相同的符号名已经存在,则后加入的忽略(忽略正在加入的符号)

(一)比较容易理解的符号介入:
liba.c

#include <stdio.h>
#include <stdlib.h>
#include "libA.h"
void libA()
{
        common();
} 
void common()
{
        printf("libA common!\n");
}

libb.c

#include <stdio.h>
#include <stdlib.h>
#include "libB.h"
void libB()
{
        common();
}
void common()
{
        printf("libB common!\n");
}

load_main.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
int main()
{
        libA();
        libB();
        return 0;
}

编译方式1,先链接liba.so 再链接libB.so:

gcc -fpic -shared -o liba.so libA.c
gcc -fpic -shared -o libb.so libB.c
gcc load_main.c -o load_test liba.so libb.so -Wl,--rpath=./
执行./load_test
$libA common!
$libA common!

编译方式2,先链接libB.so 再链接libA.so:

gcc -fpic -shared -o liba.so libA.c
gcc -fpic -shared -o libb.so libB.c
gcc load_main.c -o load_test libb.so liba,so -Wl,--rpath=./
执行./load_test
$libB common!
$libB common!

结果是加载顺序不同,执行的代码也不同,后加载的common符号会被忽略。所以打印出来的是先加载的common符号。动态符号连接时会在全局符号表中搜索未定义的符号,也会把动态库的符号添加到全局符号表中,当一个符号要被加入到全局符号表时,如果相同的符号名已经存在,则忽略将要添加的符号。

(二)稍微复杂一些的符号介入:
foo.c

#include <stdio.h>
struct AA{
	int a;
	int b;
} b = { 3, 3 };
int main();
void foo()
{
	b.a = 4;
	b.b = 4;
	printf("foo:\t(&b)=0x%08x\n\tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n",
		&b, sizeof(b), b.a, b.b, main);
}

main.c:

#include <stdio.h>
int b = 1;
int c = 1;
int main()
{
	int count = 2;
	while (count-- > 0) {
	t2();
	foo();
	printf("main:\t(&b)=0x%08x\n\t(&c)=0x%08x\n\tsizeof(b)=%d\n\tb=%d\n\tc=%d\n",
		&b, &c, sizeof(b), b, c);
	sleep(1);
	}
return 0;
}

t2.c

#include <stdio.h>
int b;
int c;
int t2()
{
	printf("t2:\t(&b)=0x%08x\n\t(&c)=0x%08x\n\tsizeof(b)=%d\n\tb=%d\n\tc=%d\n",
		&b, &c, sizeof(b), b, c);
	return 0;
}

编译:

test: main.o t2.o
	gcc -shared -fPIC -o libfoo.so foo.c
	gcc -o test main.o t2.o -L. -lfoo -Wl,--rpath=./

main.o: main.c
t2.o: t2.c

.PHONY:clean
clean:
	rm -f *.o *.so test*

执行结果

t2:	(&b)=0xab214010
	(&c)=0xab214014
	sizeof(b)=4
	b=1
	c=1
foo:	(&b)=0xab214010
	sizeof(b)=8
	b.a=4
	b.b=4
	main:0xab211189
main:	(&b)=0xab214010
	(&c)=0xab214014
	sizeof(b)=4
	b=4
	c=4
t2:	(&b)=0xab214010
	(&c)=0xab214014
	sizeof(b)=4
	b=4
	c=4
foo:	(&b)=0xab214010
	sizeof(b)=8
	b.a=4
	b.b=4
	main:0xab211189
main:	(&b)=0xab214010
	(&c)=0xab214014
	sizeof(b)=4
	b=4
	c=4

讨论帖中讨论的两个问题:

第一个问题
t2.c 和 main.c 中都定义了全局变量b,c ,但是编译时没有报重复定义的错误。
$nm main.o
0000000000000000 D b
0000000000000004 D c
                 U foo
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T main
                 U printf
                 U sleep
                 U t2
$nm t2.o 
0000000000000004 C b
0000000000000004 C c
                 U _GLOBAL_OFFSET_TABLE_
                 U printf
0000000000000000 T t2

t2.o中的b和c 的类型是C,main.o中b和c 类型是D。
“C” The symbol is common. Common symbols are uninitialized data. When linking, multiple common symbols may appear with the same name. If the symbol is defined anywhere, the common symbols are treated as undefined references. “D” “d” The symbol is in the initialized data section.
“C”这个符号表示弱符号。 弱符号是未初始化的数据。 链接时,可能会出现多个同名的弱符号。 如果强符号在任何地方定义,则弱符号将被视为未定义的引用。(链接器行为,不分配内存空间,链接时重定位relocate到强符号位置)
“D” “d” 符号在初始化数据段中。(强符号分配内存空间)
“C” 符号的解释为弱符号,关于强符号和弱符号,参考下面的强符号和弱符号章节。

初始化的全局变量是强符号,未初始化的全局变量是弱符号,强符号最多只能存在一个,弱符号可以有很多个,链接器最终会选择了强符号。

第二个问题

在t2中b和c的值是1,在foo中变成了4,看起来main中的变量好像是被foo覆盖了。
其实不是被main里的b被覆盖了,而是libfoo里的b被忽略了。这个现象也是符号介入引起的。
libfoo.so中的符号b也是分配了内存,在数据段中。

$nm libfoo.so 
0000000000004028 D b
$nm test 
0000000000004010 D b

现在有两个b符号,main里的b符号已经加入的全局符号表中,执行t2()接口时,访问的也是main.o中的b,c符号。此时数值是1.
接着将调用foo(),加载器加载libfoo.so时,准备将中libfoo.so中的符号b符号导入全局符号表中,但是此时全局符号表中已经有了b符号。libfoo.so中的b符号被忽略。foo接口访问内存的还是main.o(test)中的b和c内存.

全局符号介入只用在对符号进行定位,即指定该符号属于哪个module,哪个文件,并不限制后面对它内容进行更改(不管在哪个模块,哪个文件里面)。 已经分析出变量b采用的是文件main中的符号,那么初始值,地址都是使用的该值,后面再使用的时候,都会基于该值进行操作,所以你会看到main/t2里面还都是1,1,但是foo之后就都是4,4了。其实全局符号介入忽略的只有你在libfoo.so里面定义的b(3,3)。

强符号和弱符号

强符号:编译器默认函数和初始化了的全局变量
弱符号:未初始化的全局变量为弱符号(也可以通过gcc的__attribute__((weak))指定)

冲突的例子:如果我们在目标文件A和目标文件B都定义了一个全局整形变量g_variable,并将它们都初始化,这样在链接时链接器就会报multiple definition的错误,这种错误就是因为强符号不能被多次定义。

但如果我们在A中定义了一个double类型的g_variable,B文件中定义了一个int类型的g_variable,它们都没有被初始化过,则链接器会选择double类型8字节为g_variable分配空间。

原因:由于链接器需要支持弱符号机制(对于程序库来说这种模式便于用户进行扩展和自定义),并且链接器并不支持链接时的符号类型,所以允许多个不同类型弱符号存在

如果想关掉弱符号可以使用gcc的“-fno-common”选项

被多次定义的全局符号,链接器会按照以下规则进行处理:
1. 不允许强符号被多次定义,否则,链接器报符号重复定义错误。
2. 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号
3. 如果一个符号在所有目标文件中都是弱符号,那么选择占用空间最大的那个,见上例。
————————————————
强弱符号版权声明:强弱符号章节为CSDN博主「yihucha166」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值