Syscalls for Linux on Clang++

Here is a snippet of code to perform system calls on x86_64 in C++ (Clang or GCC) without any standard library:

#pragma once
#include <cstdint>
#include <cstddef>

template<int64_t syscall_id, int arg_count>
struct syscall;

template<int64_t syscall_id>
struct syscall<syscall_id, 1> {
    int64_t operator()(int64_t p1) const {
        int64_t ret;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

template<int64_t syscall_id>
struct syscall<syscall_id, 2> {
    int64_t operator()(int64_t p1,int64_t p2) const {
        int64_t ret;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1), "S"(p2)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

template<int64_t syscall_id>
struct syscall<syscall_id, 3> {
    int64_t operator()(int64_t p1,int64_t p2,int64_t p3) const {
        int64_t ret;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1), "S"(p2), "d"(p3)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

template<int64_t syscall_id>
struct syscall<syscall_id, 4> {
    int64_t operator()(int64_t p1,int64_t p2,int64_t p3,int64_t p4) const {
        int64_t ret;
        register long r10 asm("r10") = p4;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1), "S"(p2), "d"(p3), "r"(r10)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

template<int64_t syscall_id>
struct syscall<syscall_id, 5> {
    int64_t operator()(int64_t p1,int64_t p2,int64_t p3,int64_t p4,int64_t p5) const {
        int64_t ret;
        register long r10 asm("r10") = p4;
        register long r8 asm("r8") = p5;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1), "S"(p2), "d"(p3), "r"(r10), "r"(r8)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

template<int64_t syscall_id>
struct syscall<syscall_id, 6> {
    int64_t operator()(int64_t p1,int64_t p2,int64_t p3,int64_t p4,int64_t p5,int64_t p6) const {
        int64_t ret;
        register long r10 asm("r10") = p4;
        register long r8 asm("r8") = p5;
        register long r9 asm("r9") = p6;
        asm volatile
        (
            "syscall"
            : "=a" (ret)
            : "0"(syscall_id), "D"(p1), "S"(p2), "d"(p3), "r"(r10), "r"(r8), "r"(r9)
            : "rcx", "r11", "memory"
        );
        return ret;
    }
};

And here are a few examples of how to implement some very common calls in a more detailed fashion:


constexpr auto _read = syscall<0, 3>{};
constexpr auto _write = syscall<1, 3>{};
constexpr auto _mmap = syscall<9, 6>{};
constexpr auto _munmap = syscall<11, 2>{};
constexpr auto _exit = syscall<60, 1>{};

inline int read(int fd, char* buffer, size_t sz) {
    return _read((int64_t)fd, (int64_t)buffer, (int64_t)sz);
}

inline int write(int fd, char* buffer, size_t sz) {
    return _write((int64_t)fd, (int64_t)buffer, (int64_t)sz);
}

inline void* mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) {
    return (void*)_mmap((int64_t)addr, (int64_t)length, (int64_t)prot, (int64_t)flags, (int64_t)fd, (int64_t)offset);
}

inline int munmap(void *addr, size_t length) {
    return _munmap((int64_t)addr, (int64_t)length);
}

extern "C" {

inline __attribute__ ((__noreturn__)) void exit(int status) {
    _exit((int64_t)status);
    while(true);
}

}

Just leaving those snippets here for anyone to use them. Consider them MIT licensed.