最小化 Hello World 代码 这是一个最简单的 HelloWorld 程序
1 2 3 4 5 #include "stdio.h" int main () { printf ("Hello World!\n" ); return 0 ; }
编译运行
运行就会输出 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 的大小