动态链接更多是从.lib等模块分离组装的角度来看待问题,实际上,如果动态链接可以实现,那么在运行过程中动态加载.so对象也是可行的,这种共享对象被称为DLL,它其实和普通.so对象并无本质区别,只是程序的视角不同。
运行时链接的技术手段常用来做插件、驱动加载等处理,因为不需要开始就全部加载进入进程,可以有效地减少程序启动时间和内存使用。对于Web服务器需要长期连续运行的场景,如果某个模块更新,显然不能重启再加载,故而这种情况下使用DLL手段是合理。
动态链接是由动态链接器.ld.so全程负责,对于程序是透明的,而运行时加载,同样是由程序借助.ld.so封装提供的4个API来完成操作的:
1. dlopen()
void * dlopen(const char* filename, int flag);
打开一个动态库,并将其加载到进程的地址空间,完成初始化过程。第一个参数是动态库的路径,如果是相对路径比如只提供文件名,那么则按照LD_LIBRARY_PATH,/etc/ld.so.cache, /lib, /usr/lib等系统路径顺序查找匹配的共享库。如果filename=0
则会返回当前可执行文件的全局符号表的句柄,则可以在运行时定义任何符号的位置,并且可以执行它们,类似高级语言的“反射”机制。
flag可选RTLD_LAZY(PLT延迟绑定机制);RTLD_NOW表示模块被加载时立即完成所有符号的绑定工作,如果有任何未定义的符号引用,则返回错误。在调试程序时应该使用RTLD_NOW作为参数,因为有符号未定义引用的情况可以立即定位,而使用RTLD_LAZY延迟绑定则会导致绑定工作推迟,并且难以捕获。
dlopen()返回的相应模块的句柄,这个句柄用来在后续dlsym()等函数中用来定位模块内部内容,如果加载模块失败,则返回NULL。
2.dlsym()
void* dlsym(void* handle, char* symbol)
运行时装载查找指定符号,如果查到则返回符号的地址或者常量值,如果没有查到,则返回NULL,同时dlerror()返回相应错误信息。
3.dlerror()
每次调用dlopen()\dlsym()等函数后,可以通过dlerror(0函数来判断上次调用是否成功。dlerror()函数返回值类型为char*, 如果dlerror()返回的是NULL则标识成功,如果不是,则返回相应错误信息。
4.dlclose()
dlclose()函数作用和dlopen()相反,输入相应模块的句柄,用来从虚拟进程空间中卸载相应的模块。并在卸载之前,先执行.finit段的代码,然后将各模块的符号从全局符号表中去除掉,取消进程空间跟模块的映射关系,然后才是关闭模块文件的读取通道。
#include <stdio.h>
#include <dlfcn.h>
/*调用规范:
$RunSo /lib/foobar.so function arg1 arg2 ... return_type
$./runso /lib/i386-linux-gnu/libm-2.xx.so sin d2.0 d
*/
#define SETUP_STACK \
i = 2; \
while(++i < argc-1) { \
switch(argv[i][0]) { \
case 'i': \
asm volatile("push %0" :: "r"(atoi (&argv[i][1]))); \
esp += 4; \
break; \
case 'd':\
atof(&argv[i][1]); \
asm volatile("subl $8, %esp\n" "fstpl (%esp)" ); \
esp += 8; \
break; \
case 's': \
asm volatile("push %0" :: "r"(&argv[i][1]) ); \
esp += 4; \ break; \
default: \
printf("error argument type"); \
goto exit_runso; \
} }
#define RESTORE_STACK asm volatile("add %0, %%esp"::"r"(esp))
int main(int argc, char* argv[]) {
void* handle;
char* error;
int i;
int esp = 0;
void* func;
handle = dlopen(argv[1], RTLD_NOW);
if(handle == 0) {
printf("Can't find library:%s\n", argv[1]);
return -1;
}
func = dlsym(handle, argv[2]);
if ( (error = dlerror()) != NULL){
printf("Find symbol %s error: %s\n", argv[2], error);
goto exit_runso;
}
switch (argv[argc-1][0]){
case 'i':{
int (*func_int)() = func;
SETUP_STACK;
int ret = func_int();
RESTORE_STACK;
printf("ret = %d\n", ret);
break;
}
case 'd':{
double (*func_double) () = func;
SETUP_STACK;
double ret = func_double();
RESTORE_STACK;
printf("ret = %f\n", ret);
break;
}
case 's':{
char* (*func_str)() = func;
SETUP_STACK;
char* ret = func_str();
RESTORE_STACK;
printf("ret = %s\n", ret);
break;
}
case 'v':{
void (*func_void)() = func;
SETUP_STACK;
func_void();
RESTORE_STACK;
printf("ret = void");
break;
}
} //end of switch
exit_runso:
dlclose(handle);
}