编译链接
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=
决定选择的语言标准,例如:
=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 。以库文件 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 目录添加至 -l 所指定的依赖库文件的搜索路径。
例如:
-L./lib/ -L./
将会吧 ./lib 和 ./ 添加到待链接的库的搜索路径。
-g
生成调试信息。生成调试信息之后, gdb 可以利用调试信息进行调试,这些调试信息也可以通过 strip 命令移除。
-W
产生 的警告。
例如:
-Wunused-macros
-Werror
-Wall
这里,
-Wunused-macros 会在一些宏没有使用的情况下,在编译期间产生警告。
-Werror 会将所有的警告信息看做编译错误。
-Wall 会打开一些用户觉得有问题并且容易避免的 产生警告,但是它并不是将所有的 打开。
库文件的链接
更多的信息参考: 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: [.]