测试1:静态链接
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void test(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void test(void) {
printf("This is b\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void test(void);
int main() {
test();
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.a libb.a
gcc main.o -L. -la -lb
ba: main.o liba.a libb.a
gcc main.o -L. -lb -la
main.o: main.c
gcc main.c -c
liba.a: a.c
gcc a.c -c
ar -cr liba.a a.o
libb.a: b.c
gcc b.c -c
ar -cr libb.a b.o
clean:
rm -fr *.o *.a *.so a.out |
|
|
上面的代码执行结果如下:
代码块 |
---|
$ make ab
gcc main.c -c
gcc a.c -c
ar -cr liba.a a.o
gcc b.c -c
ar -cr libb.a b.o
gcc main.o -L. -la -lb
$ ./a.out
This is a
$ make ba
gcc main.o -L. -lb -la
$ ./a.out
This is b |
解释如下:
链接过程中从左向右扫描目标文件和库中的符号,并维护一个Undefined符号表。在遇到main.o时,由于test函数未定义,Undefined符号表中记录下test。接下来,如果先链接liba.a,则由liba.a提供test函数的实现,并且由于链接完liba.a后已经没有未解决的符号了,后面的libb.a不会再扫描,链接过程结束,main函数中的test函数为a.c中的实现。当链接顺序反过来时,则只会链接liba.a。
测试2:静态链接-符号冲突
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
int a = 1;
void test(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
int b = 1;
void test(void) {
printf("This is b\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void test(void);
extern int a;
extern ina b;
int main() {
test();
printf("a=%d, b=%d\n", a, b);
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.a libb.a
gcc main.o -L. -la -lb
ba: main.o liba.a libb.a
gcc main.o -L. -lb -la
main.o: main.c
gcc main.c -c
liba.a: a.c
gcc a.c -c
ar -cr liba.a a.o
libb.a: b.c
gcc b.c -c
ar -cr libb.a b.o
clean:
rm -fr *.o *.a *.so a.out |
|
|
执行结果:
代码块 |
---|
$ make ab
gcc main.c -c
gcc a.c -c
ar -cr liba.a a.o
gcc b.c -c
ar -cr libb.a b.o
gcc main.o -L. -la -lb
/usr/bin/ld: ./libb.a(b.o): in function `test':
b.c:(.text+0x0): multiple definition of `test'; ./liba.a(a.o):a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:5: ab] Error 1
$ make ba
gcc main.o -L. -lb -la
/usr/bin/ld: ./liba.a(a.o): in function `test':
a.c:(.text+0x0): multiple definition of `test'; ./libb.a(b.o):b.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:8: ba] Error 1 |
解释如下:
链接过程同样是从左向右扫描目标文件和库中的符号,并维护Undefined符号表。在遇到main.o时,由于test函数和外部变量a、b都未定义,Undefined符号表中记录下这三项。接下来,如果先链接liba.a,则由liba.a提供test函数和变量a的定义,但此时还有变量b未解决,所以会继续链接后面的库。接下来是链接libb.a,libb.a中有变量b的定义,但还有一个test函数的定义,由于静态链接中没有全局符号介入问题,并且两个test函数的符号优先级相同,所以报重复定义错误。不管是先链接liba.a还是先链接libb.a都存在同样的问题。
测试3:静态链接-链接顺序影响链接结果
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void testa(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testa(void);
void testb(void) {
testa();
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testb(void);
int main() {
testb();
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.a libb.a
gcc main.o -L. -la -lb
ba: main.o liba.a libb.a
gcc main.o -L. -lb -la
main.o: main.c
gcc main.c -c
liba.a: a.c
gcc a.c -c
ar -cr liba.a a.o
libb.a: b.c
gcc b.c -c
ar -cr libb.a b.o
clean:
rm -fr *.o *.a *.so a.out |
|
|
执行结果:
代码块 |
---|
$ make ab
gcc main.c -c
gcc a.c -c
ar -cr liba.a a.o
gcc b.c -c
ar -cr libb.a b.o
gcc main.o -L. -la -lb
/usr/bin/ld: ./libb.a(b.o): in function `testb':
b.c:(.text+0x9): undefined reference to `testa'
collect2: error: ld returned 1 exit status
make: *** [Makefile:5: ab] Error 1
$ make ba
gcc main.o -L. -lb -la
$ ./a.out
This is a |
解释如下:
链接过程同样是从左向右扫描目标文件和库中的符号,并维护Undefined符号表。在遇到main.o时,由于testb函数未定义,Undefined符号表中记录下testb。接下来,如果先链接liba.a,由于liba.a中并没有提供testb函数,而此时链接器还没遇到libb.a,不知道libb.a需testa函数,所以链接器认为liba.a对整个链接过程没作用,直接抛弃掉了。接下来遇到libb.a,libb.a提供了testb函数的实现,但需要testa函数的实现,而前面的liba.a已经被抛弃了,所以报未定义错误。
以上过程如果反过来,先链接libb.a,再链接liba.a,则不会有问题。
总结起来就是,链接器链接时如果发现一个库对当前的链接没有作用,那就会跳过这个库,不管后续的库是否对这个库有依赖。
测试4:动态链接
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void test(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void test(void) {
printf("This is b\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void test(void);
int main() {
test();
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.so libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
ba: main.o liba.so libb.so
gcc main.o -L. -lb -la -Wl,-rpath=.
main.o: main.c
gcc main.c -c
liba.so: a.c
gcc -fPIC -shared a.c -o liba.so
libb.so: b.c
gcc -fPIC -shared b.c -o libb.so
clean:
rm -fr *.o *.a *.so a.out |
|
|
执行结果:
代码块 |
---|
$ make ab
gcc main.c -c
gcc -fPIC -shared a.c -o liba.so
gcc -fPIC -shared b.c -o libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
$ ./a.out
This is a
$ make ba
gcc main.o -L. -lb -la -Wl,-rpath=.
$ ./a.out
This is b |
结果解释:
同静态库,liba.so和libb.so只要链接一个就可以解决所有的符号冲突,整个链接过程就结束了,所以先链接哪个就用哪个的实现。
通过ldd命令查看生成的可执行文件中依赖的动态库也可以验证上面的结论,如下:
代码块 |
---|
$ make ab
gcc main.o -L. -la -lb -Wl,-rpath=.
$ ldd a.out
linux-vdso.so.1 (0x00007ffc593a0000)
liba.so => ./liba.so (0x00007fa175d70000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa175b76000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa175d7c000)
$ make ba
gcc main.o -L. -lb -la -Wl,-rpath=.
$ ldd a.out
linux-vdso.so.1 (0x00007ffd41d2d000)
libb.so => ./libb.so (0x00007fe5505b3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe5503b9000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe5505bf000) |
可以看到,生成的可执行文件只会依赖liba.so或libb.so中的一个。
测试5:动态链接-符号冲突
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
int a = 1;
void test(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
int b = 2;
void test(void) {
printf("This is b\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void test(void);
extern int a;
extern int b;
int main() {
test();
printf("a=%d, b=%d\n", a, b);
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.so libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
ba: main.o liba.so libb.so
gcc main.o -L. -lb -la -Wl,-rpath=.
main.o: main.c
gcc main.c -c
liba.so: a.c
gcc -fPIC -shared a.c -o liba.so
libb.so: b.c
gcc -fPIC -shared b.c -o libb.so
clean:
rm -fr *.o *.a *.so a.out |
|
|
执行结果:
代码块 |
---|
$ make ab
gcc main.c -c
gcc -fPIC -shared a.c -o liba.so
gcc -fPIC -shared b.c -o libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
$ ./a.out
This is a
a=1, b=2
$ make ba
gcc main.o -L. -lb -la -Wl,-rpath=.
$ ./a.out
This is b
a=1, b=2 |
解释如下:
对比静态库的版本,动态库的版本可以链接成功,这是因为动态库存在全局符号介入问题。动态链接器在加载动态库时,会维护一份所有共享对象的全局符号表,当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号将被忽略。
测试5:动态链接-链接顺序影响链接结果
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void testa(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testa(void);
void testb(void) {
testa();
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testb(void);
int main() {
testb();
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.a libb.a
gcc main.o -L. -la -lb
ba: main.o liba.a libb.a
gcc main.o -L. -lb -la
main.o: main.c
gcc main.c -c
liba.a: a.c
gcc a.c -c
ar -cr liba.a a.o
libb.a: b.c
gcc b.c -c
ar -cr libb.a b.o
clean:
rm -fr *.o *.a *.so a.out |
|
|
执行结果:
代码块 |
---|
$ make ab
gcc main.c -c
gcc -fPIC -shared a.c -o liba.so
gcc -fPIC -shared b.c -o libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
/usr/bin/ld: ./libb.so: undefined reference to `testa'
collect2: error: ld returned 1 exit status
make: *** [Makefile:5: ab] Error 1
$ make ba
gcc main.o -L. -lb -la -Wl,-rpath=.
$ ./a.out
This is a |
与静态库的规则一样,链接过程会抛弃掉链接器认为没用的动态库。
测试7:动态链接-一种消除链接顺序影响的办法
版块 |
---|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
void testa(void) {
printf("This is a\n");
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testa(void);
void testb(void) {
testa();
} |
|
列 |
---|
| 代码块 |
---|
| #include <stdio.h>
extern void testb(void);
int main() {
testb();
return 0;
} |
|
列 |
---|
| 代码块 |
---|
| all:
@echo "Please specify a target, try: make [ab | ba]"
ab: main.o liba.so libb.so
gcc main.o -L. -la -lb -Wl,-rpath=.
ba: main.o liba.so libb.so
gcc main.o -L. -lb -la -Wl,-rpath=.
main.o: main.c
gcc main.c -c
liba.so: a.c
gcc -fPIC -shared a.c -o liba.so
libb.so: b.c liba.so
gcc -fPIC -shared b.c liba.so -o libb.so -Wl,-rpath=.
clean:
rm -fr *.o *.a *.so a.out |
|
|
这种方式下,无论是make ab还是make ba,都可以链接通过。这里的关键是在生成libb.so时,指针libb.so依赖于liba.so,所以在即使前面liba.so被抛弃了,在加载libb.so时,仍然会把liba.so重新加载进来。
测试8:静态链接中的环形引用问题
一些结论
1. 无论是动态链接还是静态链接,链接过程都是从左向右扫描库文件。
2. 从左向右扫描过程中,如果发现一个同优先级的符号出现了两次,那么在静态链接中,会报重复定义错误,如果是动态链接,则会以第一次加载的符号为准,这是由于全局符号介入的影响。
3. 无论是动态链接还是静态链接,在从左向右扫描库文件过程,如果发现扫描到某个库时所有的未定义符号都解决了,那后续的库就不会再扫描了。这说明在链接时指定一大堆多余的库对链接结果没有影响。
4. 对于动态库的全局符号介入问题,全局符号表只会记录第一次识别到的符号,后续的同名符号都被忽略,但这并不表示同名符号所在的动态库完全不会加载,因为有可能其他的符号会用到。以libc库举例,如果用户在链接libc库之前链接了一个指定的库,并且在这个库里实现了read动态库的全局符号介入问题,全局符号表只会记录第一次识别到的符号,后续的同名符号都被忽略,但这并不表示同名符号所在的动态库完全不会加载,因为有可能其他的符号会用到。以libc库举例,如果用户在链接libc库之前链接了一个指定的库,并且在这个库里实现了read/write接口,那么在程序运行时,程序调用的read/write接口就是指定库里的,而不是libc库里的。libc库仍然会被加载,因为libc库是程序的运行时库,程序不可能不依赖libc里的其他接口。因为libc库也被加载了,所以,通过一定的手段,仍然可以从libc中拿到属于libc的read/write接口,这就为hook创建了条件。程序可以定义自己的read/write接口,在接口内部先实现一些相关的操作,然后再调用libc里的read/write接口。