I’ve been trying to figure out how to use the excellent musl C standard library in a bare metal environment for quite a while. I didn’t want to damage the musl source code too much but couldn’t figure out an easy way to get at least a subset of musl’s functionality available in a bare metal environment.
Over the weekend I can up with a cute little solution that may be viable for future development. It’s based on a simple concept: Don’t change musl at all, but implement system call handling at the machine level to handle the system calls that musl needs.
I added a baremetal directory to the top-level ELLCC directory as a proof of concept. The arm sub-directory is where the initial work was done. There are just a few files in the directory:
- Makefile – To build the example
- init.S – To handle system initialization and exception handling
- main.c – A simple C source file showing the handling of two system calls
- kernel.ld – A simple linker command file to put it in the right place in the ARM’s memory.
It turns out that musl’s printf does two system calls to print “hello world\n”. The first is an ioctl() to determine how to buffer. I ignored that call since all the unimplemented system calls return error.
The interesting call that musl did make was to writev(), for which I made a simple replacement in startup.c.
After typing “make”, here is the result of running it:
[~/ellcc/baremetal/arm] dev% qemu-system-arm -cpu any -M versatilepb -m 128M -nographic -kernel kernel.bin pulseaudio: set_sink_input_volume() failed pulseaudio: Reason: Invalid argument pulseaudio: set_sink_input_mute() failed pulseaudio: Reason: Invalid argument unhandled system call 54 hello world QEMU: Terminated [~/ellcc/baremetal/arm] dev%
The 54 is the unhandled ioctl(). I’m sure that output will be very handy as I implement more of musl’s functionality for my bare metal environment.