GCC 编译选项选录

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文记录开发过程中遇到的、笔者认为值得记录的 GCC 编译器选项。

3. 编译选项

3.1 架构无关 编译选项

3.1.1 -falign-functions

-falign-functions
-falign-functions=n
 	Align the start of functions to the next power-of-two greater than n, skipping
	 up to n bytes. For instance,-falign-functions=32’ aligns functions to the
	 next 32-byte boundary, but ‘-falign-functions=24’ aligns to the next 32-byte
	 boundary only if this can be done by skipping 23 bytes or less.-fno-align-functions’ and ‘-falign-functions=1’ are equivalent and mean
	 that functions are not aligned.
	 Some assemblers only support this flag when n is a power of two; in that case,
	 it is rounded up.
	 If n is not specified or is zero, use a machine-dependent default.
	 Enabled at levels ‘-O2’,-O3’. 

该编译选项用来将函数的起始位置对齐到某个值。可以将函数起始位置对齐到 cache 行,从而提高执行效率。

3.1.2 -g,-ggdb

相信很多人对 -g 选项很熟悉,它用来生成调试器(gdb 等调试器)使用的信息。而对于 -ggdb ,可能大家不是那么熟悉,它用来生成专用于 gdb 调试器的调试信息。而且 -g-ggdb 调试器生成的调试信息,也是分等级的:指定数字等级越高,生成的调试信息越多。默认为2级,0级表示不生成调试信息。看 GCC 手册的说明:

-glevel
-ggdblevel
-gstabslevel
-gcofflevel
-gxcofflevel
-gvmslevel
 	Request debugging information and also use level to specify how much information. The default level is 2.
	 Level 0 produces no debug information at all. Thus,-g0’ negates ‘-g’.
	 Level 1 produces minimal information, enough for making backtraces in parts
	 of the program that you don’t plan to debug. This includes descriptions of
	 functions and external variables, and line number tables, but no information
	 about local variables. 
	 Level 3 includes extra information, such as all the macro definitions present in
	 the program. Some debuggers support macro expansion when you use ‘-g3’.-gdwarf-2’ does not accept a concatenated debug level, because GCC used
	 to support an option ‘-gdwarf’ that meant to generate debug information in
	 version 1 of the DWARF format (which is very different from version 2), and
	 it would have been too confusing. That debug format is long obsolete, but the
	 option cannot be changed now. Instead use an additional ‘-glevel’ option to
	 change the debug level for DWARF. 

3.2 架构相关 编译选项

3.2.1 -marm 和 -mthumb

某天编写了简单程序:

int main(void)
{
        int i = 3 + 5;
        return 0;
}

编译:

arm-linux-gnueabihf-gcc -o test test.c

出于某种目的,反汇编看了下代码:

arm-linux-gnueabihf-objdump -D test > test.S
/* 我们只看 main() 函数部分,这样容易对应到前面的代码 */
000103a0 <main>:
   103a0: b480       push {r7} // 2 字节 thumb 指令
   103a2: b083       sub sp, #12
   103a4: af00       add r7, sp, #0
   103a6: 2308       movs r3, #8
   103a8: 607b       str r3, [r7, #4]
   103aa: 2300       movs r3, #0
   103ac: 4618       mov r0, r3
   103ae: 370c       adds r7, #12
   103b0: 46bd       mov sp, r7
   103b2: f85d 7b04  ldr.w r7, [sp], #4 // 4 字节 arm 指令
   103b6: 4770       bx lr

啥情况?既有多条 2 字节的 thumb 指令,还有 1 条 4字节的 arm 指令!原来,根据编译器的默认配置,编译器会默认生成某种模式(thumb 或 arm)指令。那怎么查看这个默认配置呢?答案是通过查看 gcc 编译器 --verbose 选项输出结果中的 --with-mode= 配置项:

$ arm-linux-gnueabihf-gcc --verbose
Using built-in specs.
COLLECT_GCC=arm-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/home/bill/Work/Private/qemu-lab/arm-ubuntu/tool/gcc-linaro-5.3-2016.02-x86_64_arm-linux-gnueabihf/bin/../libexec/gcc/arm-linux-gnueabihf/5.3.1/lto-wrapper
Target: arm-linux-gnueabihf
Configured with: /home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/snapshots/gcc-linaro-5.3-2016.02/configure SHELL=/bin/bash --with-bugurl=https://bugs.linaro.org --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libstdcxx-pch --disable-libmudflap --with-cloog=no --with-ppl=no --with-isl=no --disable-nls --enable-c99 --with-tune=cortex-a9 --with-arch=armv7-a --with-fpu=vfpv3-d16 --with-float=hard --with-mode=thumb --disable-multilib --enable-multiarch --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/sysroots/arm-linux-gnueabihf --enable-lto --enable-linker-build-id --enable-long-long --enable-shared --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/libc --enable-languages=c,c++,fortran,lto --enable-checking=release --disable-bootstrap --with-bugurl=https://bugs.linaro.org --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release/label/tcwg-x86_64-ex40/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu
Thread model: posix
gcc version 5.3.1 20160113 (Linaro GCC 5.3-2016.02)

我们看到,这个交叉编译器的 --with-mode=thumb 的配置为 thumb ,所以默认情况下,该交叉编译器将生成 thumb 指令(也可能夹有 arm 指令)。要改变这种行为,第1种途径是编译交叉编译器的时候,改变 --with-mode= 配置;第2种途径是,通过 -marm 编译选项,来指示编译器来生成 arm 指令。还是前面的测试代码,我们看用 -marm 选项 编译后的结果:

$ arm-linux-gnueabihf-gcc -marm -o test test.c
$ arm-linux-gnueabihf-objdump -D test > test.S
000103a0 <main>:
   103a0: e52db004  push {fp}  ; (str fp, [sp, #-4]!)
   103a4: e28db000  add fp, sp, #0
   103a8: e24dd00c  sub sp, sp, #12
   103ac: e3a03008  mov r3, #8
   103b0: e50b3008  str r3, [fp, #-8]
   103b4: e3a03000  mov r3, #0
   103b8: e1a00003  mov r0, r3
   103bc: e24bd000  sub sp, fp, #0
   103c0: e49db004  pop {fp}  ; (ldr fp, [sp], #4)
   103c4: e12fff1e  bx lr

哈,现在我们的代码全部使用 arm 指令了。当然,也可以通过 -mthumb 来指示编译器生成 thumb 指令

4. 链接选项

4.1 架构无关 链接选项

4.1.1 --as-needed,–no-as-needed

来看一下 LD 手册对选项 的说明:

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 at that point in the link satisfies a non-weak undefined symbol 
reference from a regular object file or, if the library is not found in the DT_NEEDED lists of other needed 
libraries, a non-weak undefined symbol reference from another needed dynamic library. Object files or 
libraries appearing on the command line after the library in question do not affect whether the library is 
seen as needed. This is similar to the rules for extraction of object files from archives. --no-as-needed 
restores the default behaviour. 

Note: On Linux based systems the --as-needed option also has an affect on the behaviour of the --rpath and 
--rpath-link options. See the description of --rpath-link for more details. 

在进行程序链接的时候,可能不小心将程序不需要的库也链接进去了,这会带来如下几点影响:

1. 程序启动时会加载所有链接的库,也就是程序用不到的库也会被加载,这会影响程序启动速度。
2. 加载程序用不到的库,也会浪费内存空间。
3. 要将程序用不到的库,也打包到系统,否则程序无法正常启动。

来看一个例子:

/* test.c */

int main(void)
{
	return 0;
}

编译、链接、运行:

$ gcc -o test -Wl,--no-as-needed -lm test.c
$ ldd test
	linux-vdso.so.1 =>  (0x00007fff4ab05000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc483bcd000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc483803000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc483ed6000)
$ strace ./test
execve("./test", ["./test"], [/* 69 vars */]) = 0
...
open("/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0V\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=1088952, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8302dbe000
mmap(NULL, 3178744, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f83028aa000
mprotect(0x7f83029b2000, 2093056, PROT_NONE) = 0
mmap(0x7f8302bb1000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x107000) = 0x7f8302bb1000
close(3)                                = 0
...

看到了吧,不管程序需不需要数学库 libm ,系统照样给你加载起来。为解决这类问题,使用链接选项 --as-needed ,这样确保只有程序需要的库才被连接进来:

$ gcc -o test -Wl,--as-needed -lm test.c
$ ldd test
	linux-vdso.so.1 =>  (0x00007ffe743d8000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9c1d97c000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f9c1dd46000)
$ strace ./test
execve("./test", ["./test"], [/* 69 vars */]) = 0
brk(NULL)                               = 0x1076000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=100481, ...}) = 0
mmap(NULL, 100481, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6152dbf000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6152dbe000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f61527e9000
mprotect(0x7f61529a9000, 2097152, PROT_NONE) = 0
mmap(0x7f6152ba9000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f6152ba9000
mmap(0x7f6152baf000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f6152baf000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6152dbd000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6152dbc000
arch_prctl(ARCH_SET_FS, 0x7f6152dbd700) = 0
mprotect(0x7f6152ba9000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f6152dd8000, 4096, PROT_READ) = 0
munmap(0x7f6152dbf000, 100481)          = 0
exit_group(0)                           = ?
+++ exited with 0 +++

使用链接选项 --as-needed 后,libm 库不再被链接进来,自然也就不会被加载了。值得注意的是,现在较新版本的 LD 链接器,默认的行为已经是 --as-needed,而不是前面上面引用文档里说的 --no-as-needed,但一些旧的连接器的默认行为是 --no-as-needed,所以可以显式的加上 --as-needed 链接选项,以保持兼容性。

5. ELF 工具

5.1 获取 ELF 文件编译器信息

有时候,需要知道已经编译好的 ELF 目标文件所用的编译器版本,可以尝试以下方法:

readelf -p .comment 目标文件
objdump -s --section .comment 目标文件
strings 目标文件 | grep 'GCC' // 针对GCC

上面的基本原理都是提取 ELF .comment 段 的信息。该段是用来保存版本控制信息的。

6. 参考资料

https://gcc.gnu.org/onlinedocs/

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值