最小化 Hello World 代码

最小化 Hello World 代码

这是一个最简单的 HelloWorld 程序

1
2
3
4
5
#include "stdio.h"
int main(){
printf("Hello World!\n");
return 0;
}

编译运行

1
gcc ver1.c -o ver1

运行就会输出 Hello World!

看看编译后的输出大小

1
2
3
4
5
ls -l ver1
-rwxr-xr-x 1 neolux neolux 15440 6月11日 22:38 ver1

ls -lh ver1
-rwxr-xr-x 1 neolux neolux 16K 6月11日 22:38 ver1

当我们把静态库一起编译到文件里后再来看看

1
2
3
4
5
6
7
8
9
10
gcc -static ver1.c -o ver1_static

./ver1_static
Hello World!

ls -l ver1_static
-rwxr-xr-x 1 neolux neolux 762680 6月11日 22:38 ver1_static

ls -lh ver1_static
-rwxr-xr-x 1 neolux neolux 745K 6月11日 22:38 ver1_static

程序一下子变得很大了
下面开始精简程序

使用编译参数优化空间

C语言代码不变,更改编译参数

1
2
3
4
5
6
7
gcc -static -Os -s ver1.c -o ver1_Os

ls -l ver1_Os
-rwxr-xr-x 1 neolux neolux 682456 6月11日 22:38 ver1_Os

ls -lh ver1_Os
-rwxr-xr-x 1 neolux neolux 667K 6月11日 22:38 ver1_Os
  • 使用 -Os 优化代码的体积
  • -s 移除调试信息,能够降低 elf 文件的体积

使用汇编

C 语言优化空间有限,通过使用汇编语言来尝试优化可执行文件的体积

1
2
3
4
5
6
7
8
9
10
11
12
.text

.global main

main:
mov $str_helloworld, %rdi
call puts
mov $0, %rax
ret

str_helloworld:
.string "Hello World!\n"
1
2
3
4
5
6
7
gcc -static -Os -s ver2.s -o ver2

ls -l ver2
-rwxr-xr-x 1 neolux neolux 682456 6月11日 22:41 ver2

ls -lh ver2
-rwxr-xr-x 1 neolux neolux 667K 6月11日 22:41 ver2

和刚才相比并没有减少字节,因为代码中仍然使用了 libc 的代码,下面尝试抛弃 libc

不使用 libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text
.global _start
_start:
mov $1, %rax # syscall: 1(write)
mov $1, %rdi # fd: 1(stdout)
mov $str_helloworld, %rsi # buffer: str_helloworld
mov $13, %rdx #count: 13(strlen)
syscall

# exit(0)
mov $60, %rax
mov $0, %rdi
syscall

str_helloworld:
.string "Hello World!\n"
  • 不用 main() 做程序入口,而是使用 _start 符号作为程序入口
  • 不使用 puts()printf() 作为输出函数,而是直接调用 write 方法向 stdout 输出数据
  • 手动调用 exit 推出程序

编译

1
2
3
4
5
6
7
gcc -static -s -nostdlib ver3.s -o ver3

ls -l ver3
-rwxr-xr-x 1 neolux neolux 4536 6月11日 22:45 ver3

ls -lh ver3
-rwxr-xr-x 1 neolux neolux 4.5K 6月11日 22:45 ver3

程序只剩下 4.5K 的大小

N Magic

来查看可执行文件的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
readelf -S ver3
There are 5 section headers, starting at offset 0x1078:

节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.pr[...] NOTE 0000000000400158 00000158
0000000000000030 0000000000000000 A 0 0 8
[ 2] .note.gnu.bu[...] NOTE 0000000000400188 00000188
0000000000000024 0000000000000000 A 0 0 4
[ 3] .text PROGBITS 0000000000401000 00001000
000000000000003c 0000000000000000 AX 0 0 1
[ 4] .shstrtab STRTAB 0000000000000000 0000103c
0000000000000037 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)

