简体   繁体   中英

How do I compile C without anything but my code in the binary?

I was looking at this page about hello world binary sizes and I was wondering if I had no lib c how small can I get my binary. I started off with something very simple (code below). As you can see I had no luck and 5ish instructions are still 13K of binary. What am I doing wrong?

$ 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

Shortly:

  1. strip -s does not remove the sections but only overrides them with 0 (and thus the file size remains the same.
  2. There are a lot of program headers that we do not need in this case (in order to handle exceptions etc.)
  3. There is a default alignment in the binary, which makes the start of it be at least 4000 (and we do not need it).

Detailed

First, we can improve it slightly if we compile the binary statically:

$ 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

Lets look over our elf now: $ readelf -h 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

Looks like there is more than 8kn before the start of sections header! Let's look at what this is made of:

$ 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

This is weird, as we called the strip function which was supposed to remove this section from our elf. If we look over the response in https://unix.stackexchange.com/questions/267070/why-doesnt-strip-remove-section-headers-from-elf-executables We can see that though not mentioned specifically, strip does not remove these parts from our binary, but only removes their content (which does not help for our case).

We can use strip -R in order to remove these sections completely, the biggest one here is the ".eh_frame" segment (which is not needed for our case, look over Why GCC compiled C program needs .eh_frame section? to look over it).

$ strip -R .eh_frame static_output
$ ls -lh static_output
-rwxrwxrwx 1 graul graul 4.6K Jan 17 23:22 static_output*

Just to be clear, there is no reason to not strip the rest of the unwanted sections as well:

$ 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

Half the size! But still not good enough. looks like there is a big program header we need to remove.

looks like gcc inserts these sections without our desire:

$ 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

But this is not an elf, if we run now

$ld nolib.o -o ld_output
$ls -l ld_output
-rwxrwxrwx 1 graul graul 4760 Jan 17 23:55 ld_output

In the program ld there is a flag to remove the alignment between our sections (which is almost all of our size).

$ 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

Which is a drastic improvement (though of course a lot of more work could be done).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM