7.2 动态链接的简单实例
我们将使用一下几个文件来说明动态链接的过程:
//program1.c
#include
"lib.h"
int main() {
foobar(1);
return 0;
}
//program2.c
#include "lib.h"
int main() {
foobar(2);
return 0;
}
//Lib.c
#include <stdio.h>
#include "lib.h"
void foobar(int i) {
printf("Printing from Lib.so %d\n", i);
}
//Lib.h
#ifndef LIB_H_
#define LIB_H_
void foobar(int i);
#endif
我们将lib.c编译称为一个动态共享文件,然后和program1.c、program2,c进行链接。
***图7.2.1***
***图7.2.2***
在静态链接时,整个程序最终就只有一个可执行文件,它是一个不可以分割的整体;但是在
动态链接下,一个程序被分成了若干个文件,有程序的主要部分,即可执行文件和程序所依赖
的共享对象,很多时候我们也把这些部分称为模块,即动态链接下的可执行文件和共享对象
对可以看作是程序的一个模块。
当程序模块program1.c被编程为program1.o时,编译器还不知道foobar()函数的地址。当链接器
将program1.o链接成可执行文件时,这时候链接器必须确定program1.o中所引用的foobar()函数
的性质。如果foobar()是一个定义在其他静态目标模块中的函数,那么链接器将会按照静态链接
的规则,将program1.o中的foobar地址引用重定位;如果foobar是一个定义在某个共享对象中的
函数,那么链接器就会将这个符号的引用标记为一个动态链接的符号,不对它进行重定位,把这个
过程留到装载时进行。
那么链接器是如何知道foobar的引用是一个静态符号还是一个动态符号?这实际上就是我们用到
lib.so的原因。lib.so中保存了完整的符号信息(因为运行时动态链接还需要符号信息),把lib.so
也作为输入文件之一,链接器在解析符号时就可以知道,foobar是定义在lib.so的动态符号,这样
链接器就可以对foobar的引用做特殊的处理,使它成为一个动态符号的引用。
对于动态链接而言,除了目标文件本身,还有它所依赖的共享目标文件,这种情况下,可执行文件
的地址空间怎样进行分布?我们在foobar()函数中加入一个死循环,从而使得我们在进程执行的情况
下查看地址空间布局。
***图7.2.3***
从图7.2.3中,我们可以看出,进程的虚拟地址空间中,除了可执行文件program1和lib.so外,还有
用到了动态链接形式的C语言运行库libc-2.15.so,另外还有一个共享文件ld-2.15.so,该共享文件
就是Linux下的动态链接器。动态链接器与普通共享对象一样被映射到进程的虚拟地址空间,在系统
开始运行program1之前,首先会将控制权交给动态链接器,由它完成所有的动态链接工作以后再把
控制权交给program1,然后进程开始执行。
我们用readeld -l lib.so来查看一下该共享对象的装载属性:
***图7.2.4***
从图7.2.4中可以看到,动态链接模块的装载地址是从地址0x00000000开始的,这个地址是无效地址,
并且从图7.2.3看出,lib.so实际的装载地址是从0xb751a000开始的,因此可以说共享对象的最终
装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块
足够大小的虚拟地址空间给相应的共享对象。