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:
- Amalgamation of shell script and C source, it’s possible because of them both having
#if
style directives and similar comment syntax. Also note the usage of shebang starting with/
. - ELF header is created via
GNU Assembler
. - Custom link script allows for drop of unnecessary data produced by
GNU Linker
by default. There’s a lot of it it turns out. - One can pass a
DEBUG
argument when compiling so that debug symbols would be generated.
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 */