feat(syscall): add x86_64 raw syscall layer, errno, internal ABI headers

This commit is contained in:
2026-09-03 17:10:27 -04:00
parent 6854c7771d
commit c5a77c61fa
9 changed files with 1139 additions and 0 deletions
+190
View File
@@ -0,0 +1,190 @@
/*
* vlibc — syscall layer test (todo 1).
*
* Exercises the raw __syscall<n>() 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 <fcntl.h> 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;
}