explicit runtime linking 讓程式可以在執行過程中控制 load 及 unload 模組。這種方式讓載入模組變得更靈活,可以在程式需要時才載入某個模組,載入什麼模組也可以依執行狀況決定。因為不需要在一開始載入所有模組,所以可以減少啟動時間跟記憶體用量。另外,因為可以在執行期才載入,也就能在不關閉程式的情況下新增、刪除及更新模組。
在程式碼裡指定了什麼時候要 load 哪個模組,對程式來說它就「知道」要有載入模組的動作。如果是以 dynamic linker 來 load 跟 link,模組跟 library 對程式是透明的,它不知道它用了什麼。兩者的差別即在手動或自動 load shared library,以及load 的時機。
另一方面,如果需要知道一個程式依賴哪些模組,例如移植時要將所有相關模組移植到其他平台以確保所有功能正常。這時以 explicit runtime linking 載入的模組可能增加檢查 dependency 的難度,因為無法用 ldd
得知依賴的模組,可能要等到用到某個功能時才當掉或無法使用。當然這問題並不是太大的缺點,可以透過適當管理 dependency library 以及以一致的方式撰寫 explicit linking 程式碼來避免。
linux 以四個 function 來達到 explicit runtime linking,分別是 dlopen()
、dlsym()
、dlerror()
以及 dlclose()
,它們宣告在 <dlfcn.h>
,link 時須 -ldl
。
dlopen()
dlopen()
開啟一個 dynamic library,將其 load 到 process 的 adddress space,並完成初始化。
void *dlopen(const char *filename, int flag);
第一個參數 filename
很簡單,就是 dynamic library 的路徑。如果是絕對路徑就能直接打開 dynamic library,如果是相對路徑則會依照某個順序尋找 library,詳細的順序可參考 man dlopen
。
flag
必須指定 RTLD_LAZY
或 RTLD_NOW
之一,RTLD_LAZY
表示 lazy binding,也就是 symbol 在第一次被用到的時候才 resolve。lazy binding 只用於 function,variable 會在 library load 時就 bind 好。RTLD_NOW
當然就是在 load library 的時候就會把所有 symbol 都 resolve。除了 RTLD_LAZY
跟 RTLD_NOW
之外,還有 RTLD_GLOBAL
可以跟他們一起使用,表示之後載入的 library 都可以使用這個 library 的 symbol。其他更多 flag 就參考 manual 囉。
dlopen()
return 一個 handle,之後的操作要傳這個 handle 給其他 function。如果 load 失敗,會 return NULL。如果要 load 的 library 之前已經 load 過,會 return 跟之前相同的 handle。
如果 load 進來的 library 依賴其他 shared library(link 時指定,非 explicit runtime linking),dlopen()
會自動 load 那些 shared library。
dlsym()
讓我們可以找到 library 的 symbol。
void *dlsym(void *handle, const char *symbol);
handle
是剛剛 dlopen()
return 的 handle。
symbol
是要找的 symbol 的名字,如果找不到 symbol 會 return NULL。找到 symbol 會 return 該 symbol 的 address。根據 manual,return 值是 NULL 無法當作找不到 symbol 的判斷,因為 return 值有可能真的是 NULL(不過我想不到這種狀況的例子…),須配合 dlerror()
才能確認是不是有找到 symbol:首先 call dlerror()
清掉前面的 error,接著 call dlsym()
找 symbol,再 call dlerror()
看是否有 error 來判斷是否找到 symbol。
如果多個 library 有相同的 symbol 會用哪個?dlsym()
會以 BFS 找 library 的 dependency tree,直到找到 symbol 或完全找不到為止。也就是說 load 順序會決定 symbol 同名時使用到誰,先 load 會先用。
dlerror()
call dlopen()
、dlsym()
跟 dlclose()
之後可以 call dlerror()
看上次呼叫有沒有成功。如果成功 dlerror()
會 return NULL,反之則 return error 相關資訊。
char *dlerror(void);
dlclose()
用來 unload library。
int dlclose(void *handle);
系統會用 counter 紀錄一個 library load 的次數,也就是 dlopen()
時加一,dlclose()
則減一,當 counter 變成 0 時 library 才會真的被 unload 掉。unload 的順序跟 load 相反,會先執行 .finit
section 的程式碼,接著從 symbol table 中移除相關 symbol,取消 process address space 跟 library 的 memory mapping,最後關閉 library 檔案。
成功 return 0,失敗 return 非 0 值。
Example
簡單用個例子實作跟驗證。以下共有 libfoo.so
、libbar.so
跟 libkar.so
三個 library,主程式 main.c
以 explicit runtime linking load libfoo.so
,而 libfoo.so
依賴 libbar.so
以及 libkar.so
,後兩者有相同名稱的 function。
bar.c1 2
| #include <stdio.h> void bar() { printf("bar() in bar.c\n"); }
|
kar.c1 2
| #include <stdio.h> void bar() { printf("bar() in kar.c\n"); }
|
compile with
1 2
| $ gcc -g -fPIC -shared -o libbar.so bar.c $ gcc -g -fPIC -shared -o libkar.so kar.c
|
foo.c1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <stdio.h>
int zoo = 1; const int coo = 5566;
int foo(int a, int b) { printf("foo = %x, &zoo = %x, &coo = %x\n", foo, &zoo, &coo); return a + b; }
static int koo(int n) { return n + coo; }
|
compile with gcc -g -fPIC -shared -o libfoo.so foo.c ./libbar.so ./libkar.so
main.c1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <stdio.h> #include <dlfcn.h>
int main() { void* handle = NULL;
handle = dlopen("./libfoo.so", RTLD_LAZY);
if (handle == NULL) { printf("Load library failed. error: %s\n", dlerror()); return -1; }
int (*fn)(int, int) = NULL; dlerror(); fn = dlsym(handle, "foo");
char* error = dlerror(); if (error != NULL) { printf("Load symbol failed. error: %s\n", error); return -1; }
printf("fn = %x\n", fn);
int ret = fn(3, 5); printf("ret = %d\n", ret);
int* p = NULL; p = dlsym(handle, "zoo"); printf("p = %x\n", p);
int* c = NULL; c = dlsym(handle, "coo"); printf("c = %x, *c = %d\n", c, *c);
void (*bar)() = NULL; bar = dlsym(handle, "bar"); bar();
getchar();
if (dlclose(handle) != 0) { printf("Close library fail. error: %s\n", dlerror()); return -1; }
return 0; }
|
compile with gcc -g -o main main.c -ldl
執行結果顯示拿到主程式 libfoo.so
裡 symbol 的 address,以及 load 到 libbar.so
裡的 bar()
:
1 2 3 4 5 6 7
| $ ./main fn = ef40f740 foo = ef40f740, &zoo = ef60faf0, &coo = ef40f7a8 ret = 8 p = ef60faf0 c = ef40f7a8, *c = 5566 bar() in bar.c
|
process 的 address space:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 00400000-00401000 r-xp 00000000 08:01 11409326 /home/cjw/source/explicit-load/main 00600000-00601000 rw-p 00000000 08:01 11409326 /home/cjw/source/explicit-load/main 02552000-02573000 rw-p 00000000 00:00 0 [heap] 7fe9bee48000-7fe9bee49000 r-xp 00000000 08:01 11409316 /home/cjw/source/explicit-load/libkar.so 7fe9bee49000-7fe9bf048000 ---p 00001000 08:01 11409316 /home/cjw/source/explicit-load/libkar.so 7fe9bf048000-7fe9bf049000 rw-p 00000000 08:01 11409316 /home/cjw/source/explicit-load/libkar.so 7fe9bf049000-7fe9bf04a000 r-xp 00000000 08:01 11409324 /home/cjw/source/explicit-load/libbar.so 7fe9bf04a000-7fe9bf249000 ---p 00001000 08:01 11409324 /home/cjw/source/explicit-load/libbar.so 7fe9bf249000-7fe9bf24a000 rw-p 00000000 08:01 11409324 /home/cjw/source/explicit-load/libbar.so 7fe9bf24a000-7fe9bf24b000 r-xp 00000000 08:01 11409329 /home/cjw/source/explicit-load/libfoo.so 7fe9bf24b000-7fe9bf44a000 ---p 00001000 08:01 11409329 /home/cjw/source/explicit-load/libfoo.so 7fe9bf44a000-7fe9bf44b000 rw-p 00000000 08:01 11409329 /home/cjw/source/explicit-load/libfoo.so 7fe9bf44b000-7fe9bf5ec000 r-xp 00000000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fe9bf5ec000-7fe9bf7ec000 ---p 001a1000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fe9bf7ec000-7fe9bf7f0000 r--p 001a1000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fe9bf7f0000-7fe9bf7f2000 rw-p 001a5000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fe9bf7f2000-7fe9bf7f6000 rw-p 00000000 00:00 0 7fe9bf7f6000-7fe9bf7f9000 r-xp 00000000 08:01 7864385 /lib/x86_64-linux-gnu/libdl-2.19.so 7fe9bf7f9000-7fe9bf9f8000 ---p 00003000 08:01 7864385 /lib/x86_64-linux-gnu/libdl-2.19.so 7fe9bf9f8000-7fe9bf9f9000 r--p 00002000 08:01 7864385 /lib/x86_64-linux-gnu/libdl-2.19.so 7fe9bf9f9000-7fe9bf9fa000 rw-p 00003000 08:01 7864385 /lib/x86_64-linux-gnu/libdl-2.19.so 7fe9bf9fa000-7fe9bfa1a000 r-xp 00000000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fe9bfbf9000-7fe9bfbfc000 rw-p 00000000 00:00 0 7fe9bfc16000-7fe9bfc1a000 rw-p 00000000 00:00 0 7fe9bfc1a000-7fe9bfc1b000 r--p 00020000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fe9bfc1b000-7fe9bfc1c000 rw-p 00021000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fe9bfc1c000-7fe9bfc1d000 rw-p 00000000 00:00 0 7ffdd05ea000-7ffdd060b000 rw-p 00000000 00:00 0 [stack] 7ffdd0755000-7ffdd0757000 r-xp 00000000 00:00 0 [vdso] 7ffdd0757000-7ffdd0759000 r--p 00000000 00:00 0 [vvar] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
|
三個 library 都 load 進來啦~
用 gdb 在 _init
跟 _fini
設 break point 可以看到在 dlopen()
跟 dlclose()
時會分別執行 libfoo.so
及其他 library 的 _init
及 _fini
。同樣適用 GCC 提供的 shared library constructor 及 destructor,Ref。
可以用 ldd
看 libfoo.so
的 dependency:
1 2 3 4 5 6
| $ ldd libfoo.so linux-vdso.so.1 (0x00007fffde785000) ./libbar.so (0x00007fdf77158000) ./libkar.so (0x00007fdf76f57000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf76bac000) /lib64/ld-linux-x86-64.so.2 (0x00007fdf7755a000)
|
如果 compile libfoo.so
時改變 link 的 shared library 順序,變成 gcc -g -fPIC -shared -o libfoo.so foo.c ./libkar.so ./libbar.so
,load 到的 bar()
會變成是 libkar.so
的。
1 2 3 4 5 6 7
| $ ./main fn = a259c740 foo = a259c740, &zoo = a279caf0, &coo = a259c7a8 ret = 8 p = a279caf0 c = a259c7a8, *c = 5566 bar() in kar.c
|
ldd
看到的順序也會變:
1 2 3 4 5
| $ ldd libfoo.so linux-vdso.so.1 (0x00007fff4db40000) ./libkar.so (0x00007f5b4ddb5000) ./libbar.so (0x00007f5b4dbb4000) ...
|
Related posts