peachykeen32: Bare-Metal ARM Userspace Programming
peachykeen32 is a bare-metal ARM32 userspace runtime — no libc, no crt, no linker scripts. Everything runs directly on top of the Linux kernel through syscalls. What started as a curiosity about what the minimum viable userspace looks like turned into a toolkit with a handful of genuinely useful commands.
The Runtime
The entry point is not _start but a hand-written assembly trampoline
that zeroes .bss, sets up the stack pointer from AT_RANDOM in the
auxiliary vector, and calls main. The trampoline is small enough to
inline in the binary — 16 bytes of ARM32 instructions.
.globl _start
_start:
ldr sp, =stack_top
bl main
mov r7, #1
svc #0
All I/O goes through svc #0 with the syscall number in r7. The runtime
provides thin wrappers for read, write, exit, mmap, open, and
close. No buffering, no errno — just the raw kernel ABI.
Commands
cat
Reads a file via open/read/write and dumps it to stdout. The buffer
is 4kB (one page), allocated with mmap at startup and reused across
calls. Error handling checks the return value of open and prints a
syscall-based error message.
int cmd_cat(const char *path) {
long fd = sys_open(path, 0, 0);
if (fd < 0) return 1;
long n;
while ((n = sys_read(fd, buf, 4096)) > 0)
sys_write(1, buf, n);
sys_close(fd);
return 0;
}
hexdump
Like cat but prints a hex+ASCII side-by-side view. Each line shows the
offset, sixteen hex bytes, and the printable characters. The implementation
reuses cat’s read loop and formats directly into the write buffer to
avoid a second copy.
sysinfo
Calls sys_sysinfo() and prints the result: uptime, total RAM, free RAM,
process count, and load averages. The sysinfo struct is defined locally
since there’s no <sys/sysinfo.h>.
ls
Reads a directory via open (O_RDONLY|O_DIRECTORY) and getdents64.
The struct linux_dirent64 is defined manually; the command iterates
entries, skips . and .., and prints each name. This one touches more
of the kernel ABI than the others — getdents64 is a less common syscall
that most libc-free experiments overlook.
Why This Works
Each command is a self-contained demonstration of a specific kernel interface exercised from a minimal runtime. Together they show that a useful userspace — file I/O, directory traversal, system introspection — needs nothing more than the syscall interface and a willingness to define your own struct layouts. The entire runtime is ~300 lines of C and asm, and each command is under 50 lines. There is no startup overhead, no dynamic linker, no PT_INTERP segment.
Have a comment on this article? Send me an email.