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.

The Segfault Garden

Lu

frgmntedflower@linux.com