调查一个极小的 C 程序的大小
Investigating the size of an extremely small C program
我正在 Linux (ubuntu 20.04) 上调查一个极小的 C 程序的大小。
我编译如下:
gcc -s -nostdlib test.c -o test
以下程序:
__attribute__((naked))
void _start() {
asm("movl ,%eax;"
"xorl %ebx,%ebx;"
"int [=11=]x80");
}
基本上,我们的想法是让 Linux 系统调用退出,而不是依赖 C 运行时为我们做这件事。 (void main() { }
就是这种情况)。程序将 1 移入寄存器 EAX,清除寄存器 EBX(否则它将包含 return 值),然后执行 linux 系统调用中断 0x80。这个中断触发内核处理我们的调用。
我希望这个程序非常小(小于 1K),但是..
du -h test
# >> 16K
ldd test
# >> statically linked
为什么这个程序还是16K?
du
,默认情况下,报告文件在磁盘上使用的 space——这意味着最小值将是一个磁盘块。如果您想知道文件的实际大小,请使用 ls -l
.
du
报告文件使用的磁盘 space 而 ls
报告文件的实际大小。通常,du
报告的大小对于小文件来说要大得多。
您可以通过更改编译和链接选项并删除不必要的部分来显着减小二进制文件的大小。
$ cat test.c
void _start() {
asm("movl ,%eax;"
"xorl %ebx,%ebx;"
"int [=10=]x80");
}
$ 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
是第 Header 字符串 table。每个 ElfHeader
结构包含一个 e_shstrndx
成员,它是 .shstrtab
table 的索引。如果你使用这个索引,你可以找到那个部分的名称。
修改程序以获得等于 3 的退出代码(为了好玩):
void _start() {
asm("movl ,%eax;"
"movl ,%ebx;"
"int [=10=]x80");
}
构建:
-s
Remove all symbol table and relocation information from the executable.
-nostdlib
Do not use the standard system startup files or libraries when linking.
$ 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
代码反汇编显示,text部分实际上是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 [=13=]x1,%eax
100d: bb 03 00 00 00 mov [=13=]x3,%ebx
1012: cd 80 int [=13=]x80
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
On systems that support dynamic linking, this overrides -pie and prevents linking with the shared libraries. On other systems, this option has no effect.
文件大小减小(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
我正在 Linux (ubuntu 20.04) 上调查一个极小的 C 程序的大小。
我编译如下:
gcc -s -nostdlib test.c -o test
以下程序:
__attribute__((naked))
void _start() {
asm("movl ,%eax;"
"xorl %ebx,%ebx;"
"int [=11=]x80");
}
基本上,我们的想法是让 Linux 系统调用退出,而不是依赖 C 运行时为我们做这件事。 (void main() { }
就是这种情况)。程序将 1 移入寄存器 EAX,清除寄存器 EBX(否则它将包含 return 值),然后执行 linux 系统调用中断 0x80。这个中断触发内核处理我们的调用。
我希望这个程序非常小(小于 1K),但是..
du -h test
# >> 16K
ldd test
# >> statically linked
为什么这个程序还是16K?
du
,默认情况下,报告文件在磁盘上使用的 space——这意味着最小值将是一个磁盘块。如果您想知道文件的实际大小,请使用 ls -l
.
du
报告文件使用的磁盘 space 而 ls
报告文件的实际大小。通常,du
报告的大小对于小文件来说要大得多。
您可以通过更改编译和链接选项并删除不必要的部分来显着减小二进制文件的大小。
$ cat test.c
void _start() {
asm("movl ,%eax;"
"xorl %ebx,%ebx;"
"int [=10=]x80");
}
$ 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
.
$ 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
是第 Header 字符串 table。每个 ElfHeader
结构包含一个 e_shstrndx
成员,它是 .shstrtab
table 的索引。如果你使用这个索引,你可以找到那个部分的名称。
修改程序以获得等于 3 的退出代码(为了好玩):
void _start() {
asm("movl ,%eax;"
"movl ,%ebx;"
"int [=10=]x80");
}
构建:
-s
Remove all symbol table and relocation information from the executable.
-nostdlib
Do not use the standard system startup files or libraries when linking.
$ 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
代码反汇编显示,text部分实际上是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 [=13=]x1,%eax
100d: bb 03 00 00 00 mov [=13=]x3,%ebx
1012: cd 80 int [=13=]x80
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
On systems that support dynamic linking, this overrides -pie and prevents linking with the shared libraries. On other systems, this option has no effect.
文件大小减小(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