了解 nostdlib C 程序的 ELF 二进制大小

Understanding ELF Binary Size for nostdlib C Program

我在 Ubuntu 20.04,gcc 9.3.0,ld 2.34。 我有一个简单的 hello world 程序,它不使用 glibc 或任何其他库,只使用 write 系统调用。尽管如此,我的二进制大小大约为 8Kb。我不确定为什么它那么大而不是说 1Kb。

C 程序:

int
x64_syscall_write(int fd, char const *data, unsigned long int data_size)
{
  int result = 0;
  __asm__ __volatile__("syscall"
              : "=a" (result)
              : "a" (1), "D" (fd),
                "S" (data), "d" (data_size)
              : "r11", "rcx", "memory");
  return result;
}

__asm__(".global entry_point\n"
  "entry_point:\n"
  "xor rbp, rbp\n"
  "pop rdi\n"
  "mov rsi, rsp\n"
  "and rsp, 0xfffffffffffffff0\n"
  "call main\n"
  "mov rdi, rax\n"
  "mov rax, 60\n"
  "syscall\n"
  "ret");

int
main(int argc, char *argv[])
{
  x64_syscall_write(1, "hello\n", 6); 
  return 0;
}

构建于:

gcc -ffreestanding -static -nostdlib -no-pie -masm=intel \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
-Wl,--gc-sections -fdata-sections -Os \
hello.c -c -o hello.o

# NOTE: I know more could be done here to shave 
# off a few more bytes, but I feel this is the bulk of it.

ld -e entry_point hello.o -o hello

hello.o 是 1.7Kb。 hello 是 8.4Kb。

readelf -Wl hello

Elf file type is EXEC (Executable file)
Entry point 0x40101c
There are 6 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0001b0 0x0001b0 R   0x1000
  LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x000045 0x000045 R E 0x1000
  LOAD           0x002000 0x0000000000402000 0x0000000000402000 0x000007 0x000007 R   0x1000
  NOTE           0x000190 0x0000000000400190 0x0000000000400190 0x000020 0x000020 R   0x8
  GNU_PROPERTY   0x000190 0x0000000000400190 0x0000000000400190 0x000020 0x000020 R   0x8
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.property
   01     .text
   02     .rodata
   03     .note.gnu.property
   04     .note.gnu.property
   05

在这里您可以看到链接器创建了 3 个 LOAD 段:一个用于 ELF header 和其他元数据,一个用于 .text,一个用于 .rodata.

-z noseparate-code 链接导致更小的二进制文件(小于 hello.o):

 ls -l hello*
-rwxr-xr-x 1 user user 1384 Apr 26 22:24 hello
-rw-r--r-- 1 user user  603 Apr 26 22:22 hello.c
-rw-r--r-- 1 user user 1680 Apr 26 22:22 hello.o

readelf -Wl hello

Elf file type is EXEC (Executable file)
Entry point 0x40015c
There are 4 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x00018c 0x00018c R E 0x1000
  NOTE           0x000120 0x0000000000400120 0x0000000000400120 0x000020 0x000020 R   0x8
  GNU_PROPERTY   0x000120 0x0000000000400120 0x0000000000400120 0x000020 0x000020 R   0x8
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.property .text .rodata
   01     .note.gnu.property
   02     .note.gnu.property
   03

您可以通过删除 .note.GNU-stack.note.gnu.property 部分进一步缩小它:

objcopy -R .note.* hello.o hello1.o
ld -e entry_point hello1.o -o hello1 -z noseparate-code

ls -l hello1*
-rwxr-xr-x 1 user user 1072 Apr 26 22:38 hello1
-rw-r--r-- 1 user user 1440 Apr 26 22:37 hello1.o

readelf -Wl hello1

Elf file type is EXEC (Executable file)
Entry point 0x400094
There is 1 program header, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x0000c4 0x0000c4 R E 0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata