除了我的二进制代码之外,我如何编译 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

很快:

  1. strip -s 不会删除这些部分,只会用 0 覆盖它们(因此文件大小保持不变。
  2. 在这种情况下我们不需要很多程序header(为了处理异常等)
  3. 二进制文件中有一个默认对齐方式,这使得它的开头至少为 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

这是一个巨大的改进(当然还有很多工作要做)。