背景
在开发一个项目时,使用了非常多的第三方.a静态库文件,导致编译出的可执行文件非常大。这样一是占用ROM空间,二是会导致程序启动加载速度变慢(项目对启动时间非常敏感)。其实,这些静态库中的函数,并非所有都有调用,项目只使用了其中小部分。这种情况下,gcc的“-Wl,–gc-sections”参数,就非常有用。
参数说明
英文原版的-Wl,–gc-sections解释说明如下:
6.3.3.2 Compilation options
The operation of eliminating the unused code and data from the final executable is directly performed by the linker.In order to do this, it has to work with objects compiled with the following options: -ffunction-sections -fdata-sections.
These options are usable with C and Ada files. They will place respectively each function or data in a separate section in the resulting object file.
Once the objects and static libraries are created with these options, the linker can perform the dead code elimination. You can do this by setting the -Wl,–gc-sections option to gcc command or in the -largs section of gnatmake. This will perform a garbage collection of code and data never referenced.
If the linker performs a partial link (-r linker option), then you will need to provide the entry point using the -e / --entry linker option.
Note that objects compiled without the -ffunction-sections and -fdata-sections options can still be linked with the executable. However, no dead code elimination will be performed on those objects (they will be linked as is).
The GNAT static library is now compiled with -ffunction-sections and -fdata-sections on some platforms. This allows you to eliminate the unused code and data of the GNAT library from your executable.
大致说明如下:
- 在编译C、Ada源文件(C++也可以),在gcc/g++编译选项中增加
-ffunction-sections
、-fdata-sections
,在编译生成的.o目标文件中,会将每个函数或数据段,放在各种单独独立的section
中; - 在链接生成最终可执行文件时,如果带有
-Wl,--gc-sections
参数,并且之前编译目标文件时带有-ffunction-sections
、-fdata-sections
参数,则链接器ld不会链接未使用的函数,从而减小可执行文件大小; - 如果使用了
-r
的链接参数,来产生重定位的输出,需要显示的调用-e
参数来指定程序入口。否则-Wl,--gc-sections
不会生效。
关于GNU ld更多参数的说明,可以参考官网。
使用示例
测试程序section_test.c如下:
#include <stdio.h>
void func_0(void) {
printf("%s\n\r", __func__);
}
void func_1(void) {
printf("%s\n\r", __func__);
}
void func_2(void) {
printf("%s\n\r", __func__);
}
void func_3(void) {
printf("%s\n\r", __func__);
}
void func_4(void) {
printf("%s\n\r", __func__);
}
void func_5(void) {
printf("%s\n\r", __func__);
}
void func_6(void) {
printf("%s\n\r", __func__);
}
void func_7(void) {
printf("%s\n\r", __func__);
}
int main(void) {
func_0();
return 0;
}
Makefile如下:
.PHONY: test_sections test_normal
all: test_sections test_normal
test_sections:
gcc -ffunction-sections -fdata-sections -c section_test.c
gcc -Wl,--gc-sections -o $@ section_test.o
mv section_test.o with_section.o
test_normal:
gcc -c section_test.c
gcc -o $@ section_test.o
mv section_test.o no_section.o
clean:
rm -rf *.o main_sections main_normal
执行make
后,编译生成2个.o文件和2个可执行程序:no_section.o
、with_section.o
、test_normal
、test_sections
。
首先,通过ls -l
查看可执行文件大小。可以看到,没有链接未使用函数的test_sections
稍微小一点。如果工程中使用的静态库.a多,且未使用的函数也多的话,效果会更显著:
-rwxr-xr-x 1 user user 8864 9月 10 14:40 test_normal
-rwxr-xr-x 1 user user 8272 9月 10 14:40 test_sections
然后,查看.o目标文件,在gcc编译选项-ffunction-sections -fdata-sections
是否带来区别:
- 没有
-ffunction-sections -fdata-sections
选项的目标文件,只有1个test
和rodata
; - 带有
-ffunction-sections -fdata-sections
选项的目标文件,分别有text.func_0
到text.func_7
,以及8个rodata.__func__
。说明gcc在编译生成目标文件时,将每个函数或数据段,放在各种单独独立的section
;
$ readelf -t no_section.o
There are 13 section headers, starting at offset 0xa08:
Section Headers:
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 0]
NULL NULL 0000000000000000 0000000000000000 0
[ 1] .text
PROGBITS PROGBITS 0000000000000000 0000000000000040 0
[ 2] .rela.text
RELA RELA 0000000000000000 0000000000000670 10
[ 3] .data
PROGBITS PROGBITS 0000000000000000 0000000000000148 0
[ 4] .bss
NOBITS NOBITS 0000000000000000 0000000000000148 0
[ 5] .rodata
PROGBITS PROGBITS 0000000000000000 0000000000000148 0
$ readelf -t with_section.o
There are 38 section headers, starting at offset 0xce8:
Section Headers:
[Nr] Name
Type Address Offset Link
Size EntSize Info Align
Flags
[ 0]
NULL NULL 0000000000000000 0000000000000000 0
[ 1] .text
PROGBITS PROGBITS 0000000000000000 0000000000000040 0
[ 2] .data
PROGBITS PROGBITS 0000000000000000 0000000000000040 0
[ 3] .bss
NOBITS NOBITS 0000000000000000 0000000000000040 0
[ 4] .rodata
PROGBITS PROGBITS 0000000000000000 0000000000000040 0
[ 5] .text.func_0
PROGBITS PROGBITS 0000000000000000 0000000000000045 0
[ 6] .rela.text.func_0
RELA RELA 0000000000000000 0000000000000808 35
[ 7] .text.func_1
PROGBITS PROGBITS 0000000000000000 0000000000000064 0
[ 8] .rela.text.func_1
RELA RELA 0000000000000000 0000000000000850 35
[ 9] .text.func_2
PROGBITS PROGBITS 0000000000000000 0000000000000083 0
[10] .rela.text.func_2
RELA RELA 0000000000000000 0000000000000898 35
[11] .text.func_3
PROGBITS PROGBITS 0000000000000000 00000000000000a2 0
[12] .rela.text.func_3
RELA RELA 0000000000000000 00000000000008e0 35
[13] .text.func_4
PROGBITS PROGBITS 0000000000000000 00000000000000c1 0
[14] .rela.text.func_4
RELA RELA 0000000000000000 0000000000000928 35
[15] .text.func_5
PROGBITS PROGBITS 0000000000000000 00000000000000e0 0
[16] .rela.text.func_5
RELA RELA 0000000000000000 0000000000000970 35
[17] .text.func_6
PROGBITS PROGBITS 0000000000000000 00000000000000ff 0
[18] .rela.text.func_6
RELA RELA 0000000000000000 00000000000009b8 35
[19] .text.func_7
PROGBITS PROGBITS 0000000000000000 000000000000011e 0
[20] .rela.text.func_7
RELA RELA 0000000000000000 0000000000000a00 35
[21] .text.main
PROGBITS PROGBITS 0000000000000000 000000000000013d 0
[22] .rela.text.main
RELA RELA 0000000000000000 0000000000000a48 35
[23] .rodata.__func__.2250
PROGBITS PROGBITS 0000000000000000 000000000000014d 0
[24] .rodata.__func__.2254
PROGBITS PROGBITS 0000000000000000 0000000000000154 0
[25] .rodata.__func__.2258
PROGBITS PROGBITS 0000000000000000 000000000000015b 0
[26] .rodata.__func__.2262
PROGBITS PROGBITS 0000000000000000 0000000000000162 0
[27] .rodata.__func__.2266
PROGBITS PROGBITS 0000000000000000 0000000000000169 0
[28] .rodata.__func__.2270
PROGBITS PROGBITS 0000000000000000 0000000000000170 0
[29] .rodata.__func__.2274
PROGBITS PROGBITS 0000000000000000 0000000000000177 0
[30] .rodata.__func__.2278
PROGBITS PROGBITS 0000000000000000 000000000000017e 0
最后,再看一下,不带-Wl,–gc-sections
参数生成的可执行文件,与带-Wl,–gc-sections
参数生成的可执行文件的差别。很明显,在链接过程中,带-Wl,–gc-sections
参数后,可执行文件仅联编了调用的函数。
$ readelf -a test_normal | grep func_
55: 00000000000006c6 31 FUNC GLOBAL DEFAULT 14 func_4
57: 000000000000064a 31 FUNC GLOBAL DEFAULT 14 func_0
58: 0000000000000688 31 FUNC GLOBAL DEFAULT 14 func_2
60: 0000000000000704 31 FUNC GLOBAL DEFAULT 14 func_6
71: 00000000000006a7 31 FUNC GLOBAL DEFAULT 14 func_3
72: 0000000000000669 31 FUNC GLOBAL DEFAULT 14 func_1
73: 0000000000000723 31 FUNC GLOBAL DEFAULT 14 func_7
74: 00000000000006e5 31 FUNC GLOBAL DEFAULT 14 func_5
readelf -a test_sections | grep func_
48: 000000000000064a 31 FUNC GLOBAL DEFAULT 14 func_0