diff --git a/arch/x86_64/syscall_arch.h b/arch/x86_64/syscall_arch.h new file mode 100644 index 0000000..074e73c --- /dev/null +++ b/arch/x86_64/syscall_arch.h @@ -0,0 +1,115 @@ +#ifndef VLIBC_INTERNAL_ARCH_X86_64_SYSCALL_ARCH_H +#define VLIBC_INTERNAL_ARCH_X86_64_SYSCALL_ARCH_H + +/* + * vlibc — x86_64 raw syscall entry (arch layer). + * + * The System V AMD64 kernel ABI: the syscall number goes in rax, arguments in + * rdi, rsi, rdx, r10, r8, r9 (this order; the fourth argument is r10, NOT + * rcx), and the kernel clobbers rcx and r11. The kernel returns the raw + * result in rax: a non-negative value on success, or -errno on error. Callers + * funnel rax through syscall_ret() (see src/internal/syscall.h), which + * translates the negative-errno convention into C return/-1 + errno. + * + * These wrappers take `long` arguments and return `long` so that pointer-sized + * syscall arguments (addresses, off_t) pass through without truncation; the + * kernel reads exactly the low 64 bits of each register, so the upper bits of + * `long` arguments are irrelevant. + * + * Each wrapper is `always_inline` so that even a -O0 build collapses to a + * bare `syscall` instruction with no call frame. The "memory" clobber tells + * the compiler the syscall may read or write anything the caller can see, + * which is always the safe assumption. + */ + +/* + * The __syscall names sit in the implementation-reserved namespace (they + * are this libc's private kernel-ABI seam, never public API), and every + * argument is a `long` because that is the shape of the x86_64 syscall + * register file — both properties are intentional, so the corresponding + * bugprone checks are waived per function. + */ + +// NOLINTBEGIN(bugprone-easily-swappable-parameters,bugprone-reserved-identifier) + +static inline long __attribute__((always_inline)) +__syscall0(long n) +{ + unsigned long ret; + + __asm__ volatile("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall1(long n, long a) +{ + unsigned long ret; + + __asm__ volatile("syscall" : "=a"(ret) : "a"(n), "D"(a) : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall2(long n, long a, long b) +{ + unsigned long ret; + + __asm__ volatile("syscall" : "=a"(ret) : "a"(n), "D"(a), "S"(b) : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall3(long n, long a, long b, long c) +{ + unsigned long ret; + + __asm__ volatile("syscall" + : "=a"(ret) + : "a"(n), "D"(a), "S"(b), "d"(c) + : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall4(long n, long a, long b, long c, long d) +{ + unsigned long ret; + + __asm__ volatile("syscall" + : "=a"(ret) + : "a"(n), "D"(a), "S"(b), "d"(c), "r"(d) + : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall5(long n, long a, long b, long c, long d, long e) +{ + unsigned long ret; + + __asm__ volatile("syscall" + : "=a"(ret) + : "a"(n), "D"(a), "S"(b), "d"(c), "r"(d), "r"(e) + : "rcx", "r11", "memory"); + return (long)ret; +} + +static inline long __attribute__((always_inline)) +__syscall6(long n, long a, long b, long c, long d, long e, long f) +{ + unsigned long ret; + register long r10 __asm__("r10") = d; + register long r8 __asm__("r8") = e; + register long r9 __asm__("r9") = f; + + __asm__ volatile("syscall" + : "=a"(ret) + : "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory"); + return (long)ret; +} + +// NOLINTEND(bugprone-easily-swappable-parameters,bugprone-reserved-identifier) + +#endif /* VLIBC_INTERNAL_ARCH_X86_64_SYSCALL_ARCH_H */ diff --git a/src/internal/atomic.h b/src/internal/atomic.h new file mode 100644 index 0000000..31f73f8 --- /dev/null +++ b/src/internal/atomic.h @@ -0,0 +1,120 @@ +#ifndef VLIBC_INTERNAL_ATOMIC_H +#define VLIBC_INTERNAL_ATOMIC_H + +/* + * vlibc — internal atomics. + * + * Thin wrappers over the GCC __atomic_* builtins, so internal code writes + * portable-looking atomics without depending on a particular . + * The wrappers operate on int and long (the widths internal counters and + * refcounts actually use); pointers get their own load/store/cmpxchg trio. + * + * All wrappers are sequentially consistent by default: a libc-internal + * operation is almost never hot enough to justify weakening, and a single + * memory order everywhere is the least error-prone choice. + */ + +static inline int +atomic_load(const volatile int *p) +{ + return __atomic_load_n(p, __ATOMIC_SEQ_CST); +} + +static inline void +atomic_store(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + __atomic_store_n(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_fetch_add(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_exchange(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_exchange_n(p, v, __ATOMIC_SEQ_CST); +} + +/* + * Compare-and-swap: store want into *p iff *p == expect; return the value + * previously in *p (equal to expect on success). The (expect, want) order + * matches the C11 atomic_compare_exchange and GCC builtin convention. + */ +static inline int +atomic_cas(volatile int *p, int expect, + int want) // NOLINT(bugprone-easily-swappable-parameters,readability-non-const-parameter) +{ + int old = expect; + + __atomic_compare_exchange_n(p, &old, want, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return old; +} + +static inline long +atomic_load_l(const volatile long *p) +{ + return __atomic_load_n(p, __ATOMIC_SEQ_CST); +} + +static inline void +atomic_store_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + __atomic_store_n(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_fetch_add_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_exchange_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_exchange_n(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_cas_l( + volatile long *p, long expect, + long want) // NOLINT(bugprone-easily-swappable-parameters,readability-non-const-parameter) +{ + long old = expect; + + __atomic_compare_exchange_n(p, &old, want, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return old; +} + +static inline void * +atomic_load_p(void *const volatile *p) +{ + return __atomic_load_n(p, __ATOMIC_SEQ_CST); +} + +static inline void +atomic_store_p(void *volatile *p, void *v) // NOLINT(readability-non-const-parameter) +{ + __atomic_store_n(p, v, __ATOMIC_SEQ_CST); +} + +static inline void * +atomic_cas_p( + void *volatile *p, void *expect, + void *want) // NOLINT(bugprone-easily-swappable-parameters,readability-non-const-parameter) +{ + void *old = expect; + + __atomic_compare_exchange_n(p, &old, want, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return old; +} + +static inline void +atomic_fence(void) +{ + __atomic_thread_fence(__ATOMIC_SEQ_CST); +} + +#endif /* VLIBC_INTERNAL_ATOMIC_H */ diff --git a/src/internal/errno.c b/src/internal/errno.c new file mode 100644 index 0000000..0eb8b79 --- /dev/null +++ b/src/internal/errno.c @@ -0,0 +1,16 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "errno.h" + +/* + * Return the address of the calling thread's errno slot: the TCB address + * (read through the x86_64 FS thread pointer) plus the fixed errno-slot + * offset, an ABI constant defined in errno.h and consumed by the TCB layout. + */ +int * +__errno_location(void) // NOLINT(bugprone-reserved-identifier) +{ + return (int *)((char *)__builtin_thread_pointer() + VLIBC_TCB_ERRNO_OFF); +} diff --git a/src/internal/errno.h b/src/internal/errno.h new file mode 100644 index 0000000..8f90bd2 --- /dev/null +++ b/src/internal/errno.h @@ -0,0 +1,185 @@ +#ifndef VLIBC_INTERNAL_ERRNO_H +#define VLIBC_INTERNAL_ERRNO_H + +/* + * vlibc — internal errno (thread-local via the TCB). + * + * errno is per-thread state. It lives in a slot of the thread control block + * (TCB), addressed relative to the x86_64 FS thread pointer; there is no + * process-global errno object and no compiler-managed TLS (`__thread`) here. + * + * The offset of the errno slot inside the TCB is an ABI constant defined + * below and consumed by the authoritative TCB layout (see the startup todo, + * which owns the TCB/DTV layout). No other layer may re-derive errno's + * location: it must reference VLIBC_TCB_ERRNO_OFF. + * + * Slot 0 (offset 0) of the TCB is reserved for the TCB self pointer by + * convention: __errno_location() reads the thread pointer with + * __builtin_thread_pointer(), which on x86_64 loads the value at %fs:0, i.e. + * the self pointer the TCB layout must store at its first word. The errno + * slot therefore starts at offset 8, immediately after the self pointer, so + * it shares the TCB's first cache line (it is the hottest TCB member). The + * slot is 8 bytes for natural alignment; errno occupies the first 4. + */ +#define VLIBC_TCB_ERRNO_OFF 8 + +/* + * Return the address of the calling thread's errno slot. + * + * Requires the thread pointer (FS) to be initialized to a TCB whose first + * word is the TCB's own address — the startup todo owns that bootstrap and + * sets it before any code that can touch errno runs. This function itself + * performs no setup and no other TCB access. + * + * The name sits in the implementation-reserved namespace deliberately: it is + * this libc's private errno accessor, not public API. + */ +int * +__errno_location(void); // NOLINT(bugprone-reserved-identifier) + +/* The conventional errno lvalue; resolves to the caller's TCB slot. */ +#define errno (*__errno_location()) + +/* + * Error numbers: the Linux errno ABI (asm-generic/errno-base.h plus + * asm-generic/errno.h, which is the table x86_64 uses), transcribed as + * kernel-ABI facts. These values are fixed and shared with the kernel. The + * public is owned by a later todo; this internal copy is what + * internal code and tests compile against. + */ + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file descriptor */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain of func */ +#define ERANGE 34 /* Math result not representable */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ +#define EDEADLOCK EDEADLK /* File locking deadlock error */ +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Can not access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + +#endif /* VLIBC_INTERNAL_ERRNO_H */ diff --git a/src/internal/libc.h b/src/internal/libc.h new file mode 100644 index 0000000..a54644e --- /dev/null +++ b/src/internal/libc.h @@ -0,0 +1,15 @@ +#ifndef VLIBC_INTERNAL_LIBC_H +#define VLIBC_INTERNAL_LIBC_H + +/* + * vlibc — internal compiler-attribute shorthand. + * + * Everything internal to the library is declared with hidden visibility so + * that neither the static archive nor the shared library exports symbols + * outside the public API surface. The shared-library case matters most: + * without `hidden`, every internal helper would become a dynamic symbol that + * could be interposed or collide with a consumer's own symbols. + */ +#define hidden __attribute__((__visibility__("hidden"))) + +#endif /* VLIBC_INTERNAL_LIBC_H */ diff --git a/src/internal/syscall.h b/src/internal/syscall.h new file mode 100644 index 0000000..b126048 --- /dev/null +++ b/src/internal/syscall.h @@ -0,0 +1,423 @@ +#ifndef VLIBC_INTERNAL_SYSCALL_H +#define VLIBC_INTERNAL_SYSCALL_H + +/* + * vlibc — internal raw syscall layer. + * + * This header is the single kernel-ABI seam of the library: every syscall + * vlibc makes goes through the __syscall() entry points below (defined + * per-architecture in arch/x86_64/syscall_arch.h) and its result is + * translated by syscall_ret() into the C return-code + errno convention. + * + * The SYS_* table below holds the x86_64 Linux syscall numbers, transcribed + * from the kernel's arch/x86/entry/syscalls/syscall_64.tbl (the same values + * glibc exposes as ). These are pure kernel-ABI facts: the + * x86_64 table, NOT the asm-generic table, which differs on several numbers + * (e.g. x86_64 SYS_open is 2, SYS_mmap is 9). Numbers 337-423 are reserved + * by the kernel and intentionally absent. + */ + +#include "../../arch/x86_64/syscall_arch.h" // IWYU pragma: keep + +#include "../internal/libc.h" + +#define SYS_read 0 +#define SYS_write 1 +#define SYS_open 2 +#define SYS_close 3 +#define SYS_stat 4 +#define SYS_fstat 5 +#define SYS_lstat 6 +#define SYS_poll 7 +#define SYS_lseek 8 +#define SYS_mmap 9 +#define SYS_mprotect 10 +#define SYS_munmap 11 +#define SYS_brk 12 +#define SYS_rt_sigaction 13 +#define SYS_rt_sigprocmask 14 +#define SYS_rt_sigreturn 15 +#define SYS_ioctl 16 +#define SYS_pread64 17 +#define SYS_pwrite64 18 +#define SYS_readv 19 +#define SYS_writev 20 +#define SYS_access 21 +#define SYS_pipe 22 +#define SYS_select 23 +#define SYS_sched_yield 24 +#define SYS_mremap 25 +#define SYS_msync 26 +#define SYS_mincore 27 +#define SYS_madvise 28 +#define SYS_shmget 29 +#define SYS_shmat 30 +#define SYS_shmctl 31 +#define SYS_dup 32 +#define SYS_dup2 33 +#define SYS_pause 34 +#define SYS_nanosleep 35 +#define SYS_getitimer 36 +#define SYS_alarm 37 +#define SYS_setitimer 38 +#define SYS_getpid 39 +#define SYS_sendfile 40 +#define SYS_socket 41 +#define SYS_connect 42 +#define SYS_accept 43 +#define SYS_sendto 44 +#define SYS_recvfrom 45 +#define SYS_sendmsg 46 +#define SYS_recvmsg 47 +#define SYS_shutdown 48 +#define SYS_bind 49 +#define SYS_listen 50 +#define SYS_getsockname 51 +#define SYS_getpeername 52 +#define SYS_socketpair 53 +#define SYS_setsockopt 54 +#define SYS_getsockopt 55 +#define SYS_clone 56 +#define SYS_fork 57 +#define SYS_vfork 58 +#define SYS_execve 59 +#define SYS_exit 60 +#define SYS_wait4 61 +#define SYS_kill 62 +#define SYS_uname 63 +#define SYS_semget 64 +#define SYS_semop 65 +#define SYS_semctl 66 +#define SYS_shmdt 67 +#define SYS_msgget 68 +#define SYS_msgsnd 69 +#define SYS_msgrcv 70 +#define SYS_msgctl 71 +#define SYS_fcntl 72 +#define SYS_flock 73 +#define SYS_fsync 74 +#define SYS_fdatasync 75 +#define SYS_truncate 76 +#define SYS_ftruncate 77 +#define SYS_getdents 78 +#define SYS_getcwd 79 +#define SYS_chdir 80 +#define SYS_fchdir 81 +#define SYS_rename 82 +#define SYS_mkdir 83 +#define SYS_rmdir 84 +#define SYS_creat 85 +#define SYS_link 86 +#define SYS_unlink 87 +#define SYS_symlink 88 +#define SYS_readlink 89 +#define SYS_chmod 90 +#define SYS_fchmod 91 +#define SYS_chown 92 +#define SYS_fchown 93 +#define SYS_lchown 94 +#define SYS_umask 95 +#define SYS_gettimeofday 96 +#define SYS_getrlimit 97 +#define SYS_getrusage 98 +#define SYS_sysinfo 99 +#define SYS_times 100 +#define SYS_ptrace 101 +#define SYS_getuid 102 +#define SYS_syslog 103 +#define SYS_getgid 104 +#define SYS_setuid 105 +#define SYS_setgid 106 +#define SYS_geteuid 107 +#define SYS_getegid 108 +#define SYS_setpgid 109 +#define SYS_getppid 110 +#define SYS_getpgrp 111 +#define SYS_setsid 112 +#define SYS_setreuid 113 +#define SYS_setregid 114 +#define SYS_getgroups 115 +#define SYS_setgroups 116 +#define SYS_setresuid 117 +#define SYS_getresuid 118 +#define SYS_setresgid 119 +#define SYS_getresgid 120 +#define SYS_getpgid 121 +#define SYS_setfsuid 122 +#define SYS_setfsgid 123 +#define SYS_getsid 124 +#define SYS_capget 125 +#define SYS_capset 126 +#define SYS_rt_sigpending 127 +#define SYS_rt_sigtimedwait 128 +#define SYS_rt_sigqueueinfo 129 +#define SYS_rt_sigsuspend 130 +#define SYS_sigaltstack 131 +#define SYS_utime 132 +#define SYS_mknod 133 +#define SYS_uselib 134 +#define SYS_personality 135 +#define SYS_ustat 136 +#define SYS_statfs 137 +#define SYS_fstatfs 138 +#define SYS_sysfs 139 +#define SYS_getpriority 140 +#define SYS_setpriority 141 +#define SYS_sched_setparam 142 +#define SYS_sched_getparam 143 +#define SYS_sched_setscheduler 144 +#define SYS_sched_getscheduler 145 +#define SYS_sched_get_priority_max 146 +#define SYS_sched_get_priority_min 147 +#define SYS_sched_rr_get_interval 148 +#define SYS_mlock 149 +#define SYS_munlock 150 +#define SYS_mlockall 151 +#define SYS_munlockall 152 +#define SYS_vhangup 153 +#define SYS_modify_ldt 154 +#define SYS_pivot_root 155 +#define SYS__sysctl 156 +#define SYS_prctl 157 +#define SYS_arch_prctl 158 +#define SYS_adjtimex 159 +#define SYS_setrlimit 160 +#define SYS_chroot 161 +#define SYS_sync 162 +#define SYS_acct 163 +#define SYS_settimeofday 164 +#define SYS_mount 165 +#define SYS_umount2 166 +#define SYS_swapon 167 +#define SYS_swapoff 168 +#define SYS_reboot 169 +#define SYS_sethostname 170 +#define SYS_setdomainname 171 +#define SYS_iopl 172 +#define SYS_ioperm 173 +#define SYS_create_module 174 +#define SYS_init_module 175 +#define SYS_delete_module 176 +#define SYS_get_kernel_syms 177 +#define SYS_query_module 178 +#define SYS_quotactl 179 +#define SYS_nfsservctl 180 +#define SYS_getpmsg 181 +#define SYS_putpmsg 182 +#define SYS_afs_syscall 183 +#define SYS_tuxcall 184 +#define SYS_security 185 +#define SYS_gettid 186 +#define SYS_readahead 187 +#define SYS_setxattr 188 +#define SYS_lsetxattr 189 +#define SYS_fsetxattr 190 +#define SYS_getxattr 191 +#define SYS_lgetxattr 192 +#define SYS_fgetxattr 193 +#define SYS_listxattr 194 +#define SYS_llistxattr 195 +#define SYS_flistxattr 196 +#define SYS_removexattr 197 +#define SYS_lremovexattr 198 +#define SYS_fremovexattr 199 +#define SYS_tkill 200 +#define SYS_time 201 +#define SYS_futex 202 +#define SYS_sched_setaffinity 203 +#define SYS_sched_getaffinity 204 +#define SYS_set_thread_area 205 +#define SYS_io_setup 206 +#define SYS_io_destroy 207 +#define SYS_io_getevents 208 +#define SYS_io_submit 209 +#define SYS_io_cancel 210 +#define SYS_get_thread_area 211 +#define SYS_lookup_dcookie 212 +#define SYS_epoll_create 213 +#define SYS_epoll_ctl_old 214 +#define SYS_epoll_wait_old 215 +#define SYS_remap_file_pages 216 +#define SYS_getdents64 217 +#define SYS_set_tid_address 218 +#define SYS_restart_syscall 219 +#define SYS_semtimedop 220 +#define SYS_fadvise64 221 +#define SYS_timer_create 222 +#define SYS_timer_settime 223 +#define SYS_timer_gettime 224 +#define SYS_timer_getoverrun 225 +#define SYS_timer_delete 226 +#define SYS_clock_settime 227 +#define SYS_clock_gettime 228 +#define SYS_clock_getres 229 +#define SYS_clock_nanosleep 230 +#define SYS_exit_group 231 +#define SYS_epoll_wait 232 +#define SYS_epoll_ctl 233 +#define SYS_tgkill 234 +#define SYS_utimes 235 +#define SYS_vserver 236 +#define SYS_mbind 237 +#define SYS_set_mempolicy 238 +#define SYS_get_mempolicy 239 +#define SYS_mq_open 240 +#define SYS_mq_unlink 241 +#define SYS_mq_timedsend 242 +#define SYS_mq_timedreceive 243 +#define SYS_mq_notify 244 +#define SYS_mq_getsetattr 245 +#define SYS_kexec_load 246 +#define SYS_waitid 247 +#define SYS_add_key 248 +#define SYS_request_key 249 +#define SYS_keyctl 250 +#define SYS_ioprio_set 251 +#define SYS_ioprio_get 252 +#define SYS_inotify_init 253 +#define SYS_inotify_add_watch 254 +#define SYS_inotify_rm_watch 255 +#define SYS_migrate_pages 256 +#define SYS_openat 257 +#define SYS_mkdirat 258 +#define SYS_mknodat 259 +#define SYS_fchownat 260 +#define SYS_futimesat 261 +#define SYS_newfstatat 262 +#define SYS_unlinkat 263 +#define SYS_renameat 264 +#define SYS_linkat 265 +#define SYS_symlinkat 266 +#define SYS_readlinkat 267 +#define SYS_fchmodat 268 +#define SYS_faccessat 269 +#define SYS_pselect6 270 +#define SYS_ppoll 271 +#define SYS_unshare 272 +#define SYS_set_robust_list 273 +#define SYS_get_robust_list 274 +#define SYS_splice 275 +#define SYS_tee 276 +#define SYS_sync_file_range 277 +#define SYS_vmsplice 278 +#define SYS_move_pages 279 +#define SYS_utimensat 280 +#define SYS_epoll_pwait 281 +#define SYS_signalfd 282 +#define SYS_timerfd_create 283 +#define SYS_eventfd 284 +#define SYS_fallocate 285 +#define SYS_timerfd_settime 286 +#define SYS_timerfd_gettime 287 +#define SYS_accept4 288 +#define SYS_signalfd4 289 +#define SYS_eventfd2 290 +#define SYS_epoll_create1 291 +#define SYS_dup3 292 +#define SYS_pipe2 293 +#define SYS_inotify_init1 294 +#define SYS_preadv 295 +#define SYS_pwritev 296 +#define SYS_rt_tgsigqueueinfo 297 +#define SYS_perf_event_open 298 +#define SYS_recvmmsg 299 +#define SYS_fanotify_init 300 +#define SYS_fanotify_mark 301 +#define SYS_prlimit64 302 +#define SYS_name_to_handle_at 303 +#define SYS_open_by_handle_at 304 +#define SYS_clock_adjtime 305 +#define SYS_syncfs 306 +#define SYS_sendmmsg 307 +#define SYS_setns 308 +#define SYS_getcpu 309 +#define SYS_process_vm_readv 310 +#define SYS_process_vm_writev 311 +#define SYS_kcmp 312 +#define SYS_finit_module 313 +#define SYS_sched_setattr 314 +#define SYS_sched_getattr 315 +#define SYS_renameat2 316 +#define SYS_seccomp 317 +#define SYS_getrandom 318 +#define SYS_memfd_create 319 +#define SYS_kexec_file_load 320 +#define SYS_bpf 321 +#define SYS_execveat 322 +#define SYS_userfaultfd 323 +#define SYS_membarrier 324 +#define SYS_mlock2 325 +#define SYS_copy_file_range 326 +#define SYS_preadv2 327 +#define SYS_pwritev2 328 +#define SYS_pkey_mprotect 329 +#define SYS_pkey_alloc 330 +#define SYS_pkey_free 331 +#define SYS_statx 332 +#define SYS_io_pgetevents 333 +#define SYS_rseq 334 +#define SYS_uretprobe 335 +#define SYS_uprobe 336 +#define SYS_pidfd_send_signal 424 +#define SYS_io_uring_setup 425 +#define SYS_io_uring_enter 426 +#define SYS_io_uring_register 427 +#define SYS_open_tree 428 +#define SYS_move_mount 429 +#define SYS_fsopen 430 +#define SYS_fsconfig 431 +#define SYS_fsmount 432 +#define SYS_fspick 433 +#define SYS_pidfd_open 434 +#define SYS_clone3 435 +#define SYS_close_range 436 +#define SYS_openat2 437 +#define SYS_pidfd_getfd 438 +#define SYS_faccessat2 439 +#define SYS_process_madvise 440 +#define SYS_epoll_pwait2 441 +#define SYS_mount_setattr 442 +#define SYS_quotactl_fd 443 +#define SYS_landlock_create_ruleset 444 +#define SYS_landlock_add_rule 445 +#define SYS_landlock_restrict_self 446 +#define SYS_memfd_secret 447 +#define SYS_process_mrelease 448 +#define SYS_futex_waitv 449 +#define SYS_set_mempolicy_home_node 450 +#define SYS_cachestat 451 +#define SYS_fchmodat2 452 +#define SYS_map_shadow_stack 453 +#define SYS_futex_wake 454 +#define SYS_futex_wait 455 +#define SYS_futex_requeue 456 +#define SYS_statmount 457 +#define SYS_listmount 458 +#define SYS_lsm_get_self_attr 459 +#define SYS_lsm_set_self_attr 460 +#define SYS_lsm_list_modules 461 +#define SYS_mseal 462 +#define SYS_setxattrat 463 +#define SYS_getxattrat 464 +#define SYS_listxattrat 465 +#define SYS_removexattrat 466 +#define SYS_open_tree_attr 467 +#define SYS_file_getattr 468 +#define SYS_file_setattr 469 +#define SYS_listns 470 +#define SYS_rseq_slice_yield 471 + +/* + * Translate a raw syscall result into the C convention. + * + * On success (rax >= 0) the value is returned unchanged. On error the kernel + * returns -errno in rax; syscall_ret stores errno into the calling thread's + * TCB errno slot (via __errno_location()) and returns -1. The -4095 bound + * matches the kernel's MAX_ERRNO: results at or below -4096 are legitimate + * values (e.g. mmap addresses cannot happen to land here, but the rule is + * kept exact). + */ +hidden int +syscall_ret(long r); + +#endif /* VLIBC_INTERNAL_SYSCALL_H */ diff --git a/src/internal/syscall_ret.c b/src/internal/syscall_ret.c new file mode 100644 index 0000000..64de930 --- /dev/null +++ b/src/internal/syscall_ret.c @@ -0,0 +1,23 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "errno.h" +#include "syscall.h" + +/* + * Translate a raw syscall result into the C convention: return the value on + * success, or store the negated errno and return -1 on error. The -4095 + * bound is the kernel's MAX_ERRNO: any result at or below -4096 is a + * legitimate value, not an error code. + */ +int +syscall_ret(long r) +{ + if (r < 0 && r > -4096) + { + errno = (int)-r; + return -1; + } + return (int)r; +} diff --git a/src/internal/types.h b/src/internal/types.h new file mode 100644 index 0000000..9dc6c34 --- /dev/null +++ b/src/internal/types.h @@ -0,0 +1,52 @@ +#ifndef VLIBC_INTERNAL_TYPES_H +#define VLIBC_INTERNAL_TYPES_H + +/* + * vlibc — internal scalar types. + * + * The kernel-ABI-correct scalar types internal code needs before the public + * exists. Widths follow the x86_64 LP64 model, matching the + * kernel's UAPI: pointers and long are 64-bit, int is 32-bit. ssize_t is + * derived from the compiler's pointer-difference type so it always matches + * the machine word. The public headers own their own (possibly richer) + * typedefs; this header is deliberately the minimal internal subset. + */ + +/* Signed type for byte counts and read()/write() results (x86_64: long). */ +typedef __PTRDIFF_TYPE__ ssize_t; + +/* File offset and its Linux large-file counterpart. */ +typedef long off_t; +typedef long long loff_t; + +/* Calendar time in seconds. */ +typedef long time_t; + +/* Process ID. */ +typedef int pid_t; + +/* User and group IDs. */ +typedef unsigned int uid_t; +typedef unsigned int gid_t; + +/* File mode bits (and permissions). */ +typedef unsigned int mode_t; + +/* Socket address lengths. */ +typedef unsigned int socklen_t; + +/* Microsecond intervals. */ +typedef unsigned int useconds_t; + +/* Hard link count. */ +typedef unsigned long long nlink_t; + +/* Device and inode numbers. */ +typedef unsigned long long dev_t; +typedef unsigned long long ino_t; + +/* Block counts and sizes. */ +typedef long blkcnt_t; +typedef long blksize_t; + +#endif /* VLIBC_INTERNAL_TYPES_H */ diff --git a/tests/syscall_test.c b/tests/syscall_test.c new file mode 100644 index 0000000..d4354e5 --- /dev/null +++ b/tests/syscall_test.c @@ -0,0 +1,190 @@ +/* + * vlibc — syscall layer test (todo 1). + * + * Exercises the raw __syscall() entry points and the errno TCB-slot + * mechanism end to end: + * + * 1. happy path: __syscall3(SYS_write, 1, "x", 1) emits "x" on stdout and + * returns 1 (one byte written); + * 2. failure path: syscall_ret(SYS_openat on a nonexistent path) returns -1 + * and leaves errno == ENOENT in the calling thread's TCB slot; + * 3. TCB offset mechanics: a fake TCB (a local buffer whose first word is + * its own address, as the real TCB layout will have) is installed as + * the FS thread pointer via SYS_arch_prctl; __errno_location() must + * then point exactly at fake + VLIBC_TCB_ERRNO_OFF, a failing syscall + * must land its errno in that slot, and the real thread pointer is + * restored afterwards. + * + * All diagnostics go through raw SYS_write (no printf): between installing + * and restoring the fake thread pointer the test must not call any libc + * function, whose TLS reads would see the fake TCB. + * + * Not part of the library proper; compiled manually for this todo (the + * tests/ + make check wiring is owned by a later todo). + */ + +#include "../src/internal/errno.h" +#include "../src/internal/syscall.h" +#include "../src/internal/types.h" + +/* Flag constants the public will own; test-local copies. */ +#define TEST_AT_FDCWD (-100) +#define TEST_O_RDONLY 0 +#define TEST_O_CLOEXEC 0x80000 + +/* arch_prctl codes (kernel UAPI). */ +#define TEST_ARCH_SET_FS 0x1002 +#define TEST_ARCH_GET_FS 0x1003 + +static int failures; + +/* Write a NUL-terminated string to fd via the raw syscall layer. */ +static void +say(int fd, const char *s) +{ + ssize_t n = 0; + + while (s[n] != '\0') + { + n++; + } + syscall_ret(__syscall3(SYS_write, fd, (long)s, (long)n)); +} + +static void +check(int cond, const char *what) +{ + if (!cond) + { + say(2, "FAIL: "); + say(2, what); + say(2, "\n"); + failures++; + } +} + +/* + * A stand-in thread control block. The real TCB layout (owned by the + * startup todo) will store the TCB's own address at offset 0 — the self + * pointer __builtin_thread_pointer() reads through %fs:0 — and errno at + * VLIBC_TCB_ERRNO_OFF. This buffer mirrors exactly those two slots. + */ +static unsigned long fake_tcb[32]; + +/* + * The fake TCB must be installed over a saved copy of the real FS base: + * the kernel's arch_prctl only reads or writes the current base, so the + * save must happen while the real thread pointer is still installed. + */ +static int +install_fake_tcb(unsigned long *real_fs) // NOLINT(readability-non-const-parameter) +{ + long r; + + fake_tcb[0] = (unsigned long)fake_tcb; /* TCB self pointer at slot 0 */ + r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_GET_FS, (long)real_fs)); + if (r != 0) + { + return 1; + } + r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)fake_tcb)); + if (r != 0) + { + return 1; + } + return 0; +} + +static int +restore_real_tcb(unsigned long real_fs) +{ + return syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)real_fs)) != 0; +} + +/* + * Failure scenario (-f): SYS_openat on a nonexistent path must return -1 + * and leave errno == ENOENT in the TCB slot. Prints the observed result to + * stdout; exits 0 only when the failure behaved exactly as specified. + */ +static int +failure_scenario(void) +{ + unsigned long real_fs = 0; + long r; + int saved_errno; + + if (install_fake_tcb(&real_fs)) + { + return 1; + } + r = syscall_ret(__syscall4(SYS_openat, TEST_AT_FDCWD, (long)"/nonexistent/vlibc", + TEST_O_RDONLY | TEST_O_CLOEXEC, 0)); + saved_errno = errno; + if (restore_real_tcb(real_fs)) + { + return 1; + } + + if (r == -1 && saved_errno == ENOENT) + { + say(1, "openat=-1 errno=ENOENT\n"); + return 0; + } + say(1, "openat FAIL\n"); + return 1; +} + +int +main(int argc, char **argv) +{ + long r; + unsigned long real_fs = 0; + + if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') + { + return failure_scenario(); + } + + /* 1. Happy path: one raw byte to stdout, byte count returned. */ + r = __syscall3(SYS_write, 1, (long)"x", 1); + check(r == 1, "SYS_write returned byte count"); + + /* 2. Point FS at the fake TCB and verify the errno slot mechanics. */ + check(install_fake_tcb(&real_fs) == 0, "arch_prctl fake TCB install"); + + { + unsigned long now_fs = 0; + + r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_GET_FS, (long)&now_fs)); + check(r == 0 && now_fs == (unsigned long)fake_tcb, + "arch_prctl(ARCH_GET_FS) reads fake base"); + } + + check((char *)__errno_location() == (char *)fake_tcb + VLIBC_TCB_ERRNO_OFF, + "__errno_location == fake TCB + VLIBC_TCB_ERRNO_OFF"); + + r = syscall_ret(__syscall4(SYS_openat, TEST_AT_FDCWD, (long)"/nonexistent/vlibc", + TEST_O_RDONLY | TEST_O_CLOEXEC, 0)); + check(r == -1, "SYS_openat on nonexistent path returns -1"); + check(errno == ENOENT, "errno == ENOENT via TCB slot"); + + /* 3. Restore the real thread pointer; the fake slot keeps its value. */ + check(restore_real_tcb(real_fs) == 0, "arch_prctl(ARCH_SET_FS, real TCB) restored"); + check(*(int *)((char *)fake_tcb + VLIBC_TCB_ERRNO_OFF) == ENOENT, + "fake TCB errno slot retains ENOENT after restore"); + + /* + * VLIBC_TCB_ERRNO_OFF addresses slot 1 of whichever TCB the FS thread + * pointer selects; under the host libc that slot is its private TLS + * state, so an errno round-trip through the real thread pointer would + * corrupt the host libc. The fake-TCB path proves the offset mechanics; + * the real TCB is exercised once the startup todo installs vlibc's own + * thread pointer. + */ + + if (failures > 0) + { + say(2, "FAILED\n"); + } + return failures == 0 ? 0 : 1; +}