-Wl選項告訴編譯器將后面的參數傳遞給鏈接器。
-soname則指定了動態庫的soname(簡單共享名,Short for shared object name)
soname的關鍵功能是它提供了兼容性的標准:
當要升級系統中的一個庫時,並且新庫的soname和老庫的soname一樣,用舊庫鏈接生成的程序使用新庫依然能正常運行。這個特性使得在Linux下,升級使得共享庫的程序和定位錯誤變得十分容易。
在Linux中,應用程序通過使用soname,來指定所希望庫的版本,庫作者可以通過保留或改變soname來聲明,哪些版本是兼容的,這使得程序員擺脫了共享庫版本沖突問題的困擾。
可以通過readelf -d來查看每個動態庫的SONAME
1. 聲明libto.so.1,並生成libto.so.1.2
[root@localhost c]# gcc -fPIC -shared -Wl,-soname,libto.so.1 -o libto.so.1.2 to.c [root@localhost c]# ls -lh -rwxr-xr-x 1 root root 4268 Jan 10 17:22 libto.so.1.2 [root@localhost c]# ldconfig -n ./ lrwxrwxrwx 1 root root 12 Jan 10 17:23 libto.so.1 -> libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2
[root@localhost c]# readelf -d libto.so.1.2 Dynamic section at offset 0x504 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000e (SONAME) Library soname: [libto.so.1] 0x0000000c (INIT) 0x2cc 0x0000000d (FINI) 0x4c4 0x6ffffef5 (GNU_HASH) 0xb4 0x00000005 (STRTAB) 0x1b4 0x00000006 (SYMTAB) 0xf4 0x0000000a (STRSZ) 150 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000003 (PLTGOT) 0x15d8 0x00000002 (PLTRELSZ) 24 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x2b4 0x00000011 (REL) 0x294 0x00000012 (RELSZ) 32 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x264 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x24a 0x6ffffffa (RELCOUNT) 1 0x00000000 (NULL) 0x0
2. 聲明libto.so.1,並生成libto.so.1.3
[root@localhost c]# gcc -fPIC -shared -Wl,-soname,libto.so.1 -o libto.so.1.3 to.c [root@localhost c]# ls -lh lrwxrwxrwx 1 root root 12 Jan 10 17:23 libto.so.1 -> libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:23 libto.so.1.3 [root@localhost c]# ldconfig -n ./ lrwxrwxrwx 1 root root 12 Jan 10 17:24 libto.so.1 -> libto.so.1.3 #重新ldconfig,指向新的庫文件 -rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:23 libto.so.1.3
[root@localhost c]# readelf -d libto.so.1.3 Dynamic section at offset 0x504 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000e (SONAME) Library soname: [libto.so.1] 0x0000000c (INIT) 0x2cc 0x0000000d (FINI) 0x4c4 0x6ffffef5 (GNU_HASH) 0xb4 0x00000005 (STRTAB) 0x1b4 0x00000006 (SYMTAB) 0xf4 0x0000000a (STRSZ) 150 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000003 (PLTGOT) 0x15d8 0x00000002 (PLTRELSZ) 24 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x2b4 0x00000011 (REL) 0x294 0x00000012 (RELSZ) 32 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x264 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x24a 0x6ffffffa (RELCOUNT) 1 0x00000000 (NULL) 0x0
3. 聲明libto.so.2,並生成libto.so.1.4
[root@localhost c]# gcc -fPIC -shared -Wl,-soname,libto.so.2 -o libto.so.1.4 to.c [root@localhost c]# ls -lh lrwxrwxrwx 1 root root 12 Jan 10 17:24 libto.so.1 -> libto.so.1.3 -rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:23 libto.so.1.3 -rwxr-xr-x 1 root root 4.2K Jan 10 17:24 libto.so.1.4 [root@localhost c]# ldconfig -n ./ lrwxrwxrwx 1 root root 12 Jan 10 17:24 libto.so.1 -> libto.so.1.3 #重新ldconfig,不指向新的庫文件,因為新庫(1.4)的soname為libto.so.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2 -rwxr-xr-x 1 root root 4.2K Jan 10 17:23 libto.so.1.3 -rwxr-xr-x 1 root root 4.2K Jan 10 17:24 libto.so.1.4 lrwxrwxrwx 1 root root 12 Jan 10 17:24 libto.so.2 -> libto.so.1.4
[root@localhost c]# readelf -d libto.so.1.4 Dynamic section at offset 0x504 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000e (SONAME) Library soname: [libto.so.2] 0x0000000c (INIT) 0x2cc 0x0000000d (FINI) 0x4c4 0x6ffffef5 (GNU_HASH) 0xb4 0x00000005 (STRTAB) 0x1b4 0x00000006 (SYMTAB) 0xf4 0x0000000a (STRSZ) 150 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000003 (PLTGOT) 0x15d8 0x00000002 (PLTRELSZ) 24 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x2b4 0x00000011 (REL) 0x294 0x00000012 (RELSZ) 32 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x6ffffffe (VERNEED) 0x264 0x6fffffff (VERNEEDNUM) 1 0x6ffffff0 (VERSYM) 0x24a 0x6ffffffa (RELCOUNT) 1 0x00000000 (NULL) 0x0
gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor
DT_NEEDED tag:
DT_NEEDED: 表示一個列表,列表里面以(NEEDED)為標志的項,就是當前庫加載時要依賴的其它庫。注意 DT_NEEDED 中的 DT 不是 DON'T 的意思。
1 gcc/g++鏈接時.o文件以及庫的順序問題
1.1 寫在前面
最近換了xubuntu12.4,把原來的項目co出來編譯的時候報“undefined reference to”。猜測是gcc的版本問題,用-v跟蹤一下,發現gcc-4.6默認開 啟了ld的–as-needed選項。關閉該選項(–no-as-needed)后編譯正常。深 入挖掘發現還是一個比較有意思的問題。
1.3 技術鋪墊
1.3.2 linux下查看一個可執行文件或動態庫依賴哪些動態庫的辦法
你有一個library或者是可執行文件,你可以這樣查看他的依賴關系:
- readelf -d
ocaml@ocaml:~$ readelf -d PyGalaxy.so Dynamic section at offset 0x7dd8 contains 26 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libGalaxyParser.so] ... 0x000000000000000c (INIT) 0x18e0 0x000000000000000d (FINI) 0x6398
- ldd工具,如下:
ocaml@ocaml:~$ ldd PyGalaxy.so linux-vdso.so.1 => (0x00007fffc8ad3000) libGalaxyParser.so => /home/ocaml/lib/libGalaxyParser.so (0x00007f1736e6d000) ...
1.4 gcc/g++鏈接時對庫的順序要求
先看看gcc手冊對-L和-l的描述
-Ldir Add directory dir to the list of directories to be searched for -l. -llibrary -l library Search the library named library when linking. (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.) It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, `foo.o -lz bar.o' searches library `z' after file foo.o but before bar.o. If bar.o refers to functions in `z', those functions may not be loaded. The linker searches a standard list of directories for the library, which is actually a file named liblibrary.a. The linker then uses this file as if it had been specified precisely by name. The directories searched include several standard system directories plus any that you specify with -L. Normally the files found this way are library files—archive files whose members are object files. The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined. But if the file that is found is an ordinary object file, it is linked in the usual fashion. The only difference between using an -l option and specifying a file name is that -l surrounds library with `lib' and `.a' and searches several directories.
基本的意思就是從左向右查找,如果是鏈接成動態庫會稍微不同一點。
1.5 –as-needed對鏈接動態庫的影響
先看看與動態庫鏈接相關的幾個選項的man說明:
--as-needed --no-as-needed This option affects ELF DT_NEEDED tags for dynamic libraries mentioned on the command line after the --as-needed option. Normally the linker will add a DT_NEEDED tag for each dynamic library mentioned on the command line, regardless of whether the library is actually needed or not. --as-needed causes a DT_NEEDED tag to only be emitted for a library that satisfies an undefined symbol reference from a regular object file or, if the library is not found in the DT_NEEDED lists of other libraries linked up to that point, an undefined symbol reference from another dynamic library. --no-as-needed restores the default behaviour. --add-needed --no-add-needed These two options have been deprecated because of the similarity of their names to the --as-needed and --no-as-needed options. They have been replaced by --copy-dt-needed-entries and --no-copy-dt-needed-entries. --copy-dt-needed-entries --no-copy-dt-needed-entries This option affects the treatment of dynamic libraries referred to by DT_NEEDED tags inside ELF dynamic libraries mentioned on the command line. Normally the linker won't add a DT_NEEDED tag to the output binary for each library mentioned in a DT_NEEDED tag in an input dynamic library. With --copy-dt-needed-entries specified on the command line however any dynamic libraries that follow it will have their DT_NEEDED entries added. The default behaviour can be restored with --no-copy-dt-needed-entries. This option also has an effect on the resolution of symbols in dynamic libraries. With --copy-dt-needed-entries dynamic libraries mentioned on the command line will be recursively searched, following their DT_NEEDED tags to other libraries, in order to resolve symbols required by the output binary. With the default setting however the searching of dynamic libraries that follow it will stop with the dynamic library itself. No DT_NEEDED links will be traversed to resolve symbols.
再看看ld的幫助
--add-needed Set DT_NEEDED tags for DT_NEEDED entries in following dynamic libs --no-add-needed Do not set DT_NEEDED tags for DT_NEEDED entries in following dynamic libs --as-needed Only set DT_NEEDED for following dynamic libs if used --no-as-needed Always set DT_NEEDED for following dynamic libs
關鍵的看–as-needed,意思是說:只給用到的動態庫設置DT_NEEDED。比如:
g++ -shared PyGalaxy.o -lGalaxyParser -lxxx -lrt -o PyGalaxy.so
像這樣鏈接一個PyGalaxy.so的時候,假設PyGalaxy.so里面用到了libGalaxyParser.so但是沒 有用到libxxx.so。查看依賴關系如下:
ocaml@ocaml:~$ readelf -d PyGalaxy.so Dynamic section at offset 0x7dd8 contains 26 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libGalaxyParser.so] 0x0000000000000001 (NEEDED) Shared library: [libxxx.so] ...
當開啟–as-needed的時候,像
g++ -shared -Wl,--as-needed PyGalaxy.o -lGalaxyParser -lxxx -lrt -o PyGalaxy.so
這樣鏈接PyGalaxy.so的時候,查看依賴關系如下:
ocaml@ocaml:~$ readelf -d PyGalaxy.so Dynamic section at offset 0x7dd8 contains 26 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libGalaxyParser.so] ...
–as-needed就是忽略鏈接時沒有用到的動態庫,只將用到的動態庫set NEEDED。
開啟–as-needed的一些常見的問題:
1.5.1 鏈接主程序模塊或者是靜態庫的時的‘undefined reference to: xxx’
g++ -Wl,--as-needed -lGalaxyRT -lc -lm -ldl -lpthread -L/home/ocaml/lib/ -lrt -o mutex mutex.o
假設mutex依賴libGalaxyRT.so中的東西。想想,因為gcc對庫的順序要求 和–as-needed(因為libGalaxyRT.so在mutex.o的左邊,所以gcc認為沒有 用到它,–as-needed將其忽略),ld忽略libGalaxyRT.so,定位mutex.o的 符號的時候當然會找不到符號的定義!所以‘undefined reference to’這個 錯誤是正常地!
正確的鏈接方式是:
g++ -Wl,--as-needed mutex.o -lGalaxyRT -lc -lm -ldl -lpthread -L/home/ocaml/lib/ -lrt -o mutex
1.5.2 編譯動態庫(shared library)的時候會導致一個比較隱晦的錯誤
編譯出來的動態庫的時候沒有問題,但是加載的時候有“undefined symbol: xxx”這樣的錯誤。假如像這也鏈接PyGalaxy.so
g++ -shared -Wl,--as-needed -lGalaxyParser -lc -lm -ldl -lpthread -L/home/ocaml/lib/ -lrt -o PyGalaxy.so PyGalaxy.o
load PyGalaxy.so的時候會有上面的運行時錯誤!
簡單分析原因:因為libGalaxyParser.so在mutex.o的左邊,所以gcc認為沒 有用到它,–as-needed將其忽略。但是前面說的動態庫符號解析的特點導 致ld認為某些符號是加載的時候才去地址重定位的。但是 libGalaxyParser.so已經被忽略了。所以就算你寫上了依賴的庫,load的時 候也會找不到符號。但是為什么沒有-Wl–as-needed的時候是正確的呢?沒 有的話,ld會set NEEDED libGalaxyParser.so(用前面提到的查看動態庫 依賴關系的辦法可以驗證)。load的時候還是可以找到符號的,所以正確。
正確的鏈接方式是:
g++ -shared -Wl,--as-needed PyGalaxy.o -lGalaxyParser -lc -lm -ldl -lpthread -L/home/ocaml/lib/ -lrt -o PyGalaxy.so
1.6 對鏈接順序導致問題的解決方案
1.6.1 在項目開發過層中盡量讓lib是垂直關系,避免循環依賴;越是底層的庫,越是往后面寫!
例如:
g++ ... obj($?) -l(上層邏輯lib) -l(中間封裝lib) -l(基礎lib) -l(系統lib) -o $@
這樣寫可以避免很多問題,這個是在搭建項目的構建環境的過程中需要考慮 清楚地,在編譯和鏈接上浪費太多的生命不值得!
1.6.2 通過-(和-)強制repeat
-(和-),它能夠強制"The specified archives are searched repeatedly", 這就是我們要找的啦。比如:
g++ -shared -Wl,--as-needed PyGalaxy.o Xlinker "-("-lGalaxyParser -lxxx -lrt"-)" -o PyGalaxy.so
簡單解釋一下,Xlinker是將后面的一個參數傳給ld(這里就是 "-("-lGalaxyParser -lxxx -lrt"-)"),然后-(和-)強制repeat當然就 可以找到了。但是這樣的repeat需要浪費一些時間。