动态链接
动态连接基本上分三步
- 启动动态连接器本身
- 装载所有需要的共享对象
- 重定位和初始化
动态连接器本身也是一个共享对象,但其不依赖于其他任何共享对象,其本身所需的全局和静态变量的重定位工作由它本身完成。动态链接器必须在启动时有一段非常精巧的代码可以完成这项工作而同时保证不用到全局和静态变量。这种具有一定限制条件的启动代码往往被称为自举(Bootstrap)。
实际上动态连接器的自举代码中,除了不可以使用全局变量和静态变量之外,甚至不能调用函数,即使动态链接器本身的函数也不能调用。
完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,称之为全局符号表(Global Symbol Table).当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。
显示运行时链接
支持动态链接的系统往往都支持一种更灵活的模块加载方式,叫做显式运行时链接(Explicit Run-time Linking),也叫做运行时加载。就是让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载。一般的共享对象不需要进行任何修改就可以进行运行时装载,这种共享对象往往被叫做动态装载库(Dynamic Loading Library).
动态库的装载是通过一系列有动态链接器提供的API完成的
- dlopen——打开动态库;
- dlsym——查找符号;
- dlerror——错误处理;
- dlclose——关闭动态库。
void * dlopen(const char *filename, int flag);
filename——被加载动态库的路径
flag——表示函数符号的解析方式,取值为:
RTLD_LAZY——表示使用延迟绑定,当函数第一次被用到时才进行绑定,即PLT机制;
RTLD_NOW——表示当模块被加载时即完成所有函数的绑定工作。
RTLD_GLOBAL——可以跟上述两者中任意一个一起使用,表示将被加载模块的全局符号合并到进程的全局符号表中。
flag的取值必须至少选择RTLD_LAZY和RTLD_NOW两者中任意一个。
dlopen返回的是被加载模块的句柄,如果加载失败,返回NULL。
void * dlsym(void *handle, char * symbol);
handle——由dlopen返回的句柄
symbol——所要查找的符号的名字,一个以’\0’结尾的C字符串
如果找到了相应符号,则返回该符号的值;如果没有找到,则返回NULL。如果查找的符号是函数,则返回函数的地址,如果是个变量,则返回变量的地址,如果是个常量,则返回常量的值,如果常量的值正好是NULL,则需要调用dlerror来判断是否找到了,如果符号找到了,则dlerror返回NULL,否则返回相应的错误信息。
符号的优先级,如果是在全局符号表中进行查找,即dlopen时filename传递的是NULL,那么由于全局符号表使用的是装载序列,所以dlsym()使用的也是装载序列。如果dlopen打开的是共享对象,那么采用的是有一种叫做依赖序列(Dependency Ordering)的优先级,即对该共享对象所依赖的所有共享对象进行广度优先遍历,直到找到符号为止。
char *dlerror (void);
用来检测dlopen()、dlsym()和dlclose()的调用是否正常,如果返回NULL,则表示上一次调用成功,否则返回相应的错误消息。
int dlclose (void *__handle);
用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。