diff --git a/documentation/4 - musl-libc.md b/documentation/4 - musl-libc.md new file mode 100644 index 0000000..97b4cde --- /dev/null +++ b/documentation/4 - musl-libc.md @@ -0,0 +1,132 @@ +# Musl libc in the Mittos kernel + +The Mittos kernel is linked with Musl libc which provides functionality such as fine-grained memory management and string formating. + +The [musl library](https://musl.libc.org/) is a tiny c standard library impelentation which is easy to use. It is however highly linux-specific and therefore relies of the availability of a large number of linux syscalls. + +In building musl for the kernel, I patch away this reliance entirely and replace syscalls with calls to internal functions that are prototyped and can then be provided by the kernel or a separate library when needed. + +By syncing a patch directory into the musl source with rsync before compilation: +``` +> rsync -a src/patch-musl/ external/musl/ +``` +I can provide ovrrides for specific parts of the musl code such as the following which replaces all `syscall` instructions with calls to external functions: + +`src/patch-musl/arch/x86_64/syscall_arch.h` +```c +#define __SYSCALL_LL_E(x) (x) +#define __SYSCALL_LL_O(x) (x) + +extern long syscall0(long); +extern long syscall1(long, long); +... +extern long syscall6(long, long, long, long, long, long, long); + +static __inline long __syscall0(long n) { + return syscall0(n); +} +... +static __inline long __syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) { + return syscall6(n, a1, a2, a3, a4, a5, a6); +} + +#define VDSO_USEFUL +#define VDSO_CGT_SYM "__vdso_clock_gettime" +#define VDSO_CGT_VER "LINUX_2.6" +#define VDSO_GETCPU_SYM "__vdso_getcpu" +#define VDSO_GETCPU_VER "LINUX_2.6" + +#define IPC_64 0 +``` + +The kernel is then linked with `-L/opt/sysroot/usr/lib -lc`, but must provide the functions `syscall0(long)` through `syscall6(long, long, long, long, long, long, long)` itself. + +At first, those can be very simple: +```c +long syscall0(long num) { + PANIC("Unknown syscall: %d()\n", num); +} +``` +This will clearly tell me when I try to use a c function that requires support for a syscall, such as `malloc` which will result in the panic message "Unknown syscall: 12(0)". + +Looking into the musl source code, the file `arch/x86_64/bits/syscall.h.in` tells me that syscall 12 is `brk`, so that's a good thing to start implementing. + +Once that's done, I put a reference to my `k_brk` function into an array and update the `syscallN` functions to call my implementations if available: +```c +long syscall0(long num) { + long retval = 0; + if(syscall_handlers[num]) + retval = syscall_handlers[num](0, 0, 0, 0, 0, 0); + else + PANIC("Unknown syscall: %d()\n", num); + return retval; +} +``` + +The `k_brk` implementation is very simple too. Just allocate pages until the brk requirement is fulfilled: +```c +static long _brk = KERNEL_BRK0; +long k_brk(long brk, long, long, long, long, long) { + if(brk) { + while(_brk < brk) { + vmm_set_page(kernel_P4, _brk, pmm_alloc(), PAGE_GLOBAL | PAGE_WRITE | PAGE_PRESENT); + _brk += PAGE_SIZE; + } + } + return _brk; +} +``` + +And now I can use standard `malloc` and `calloc` in my kernel. After implementing `mmap` as well, I also have `realloc`. Nice! + +## printf +In order to enable `printf` for debug output, I only need two "syscalls" mocked - `ioctl` and `writev`. + +When writing to stdout, musl will call `ioctl` with command `0x5413` to get the terminal size: +```c +long k_ioctl(long fd, long cmd, long arg3, long, long, long) { + if(fd == 1 && cmd == 0x5413) { + struct { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xp; + unsigned short ws_yp; + } *wsz = (void *)arg3; + wsz->ws_row = 24; + wsz->ws_col = 80; + return 0; + } + long retval = -1; + PANIC("Unknown IOCTL request: fd: %d, command: %x\n", fd, cmd); + return retval; +} +``` + +And it will then call `writev` with the vectors of data to write: +```c +#include // for struct iovec +long k_writev(long fd, long iov, long iovcnt, long, long, long) { + if(fd == 1) + { + size_t len = 0; + struct iovec *v = (void *) iov; + for(int i = 0; i < iovcnt; i++) { + debug_putsn(v[i].iov_base, v[i].iov_len); + len += v[i].iov_len; + } + return len; + } + long retval = 0; + PANIC("Unknown writev request: fd: %d\n", fd); + return retval; +} +``` + +I then have a fully featured printf implementation to help me in "printf debugging". + +## Relevant files +- `toolchain/build-musl.sh` +- `src/patch-musl/arch/x86_64` +- `src/kernel/lib/musl-glue.c` +- `src/kernel/include/musl-glue.c` +- `src/kernel/memory/kbrk.c` \ No newline at end of file