调查极小的 C 程序的大小



我正在研究Linux(ubuntu 20.04)上一个极小的C程序的大小。

我编译如下:

gcc -s -nostdlib test.c -o test

以下方案:

__attribute__((naked))
void _start() {
asm("movl $1,%eax;"
"xorl %ebx,%ebx;"
"int  $0x80");
}

基本上,这个想法是使Linux系统调用退出,而不是依赖于C运行时为我们执行此操作。(void main() { }就是这种情况)。程序将 1 移动到寄存器 EAX,清除寄存器 EBX(否则将包含返回值),然后执行 linux 系统调用中断0x80。此中断会触发内核处理我们的调用。

我希望这个程序非常小(小于 1K),但是......

du -h test
# >> 16K
ldd test
# >> statically linked

为什么这个程序仍然是 16K?

du报告文件使用的磁盘空间,而ls报告文件的实际大小。通常,对于小文件,du报告的大小要大得多。

您可以通过更改编译和链接选项并删除不必要的部分来显著减小二进制文件的大小。

$ cat test.c
void _start() {
asm("movl $1,%eax;"
"xorl %ebx,%ebx;"
"int  $0x80");
}
$ gcc -s -nostdlib test.c -o test
$ ./test
$ ls -l test
-rwxrwxr-x 1 fpm fpm 8840 Dec  9 04:09 test
$ readelf -W --section-headers test
There are 7 section headers, starting at offset 0x20c8:
Section Headers:
[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
[ 1] .note.gnu.build-id NOTE            0000000000400190 000190 000024 00   A  0   0  4
[ 2] .text             PROGBITS        0000000000401000 001000 000010 00  AX  0   0  1
[ 3] .eh_frame_hdr     PROGBITS        0000000000402000 002000 000014 00   A  0   0  4
[ 4] .eh_frame         PROGBITS        0000000000402018 002018 000038 00   A  0   0  8
[ 5] .comment          PROGBITS        0000000000000000 002050 00002e 01  MS  0   0  1
[ 6] .shstrtab         STRTAB          0000000000000000 00207e 000045 00      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),
l (large), p (processor specific)
$
$ gcc -s -nostdlib -Wl,--nmagic test.c -o test
$ ls -l test
-rwxrwxr-x 1 fpm fpm 984 Dec  9 16:55 test
$ strip -R .comment -R .note.gnu.build-id test
$ strip -R .eh_frame_hdr -R .eh_frame test
$ ls -l test
-rwxrwxr-x 1 fpm fpm 520 Dec  9 17:03 test
$ 

请注意,在此特定实例中,clang可以生成比默认gcc小得多的二进制文件。但是,在使用clang进行编译并去除不必要的部分后,二进制文件的最终大小为 736 字节,这比gcc -s -nostdlib -Wl,--nmagic test.c -o test可能的大小 520 字节大。

$ clang -static -nostdlib -flto -fuse-ld=lld -o test test.c
$ ls -l test
-rwxrwxr-x 1 fpm fpm 1344 Dec  9 04:15 test
$
$ readelf -W --section-headers test
There are 9 section headers, starting at offset 0x300:
Section Headers:
[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
[ 1] .note.gnu.build-id NOTE            0000000000200190 000190 000018 00   A  0   0  4
[ 2] .eh_frame_hdr     PROGBITS        00000000002001a8 0001a8 000014 00   A  0   0  4
[ 3] .eh_frame         PROGBITS        00000000002001c0 0001c0 00003c 00   A  0   0  8
[ 4] .text             PROGBITS        0000000000201200 000200 00000f 00  AX  0   0 16
[ 5] .comment          PROGBITS        0000000000000000 00020f 000040 01  MS  0   0  1
[ 6] .symtab           SYMTAB          0000000000000000 000250 000048 18      8   2  8
[ 7] .shstrtab         STRTAB          0000000000000000 000298 000055 00      0   0  1
[ 8] .strtab           STRTAB          0000000000000000 0002ed 000012 00      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),
l (large), p (processor specific)
$ 

