Static Linking 的問題 static link 拆分了可執行檔,讓不同人或組織可以開發自己的 module,最後再 link 成執行檔。隨著系統變複雜,OS 裡有多個 process 在執行,當多個 process 以 static linking 連結相同的 library 時,例如擁有 printf()
的 standard library,memory 會有多份類似的 library 程式碼,造成浪費。
另一個問題是 static linking 不易更新 module。例如 App 這個程式用到 Lib.o
,Lib.o
更新時(Lib.o
可能是別人提供的 module,App 開發者無法控制其版本),App 的開發者必須拿 App.o
跟 Lib.o
再 link 成新版 App,再給發佈新版 App。
Dynamic Linking 基本概念 為了解決 static link 在 memory 浪費以及不易更新的問題,最簡單的做法是將程式拆成多個 module,不在 compile 時期 link library,等到執行時才 link。如此一來就能只放一份共用 module 在 memory,需要該 module 的 executable file 就去 link。
不過現實世界總是不那麼美好,dynamic linking 還是會遇到其他問題,例如如果 program A 需要 1.0 版的 module、program B 需要 2.0 版的 module,或者一個程式更新共用 module 之後,其他程式因為 interface 相依性問題就壞了。
Linux 稱 ELF 動態連結檔為 Dynamic Shared Objects(DSO),簡稱 shared object,通常以 .so
為副檔名。Windows 則稱為 Dynamical Linking Library,以 .dll
為副檔名。這系列筆記會混用 DSO、shared object、shared library、module 等詞,皆指動態連結檔。
Usage in Linux 先用簡單範例看看 Linux 上如何使用 dynamic link。
source code foo.h 1 2 3 4 #ifndef _LIB_H #define _LIB_H int foo (int a, int b) ;#endif
foo.c 1 2 3 4 int foo (int a, int b) { return (a + b); }
main.c 1 2 3 4 5 6 7 #include "lib.h" int main () { foo(3 , 4 ); return 0 ; }
compile & link 1 2 $ gcc -fPIC -shared -o lib.so lib.c $ gcc -o main.dyn main.c ./lib.so
-shared
表示要編出 shared object。link 出 main.dyn
時,linker 會看 foo()
是定義在其他 static object file 還是在 DSO 裡,如果在 DSO 裡就會標示這個 symbol 為 dynamic linking,等到 load 才進行 symbol relocation。上面第二行要寫 ./lib.so
是為了讓 linker 知道 foo()
是在 DSO 裡,而且 main.dyn
也會記錄 lib.so
的路徑。
1 2 3 4 5 $ ldd main.dyn linux-vdso.so.1 (0x00007fff29986000) ./lib.so (0x00007febdc1b6000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007febdbe0b000) /lib64/ld-linux-x86-64.so.2 (0x00007febdc3b7000)
比較常用的寫法是 -l
指定 library、-L
指定找 library 的 path,例如 -lfoo -L./
會找 libfoo.so
來 link,尋找順序則是先找目前目錄再找系統預設目錄。這種寫法在 main.dyn
裡只會記錄 lib.so
、不會有路徑,執行時會從系統以及相關參數所設的路徑找 library,如果找不到會跳 error。
1 2 $ readelf -h lib.so | grep Type Type: DYN (Shared object file)
1 2 3 4 5 6 7 8 9 10 11 12 $ readelf -s lib.so Symbol table '.dynsym' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name ... 8: 0000000000000660 20 FUNC GLOBAL DEFAULT 11 foo ... Symbol table '.symtab' contains 53 entries: Num: Value Size Type Bind Vis Ndx Name ... 46: 0000000000000660 20 FUNC GLOBAL DEFAULT 11 foo ...
lib.so
的 symbol table 有給 dynamic link 用的 .dynsym
section 跟一般的 .symtab
section。
1 2 3 4 5 6 7 8 9 10 $ readelf -s main.dyn Symbol table '.dynsym' contains 12 entries: Num: Value Size Type Bind Vis Ndx Name ... 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foo Symbol table '.symtab' contains 65 entries: Num: Value Size Type Bind Vis Ndx Name ... 56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foo
重點來啦,main.dyn
是 dynamic link lib.so
的可執行檔,它的 symbol table 標示了 foo
仍然是 undefined,而且在 .dynsym
裡也有這個 symbol。.dynsym
section 不會出現在其他 object file 裡的 symbol。
virtual memory space 編出 static linking 版本的執行檔:ld -e main main.o lib.o -o main.st
。執行後 cat /proc/<pid>/maps
可以看到 process 的 virtual memory space:
1 2 3 4 5 00400000-00401000 r-xp 00000000 08:01 11409244 /home/cjw/source/dynamic-link/main.st 7ffeaa7bb000-7ffeaa7dc000 rw-p 00000000 00:00 0 [stack] 7ffeaa7ed000-7ffeaa7ef000 r-xp 00000000 00:00 0 [vdso] 7ffeaa7ef000-7ffeaa7f1000 r--p 00000000 00:00 0 [vvar] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
簡單明瞭 load 進 main.st
,比較 dynamic linking 的 main.dyn
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 00400000-00401000 r-xp 00000000 08:01 11409246 /home/cjw/source/dynamic-link/main.dyn 00600000-00601000 rw-p 00000000 08:01 11409246 /home/cjw/source/dynamic-link/main.dyn 7fdee17b3000-7fdee1954000 r-xp 00000000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fdee1954000-7fdee1b54000 ---p 001a1000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fdee1b54000-7fdee1b58000 r--p 001a1000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fdee1b58000-7fdee1b5a000 rw-p 001a5000 08:01 7864382 /lib/x86_64-linux-gnu/libc-2.19.so 7fdee1b5a000-7fdee1b5e000 rw-p 00000000 00:00 0 7fdee1b5e000-7fdee1b5f000 r-xp 00000000 08:01 11409245 /home/cjw/source/dynamic-link/lib.so 7fdee1b5f000-7fdee1d5e000 ---p 00001000 08:01 11409245 /home/cjw/source/dynamic-link/lib.so 7fdee1d5e000-7fdee1d5f000 rw-p 00000000 08:01 11409245 /home/cjw/source/dynamic-link/lib.so 7fdee1d5f000-7fdee1d7f000 r-xp 00000000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fdee1f5e000-7fdee1f61000 rw-p 00000000 00:00 0 7fdee1f7d000-7fdee1f7f000 rw-p 00000000 00:00 0 7fdee1f7f000-7fdee1f80000 r--p 00020000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fdee1f80000-7fdee1f81000 rw-p 00021000 08:01 7864371 /lib/x86_64-linux-gnu/ld-2.19.so 7fdee1f81000-7fdee1f82000 rw-p 00000000 00:00 0 7fffbbf58000-7fffbbf79000 rw-p 00000000 00:00 0 [stack] 7fffbbfce000-7fffbbfd0000 r-xp 00000000 00:00 0 [vdso] 7fffbbfd0000-7fffbbfd2000 r--p 00000000 00:00 0 [vvar] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
除了 main.dyn
跟 lib.so
之外也 load 進 C Runtime /lib/x86_64-linux-gnu/libc-2.19.so
以及動態連結器 /lib/x86_64-linux-gnu/ld-2.19.so
。
那麼 DSO 會 load 到哪呢?從下面 lib.so
的 segment 來看,它會被 load 到的 address 是 0x00000000
,但顯然這個 address 不合法。compile time 無法知道 DSO 會被 load 去哪,是在 runtime 才決定(最簡單的想法是 OS 找到一塊放得下 DSO 的 memory 放)。這也是可以想像的,如果 DSO 跟 executable file 一樣編譯時就決定自己要 load 到某個 address,多個 DSO 在系統裡可能會互撞。kill 掉原本的 main.dyn
再跑一次會發現 load lib.so
的 address 不一樣。
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 $ readelf -l lib.so Elf file type is DYN (Shared object file) Entry point 0x560 There are 6 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000704 0x0000000000000704 R E 200000 LOAD 0x0000000000000708 0x0000000000200708 0x0000000000200708 0x0000000000000230 0x0000000000000238 RW 200000 DYNAMIC 0x0000000000000720 0x0000000000200720 0x0000000000200720 0x00000000000001c0 0x00000000000001c0 RW 8 NOTE 0x0000000000000190 0x0000000000000190 0x0000000000000190 0x0000000000000024 0x0000000000000024 R 4 GNU_EH_FRAME 0x0000000000000680 0x0000000000000680 0x0000000000000680 0x000000000000001c 0x000000000000001c R 4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 10 Section to Segment mapping: Segment Sections... 00 .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .eh_frame_hdr .eh_frame 01 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 02 .dynamic 03 .note.gnu.build-id 04 .eh_frame_hdr 05
Related posts & Ref