Slim Summer Elf

Making of minimal x86 (Linux) ELF executable.

– Created: May 21, 2023 UTC

– Edited: July 28, 2024 UTC

– Tags: Programming, Linux, C, Bash, Linker, Low-level


Code below was composed for 4mb-jam which I didn’t finish.

It was tested on Zorin OS 16.2 x86_64, but should be compatible with any x86/i386 operating system using ELF executable format (besides the system call part)

What’s going on:

Source

//bin/sh
#if 0
set -e
cat << ELF_HEADER_SOURCE > ./elf-header.as
elfoff = 0x08048000
vaddr = 0x08048000
.text
ehdrstart:
  .byte      0x7F                       # e_ident
  .ascii     "ELF"
  .skip      3, 1
  .skip      9, 0
  .word      2                          # e_type
  .word      3                          # e_machine
  .long      1                          # e_version
  .long      entry                      # e_entry
  .long      phdrstart - elfoff         # e_phoff
  .long      0                          # e_shoff
  .long      0                          # e_flags
  .word      ehdrsize                   # e_ehsize
  .word      32                         # e_phentsize
  .word      1                          # e_phnum
  .skip      6, 0
ehdrsize = . - ehdrstart

phdrstart:
#PT_LOAD
  .long      1                          # p_type
  .long      0                          # p_offset
  .long      vaddr                      # p_vaddr
  .long      0                          # p_paddr
  .long      filesz                     # p_filesz
  .long      memsz                      # p_memsz
  .long      7                          # p_flags
  .long      0x0000                     # p_align
ELF_HEADER_SOURCE

cat << LINKER_SCRIPT_SOURCE > ./ld.scr
SECTIONS {
  . = 0x08048000;
  filestart = .;

  .elf : { ./elf-header.o (.text) }
  .text ALIGN(0x1) : SUBALIGN(0x1) { *(.text*) *(.rodata*) }
  .data ALIGN(0x1) : SUBALIGN(0x1) { *(.data*) }

  filesz = . - filestart;
  .bss : { *(.bss*) }
  memsz = . - filestart;

  /DISCARD/ : {
    *(.note.*)
    *(.gnu*)
    *(.gcc*)
    *(.comment)
    *(.eh_frame*)
  }
}
OUTPUT_FORMAT(binary)
LINKER_SCRIPT_SOURCE

CFLAGS="-x c -std=gnu99 $0 -m32 -nostdlib -fno-pie -DELF -Wall -Wextra -Wpedantic -Werror"
LDFLAGS="-m elf_i386"

if [ ! -z "$1" ] && [ $1 = DEBUG ];
then
  CFLAGS="$CFLAGS -O0 -g3 -ggdb -DDEBUG"
  LDFLAGS="$LDFLAGS -eentry"
else
  CFLAGS="$CFLAGS -Os"
  LDFLAGS="$LDFLAGS -T ld.scr -s"
fi

as --32 -o elf-header.o elf-header.as
cc -c -o ./elf.o $CFLAGS
ld -o elf ./elf-header.o ./elf.o $LDFLAGS
./elf
exit;
#endif

#ifdef ELF

/* https://man7.org/linux/man-pages/man2/exit.2.html */
#define SYS_EXIT(p_return_code)                                                \
  {                                                                            \
    asm volatile("int $0x80" : : "a"(1), "b"(p_return_code));                  \
    __builtin_unreachable();                                                   \
  }

/* https://man7.org/linux/man-pages/man2/write.2.html */
#define SYS_WRITE(p_fd, p_msg, p_msg_len)                                      \
  asm volatile("int $0x80"                                                     \
               : "=a"(sys_result)                                              \
               : "a"(4), "b"(p_fd), "c"(p_msg), "d"(p_msg_len))

__attribute((naked)) void entry(void) {
    static int sys_result;
    SYS_WRITE(1, "hello world!\n", 13);
    SYS_EXIT(0);
}

#endif /* #ifdef ELF */