Without syscalls the program cannot exit. The way it works is typically something like this:
// Not how it's actually implemented... just a sketch.
void _start() {
char **argv = ...;
int argc = ...;
// ... other initialization code ...
int retcode = main(argc, argv);
exit(retcode);
}
The exact details depend on the operating system, but exit()
, which terminates the process, typically has to be a system call or is implemented with system calls.
Note that this is true for "hosted" C implementations, not for "freestanding" C implementations, and is highly operating-system specific. There are freestanding C implementations can run on bare metal, but hosted C implementations usually need an operating system.
You can compile without standard libraries and without the runtime but your entry point cannot return... there is nothing to return to, without a runtime.
Creating a baremetal program
It is generally possible to compile programs capable of running baremetal.
Use -ffreestanding
. This makes GCC generate code that does not assume that the standard library is available (and has other effects).
Use -nostdlib
. This will prevent GCC from linking with the standard library. Note that memcmp
, memset
, memcpy
, and memmove
calls may be generated anyway, so you may have to provide these yourself.
At this point you can write your program, but you typically have to use _start
instead of main
:
void _start(void) {
while (1) { }
}
Note that you can't return from _start
! Think about it... there is nowhere to return to. When you compile a program like this you can see that it doesn't use any system calls and doesn't have a loader.
$ gcc -ffreestanding -nostdlib test.c
We can verify that it loads no libraries:
$ ldd a.out
statically linked
$ readelf -d a.out
Dynamic section at offset 0xf30 contains 8 entries:
Tag Type Name/Value
0x000000006ffffef5 (GNU_HASH) 0x278
0x0000000000000005 (STRTAB) 0x2b0
0x0000000000000006 (SYMTAB) 0x298
0x000000000000000a (STRSZ) 1 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x000000006ffffffb (FLAGS_1) Flags: PIE
0x0000000000000000 (NULL) 0x0
We can also see that it doesn't contain any code that makes system calls:
$ objdump -d a.out
a.out: file format elf64-x86-64
Disassembly of section .text:
00000000000002c0 <_start>:
2c0: eb fe jmp 2c0 <_start>