.note.gnu.bu 从 00000188 开始,大小仅有 0x24, 而 .text 在 00001000, 这中间整整有 0x0E78 的空间都被浪费了,
因为默认情况下,.text 按照页对齐,有利于提升加载速度,我们可以关闭这个特性,使用 --nmagic

1
2
3
4
5
6
7
gcc -static -s nostdlib ver3.s -Wl,--nmagic -o ver3_nmagic

ls -l ver3_nmagic
-rwxr-xr-x 1 neolux neolux 808 6月11日 22:47 ver3_nmagic

ls -lh ver3_nmagic
-rwxr-xr-x 1 neolux neolux 808 6月11日 22:47 ver3_nmagic

文件只有 808 了,相比于 C语言链接静态库的体积已经减少了 99.89%

禁用 build-id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
readelf -a ver3_nmagic
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400174
程序头起点: 64 (bytes into file)
Start of section headers: 488 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 4
Size of section headers: 64 (bytes)
Number of section headers: 5
Section header string table index: 4

节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.pr[...] NOTE 0000000000400120 00000120
0000000000000030 0000000000000000 A 0 0 8
[ 2] .note.gnu.bu[...] NOTE 0000000000400150 00000150
0000000000000024 0000000000000000 A 0 0 4
[ 3] .text PROGBITS 0000000000400174 00000174
000000000000003c 0000000000000000 AX 0 0 1
[ 4] .shstrtab STRTAB 0000000000000000 000001b0
0000000000000037 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)

There are no section groups in this file.

程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000120 0x0000000000400120 0x0000000000400120
0x0000000000000090 0x0000000000000090 R E 0x8
NOTE 0x0000000000000120 0x0000000000400120 0x0000000000400120
0x0000000000000030 0x0000000000000030 R 0x8
NOTE 0x0000000000000150 0x0000000000400150 0x0000000000400150
0x0000000000000024 0x0000000000000024 R 0x4
GNU_PROPERTY 0x0000000000000120 0x0000000000400120 0x0000000000400120
0x0000000000000030 0x0000000000000030 R 0x8

Section to Segment mapping:
段节...
00 .note.gnu.property .note.gnu.build-id .text
01 .note.gnu.property
02 .note.gnu.build-id
03 .note.gnu.property

There is no dynamic section in this file.

该文件中没有重定位信息。
No processor specific unwind information to decode

No version information found in this file.

Displaying notes found in: .note.gnu.property
所有者 Data size Description
GNU 0x00000020 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature used: x86
x86 ISA used: x86-64-baseline

Displaying notes found in: .note.gnu.build-id
所有者 Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: c420275597ab00b8ff7692020a0f3955353c7188

一个 Section Header 的大小是 64B, 而且 .note.gnu.build-id 占了 36B。可以禁用 build-id 来节省空间

1
2
3
4
gcc -static -s -nostdlib ver3.s -Wl,--nmagic -Wl,--build-id=none -o ver3_nmagic_nobuildid

ls -l ver3_nmagic_nobuildid
-rwxr-xr-x 1 neolux neolux 632 6月11日 22:49 ver3_nmagic_nobuildid

又减少了 176B

优化指令

  • 在原本的汇编指令中,mov $0, %R 来给寄存器置 0,占用了7个字节,而使用 xor %R, %R 一样可以置0,只占 3 字节
  • xor %R, %R; inc %R 占用 6 字节,而 mov $1, %R 占用 7 字节

因此可以优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text

.global _start

_start:
xor %rax, %rax
inc %rax
mov %rax, %rdi
mov $str_helloworld, %rsi
mov $13, %rdx
syscall

mov $60, %rax
xor %rdi, %rdi
syscall

str_helloworld:
.string "Hello World!\n"

编译输出

1
2
3
4
gcc -static -s -nostdlib ver3.s -Wl,--nmagic -Wl,--build-id=none -o ver3_xor

ls -l ver3_xor
-rwxr-xr-x 1 neolux neolux 624 6月11日 22:59 ver3_xor

至此 HelloWorld 程序只有 624B 的大小