文章目錄
- GCC 如何縮減可執行文件size
- 測試代碼
上篇文章:ARM 嵌入式 編譯系列 9-- GCC 編譯符號表(Symbol Table)的詳細介紹
下篇文章:ARM 嵌入式 編譯系列 10.1 – GCC 編譯縮減可執行文件 elf 文件大小
GCC 如何縮減可執行文件size
在開發過程總,總是希望編譯出來的可執行文件盡量小,因為這樣可以節省更多的磁盤空間,那么有什么方法可以縮小可執行文件的大小的?
A: 通常我們會首先移除了debug信息,移除了符號表信息,同時我們還希望萬一出事了,比如coredump了,我們能獲取更多的信息。
Linux下是怎么解決這個矛盾的呢?
先看第一個問題,移除debug相關信息的影響。
測試代碼
如下實現了測試代碼,main
調用了 foo
,foo
調用了 bar
,其中bar
故意訪問了非法地址,為了引起 core dump
。
#include<stdio.h>
#include<stdlib.h>static int bar(void)
{char *p = NULL;printf("I am bar,I will core dump\n");printf("%s",p);*p =0x0;return 0;
}static int foo(void)
{int i ;printf("I am foo,I will call bar\n");bar();return 0;
}int main(void)
{printf("I am main, I wll can foo\n");foo();return 0;
}
先編譯出一個 debug 版本來,然后我們看到可執行程序的大小為 17464
bytes.
gcc -g test.c -o test
ls -rtl test
-rwxrwxr-x 1 codingcos codingcos 17464 8月 14 09:43 test
再看下 section 信息:
readelf -S test
There are 37 section headers, starting at offset 0x3af8:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .interp PROGBITS 0000000000000318 00000318000000000000001c 0000000000000000 A 0 0 1[ 2] .note.gnu.pr[...] NOTE 0000000000000338 000003380000000000000030 0000000000000000 A 0 0 8[ 3] .note.gnu.bu[...] NOTE 0000000000000368 000003680000000000000024 0000000000000000 A 0 0 4[ 4] .note.ABI-tag NOTE 000000000000038c 0000038c0000000000000020 0000000000000000 A 0 0 4[ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b00000000000000024 0000000000000000 A 6 0 8[ 6] .dynsym DYNSYM 00000000000003d8 000003d800000000000000c0 0000000000000018 A 7 1 8[ 7] .dynstr STRTAB 0000000000000498 000004980000000000000094 0000000000000000 A 0 0 1[ 8] .gnu.version VERSYM 000000000000052c 0000052c0000000000000010 0000000000000002 A 6 0 2[ 9] .gnu.version_r VERNEED 0000000000000540 000005400000000000000030 0000000000000000 A 7 1 8[10] .rela.dyn RELA 0000000000000570 0000057000000000000000c0 0000000000000018 A 6 0 8[11] .rela.plt RELA 0000000000000630 000006300000000000000030 0000000000000018 AI 6 24 8[12] .init PROGBITS 0000000000001000 00001000000000000000001b 0000000000000000 AX 0 0 4[13] .plt PROGBITS 0000000000001020 000010200000000000000030 0000000000000010 AX 0 0 16[14] .plt.got PROGBITS 0000000000001050 000010500000000000000010 0000000000000010 AX 0 0 16[15] .plt.sec PROGBITS 0000000000001060 000010600000000000000020 0000000000000010 AX 0 0 16[16] .text PROGBITS 0000000000001080 000010800000000000000174 0000000000000000 AX 0 0 16[17] .fini PROGBITS 00000000000011f4 000011f4000000000000000d 0000000000000000 AX 0 0 4[18] .rodata PROGBITS 0000000000002000 000020000000000000000055 0000000000000000 A 0 0 4[19] .eh_frame_hdr PROGBITS 0000000000002058 000020580000000000000044 0000000000000000 A 0 0 4[20] .eh_frame PROGBITS 00000000000020a0 000020a000000000000000ec 0000000000000000 A 0 0 8[21] .init_array INIT_ARRAY 0000000000003db0 00002db00000000000000008 0000000000000008 WA 0 0 8[22] .fini_array FINI_ARRAY 0000000000003db8 00002db80000000000000008 0000000000000008 WA 0 0 8[23] .dynamic DYNAMIC 0000000000003dc0 00002dc000000000000001f0 0000000000000010 WA 7 0 8[24] .got PROGBITS 0000000000003fb0 00002fb00000000000000050 0000000000000008 WA 0 0 8[25] .data PROGBITS 0000000000004000 000030000000000000000010 0000000000000000 WA 0 0 8[26] .bss NOBITS 0000000000004010 000030100000000000000008 0000000000000000 WA 0 0 1[27] .comment PROGBITS 0000000000000000 00003010000000000000002b 0000000000000001 MS 0 0 1[28] .debug_aranges PROGBITS 0000000000000000 0000303b0000000000000030 0000000000000000 0 0 1[29] .debug_info PROGBITS 0000000000000000 0000306b000000000000011a 0000000000000000 0 0 1[30] .debug_abbrev PROGBITS 0000000000000000 0000318500000000000000cd 0000000000000000 0 0 1[31] .debug_line PROGBITS 0000000000000000 000032520000000000000076 0000000000000000 0 0 1[32] .debug_str PROGBITS 0000000000000000 000032c800000000000000ea 0000000000000001 MS 0 0 1[33] .debug_line_str PROGBITS 0000000000000000 000033b2000000000000003d 0000000000000001 MS 0 0 1[34] .symtab SYMTAB 0000000000000000 000033f000000000000003a8 0000000000000018 35 20 8[35] .strtab STRTAB 0000000000000000 0000379800000000000001f5 0000000000000000 0 0 1[36] .shstrtab STRTAB 0000000000000000 0000398d000000000000016a 0000000000000000 0 0 1
Key to Flags:W (write), A (alloc), X (execute), M (merge), S (strings), I (info),L (link order), O (extra OS processing required), G (group), T (TLS),C (compressed), x (unknown), o (OS specific), E (exclude),D (mbind), l (large), p (processor specific)
然后,我們用 strip
命令將 debug info 去除,指令如下:
strip --strip-debug test
ls -rtl test
-rwxrwxr-x 1 codingcos codingcos 15912 8月 14 09:43 test
可執行文件的大小從17464
減小到了15912
。
去除掉 debug info 的 test 和之前的 test 有什么區別呢? 我們看下去除后的 section 信息:
readelf -S test
There are 31 section headers, starting at offset 0x3668:Section Headers:[Nr] Name Type Address OffsetSize EntSize Flags Link Info Align[ 0] NULL 0000000000000000 000000000000000000000000 0000000000000000 0 0 0[ 1] .interp PROGBITS 0000000000000318 00000318000000000000001c 0000000000000000 A 0 0 1[ 2] .note.gnu.pr[...] NOTE 0000000000000338 000003380000000000000030 0000000000000000 A 0 0 8[ 3] .note.gnu.bu[...] NOTE 0000000000000368 000003680000000000000024 0000000000000000 A 0 0 4[ 4] .note.ABI-tag NOTE 000000000000038c 0000038c0000000000000020 0000000000000000 A 0 0 4[ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b00000000000000024 0000000000000000 A 6 0 8[ 6] .dynsym DYNSYM 00000000000003d8 000003d800000000000000c0 0000000000000018 A 7 1 8[ 7] .dynstr STRTAB 0000000000000498 000004980000000000000094 0000000000000000 A 0 0 1[ 8] .gnu.version VERSYM 000000000000052c 0000052c0000000000000010 0000000000000002 A 6 0 2[ 9] .gnu.version_r VERNEED 0000000000000540 000005400000000000000030 0000000000000000 A 7 1 8[10] .rela.dyn RELA 0000000000000570 0000057000000000000000c0 0000000000000018 A 6 0 8[11] .rela.plt RELA 0000000000000630 000006300000000000000030 0000000000000018 AI 6 24 8[12] .init PROGBITS 0000000000001000 00001000000000000000001b 0000000000000000 AX 0 0 4[13] .plt PROGBITS 0000000000001020 000010200000000000000030 0000000000000010 AX 0 0 16[14] .plt.got PROGBITS 0000000000001050 000010500000000000000010 0000000000000010 AX 0 0 16[15] .plt.sec PROGBITS 0000000000001060 000010600000000000000020 0000000000000010 AX 0 0 16[16] .text PROGBITS 0000000000001080 000010800000000000000174 0000000000000000 AX 0 0 16[17] .fini PROGBITS 00000000000011f4 000011f4000000000000000d 0000000000000000 AX 0 0 4[18] .rodata PROGBITS 0000000000002000 000020000000000000000055 0000000000000000 A 0 0 4[19] .eh_frame_hdr PROGBITS 0000000000002058 000020580000000000000044 0000000000000000 A 0 0 4[20] .eh_frame PROGBITS 00000000000020a0 000020a000000000000000ec 0000000000000000 A 0 0 8[21] .init_array INIT_ARRAY 0000000000003db0 00002db00000000000000008 0000000000000008 WA 0 0 8[22] .fini_array FINI_ARRAY 0000000000003db8 00002db80000000000000008 0000000000000008 WA 0 0 8[23] .dynamic DYNAMIC 0000000000003dc0 00002dc000000000000001f0 0000000000000010 WA 7 0 8[24] .got PROGBITS 0000000000003fb0 00002fb00000000000000050 0000000000000008 WA 0 0 8[25] .data PROGBITS 0000000000004000 000030000000000000000010 0000000000000000 WA 0 0 8[26] .bss NOBITS 0000000000004010 000030100000000000000008 0000000000000000 WA 0 0 1[27] .comment PROGBITS 0000000000000000 00003010000000000000002b 0000000000000001 MS 0 0 1[28] .symtab SYMTAB 0000000000000000 000030400000000000000330 0000000000000018 29 15 8[29] .strtab STRTAB 0000000000000000 0000337000000000000001db 0000000000000000 0 0 1[30] .shstrtab STRTAB 0000000000000000 0000354b000000000000011a 0000000000000000 0 0 1
我們可以看到.debug_aranges .debug_info .debug_abbrev .debug_line .debug_str .debug_line_str
debug 相關的 section 都已經不在了,原來的 37個section減少到了31個 sections。
但是我們注意到.symtab .strtab .shstrtab
符號表信息 和 字符串信息還在。此外,可以通過nm
命可以看到它們的具體信息這:
[09:53:16]shiqiang.zhu@selab-ThinkStation-P350 (*^~^*) ~/workbase/test> nm test
000000000000038c r __abi_tag
0000000000001169 t bar
0000000000004010 B __bss_start
0000000000004010 b completed.0w __cxa_finalize@GLIBC_2.2.5
0000000000004000 D __data_start
0000000000004000 W data_start
00000000000010b0 t deregister_tm_clones
0000000000001120 t __do_global_dtors_aux
0000000000003db8 d __do_global_dtors_aux_fini_array_entry
0000000000004008 D __dso_handle
0000000000003dc0 d _DYNAMIC
0000000000004010 D _edata
0000000000004018 B _end
00000000000011f4 T _fini
00000000000011ae t foo
0000000000001160 t frame_dummy
0000000000003db0 d __frame_dummy_init_array_entry
0000000000002188 r __FRAME_END__
0000000000003fb0 d _GLOBAL_OFFSET_TABLE_w __gmon_start__
0000000000002058 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_usedw _ITM_deregisterTMCloneTablew _ITM_registerTMCloneTableU __libc_start_main@GLIBC_2.34
00000000000011d1 T mainU printf@GLIBC_2.2.5U puts@GLIBC_2.2.5
00000000000010e0 t register_tm_clones
0000000000001080 T _start
0000000000004010 D __TMC_END__
此時如果執行這個 test
可執行程序,會產生coredump
文件,如果使用gdb
調試coredump
文件的時候,我們可以打印出堆棧信息,因為符號表還在。
在往下進行之前我們先學習一個命令: ulimit
:
ulimit
是 如 Linux 中用于控制shell
和其創建的進程可以使用的系統資源。
ulimit -c
選項則用于設置核心文件(core dump
)的最大大小。當一個程序奔潰時,操作系統可以將程序的內存內容和一些調試信息保存到一個核心文件中,以便開發者可以查看這些信息來調試程序。這個文件通常被稱為core dump。
使用ulimit -c
命令可以查詢或設置 core文件的最大大小。例如:
ulimit -c
:查詢當前core文件的最大大小。如果返回的是0,那么表示不會生成core文件;ulimit -c unlimited
:設置core文件的最大大小為無限,即允許core文件的大小不受限制。
注意:
ulimit -c
設置的限制僅對當前shell及其子進程有效,不會影響到其他shell或全局設置。在一些系統中,為了安全考慮,默認可能不啟用 core dump,即使你使用ulimit -c unlimited
也不會生成 core文件。在這種情況下,你可能需要修改系統的核心設置或其他配置來啟用core dump。1)使用ulimit -c查看core dump是否打開。如果結果為0,則表示此功能處于關閉狀態,不會生成core文件, 執行 ulimit -c 1024
2)修改/etc/sysctl.conf文件【sudo vi /etc/sysctl.conf】,添加需要保存的路徑【kernel.core_pattern = /tmp/corefile/core.%e.%t】
3)輸入 sudo sysctl -p /etc/sysctl.conf 命令即刻生效
由于 symtab .strtab
.shstrtab
符號表信息 和 字符串信息還在,我們仍然可以使用 gdb
進行調試:
ulimit -c unlimited or ulimit -c 1024gdb -c /tmp/corefile/core.test.1691982639.1098584 test
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000560eb28091ab in bar () at test.c:10
10 *p =0x0;
(gdb) bt
#0 0x0000560eb28091ab in bar () at test.c:10
#1 0x0000560eb28091d1 in foo () at test.c:19
#2 0x0000560eb28091f4 in main () at test.c:27
(gdb)
雖然 debug 相關section已經去除,但是還有符號表信息,一旦出了core dump還可以進行debug。大部分的發行版的程序都會將符號表信息刪除。如果符號表與可執行程序完全隔離,那將是一種什么樣的情況的?請見下篇文章。
上篇文章:ARM 嵌入式 編譯系列 9-- GCC 編譯符號表(Symbol Table)的詳細介紹
下篇文章:ARM 嵌入式 編譯系列 10.1 – GCC 編譯縮減可執行文件 elf 文件大小