与 gcc 链接将文件大小增加到 16 KB
Linking with gcc increases file size to 16 KB
我现在在 linux。我正在编译一个超级简单的 C 程序:
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
并编译
gcc main.c -o main
经过运行ll
得到文件大小,就是这样returns:
-rwxr-xr-x 1 xylight xylight 16K Nov 5 11:30 main
-rw-r--r-- 1 xylight xylight 68 Nov 5 11:23 main.c
-rw-r--r-- 1 xylight xylight 1.5K Nov 5 11:30 main.o
链接后main.o
,文件大小变成16KB!我怎样才能把它变小?任何链接器选项?
我不确定这是否重复,我在这里找不到任何内容。让我知道它是否重复。
运行readelf -h main
后说ELF类型是这样的:DYN (Shared object file)
有人知道我可以做些什么来缩小它吗?
我在 SO 上找不到很好的解释,所以让我 post 在这里解释一下。
首先,executable默认包含静态符号table,用于调试,运行时不加载。我们可以用 strip main
摆脱它,这将为我们节省大约 2K(在我的 Ubuntu 20 上减少到 15K)。
现在,我们可以通过 运行 readelf -SW main
:
仔细查看开销
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
[ 2] .note.gnu.property NOTE 0000000000000338 000338 000020 00 A 0 0 8
[ 3] .note.gnu.build-id NOTE 0000000000000358 000358 000024 00 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 00037c 000020 00 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 0003a0 000024 00 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 0003c8 0000a8 18 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 000470 000082 00 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004f2 0004f2 00000e 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000500 000500 000020 00 A 7 1 8
[10] .rela.dyn RELA 0000000000000520 000520 0000c0 18 A 6 0 8
[11] .rela.plt RELA 00000000000005e0 0005e0 000018 18 AI 6 24 8
[12] .init PROGBITS 0000000000001000 001000 00001b 00 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 001020 000020 10 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001040 001040 000010 10 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001050 001050 000010 10 AX 0 0 16
[16] .text PROGBITS 0000000000001060 001060 000185 00 AX 0 0 16
[17] .fini PROGBITS 00000000000011e8 0011e8 00000d 00 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 002000 000012 00 A 0 0 4
[19] .eh_frame_hdr PROGBITS 0000000000002014 002014 000044 00 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002058 002058 000108 00 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003db8 002db8 000008 08 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003dc0 002dc0 000008 08 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003dc8 002dc8 0001f0 10 WA 7 0 8
[24] .got PROGBITS 0000000000003fb8 002fb8 000048 08 WA 0 0 8
[25] .data PROGBITS 0000000000004000 003000 000010 00 WA 0 0 8
[26] .bss NOBITS 0000000000004010 003010 000008 00 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 003010 00002a 01 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00303a 00010a 00 0 0 1
如您所见,前 1.5K(最多但不包括 .init
)保存 ELF header 和用于加载共享库的簿记数据(所有这些 .gnu.hash
, .dynsym
, 等等).
接下来是 500 字节的代码(.init
、.plt
等,但不包括 .rodata
)。请注意,代码分配从 4K 页面边界开始,因此我们浪费了 2.5K 用于填充。
然后 3.5K 被浪费在 ~1K 数据部分之前的 4K 页边界处重新对齐代码(.rodata
、展开 tables 等)。 .eh_frame
和 .init_array
之间有一个有趣的 3K 浪费,这是由于只读数据和普通数据之间的一些奇怪对齐而发生的(有关更多详细信息,请参见 this question)。
所以总而言之,只有一小部分 ELF 大小(1.5+0.5+1=3K,即 20%)被真正使用,其余的浪费在 ELF 被 mmap
ed 时正确对齐地址记忆。需要地址对齐,以便动态加载程序可以为内存页分配不同的权限(例如,代码页不能写入但可以执行,数据页权限相反)。
我现在在 linux。我正在编译一个超级简单的 C 程序:
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
并编译
gcc main.c -o main
经过运行ll
得到文件大小,就是这样returns:
-rwxr-xr-x 1 xylight xylight 16K Nov 5 11:30 main
-rw-r--r-- 1 xylight xylight 68 Nov 5 11:23 main.c
-rw-r--r-- 1 xylight xylight 1.5K Nov 5 11:30 main.o
链接后main.o
,文件大小变成16KB!我怎样才能把它变小?任何链接器选项?
我不确定这是否重复,我在这里找不到任何内容。让我知道它是否重复。
运行readelf -h main
后说ELF类型是这样的:DYN (Shared object file)
有人知道我可以做些什么来缩小它吗?
我在 SO 上找不到很好的解释,所以让我 post 在这里解释一下。
首先,executable默认包含静态符号table,用于调试,运行时不加载。我们可以用 strip main
摆脱它,这将为我们节省大约 2K(在我的 Ubuntu 20 上减少到 15K)。
现在,我们可以通过 运行 readelf -SW main
:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
[ 2] .note.gnu.property NOTE 0000000000000338 000338 000020 00 A 0 0 8
[ 3] .note.gnu.build-id NOTE 0000000000000358 000358 000024 00 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 00037c 000020 00 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 0003a0 000024 00 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 0003c8 0000a8 18 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 000470 000082 00 A 0 0 1
[ 8] .gnu.version VERSYM 00000000000004f2 0004f2 00000e 02 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000500 000500 000020 00 A 7 1 8
[10] .rela.dyn RELA 0000000000000520 000520 0000c0 18 A 6 0 8
[11] .rela.plt RELA 00000000000005e0 0005e0 000018 18 AI 6 24 8
[12] .init PROGBITS 0000000000001000 001000 00001b 00 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 001020 000020 10 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001040 001040 000010 10 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001050 001050 000010 10 AX 0 0 16
[16] .text PROGBITS 0000000000001060 001060 000185 00 AX 0 0 16
[17] .fini PROGBITS 00000000000011e8 0011e8 00000d 00 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 002000 000012 00 A 0 0 4
[19] .eh_frame_hdr PROGBITS 0000000000002014 002014 000044 00 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002058 002058 000108 00 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003db8 002db8 000008 08 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003dc0 002dc0 000008 08 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003dc8 002dc8 0001f0 10 WA 7 0 8
[24] .got PROGBITS 0000000000003fb8 002fb8 000048 08 WA 0 0 8
[25] .data PROGBITS 0000000000004000 003000 000010 00 WA 0 0 8
[26] .bss NOBITS 0000000000004010 003010 000008 00 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 003010 00002a 01 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00303a 00010a 00 0 0 1
如您所见,前 1.5K(最多但不包括 .init
)保存 ELF header 和用于加载共享库的簿记数据(所有这些 .gnu.hash
, .dynsym
, 等等).
接下来是 500 字节的代码(.init
、.plt
等,但不包括 .rodata
)。请注意,代码分配从 4K 页面边界开始,因此我们浪费了 2.5K 用于填充。
然后 3.5K 被浪费在 ~1K 数据部分之前的 4K 页边界处重新对齐代码(.rodata
、展开 tables 等)。 .eh_frame
和 .init_array
之间有一个有趣的 3K 浪费,这是由于只读数据和普通数据之间的一些奇怪对齐而发生的(有关更多详细信息,请参见 this question)。
所以总而言之,只有一小部分 ELF 大小(1.5+0.5+1=3K,即 20%)被真正使用,其余的浪费在 ELF 被 mmap
ed 时正确对齐地址记忆。需要地址对齐,以便动态加载程序可以为内存页分配不同的权限(例如,代码页不能写入但可以执行,数据页权限相反)。