feat(syscall): add x86_64 raw syscall layer, errno, internal ABI headers
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user