Eri*_*tch 5 c assembly gcc x86-64
我正在查看有关 hello world 二进制大小的页面,我想知道如果我没有 lib c 我可以获得多小的二进制文件。我从一些非常简单的事情开始(代码如下)。正如你所看到的,我运气不佳,5 条指令仍然是 13K 的二进制文件。我究竟做错了什么?
$ cat nolib.c
void _start() {
asm("mov $60,%rax; mov $1,%rdi; syscall");
}
$ gcc -nostdlib nolib.c
$ strip a.out
$ ls -lh
-rwxr-xr-x 1 eric eric 13K Nov 21 18:03 a.out
Run Code Online (Sandbox Code Playgroud)
小智 5
首先,如果我们静态编译二进制文件,我们可以稍微改进一下:
$ gcc -nostdlib -static nolib.c -o static_output
$ strip -s static_output # strip -s in order to strip all (not helping here)
$ ls -lh static_output
-rwxrwxrwx 1 graul graul 8.7K Jan 17 22:59 static_output
Run Code Online (Sandbox Code Playgroud)
现在让我们看看我们的 elf: $ readelf -h static_output ELF 标头: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类: ELF64 数据: 2 的补码,小端版本: 1(当前)操作系统/ABI: UNIX - System V ABI 版本:0 类型:EXEC(可执行文件) 机器:Advanced Micro Devices X86-64 版本:0x1 入口点地址:0x401000 程序头开始:64(文件中的字节) 节头开始: 8368(文件字节数) 标志:0x0 该标头的大小:64(字节) 程序标头的大小:56(字节) 程序标头的数量:7 节标头的大小:64(字节) 节标头的数量:7 节标头字符串表索引:6
看起来距离节头开始还有 8kn 多!让我们看看它是由什么制成的:
$ readelf -e static_output
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x401000
Start of program headers: 64 (bytes into file)
Start of section headers: 8368 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 7
Size of section headers: 64 (bytes)
Number of section headers: 7
Section header string table index: 6
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.propert NOTE 00000000004001c8 000001c8
0000000000000020 0000000000000000 A 0 0 8
[ 2] .note.gnu.build-i NOTE 00000000004001e8 000001e8
0000000000000024 0000000000000000 A 0 0 4
[ 3] .text PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 1
[ 4] .eh_frame PROGBITS 0000000000402000 00002000
0000000000000038 0000000000000000 A 0 0 8
[ 5] .comment PROGBITS 0000000000000000 00002038
000000000000002a 0000000000000001 MS 0 0 1
[ 6] .shstrtab STRTAB 0000000000000000 00002062
000000000000004a 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),
l (large), p (processor specific)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000020c 0x000000000000020c R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x000000000000001b 0x000000000000001b R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x0000000000000038 0x0000000000000038 R 0x1000
NOTE 0x00000000000001c8 0x00000000004001c8 0x00000000004001c8
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x00000000000001e8 0x00000000004001e8 0x00000000004001e8
0x0000000000000024 0x0000000000000024 R 0x4
GNU_PROPERTY 0x00000000000001c8 0x00000000004001c8 0x00000000004001c8
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
Section to Segment mapping:
Segment Sections...
00 .note.gnu.property .note.gnu.build-id
01 .text
02 .eh_frame
03 .note.gnu.property
04 .note.gnu.build-id
05 .note.gnu.property
06
Run Code Online (Sandbox Code Playgroud)
这很奇怪,因为我们调用了 strip 函数,该函数应该从我们的 elf 中删除此部分。如果我们查看https://unix.stackexchange.com/questions/267070/why-doesnt-strip-remove-section-headers-from-elf-executables中的响应 ,我们可以看到,虽然没有具体提及,但 strip 并没有从我们的二进制文件中删除这些部分,但仅删除它们的内容(这对我们的情况没有帮助)。
我们可以使用 strip -R 来完全删除这些部分,这里最大的一个是“.eh_frame”段(我们的例子不需要它,请查看为什么GCC编译的C程序需要.eh_frame部分?来查看它)。
$ strip -R .eh_frame static_output
$ ls -lh static_output
-rwxrwxrwx 1 graul graul 4.6K Jan 17 23:22 static_output*
Run Code Online (Sandbox Code Playgroud)
需要明确的是,没有理由不删除其余不需要的部分:
$ strip -R .eh_frame -R .note.gnu.property -R .note.gnu.build-id -R .note.gnu.property static_output
-rwxrwxrwx 1 graul graul 4.4K Jan 17 23:31 static_output
Run Code Online (Sandbox Code Playgroud)
尺寸减半!但仍然不够好。看起来我们需要删除一个大的程序头。
看起来 gcc 在我们不希望的情况下插入了这些部分:
$ gcc -c -nostdlib -static nolib.c -o nolib.o
$ ls -l nolib.o
-rwxrwxrwx 1 graul graul 1376 Jan 17 23:40 nolib.o
$ strip -R .data -R .bss -R .comment -R .note.GNU-stack -R .note.GNU-stack -R .note.gnu.propery -R .eh_frame -R .real.eh_frame -R .symtab -R.strtab -R.shstrtab nolib.o
$ ls -l nolib.o
-rwxrwxrwx 1 graul graul 424 Jan 17 23:41 nolib.o
Run Code Online (Sandbox Code Playgroud)
但这不是精灵,如果我们现在跑
$ld nolib.o -o ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 4760 Jan 17 23:55 ld_output
Run Code Online (Sandbox Code Playgroud)
在程序中,ld有一个标志可以删除我们的部分之间的对齐方式(这几乎是我们所有的大小)。
$ ld -n -static nolib.o -o ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 928 Jan 17 23:57 ld_output
$strip -R .note.gnu.property ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 472 Jan 17 23:58 ld_output
Run Code Online (Sandbox Code Playgroud)
这是一个巨大的改进(当然还有很多工作可以做)。