$ strip -R .eh_frame_hdr -R .eh_frame test
$ strip -R .comment -R .note.gnu.build-id test
strip: test: warning: empty loadable segment detected at vaddr=0x200000, is this intentional?
$ ls -l test
-rwxrwxr-x 1 fpm fpm 736 Dec  9 04:19 test
$ readelf -W --section-headers test
There are 3 section headers, starting at offset 0x220:
Section Headers:
[Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
[ 1] .text             PROGBITS        0000000000201200 000200 00000f 00  AX  0   0 16
[ 2] .shstrtab         STRTAB          0000000000000000 00020f 000011 00      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),
l (large), p (processor specific)
$ 

.text是代码,.shstrtab是节标题字符串表。每个ElfHeader结构都包含一个e_shstrndx成员,该成员是.shstrtab表的索引。如果使用此索引,则可以找到该部分的名称。

du默认报告文件在磁盘上使用的空间 - 这意味着最小值将是一个磁盘块。 如果想知道文件的实际大小,请使用ls -l

修改后的程序,以获得等于 3 的退出代码(为了好玩):

void _start() {
asm("movl $1,%eax;"
"movl $3,%ebx;"
"int  $0x80");
}

具有以下功能的构建:

-s
从可执行文件中删除所有符号表和重定位信息.
-nostdlib
链接时不要使用标准系统启动文件或库。

$ gcc -s -nostdlib pg.c -o pg
$ ./pg
$ echo $?
3
$ ldd ./pg
statically linked

生成的可执行文件的大小为 13 KB:

$ ls -l ./pg
-rwxrwxr-x 1 xxx xxx 13296 dec.   9 11:42 ./pg

代码反汇编显示文本部分实际上有 23 个字节长,并且没有数据:

$ objdump -S ./pg
./pg:     file format elf64-x86-64
Disassembly of section .text:
0000000000001000 <.text>:
1000:   f3 0f 1e fa             endbr64 
1004:   55                      push   %rbp
1005:   48 89 e5                mov    %rsp,%rbp
1008:   b8 01 00 00 00          mov    $0x1,%eax
100d:   bb 03 00 00 00          mov    $0x3,%ebx
1012:   cd 80                   int    $0x80
1014:   90                      nop
1015:   5d                      pop    %rbp
1016:   c3                      retq

但是size实用程序显示数据部分为 224(.dynamic部分的大小),为文本报告的大小为 248 字节。这是其他部分的总大小减去.comment

$ size pg
text    data     bss     dec     hex filename
248     224       0     472     1d8 pg
$ size pg --format=SysV
pg  :
section              size    addr
.interp                28     792
.note.gnu.property     32     824
.note.gnu.build-id     36     856
.gnu.hash              28     896
.dynsym                24     928
.dynstr                 1     952
.text                  23    4096
.eh_frame_hdr          20    8192
.eh_frame              56    8216
.dynamic              224   16160
.comment               42       0
Total                 514

如果我们重建程序,添加:

-static
在支持动态链接的系统上,这将覆盖 -pie 并阻止与共享库链接。 在其他系统上,此选项不起作用。

文件大小减小(8816 字节而不是 13 KB):

$ gcc -s -static -nostdlib pg.c -o pg
$ ls -l ./pg
-rwxrwxr-x 1 xxxx xxxx 8816 dec.   9 13:03 ./pg

"-static"选项使几个动态链接相关部分消失:

$ size pg
text    data     bss     dec     hex filename
147       0       0     147      93 pg
$ size pg --format=SysV
pg  :
section              size      addr
.note.gnu.property     32   4194760
.note.gnu.build-id     36   4194792
.text                  23   4198400
.eh_frame              56   4202496
.comment               42         0
Total                 189

最新更新