動態鏈接更多是從.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);
}