1.动态共享对象与动态链接库
- 在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO, Dynamic Shared Object),一般以 .so 为扩展名;
- 在Windows中,动态链接文件被称为动态链接库DLL,以**.dll**为扩展名。
2.动态链接程序运行时地址空间分布
- 共享对象.so的最终装载地址在编译时时不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给相应的共享对象。
- 可执行文件 往往是第一个被加载的文件,它可以选择一个固定空闲的地址,在Linux下它是0x08040000,在Windows下它是0x0040000。
3.4种寻址模式
1)模块内部的函数调用、跳转等;
2)模块内部的数据访问(如模块内部的全局变量、静态变量);
3)模块外部的函数调用、跳转等;
4)模块外部的数据访问(如其他模块中定义的全局变量)。
static int a;
extern int b;
extern void ext();
void bar()
{
a = 1; //Type2, 模块内部的数据访问
b = 2; //Type4, 模块外部的数据访问
}
void foo()
{
bar(); //Type1, 模块内部的函数调用
ext(); //Type3, 模块外部的函数调用
}
- 模块内部的函数调用
foo对bar的调用的指令实际上是一条相对地址调用指令 - 模块间数据访问
模块间的数据访问需要等到目标装载时才能决定。ELF的做法是在数据段里建立全局偏移表,它是指向模块外变量的指针数组。
4.独立副本的Lib.so
- 当Lib.so被两个进程(A和B)加载时,它的数据段部分在每个进程中都有独立的副本。因此假设A修改了Lib.so的全局变量G,B中的G不会受到影响。
- 但是,由于两个线程共享同一个进程地址空间,线程A对G的修改对B是可见的。
5.延迟绑定实现
- 在动态链接下,程序模块之间包含了大量的函数引用,所以在程序开始执行之前,动态链接会耗费不少时间用于解决模块之间的函数引用的符号查找以及重定位,这减慢了动态链接的性能。
- ELF采用了延迟绑定(Lazy Binding),即当函数第一次被用到时才进行绑定,这大大加快了程序的启动速度,特别有利于一些有大量函数引用和大量模块的程序。
6.动态链接重定位相关数据结构
- .rel.text: 动态链接中代码段的重定位表;
- .rel.data: 动态链接中数据段的重定位表;
7.动态链接器的4个API
- 操作系统通过动态链接器提供的4个API来实现链接库的装载:
- void * dlopen(const char * filename, int flag );
打开一个动态库,并将其加载到进程的地址空间。
1)filename参数是加载动态库的路径;flag参数是函数符号的解析方式,常量RTLD_LAZY表示延迟绑定,RTLD_NOW表示当模块被加载时即完成所有的函数绑定工作;
2)dlopen的返回值是被加载的模块的句柄。 - void * dlsym(void * handle, char * symbol);
如果dlsym()找到了相应的符号,则返回该符号的值;如果没有找到相应的符号,则返回NULL - dlerror()判断上一次调用是否成功
- dlclose() 将一个已经加载的模块卸载