GCC 正在生成充满零的二进制文件
GCC is generating binaries filled with zeroes
我想弄清楚为什么 GCC 生成的二进制文件如此之大。
考虑这个空程序:
int main() {
return 0;
}
现在我用 GCC 9.2.1 20190827 (Red Hat 9.2.1-1) 和 glibc 2.29 构建它,没有任何额外的参数:
gcc -o test test.c
生成的二进制文件为 21984 字节 (~22 KB)。使用xxd
查看生成的文件,多处有长运行个空字节:
00000370: 006c 6962 632e 736f 2e36 005f 5f6c 6962 .libc.so.6.__lib
00000380: 635f 7374 6172 745f 6d61 696e 0047 4c49 c_start_main.GLI
00000390: 4243 5f32 2e32 2e35 005f 5f67 6d6f 6e5f BC_2.2.5.__gmon_
000003a0: 7374 6172 745f 5f00 0000 0200 0000 0000 start__.........
000003b0: 0100 0100 0100 0000 1000 0000 0000 0000 ................
000003c0: 751a 6909 0000 0200 1d00 0000 0000 0000 u.i.............
000003d0: f03f 4000 0000 0000 0600 0000 0100 0000 .?@.............
000003e0: 0000 0000 0000 0000 f83f 4000 0000 0000 .........?@.....
000003f0: 0600 0000 0200 0000 0000 0000 0000 0000 ................
00000400: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3040 bytes of zeroes>
00000ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001000: f30f 1efa 4883 ec08 488b 05e9 2f00 0048 ....H...H.../..H
<not zeroes>
00001190: f30f 1efa c300 0000 f30f 1efa 4883 ec08 ............H...
000011a0: 4883 c408 c300 0000 0000 0000 0000 0000 H...............
000011b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3632 bytes of zeros>
00001ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00002000: 0100 0200 0000 0000 0000 0000 0000 0000 ................
00002010: 011b 033b 3400 0000 0500 0000 10f0 ffff ...;4...........
<not zeroes>
000020e0: 410e 2842 0e20 420e 1842 0e10 420e 0800 A.(B. B..B..B...
000020f0: 1000 0000 ac00 0000 98f0 ffff 0500 0000 ................
00002100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3376 bytes of zeroes>
00002e40: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00002e50: 0011 4000 0000 0000 d010 4000 0000 0000 ..@.......@.....
...
因此生成的二进制文件有大约 10 KB,或几乎一半,其中什么都没有。
与 size -A
相比,大小更像是一个除了返回退出代码之外什么都不做的程序所期望的大小:
test :
section size addr
.interp 28 4194984
.note.ABI-tag 32 4195012
.note.gnu.build-id 36 4195044
.gnu.hash 28 4195080
.dynsym 72 4195112
.dynstr 56 4195184
.gnu.version 6 4195240
.gnu.version_r 32 4195248
.rela.dyn 48 4195280
.init 27 4198400
.text 373 4198432
.fini 13 4198808
.rodata 16 4202496
.eh_frame_hdr 52 4202512
.eh_frame 192 4202568
.init_array 8 4210256
.fini_array 8 4210264
.dynamic 400 4210272
.got 16 4210672
.got.plt 24 4210688
.data 4 4210712
.bss 4 4210716
.comment 44 0
.gnu.build.attributes 4472 4218912
Total 5991
当使用 GCC 9.2.0 和 musl 1.1.23 为 PowerPC 交叉编译时,情况更糟。二进制文件的大小增长到 67872 字节(~67 KB),并且查看 xxd
,有一个连续的 运行 64074 字节只有零。
不过,size -A
报告此版本的尺寸更小:
test :
section size addr
.interp 26 268435796
.note.gnu.build-id 36 268435824
.hash 36 268435860
.dynsym 64 268435896
.dynstr 39 268435960
.rela.plt 12 268436000
.init 28 268436012
.text 496 268436048
.fini 28 268436544
.eh_frame_hdr 28 268436572
.eh_frame 80 268436600
.init_array 4 268566284
.fini_array 4 268566288
.dynamic 216 268566292
.branch_lt 8 268566508
.got 12 268566516
.plt 4 268566528
.data 4 268566532
.bss 28 268566536
.comment 17 0
Total 1170
我还尝试用手头有的旧版 GCC 编译程序:GCC 4.7.2 和 uClibc 1.0.12。通过这种组合,生成的二进制文件只有 4769 字节(~4 KB),并且其中没有明显的 运行 空字节。
为了确保这不仅发生在什么都不做的小程序上,我查看了一些我用 GCC 9.2.0 交叉编译的真实程序和 musl 1.1.23。例如,使用 -Os
编译并剥离的 tcpdump 二进制文件包含 32628 字节长的连续 运行 空字节。那么,为什么零试图消耗我所有的磁盘space?
最近的 binutils 默认为 -z separate-code
,它向需要进一步对齐的程序添加额外的 PT_LOAD
段。
您可以像这样覆盖默认值:
gcc -Wl,-z,noseparate-code -o test test.c
由于对齐要求,此更改仍会保留一些零。
So, why are zeroes trying to consume all of my disk space?
因为在大多数现代系统上,磁盘上 22K 的额外字节是无关紧要的。
您观察到的一些成本是由于动态链接,一些是由于填充,一些是为了帮助您进行调试(例如 .comment
、.note.gnu.build-id
、.eh_frame*
。) .
我可以通过不使用 libc 并静态链接和剥离将二进制文件减少到 624 字节:
cat t.c
void _start()
{
__asm__("movq ,%rax; xorq %rdi,%rdi; syscall");
}
gcc -O3 t.c -static -nostdlib -Wl,-z,noseparate-code,--build-id=none &&
strip --strip-all a.out &&
./a.out && ls -l a.out
-rwxr-x--- 1 me mygroup 624 Nov 25 19:34 a.out
还有 .comment
和 .eh_frame
可以删除。
帮助我找到了正确的方向。罪魁祸首不是 -z separate-code,而是 -z relro。
通过将 -Wl,-z,norelro 添加到 PowerPC GCC 选项,空程序的文件大小从 67872 字节减少到 3772 字节!在 x64 上影响较小:从 21984 字节到 18584 字节。在一个小但实际功能的程序上,PowerPC 上的差异大约小 50%,而使用 tcpdump,我之前比较过,它几乎是 32 KB。
relro链接器选项显然创建了一个新段,用于重新映射全局偏移量table并将其标记为只读,从而保护程序免受堆栈溢出攻击。这种解释很可能是不准确的;我在尝试弄清楚时并没有完全理解我读到的内容。
PPC 上的大小差异恰好是 62 KB。为什么要创建这么大的区域,我不知道。
虽然该设置作为强化措施保持启用会很好,但不幸的是我的目标板只有 11 MB 的可用闪存,我正在尝试在其上安装基于 Linux 的系统,所以每个字节都很重要,我将禁用该设置以减小二进制大小。
我想弄清楚为什么 GCC 生成的二进制文件如此之大。
考虑这个空程序:
int main() {
return 0;
}
现在我用 GCC 9.2.1 20190827 (Red Hat 9.2.1-1) 和 glibc 2.29 构建它,没有任何额外的参数:
gcc -o test test.c
生成的二进制文件为 21984 字节 (~22 KB)。使用xxd
查看生成的文件,多处有长运行个空字节:
00000370: 006c 6962 632e 736f 2e36 005f 5f6c 6962 .libc.so.6.__lib
00000380: 635f 7374 6172 745f 6d61 696e 0047 4c49 c_start_main.GLI
00000390: 4243 5f32 2e32 2e35 005f 5f67 6d6f 6e5f BC_2.2.5.__gmon_
000003a0: 7374 6172 745f 5f00 0000 0200 0000 0000 start__.........
000003b0: 0100 0100 0100 0000 1000 0000 0000 0000 ................
000003c0: 751a 6909 0000 0200 1d00 0000 0000 0000 u.i.............
000003d0: f03f 4000 0000 0000 0600 0000 0100 0000 .?@.............
000003e0: 0000 0000 0000 0000 f83f 4000 0000 0000 .........?@.....
000003f0: 0600 0000 0200 0000 0000 0000 0000 0000 ................
00000400: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3040 bytes of zeroes>
00000ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001000: f30f 1efa 4883 ec08 488b 05e9 2f00 0048 ....H...H.../..H
<not zeroes>
00001190: f30f 1efa c300 0000 f30f 1efa 4883 ec08 ............H...
000011a0: 4883 c408 c300 0000 0000 0000 0000 0000 H...............
000011b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3632 bytes of zeros>
00001ff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00002000: 0100 0200 0000 0000 0000 0000 0000 0000 ................
00002010: 011b 033b 3400 0000 0500 0000 10f0 ffff ...;4...........
<not zeroes>
000020e0: 410e 2842 0e20 420e 1842 0e10 420e 0800 A.(B. B..B..B...
000020f0: 1000 0000 ac00 0000 98f0 ffff 0500 0000 ................
00002100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
<3376 bytes of zeroes>
00002e40: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00002e50: 0011 4000 0000 0000 d010 4000 0000 0000 ..@.......@.....
...
因此生成的二进制文件有大约 10 KB,或几乎一半,其中什么都没有。
与 size -A
相比,大小更像是一个除了返回退出代码之外什么都不做的程序所期望的大小:
test :
section size addr
.interp 28 4194984
.note.ABI-tag 32 4195012
.note.gnu.build-id 36 4195044
.gnu.hash 28 4195080
.dynsym 72 4195112
.dynstr 56 4195184
.gnu.version 6 4195240
.gnu.version_r 32 4195248
.rela.dyn 48 4195280
.init 27 4198400
.text 373 4198432
.fini 13 4198808
.rodata 16 4202496
.eh_frame_hdr 52 4202512
.eh_frame 192 4202568
.init_array 8 4210256
.fini_array 8 4210264
.dynamic 400 4210272
.got 16 4210672
.got.plt 24 4210688
.data 4 4210712
.bss 4 4210716
.comment 44 0
.gnu.build.attributes 4472 4218912
Total 5991
当使用 GCC 9.2.0 和 musl 1.1.23 为 PowerPC 交叉编译时,情况更糟。二进制文件的大小增长到 67872 字节(~67 KB),并且查看 xxd
,有一个连续的 运行 64074 字节只有零。
不过,size -A
报告此版本的尺寸更小:
test :
section size addr
.interp 26 268435796
.note.gnu.build-id 36 268435824
.hash 36 268435860
.dynsym 64 268435896
.dynstr 39 268435960
.rela.plt 12 268436000
.init 28 268436012
.text 496 268436048
.fini 28 268436544
.eh_frame_hdr 28 268436572
.eh_frame 80 268436600
.init_array 4 268566284
.fini_array 4 268566288
.dynamic 216 268566292
.branch_lt 8 268566508
.got 12 268566516
.plt 4 268566528
.data 4 268566532
.bss 28 268566536
.comment 17 0
Total 1170
我还尝试用手头有的旧版 GCC 编译程序:GCC 4.7.2 和 uClibc 1.0.12。通过这种组合,生成的二进制文件只有 4769 字节(~4 KB),并且其中没有明显的 运行 空字节。
为了确保这不仅发生在什么都不做的小程序上,我查看了一些我用 GCC 9.2.0 交叉编译的真实程序和 musl 1.1.23。例如,使用 -Os
编译并剥离的 tcpdump 二进制文件包含 32628 字节长的连续 运行 空字节。那么,为什么零试图消耗我所有的磁盘space?
最近的 binutils 默认为 -z separate-code
,它向需要进一步对齐的程序添加额外的 PT_LOAD
段。
您可以像这样覆盖默认值:
gcc -Wl,-z,noseparate-code -o test test.c
由于对齐要求,此更改仍会保留一些零。
So, why are zeroes trying to consume all of my disk space?
因为在大多数现代系统上,磁盘上 22K 的额外字节是无关紧要的。
您观察到的一些成本是由于动态链接,一些是由于填充,一些是为了帮助您进行调试(例如 .comment
、.note.gnu.build-id
、.eh_frame*
。) .
我可以通过不使用 libc 并静态链接和剥离将二进制文件减少到 624 字节:
cat t.c
void _start()
{
__asm__("movq ,%rax; xorq %rdi,%rdi; syscall");
}
gcc -O3 t.c -static -nostdlib -Wl,-z,noseparate-code,--build-id=none &&
strip --strip-all a.out &&
./a.out && ls -l a.out
-rwxr-x--- 1 me mygroup 624 Nov 25 19:34 a.out
还有 .comment
和 .eh_frame
可以删除。
通过将 -Wl,-z,norelro 添加到 PowerPC GCC 选项,空程序的文件大小从 67872 字节减少到 3772 字节!在 x64 上影响较小:从 21984 字节到 18584 字节。在一个小但实际功能的程序上,PowerPC 上的差异大约小 50%,而使用 tcpdump,我之前比较过,它几乎是 32 KB。
relro链接器选项显然创建了一个新段,用于重新映射全局偏移量table并将其标记为只读,从而保护程序免受堆栈溢出攻击。这种解释很可能是不准确的;我在尝试弄清楚时并没有完全理解我读到的内容。
PPC 上的大小差异恰好是 62 KB。为什么要创建这么大的区域,我不知道。
虽然该设置作为强化措施保持启用会很好,但不幸的是我的目标板只有 11 MB 的可用闪存,我正在尝试在其上安装基于 Linux 的系统,所以每个字节都很重要,我将禁用该设置以减小二进制大小。