編譯鏈接
gcc的CFLAGS/CXXFLAGS編譯選項
更多信息參考: man gcc
.
-Wl,option1,option2,option3,value3,option4=value4[:value5:value6],option5
將選項從gcc傳遞給linker,例如:
-Wl,-Map,output.map
將 -Map output.map 傳遞給linker。
當時用GNU的linker的時候,你也可以使用 -Wl,-Map=output.map
。
-std=<standard>
決定選擇的語言標準,例如:
=std=gnu++0x=
這個與 std=c++11
類似,2011 ISO 標準C++修改案。包含一些特定的語法,如: nullptr
, 或 enum class {...};
.
-fno-rtti
取消C++運行時動態類型診斷特性(rtti),(i.e. dynamic_cast
以及 typeid
)
-fno-exceptions
取消C++對異常處理的支持。(i.e. 類似 try{...} catch {...}
的語法)
-l<libname>
鏈接時,搜索庫名稱 libname
。以庫文件 liba.so.1
爲例,
libname
是庫的linker name。這裏是:a
soname
是運行時搜索的動態庫,這裏是:liba.so
-
realname
是庫對應的實際庫文件,這裏是:liba.so.1
因爲linker name是
a
, 主要用於編譯之後的鏈接,所以我們在編譯傳遞選項的時候,使用linkername, 即:-la
。我們也需要注意
-l
選項在CFLAGS/CXXFLAGS
中的位置,例如:如果foo.o
引用了z
中的一個函數,如果選項位置不對,那些函數可能不會被加載。foo.o -lz bar.o
因爲,
z
庫的搜索是在bar.o
之前,但是卻在foo.o
之後。
-L<libdir>
指定編譯結束前的鏈接階段時,依賴庫的搜索路徑。即: 將 libdir
目錄添加至 -l
所指定的依賴庫文件的搜索路徑。
例如:
-L./lib/ -L./
將會吧 ./lib
和 ./
添加到待鏈接的庫的搜索路徑。
-g
生成調試信息。生成調試信息之後, gdb
可以利用調試信息進行調試,這些調試信息也可以通過 strip
命令移除。
-W<warnname>
產生 <warn>
的警告。
例如:
-Wunused-macros
-Werror
-Wall
這裏,
-
-Wunused-macros
會在一些宏沒有使用的情況下,在編譯期間產生警告。 -
-Werror
會將所有的警告信息看做編譯錯誤。 -
-Wall
會打開一些用戶覺得有問題並且容易避免的<warname>
產生警告,但是它並不是將所有的<warnname>
打開。
庫文件的鏈接
更多的信息參考: man ld
, 尤其是對 -rpath
和 -rpath-link
選項的相關部分。
搜索規則
當加載庫的時候(例如 libm.so
,可能是動態加載庫,或者通過 dlopen()
函數加載庫)
將使用如下的過程對這個 libm.so
庫進行搜索:
假設:
libm.so被libn.so加載(即,libn.so->libm.so)。
libn-1.so被libn-2.so加載(即,libn-2.so->libn-1.so)。
......
lib1.so被可執行文件a.out加載(即,a.out->lib1.so),
或者lib1.so被另外一個二進制庫liba.so加載,而liba.so是通過dlopen("liba.so")加載的(即,liba.so->lib1.so)。
1. 如果libn.so具有RUNPATH值,(通過readelf -d可以檢查), 轉至步驟8。
2. 否則(即libn.so無RUNPATH),搜索libn.so的RPATH值所表示的路徑。
3. 如果加載libn.so的加載者(即libn-1.so)具有RUNPATH值,則轉至步驟5。
4. 否則(即libn-1.so無RUNPATH),搜索libn-1.so的RPATH值所表示的路徑。
5. 重複步驟3~4,搜索libn-2.so,libn-3.so,...(如果無RUNPATH)...的RPATH值所表示的路徑,直至到達lib1.so(即搜索完lib1.so的RPATH)
6. 如果lib1.so的加載者(a.out或liba.so)具有RUNPATH,轉至步驟8。
7. 否則(無RUNPATH)搜索lib1.so加載者(即,a.out或liba.so)的RPATH值鎖表示的路徑。
8. 搜索 LD_LIBRARY_PATH 表示的路徑(環境變量)。
9. 搜索 libn.so的RUNPATH所表示的路徑(可通過readelf命令手動查看)。
10. 搜索默認目錄(如:/usr/lib, /lib, 等,這些路徑取決於在生成編譯器時對編譯器的配置選項是如何指定的,可以通過gcc -v來查看)
11. 搜索ld.so.conf所指定的路徑(每次更新需通過ldconfig將其值存於ld.so.cache,可通過strings命令對ld.so.cache進行檢查)
注意,當尋找到 libm.so
的時候,整個的搜索過程將會停止。
我們可以看到,
- 期間涉及的所有層級的庫文件的RUNPATH將會導致該庫的RPATH被禁用。
- 每次都儘可能的搜索完最底層庫(最接近libm.so)的所有可能路徑,再搜索更高層次。
LD_LIBRARY_PATH
LD_LIBRARY_PATH
,是用於搜索庫文件(即lib*.so)的環境變量,在動態加載或 dlopen()
的時候會使用到它。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GSTDIR/lib
-rpath
, -rpath-link
, --enable-new-dtags
鏈接其用於操作 RPATH
和 RUNPATH
的選項
-rpath
用於庫的DT_RPATH
。-rpath... --enable-new-dtags
用於庫的DT_RUNPATH
。
如果 DT_RUNPATH
存在,那麼 DT_RPATH
會被忽略。
-rpath-link
與 -rpath
一樣,不同在於 -rpath-link
是用於鏈接期間,而 -rpath
適用於運行時的鏈接。
如果 -rpath
或 -rpath-link
沒有被指定,那麼可以通過 LD_RUN_PATH
環境變量來指定其值。
這個選項可以指定一系列的目錄名稱,通過冒號分割的名稱列表或者通過多次選項指定都可以。
LIBS += -Wl,-rpath,/data/3rd/widevine_eme:/data/3rd/browser_engine:/3rd/widevine_eme:/3rd/browser_engine
LIBS += -Wl,-rpath-link=$(BROWSER_ENG_PATH),-rpath-link=/data/3rd/browser_engine:/3rd/browser_engine
舉例
假設有如下依賴關係
a.out -> liba.so
liba.so -> libb.so
path:
./a.out
./liba.so
./libb.so
/usr/lib/libb.so
默認情況,取決於 gcc -v
看到的編譯器配置選項(使用 /usr/lib/libb.so
)
$gcc main.c
如有有 LD_LIBRARY_PATH
將優先採用之。
使用 RPATH
, 指定 ./libb.so
(使用 ./libb.so
)
$gcc main.c -Wl,--rpath=.
這樣, RPATH
的搜索會先於 LD_LIBRARY_PATH
(和默認搜索), 可能導致 LD_LIBRARY_PATH
(和默認搜索)被覆蓋。
使用 RUNPATH
$gcc main.c -ldl -Wl,--rpath=.,--enable-new-dtags
$export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH
這會導致 RPATH
的搜索被忽略,進而使得 LD_LIBRARY_PATH
的搜索(或默認搜索)起作用( /usr/lib/libb.so
)。
binutils
objdump
-x
顯示所有可用頭部信息,包括符號表,或者重定位位置。 (需要打開 gcc 的 -g
)
$objdump -x browser
./browser: file format elf32-little
architecture: UNKNOWN!, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x000100d8
Program Header:
0x70000001 off 0x000312f0 vaddr 0x000392f0 paddr 0x000392f0 align 2**2
filesz 0x00000790 memsz 0x00000790 flags r--
...
Dynamic Section:
NEEDED libAvodIpc.so
NEEDED libAvodQos.so
NEEDED libSSPK.so
...
RPATH ../browser_engine:/3rd/widevine_eme
...
SYMBOL TABLE:
...
00032ee8 l O .rodata 00000012 _ZZ17prof_get_key_infoE12__FUNCTION__
000105b8 l .text 00000000 $a
00010a90 l .text 00000000 $d
00032efc l O .rodata 00000012 _ZZ17prof_load_profileE12__FUNCTION__
...
-T
打印動態符號表位置中的信息. 這個一般被動態鏈接對象使用,與 nm -D
的輸出類似。(可能需要打開 gcc 的 -g
)
$objdump -T libbrowser_engine.so
libbrowser_engine.so: file format elf32-little
DYNAMIC SYMBOL TABLE:
00010d98 l d .init 00000000 .init
000fd50c l d .jcr 00000000 .jcr
00000000 DF *UND* 00000000 GLIBC_2.4 signal
...
00041824 g DF .text 00000330 BWS_ENGINE _Z24x_browser_set_default_fsh
00000000 D *UND* 00000000 x_sema_delete
0022a7d4 g DO .bss 00000004 BWS_ENGINE iBasePiStrmDbgEnable
00000000 D *UND* 00000000 _ZN5opera5OperaC1ERKS0_
...
-t
打印文件符號表所在位置包含的信息,類似 nm
。(可能需要打開 gcc 的 -g
)
$objdump -t browser
browser: file format elf32-little
SYMBOL TABLE:
00008134 l d .interp 00000000 .interp
00008148 l d .note.ABI-tag 00000000 .note.ABI-tag
00008168 l d .hash 00000000 .hash
...
00032ee8 l O .rodata 00000012 _ZZ17prof_get_key_infoE12__FUNCTION__
000105b8 l .text 00000000 $a
00010a90 l .text 00000000 $d
00032efc l O .rodata 00000012 _ZZ17prof_load_profileE12__FUNCTION__
...
nm
參見前面部分解釋,這個命令主要檢查elf格式文件的符號定義。
ldd
主要用於檢查文件所依賴的庫,交叉編譯中需使用 arm-xxx-ldd
類似的命令。
strip
可以將elf文件中所有調試信息(這些信息是在用 -g
的 gcc
命令編譯時生成的)去掉。
readelf
查看 RPATH
和 RUNPATH
$ readelf -d a.out
-
沒有 RPATH&RUNPATH的情況
Dynamic section at offset 0xf20 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x8048360 ...
-
有RPATH的情況
Dynamic section at offset 0xf18 contains 22 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [.] 0x0000000c (INIT) 0x8048360 ....
-
有RUNPATH的情況
Dynamic section at offset 0xf10 contains 23 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [.] 0x0000001d (RUNPATH) Library runpath: [.]