1. 程式人生 > >Linux動態鏈接(5)動態庫符號搜索順序

Linux動態鏈接(5)動態庫符號搜索順序

文件中 動態庫 通過 int ade class open -s 相同

一、動態搜索與靜態搜索
這裏的動態搜索是指通過dlopen+dlsym來搜索動態庫符號的過程,而靜態搜索則是指程序在運行的過程中的惰性鏈接實現。這裏其實又是一個比較邊界的問題,但是也是可能存在的,另外這些問題可以促使感興趣的同學看一下真正的實現代碼。
問題是這樣的:
假設說一個靜態鏈接的文件通過dlopen打開一個文件,例如pthread.so文件,這個pthread庫會依賴glibc.so,但是glibc的實現代碼已經通過靜態鏈接存在於主程序中,此時如果pthread庫中執行printf(註意:pthread庫是通過dlopen打開的,它並沒有靜態鏈接入可執行程序),此時pthread庫中執行的printf到底位於exe中還是glibc.so中?
二、測試代碼工程
[tsecer@Harry searchorder]$ ls
callee.c caller.c main.c Makefile

[tsecer@Harry searchorder]$ cat callee.c
#include <stdio.h>
void callee(void)
{
printf("callee in %s\n",__FILE__);
}

[tsecer@Harry searchorder]$ cat caller.c caller庫在被加載的時候執行callee函數,這個函數在它的加載者main.exe和它的依賴庫libcallee.so中均有定義。

void __attribute__((constructor)) caller(void)
{
extern void callee(void);
callee();
}

[tsecer@Harry searchorder]$ cat main.c
#include <stdio.h>
#include <dlfcn.h>
void callee(void)
{
printf("callee in %s \n",__FILE__);
}

int main()
{
void * handle = dlopen("libcaller.so",RTLD_LAZY);
printf("handle is %x\n",handle);
void (*callcallee)();
callcallee=dlsym(handle,"callee"); 之後將會看到,在caller中執行的callee函數和通過dlsym搜索到的符號不同
(*callcallee)();
return 0;
}

[tsecer@Harry searchorder]$ cat Makefile
default:libcaller.so
gcc -L. -lcaller main.c -o main.exe -ldl
LD_LIBRARY_PATH=. ./main.exe
libcaller.so:caller.c libcallee.so
gcc -shared -fPIC -o $@ $< -L. -lcallee
libcallee.so:callee.c
gcc -shared -fPIC -o $@ $<
clean:
rm -f *.so *.o *.exe

[tsecer@Harry searchorder]$ make
gcc -shared -fPIC -o libcallee.so callee.c
gcc -shared -fPIC -o libcaller.so caller.c -L. -lcallee
gcc -L. -lcaller main.c -o main.exe -ldl
/usr/bin/ld: warning: libcallee.so, needed by ./libcaller.so, not found (try using -rpath or -rpath-link)
LD_LIBRARY_PATH=. ./main.exe
callee in main.c 這個函數調用是libcaller.so文件中的初始化函數calle調用的callee,它搜索到了主程序中的callee定義
handle is b7897410
callee in callee.c 這裏是通過dlsym搜索到的callee定義,它搜索到了libcallee.so中定義。
[tsecer@Harry searchorder]$
三、搜索列表的構造
主要在dl-object.c文件中_dl_new_object函數,它構造了兩個獨立的搜索列表(用C庫的表達方法叫scope),每個scope是一個so搜索鏈表,所以可以認為其中的scope都是四重指針, link_map ****scope,這是一個本質數據結構,大家可以慢慢理解。
if (GL(dl_ns)[nsid]._ns_loaded != NULL)
{
……
/* Add the global scope. */
new->l_scope[idx++] = &GL(dl_ns)[nsid]._ns_loaded->l_searchlist;
}
……
if (idx == 0 || &loader->l_searchlist != new->l_scope[0])
{
if ((mode & RTLD_DEEPBIND) != 0 && idx != 0)
{
new->l_scope[1] = new->l_scope[0];
idx = 0;
}

new->l_scope[idx] = &loader->l_searchlist;
}

new->l_local_scope[0] = &new->l_searchlist;
這裏可以看到,每個so構造了兩個搜索空間,它們相互獨立又有聯系,具體來說,l_scope的第一項為全局搜索空間(也就是RTLD_GLOBAL空間),第二項為局部搜索空間,它和l_local_scope搜索空間相同,而l_local_scope則只有自己的一個l_searchlist空間這空間在_dl_map_object_deps函數中初始化,它的初始化順序是從自己的依賴中搜索的,所以稱之為“局部”空間。這個搜索順序和初始化函數執行的順序剛好完全相反,使用廣度優先搜索。為了了解這個代碼中廣度優先方法,大家需要首先理解廣度優先算法的實現原理。在_dl_map_object_deps函數中,runp就是深度優先隊列的頭指針,依賴被不斷的加到這個隊列中,然後再循環,所以這個廣度優先本質上還是比較簡單的。
四、dlsym的搜索順序
dlsym是一個主動搜索過程glibc-2.7\elf\dl-sym.c:do_sym函數中
{
/* Search the scope of the given object. */
struct link_map *map = handle;
result = GLRO(dl_lookup_symbol_x) (name, map, &ref, map->l_local_scope,
vers, 0, flags, NULL);
}
主動調用dlsym函數時,它只搜索自己和自己的依賴,所以能夠搜索到libcallee.so中定義的callee函數。
五、惰性鏈接時搜索順序
dl-runtime.c:_dl_fixup 函數

result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
這裏優先搜索全局空間,所以搜索到主程序中callee定義,如果主程序main中沒有定義,那麽就會搜索到libcallee.so中實現。但是這個行為可以通過dlopen時使用RTLD_DEEPBIND標誌位來讓動態鏈接優先搜索到直接依賴中定義,有興趣的同學可以自己試一下,我這裏的glibc2.7版本是有定義這個標誌的,不知道是什麽時候引入C庫的,如果提示找不到,那需要換一個高版本的c庫。

Linux動態鏈接(5)動態庫符號搜索順序