Linux嵌入式开发日常技术总结(2)
编译链接
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: [.]