> learning the linux syscall stuff

I've been studying this for a while. Turns out Linux has an amazing interface. It's stable and language-agnostic. All you need to do is put the values in specific registers and execute a special instruction. The result comes back in one of those same registers.

The high level documentation is here:

https://man7.org/linux/man-pages/man2/syscall.2.html

https://man7.org/linux/man-pages/man2/syscalls.2.html

https://www.kernel.org/doc/Documentation/ABI/stable/syscalls

https://www.kernel.org/doc/Documentation/ABI/stable/vdso

On Windows there is a similar interface but it is not stable. The system call numbers can change. Developers are supposed to use the good old Microsoft DLLs in order to get anything done. Just like how everyone uses libc on other systems.

Linux is different. The system call binary interface is the Linux interface. So it's actually possible to trash all of GNU and rewrite the entire Linux user space in Rust or Lisp or whatever. It doesn't have to be written in C. It doesn't even have to be POSIX compliant. Could be GUI-focused!

All you need to make any x86_64 Linux system call is this code:

long
system_call(long number, long _1, long _2, long _3, long _4, long _5, long _6)
{
register long rax __asm__("rax") = number;
register long rdi __asm__("rdi") = _1;
register long rsi __asm__("rsi") = _2;
register long rdx __asm__("rdx") = _3;
register long r10 __asm__("r10") = _4;
register long r8 __asm__("r8") = _5;
register long r9 __asm__("r9") = _6;

/* r8, r9 and r10 may be clobbered but can't be in the clobbers list
because the compiler won't use clobbered registers as inputs.
So they're placed in the outputs list instead. */
__asm__ volatile
("syscall"

: "+r" (rax),
"+r" (r8), "+r" (r9), "+r" (r10)
: "r" (rdi), "r" (rsi), "r" (rdx)
: "rcx", "r11", "cc", "memory");

return rax;
}

This is all you need to do anything. You can perform I/O. You can allocate memory. You can obtain your terminal's dimensions. You can perform ioctl's to your laptop's camera. You could make a new programming language today and all it really needs to be complete is this single function. What if instead of having this function the compiler could simply emit code that conform to this binary interface? The language could have a system_call keyword that generates Linux system call code!

Once I realized this I tried to turn it into a library called liblinux... I stopped working on it when I found out the kernel already has an awesome single file header you can include that lets you build freestanding Linux executables for a ton of architectures. They use it on the kernel to build their own tools!

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...

It even includes process entry point code! Linux copies the argument and environment vectors to the stack before entering the executable. The process start up code obtains those pointers and passes them to the main function. It also ensures the exit system call is called.

The process entry point is usually called _start because that's what linkers look for by default. In reality the ELF header has a pointer to the program's entry point, the actual symbol doesn't matter. You can tell the linker to set it to any other address or symbol. Also note that it's an entry point, not a function. There is no return address. Allowing that code to terminate results in a segmentation violation. Hence the need to ensure exit is called before that happens.

The only feature that seems to be missing is support for the table of auxiliary values:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...

The auxiliary values are placed on the stack immediately after the environment vector. So all you need to do to find this pointer is loop through it until it goes out of bounds. I wrote this code and it works:

https://github.com/matheusmoreira/liblinux/blob/master/start...

struct auxiliary { Elf64_Off type; Elf64_Off value; };

static void *after(void *vector)
{
void **pointer = (void **) vector;
while (*pointer++ != 0);
return pointer;
}

int liblinux_start(void *stack_pointer)
{
long count;
char **arguments;
char **environment;
struct auxiliary *values;

count = *((long *) stack_pointer);
arguments = ((char **) stack_pointer) + 1;
environment = arguments + count + 1;
values = after(environment);

return start(count, arguments, environment, values);
}

You can just loop over the pointer to the structure until you find one with type equal to AT_NULL. Example here:

https://github.com/matheusmoreira/liblinux/blob/master/examp...