查询gcc的-ffunction-section和-fdata-sections选项

Jay*_*Jay 28 c size optimization linker gcc

以下在GCC页面中提到的功能部分和数据部分选项:

-ffunction-sections
-fdata-sections
Run Code Online (Sandbox Code Playgroud)

如果目标支持任意节,则将每个函数或数据项放入输出文件中的自己的部分.函数名称或数据项名称确定输出文件中节的名称.在链接器可以执行优化以改善指令空间中引用的位置的系统上使用这些选项.大多数使用ELF对象格式的系统和运行Solaris 2的SPARC处理器都具有这种优化的链接器.AIX可能会在将来进行这些优化.

只有在获得重大好处时才使用这些选项.指定这些选项时,汇编器和链接器将创建更大的对象和可执行文件,并且速度也会更慢.如果指定此选项,则无法在所有系统上使用gprof,如果同时指定了此选项和-g,则可能无法进行调试.

我的印象是这些选项有助于减少可执行文件的大小.为什么这个页面会说它会创建更大的可执行文件?我错过了什么吗?

Ant*_*aaf 30

有趣的是,使用-fdata-sections可以创建函数的文字池,从而使您的函数本身更大.特别是我在ARM上注意到这一点,但在其他地方可能也是如此.我测试的二进制文件只增长了25%,但确实增长了.看看改变后的功能的反汇编,很明显为什么.

如果目标文件中的所有BSS(或DATA)条目都分配给单个部分,则编译器可以将该部分的地址存储在函数文字池中,并使用函数中的该地址生成具有已知偏移的加载来访问数据.但是如果你启用-fdata-sections它将每个BSS(或DATA)数据放入它自己的部分,并且由于它不知道以后哪些部分可能被垃圾收集,或者链接器将所有这些部分放入最终的可执行映像,它不能再使用来自单个地址的偏移来加载数据.所以相反,它必须在每个使用的数据的文字池中分配一个条目,并且一旦链接器找出了最终图像的内容以及在哪里,那么它可以用实际的地址来修复这些文字池条目.数据.

所以是的,即使-Wl,--gc-sections结果图像可能更大,因为实际的功能文本更大.

下面我添加了一个最小的例子

下面的代码足以看到我正在谈论的行为.请不要被挥发性声明和全局变量的使用抛弃,这两者在实际代码中都是有问题的.在这里,当使用-fdata-sections时,它们确保创建两个数据部分.

static volatile int head;
static volatile int tail;

int queue_empty(void)
{
    return head == tail;
}
Run Code Online (Sandbox Code Playgroud)

用于此测试的GCC版本是:

gcc version 6.1.1 20160526 (Arch Repository)
Run Code Online (Sandbox Code Playgroud)

首先,没有-fdata-sections我们得到以下内容.

> arm-none-eabi-gcc -march=armv6-m \
                    -mcpu=cortex-m0 \
                    -mthumb \
                    -Os \
                    -c \
                    -o test.o \
                    test.c

> arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]   ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 685b     ldr   r3, [r3, #4]
 6: 1ac0     subs  r0, r0, r3
 8: 4243     negs  r3, r0
 a: 4158     adcs  r0, r3
 c: 4770     bx    lr
 e: 46c0     nop                   ; (mov r8, r8)
10: 00000000 .word 0x00000000
             10: R_ARM_ABS32 .bss

> arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail
Run Code Online (Sandbox Code Playgroud)

arm-none-eabi-nm我们看到queue_empty是20个字节长(14个十六进制),并且arm-none-eabi-objdump输出显示在函数末尾有一个重定位字,它是BSS部分的地址(未初始化数据的部分).函数中的第一条指令将该值(BSS的地址)加载到r3中.接下来的两条指令相对于r3加载,分别偏移0和4字节.这两个载荷是头部和尾部的载荷.我们可以在输出的第一列中看到这些偏移arm-none-eabi-nm.该nop函数的末尾是字对齐文字池的地址.

接下来我们将看到添加-fdata-sections时会发生什么.

arm-none-eabi-gcc -march=armv6-m \
                  -mcpu=cortex-m0 \
                  -mthumb \
                  -Os \
                  -fdata-sections \
                  -c \
                  -o test.o \
                  test.c

arm-none-eabi-objdump -dr test.o

00000000 <queue_empty>:
 0: 4b03     ldr   r3, [pc, #12]    ; (10 <queue_empty+0x10>)
 2: 6818     ldr   r0, [r3, #0]
 4: 4b03     ldr   r3, [pc, #12]    ; (14 <queue_empty+0x14>)
 6: 681b     ldr   r3, [r3, #0]
 8: 1ac0     subs  r0, r0, r3
 a: 4243     negs  r3, r0
 c: 4158     adcs  r0, r3
 e: 4770     bx    lr
    ...
             10: R_ARM_ABS32 .bss.head
             14: R_ARM_ABS32 .bss.tail

arm-none-eabi-nm -S test.o

00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail
Run Code Online (Sandbox Code Playgroud)

我们立即看到queue_empty的长度增加了4个字节到24个字节(18个十六进制),并且现在在queue_empty的文字池中有两个重定位.这些重定位对应于创建的两个BSS部分的地址,每个全局变量一个.这里需要有两个地址,因为编译器无法知道链接器最终将这两个部分放入的相对位置.查看queue_empty开头的指令,我们看到有一个额外的负载,编译器必须生成单独的加载对以获取该部分的地址,然后生成该部分中变量的值.此版本的queue_empty中的额外指令不会使函数的主体更长,它只需要以前是nop的点,但通常情况并非如此.


lep*_*pie 25

使用这些编译器选项时,可以添加-Wl,--gc-sections将删除所有未使用代码的链接器选项.


小智 13

您可以使用-ffunction-sections-fdata-sections静态库,这将增加静态库的大小,因为每个函数和全局变量的数据将被放置在一个单独的部分.

然后使用-Wl,--gc-sections与此静态库链接的程序,这将删除未使用的部分.

因此,最终的二进制文件将更小,没有那些标志.

但要小心,因为-Wl,--gc-sections可以破坏事情.

  • 怎么-Wl, - gc-sections可以破坏东西?举个例子? (10认同)
  • @buddy“如果程序使用特殊部分,当 .o 文件的其他部分被拉入时,事情就会被拉入程序,即使这些“神奇”部分本身没有被引用”:https://elinux .org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf (2认同)

Rei*_*ilo 5

添加一个额外的步骤并构建一个.a存档,我得到了更好的结果:

  1. 首先,gcc 和 g++ 与-ffunction-sections -fdata-sections标志一起使用
  2. 然后,所有.o对象都被放入一个.a存档中ar rcs file.a *.o
  3. 最后,使用-Wl,-gc-sections,-u,main选项调用链接器
  4. 总之,优化设置为-Os

  • 好奇为什么链接器要使用 -u,main ?其他带有 -Wl,-gc-sections 文件大小优化的 SO 条目也不使用 -u,main (2认同)