~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ ~~ System calls for the Linux kernel ~~ ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ ~ The kernel preserves every register except rax, rcx, and r11. The system ~ call number goes in rax, as does the return value. Parameters go in rdi, ~ rsi, rdx, r10, r8, and r9, in that order. [SysV] A.2.1. ~ ~ Notice that rsi is our control stack, so we have to save it (for ~ syscalls with at least two parameters). We can use the value stack to do ~ that, since rsp is preserved, or we can use one of the other registers. We ~ don't ourselves save other registers because our caller should do that, if ~ it cares. ~ ~ In the kernel source, you may find the following files useful to ~ reference: ~ ~ * arch/x86/entry/syscalls/syscall_64.tbl ~ * include/linux/syscalls.h ~ * include/linux/compat.h ~ ~ Don't be confused by tools/scripts/syscall.tbl, it's not for x86. ~ ~ This file loads early, before dynamic.e. So, although it superficially ~ appears to be able to do allocation and high-level flow control, those come ~ from the transforms, which means they're shallow implementations. The stuff ~ here can't be moved later, because input.e relies on it, and the dynamic ~ stuff relies on that. So, there's additional Linux functionality in another ~ file that loads later, linux-dynamic.e. ~ (call number -- return value) : syscall-0 [ here @ :rax pop-reg64 ~ syscall number syscall :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, call number -- return value) : syscall-1 [ here @ :rax pop-reg64 ~ syscall number :rdi pop-reg64 ~ first param syscall :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, second param, call number -- return value) : syscall-2 [ here @ :rsi :rbx mov-reg64-reg64 ~ save rsi :rax pop-reg64 ~ syscall number :rsi pop-reg64 ~ second param :rdi pop-reg64 ~ first param syscall :rbx :rsi mov-reg64-reg64 ~ restore rsi :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, second param, third param, call number -- return value) : syscall-3 [ here @ :rsi :rbx mov-reg64-reg64 ~ save rsi :rax pop-reg64 ~ syscall number :rdx pop-reg64 ~ third param :rsi pop-reg64 ~ second param :rdi pop-reg64 ~ first param syscall :rbx :rsi mov-reg64-reg64 ~ restore rsi :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, second param, third param, fourth param, call number ~ -- return value) : syscall-4 [ here @ :rsi :rbx mov-reg64-reg64 ~ save rsi :rax pop-reg64 ~ syscall number :r10 pop-extrareg64 ~ fourth param :rdx pop-reg64 ~ third param :rsi pop-reg64 ~ second param :rdi pop-reg64 ~ first param syscall :rbx :rsi mov-reg64-reg64 ~ restore rsi :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, second param, third param, fourth param, fifth param, ~ call number -- return value) : syscall-5 [ here @ :rsi :rbx mov-reg64-reg64 ~ save rsi :rax pop-reg64 ~ syscall number :r8 pop-extrareg64 ~ fifth param :r10 pop-extrareg64 ~ fourth param :rdx pop-reg64 ~ third param :rsi pop-reg64 ~ second param :rdi pop-reg64 ~ first param syscall :rbx :rsi mov-reg64-reg64 ~ restore rsi :rax push-reg64 ~ return value here ! ] ;asm ~ (first param, second param, third param, fourth param, fifth param, ~ sixth param, call number -- return value) : syscall-6 [ here @ :rsi :rbx mov-reg64-reg64 ~ save rsi :rax pop-reg64 ~ syscall number :r9 pop-extrareg64 ~ sixth param :r8 pop-extrareg64 ~ fifth param :r10 pop-extrareg64 ~ fourth param :rdx pop-reg64 ~ third param :rsi pop-reg64 ~ second param :rdi pop-reg64 ~ first param syscall :rbx :rsi mov-reg64-reg64 ~ restore rsi :rax push-reg64 ~ return value here ! ] ;asm ~ Raw system calls ~ ~~~~~~~~~~~~~~~~ ~ This does the Linux exit() system call, passing it an exit code taken ~ from the stack. It does not return. ~ ~ (exit code -- *) : sys-exit 60 syscall-1 ~ In the event we're still here, let's minimize confusion. crash ; ~ This does the Linux write() system call, passing it an address from the ~ top of the stack and a length from the second position on the stack. It ~ uses a Unix file descriptor, which is a non-negative integer, to tell it ~ where to write to. For stdout, use fd 1. ~ ~ (file descriptor, base address, length to write -- result code or length) : sys-write 1 syscall-3 ; ~ (file descriptor, base address, length to read -- result code or length) : sys-read 0 syscall-3 ; ~ (filename, flags, mode -- result code or file descriptor) : sys-open 2 syscall-3 ; ~ (file descriptor -- result code) : sys-close 3 syscall-1 ;