除了我的二进制代码之外,我如何编译 C?
How do I compile C without anything but my code in the binary?
我正在查看 this page about hello world binary sizes,我想知道如果我没有 lib c,我的二进制文件能有多小。我从非常简单的事情开始(下面的代码)。如您所见,我运气不好,5 条指令仍然是 13K 的二进制文件。我做错了什么?
$ cat nolib.c
void _start() {
asm("mov ,%rax; mov ,%rdi; syscall");
}
$ gcc -nostdlib nolib.c
$ strip a.out
$ ls -lh
-rwxr-xr-x 1 eric eric 13K Nov 21 18:03 a.out
很快:
- strip -s 不会删除这些部分,只会用 0 覆盖它们(因此文件大小保持不变。
- 在这种情况下我们不需要很多程序header(为了处理异常等)
- 二进制文件中有一个默认对齐方式,这使得它的开头至少为 4000(我们不需要它)。
详细
首先,如果我们静态编译二进制文件,我们可以稍微改进一下:
$ 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
现在让我们来看看我们的小精灵:
$ readelf -h static_output
小精灵 Header:
魔法:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:ELF64
数据:2的补码,小端
版本:1(当前)
OS/ABI: UNIX - 系统 V
ABI 版本:0
类型:EXEC(执行table文件)
机器:Advanced Micro Devices X86-64
版本:0x1
入口点地址:0x401000
程序开始 headers:64(字节到文件)
headers 节开始:8368(字节到文件)
标志:0x0
header 的大小:64(字节)
程序大小headers:56(字节)
程序数 headers: 7
headers 部分的大小:64(字节)
节数 headers: 7
节 header 字符串 table 索引:6
距离路段开始还有8公里多header!
让我们看看它是由什么组成的:
$ 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
这很奇怪,因为我们调用了 strip 函数,它应该从我们的精灵中删除这个部分。如果我们查看 https://unix.stackexchange.com/questions/267070/why-doesnt-strip-remove-section-headers-from-elf-executables 中的响应
我们可以看到,虽然没有特别提到,但 strip 并没有从我们的二进制文件中删除这些部分,而只是删除了它们的内容(这对我们的情况没有帮助)。
我们可以使用 strip -R 来完全删除这些部分,这里最大的部分是“.eh_frame”部分(我们的案例不需要它,请查看 Why GCC compiled C program needs .eh_frame section?查看它)。
$ strip -R .eh_frame static_output
$ ls -lh static_output
-rwxrwxrwx 1 graul graul 4.6K Jan 17 23:22 static_output*
明确一点,没有理由不删除其余不需要的部分:
$ 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
一半的尺寸!但仍然不够好。看起来有一个大程序 header 我们需要删除。
看起来 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
但这不是精灵,如果我们运行现在
$ld nolib.o -o ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 4760 Jan 17 23:55 ld_output
在程序 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
这是一个巨大的改进(当然还有很多工作要做)。
我正在查看 this page about hello world binary sizes,我想知道如果我没有 lib c,我的二进制文件能有多小。我从非常简单的事情开始(下面的代码)。如您所见,我运气不好,5 条指令仍然是 13K 的二进制文件。我做错了什么?
$ cat nolib.c
void _start() {
asm("mov ,%rax; mov ,%rdi; syscall");
}
$ gcc -nostdlib nolib.c
$ strip a.out
$ ls -lh
-rwxr-xr-x 1 eric eric 13K Nov 21 18:03 a.out
很快:
- strip -s 不会删除这些部分,只会用 0 覆盖它们(因此文件大小保持不变。
- 在这种情况下我们不需要很多程序header(为了处理异常等)
- 二进制文件中有一个默认对齐方式,这使得它的开头至少为 4000(我们不需要它)。
详细
首先,如果我们静态编译二进制文件,我们可以稍微改进一下:
$ 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
现在让我们来看看我们的小精灵: $ readelf -h static_output 小精灵 Header: 魔法:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class:ELF64 数据:2的补码,小端 版本:1(当前) OS/ABI: UNIX - 系统 V ABI 版本:0 类型:EXEC(执行table文件) 机器:Advanced Micro Devices X86-64 版本:0x1 入口点地址:0x401000 程序开始 headers:64(字节到文件) headers 节开始:8368(字节到文件) 标志:0x0 header 的大小:64(字节) 程序大小headers:56(字节) 程序数 headers: 7 headers 部分的大小:64(字节) 节数 headers: 7 节 header 字符串 table 索引:6
距离路段开始还有8公里多header! 让我们看看它是由什么组成的:
$ 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
这很奇怪,因为我们调用了 strip 函数,它应该从我们的精灵中删除这个部分。如果我们查看 https://unix.stackexchange.com/questions/267070/why-doesnt-strip-remove-section-headers-from-elf-executables 中的响应 我们可以看到,虽然没有特别提到,但 strip 并没有从我们的二进制文件中删除这些部分,而只是删除了它们的内容(这对我们的情况没有帮助)。
我们可以使用 strip -R 来完全删除这些部分,这里最大的部分是“.eh_frame”部分(我们的案例不需要它,请查看 Why GCC compiled C program needs .eh_frame section?查看它)。
$ strip -R .eh_frame static_output
$ ls -lh static_output
-rwxrwxrwx 1 graul graul 4.6K Jan 17 23:22 static_output*
明确一点,没有理由不删除其余不需要的部分:
$ 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
一半的尺寸!但仍然不够好。看起来有一个大程序 header 我们需要删除。
看起来 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
但这不是精灵,如果我们运行现在
$ld nolib.o -o ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 4760 Jan 17 23:55 ld_output
在程序 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
这是一个巨大的改进(当然还有很多工作要做)。