From 0d07a914a156fa22eb3bb95effd76b0b8aaabc99 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Tue, 8 Sep 2026 15:02:26 -0400 Subject: [PATCH] feat(thread): futex + TLS + pthread_self substrate --- Makefile.am | 6 ++ src/internal/atomic.h | 68 ++++++++++++++++ src/internal/futex.h | 101 ++++++++++++++++++++++++ src/start/tcb.h | 30 +++++++ src/start/tls.c | 24 ++++++ src/thread/futex.c | 71 +++++++++++++++++ src/thread/pthread_impl.h | 109 ++++++++++++++++++++++++++ src/thread/pthread_self.c | 25 ++++++ src/thread/tls.c | 83 ++++++++++++++++++++ src/thread/x86_64/__set_thread_area.s | 34 ++++++++ 10 files changed, 551 insertions(+) create mode 100644 src/internal/futex.h create mode 100644 src/thread/futex.c create mode 100644 src/thread/pthread_impl.h create mode 100644 src/thread/pthread_self.c create mode 100644 src/thread/tls.c create mode 100644 src/thread/x86_64/__set_thread_area.s diff --git a/Makefile.am b/Makefile.am index 45b237c..c49a3c2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -73,6 +73,10 @@ VLIBC_CORE_SRCS = \ src/start/exit.c \ src/start/environ.c \ src/start/tls.c \ + src/thread/futex.c \ + src/thread/pthread_self.c \ + src/thread/tls.c \ + src/thread/x86_64/__set_thread_area.s \ src/errno/strerror.c \ src/setjmp/x86_64/setjmp.s \ src/setjmp/x86_64/longjmp.s \ @@ -693,6 +697,7 @@ EXTRA_DIST = \ arch/x86_64/syscall_arch.h \ src/internal/atomic.h \ src/internal/errno.h \ + src/internal/futex.h \ src/internal/libc.h \ src/internal/malloc.h \ src/internal/strtox.h \ @@ -700,6 +705,7 @@ EXTRA_DIST = \ src/internal/types.h \ src/start/start.h \ src/start/tcb.h \ + src/thread/pthread_impl.h \ src/dirent/dirent_impl.h \ src/process/atfork_impl.h \ src/stat/stat_impl.h \ diff --git a/src/internal/atomic.h b/src/internal/atomic.h index 31f73f8..1b8a346 100644 --- a/src/internal/atomic.h +++ b/src/internal/atomic.h @@ -12,6 +12,14 @@ * 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. + * + * The fetch_or / fetch_and / fetch_sub family and the add_fetch / sub_fetch + * result-returning forms were added by todo 44 for the futex lock words of + * the pthread layer (#46-#48): futex wait words are built with or/and + * (musl's a_or/a_and) and waiter counts with add/sub that return the NEW + * value (musl's a_inc/a_dec pair). The fetch_* forms return the OLD value, + * matching the C11/GCC __atomic_fetch_* convention; the *_fetch forms + * return the NEW one. */ static inline int @@ -32,6 +40,36 @@ atomic_fetch_add(volatile int *p, int v) // NOLINT(readability-non-const-paramet return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST); } +static inline int +atomic_add_fetch(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_add_fetch(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_fetch_sub(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_sub(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_sub_fetch(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_sub_fetch(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_fetch_or(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_or(p, v, __ATOMIC_SEQ_CST); +} + +static inline int +atomic_fetch_and(volatile int *p, int v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_and(p, v, __ATOMIC_SEQ_CST); +} + static inline int atomic_exchange(volatile int *p, int v) // NOLINT(readability-non-const-parameter) { @@ -71,6 +109,36 @@ atomic_fetch_add_l(volatile long *p, long v) // NOLINT(readability-non-const-par return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST); } +static inline long +atomic_add_fetch_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_add_fetch(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_fetch_sub_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_sub(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_sub_fetch_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_sub_fetch(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_fetch_or_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_or(p, v, __ATOMIC_SEQ_CST); +} + +static inline long +atomic_fetch_and_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) +{ + return __atomic_fetch_and(p, v, __ATOMIC_SEQ_CST); +} + static inline long atomic_exchange_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter) { diff --git a/src/internal/futex.h b/src/internal/futex.h new file mode 100644 index 0000000..167321d --- /dev/null +++ b/src/internal/futex.h @@ -0,0 +1,101 @@ +#ifndef VLIBC_INTERNAL_FUTEX_H +#define VLIBC_INTERNAL_FUTEX_H + +/* + * vlibc — internal futex substrate (todo 44). + * + * Linux futex operations over SYS_futex, the kernel's process-shared sleep + * primitive that every lock, condvar, barrier, and rwlock in the library + * rests on. Two calling conventions are deliberately provided: + * + * - __futex() is the RAW seam: it returns exactly what the kernel put in + * rax (0 or a positive value on success, a NEGATIVE errno on failure) + * and never touches errno. Consumers that need custom error handling + * (retry-on-EINTR, -ENOSYS fallbacks, or errno-code returns) use this. + * + * - __futex_wait() / __futex_timedwait() / __futex_wake() are the mapped + * wrappers in the house syscall_ret() convention: 0 (or the number of + * waiters woken) on success, -1 with errno set on failure. These are + * what the pthread layer (#45-#48) calls. + * + * Op codes and flags are kernel UAPI constants (linux/futex.h); the values + * are pinned below so this header stays self-contained. FUTEX_PRIVATE adds + * no extra atomics: the wait word is shared only between threads of one + * process. The ancient-kernel ENOSYS fallback path musl carries (retry + * without FUTEX_PRIVATE) is deliberately NOT implemented — every kernel + * since 2.6.22 handles the private flag, and the plan targets modern + * x86_64 only. + * + * Timeout semantics: __futex_timedwait() takes an ABSOLUTE deadline and + * uses FUTEX_WAIT_BITSET (kernel-compared, no userspace clock read): + * CLOCK_MONOTONIC by default, CLOCK_REALTIME when the FUTEX_CLOCK_REALTIME + * flag is added. A deadline already in the past returns -1/ETIMEDOUT from + * the kernel immediately. musl's relative-timeout __timedwait shape is not + * copied: it exists there only to keep pre-WAIT_BITSET kernels working. + */ + +#include + +#include "../internal/libc.h" + +/* futex op codes (linux/futex.h). */ +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_FD 2 +#define FUTEX_REQUEUE 3 +#define FUTEX_CMP_REQUEUE 4 +#define FUTEX_WAKE_OP 5 +#define FUTEX_LOCK_PI 6 +#define FUTEX_UNLOCK_PI 7 +#define FUTEX_TRYLOCK_PI 8 +#define FUTEX_WAIT_BITSET 9 + +/* op flags (linux/futex.h). */ +#define FUTEX_PRIVATE 128 +#define FUTEX_CLOCK_REALTIME 256 + +/* bitset for "any bits match" (linux/futex.h). */ +#define FUTEX_BITSET_MATCH_ANY 0xffffffffu + +/* + * The __futex family lives in the implementation-reserved namespace, same + * as the __syscall seam — intentional, waived here for the whole block. + */ +// NOLINTBEGIN(bugprone-reserved-identifier) + +/* + * Raw futex syscall: return the kernel's raw result — 0 or a positive value + * on success, a negative errno on failure. errno is never touched. op may + * be any FUTEX_* op code; ts is the timeout pointer for FUTEX_WAIT / + * FUTEX_WAIT_BITSET, NULL for the others. + */ +hidden long +__futex(volatile int *addr, int op, int val, void *ts); + +/* + * Mapped wait: block until *addr != val or a wakeup arrives. Returns 0 on + * wakeup, -1 with errno set on failure. priv != 0 selects FUTEX_PRIVATE. + */ +hidden int +__futex_wait(volatile int *addr, int val, int priv); + +/* + * Mapped timed wait: like __futex_wait, but with an ABSOLUTE deadline on + * clock clk (CLOCK_MONOTONIC or CLOCK_REALTIME). Returns 0 on wakeup, -1 + * with errno set (ETIMEDOUT when the deadline passes, EINTR when a signal + * interrupts the wait) on failure. + */ +hidden int +__futex_timedwait(volatile int *addr, int val, int priv, int clk, const struct timespec *at); + +/* + * Mapped wake: wake up to cnt waiters on *addr (cnt < 0 = wake all, the + * INT_MAX kernel convention musl uses). Returns the number of waiters + * actually woken, or -1 with errno set on failure. + */ +hidden int +__futex_wake(volatile int *addr, int cnt, int priv); + +// NOLINTEND(bugprone-reserved-identifier) + +#endif /* VLIBC_INTERNAL_FUTEX_H */ diff --git a/src/start/tcb.h b/src/start/tcb.h index b395951..84b9b1f 100644 --- a/src/start/tcb.h +++ b/src/start/tcb.h @@ -74,6 +74,9 @@ #include "../internal/errno.h" /* VLIBC_TCB_ERRNO_OFF */ +#include +#include + #define VLIBC_TCB_SELF_OFF 0 /* VLIBC_TCB_ERRNO_OFF (8) is consumed from src/internal/errno.h. */ @@ -84,4 +87,31 @@ #define VLIBC_TCB_SIZE 24 +/* + * New-thread TLS geometry (todo 44). All four are set by init_main_tls() + * in src/start/tls.c and read by src/thread/tls.c (__vlibc_tls_setup): + * + * __vlibc_static_tls_size PT_TLS p_memsz of the main executable — the + * one DEFAULT-VISIBILITY EXPORTED symbol of + * the group: #62 grows it on dlopen, #45 reads + * it when sizing new-thread TLS blocks. + * __vlibc_static_tls_filesz PT_TLS p_filesz (.tdata part). + * __vlibc_static_tls_align PT_TLS p_align (16 when there is no PT_TLS). + * __vlibc_static_tls_template the PRIMAL TLS image address: the mapped + * .tdata at p_vaddr, never the main thread's + * LIVE TLS block (which mutates). In the + * file-fallback path it is the main block + * itself — the only copy that exists then. + * + * The three hidden ones keep their attribute on the definitions (as with + * environ's hidden alias in src/start/environ.c); __vlibc_static_tls_size + * deliberately carries none so it stays an exported ABI object. + */ +// NOLINTBEGIN(bugprone-reserved-identifier) +extern size_t __vlibc_static_tls_size; +extern uintptr_t __vlibc_static_tls_template; +extern size_t __vlibc_static_tls_filesz; +extern size_t __vlibc_static_tls_align; +// NOLINTEND(bugprone-reserved-identifier) + #endif /* VLIBC_INTERNAL_START_TCB_H */ diff --git a/src/start/tls.c b/src/start/tls.c index 472c95d..4c765e1 100644 --- a/src/start/tls.c +++ b/src/start/tls.c @@ -76,6 +76,17 @@ struct elf64_phdr uint64_t p_align; }; +/* + * New-thread TLS geometry, consumed by src/thread/tls.c (todo 44) — see + * tcb.h for the full contract. __vlibc_static_tls_size is the EXPORTED + * default-visibility object (no attribute!); the other three are internal + * and hidden. + */ +size_t __vlibc_static_tls_size = 0; +hidden uintptr_t __vlibc_static_tls_template = 0; +hidden size_t __vlibc_static_tls_filesz = 0; +hidden size_t __vlibc_static_tls_align = 16; + /* * Round v up to a multiple of a. a must be a power of two, which ELF * p_align of a PT_TLS segment always is. @@ -253,6 +264,13 @@ init_main_tls(char **envp) align = 16; } + /* Record the geometry for new-thread TLS blocks (#44/#45) BEFORE the + * template address is known: size/filesz/align are PT_TLS facts, the + * template address depends on the mmap below. */ + __vlibc_static_tls_size = memsz; + __vlibc_static_tls_filesz = filesz; + __vlibc_static_tls_align = align; + /* * Single allocation: TLS image at the base, TCB at tp. tp uses exactly * p_align (never bumped): the linker bakes tpoff against @@ -273,6 +291,12 @@ init_main_tls(char **envp) tp = base + block; image = (char *)base; + /* The PRIMAL image is the new-thread template: the still-pristine + * .tdata at p_vaddr when mapped, else this block (the file-fallback + * path leaves no other copy). Never the main thread's live TLS — its + * mutations must not leak into threads created later. */ + __vlibc_static_tls_template = mapped ? vaddr : base; + if (filesz > 0) { copy_tls_image(image, offset, vaddr, filesz, mapped); diff --git a/src/thread/futex.c b/src/thread/futex.c new file mode 100644 index 0000000..8e59bf3 --- /dev/null +++ b/src/thread/futex.c @@ -0,0 +1,71 @@ +#include +#include +#include + +#include "../internal/futex.h" +#include "../internal/syscall.h" + +/* + * vlibc — futex implementation (todo 44). + * + * The raw seam returns exactly what SYS_futex left in rax (0/positive on + * success, negative errno on failure); the mapped wrappers translate that + * through syscall_ret() into the house convention (0/value or -1 with + * errno set). __futex_wake maps a negative cnt to INT_MAX before the + * syscall: the kernel's futex_wake_mark stops scanning when the count it + * has already woken exceeds nr_wake, so -1 would stop after ONE waiter, + * while INT_MAX is the "wake all" sentinel musl uses (after musl + * src/thread/__futex.c + pthread_impl.h's __wake). + */ + +long +__futex(volatile int *addr, int op, int val, void *ts) +{ + return __syscall4(SYS_futex, (long)addr, op, val, (long)ts); +} + +int +__futex_wait(volatile int *addr, int val, int priv) +{ + long r = __futex(addr, FUTEX_WAIT | (priv ? FUTEX_PRIVATE : 0), val, 0); + + /* The kernel reports a value mismatch as -EAGAIN: the wake condition + * already holds, nothing to wait for. That is success for every + * futex consumer, not an error (musl's __timedwait applies the same + * rule). */ + if (r == -EAGAIN) + { + return 0; + } + return syscall_ret(r); +} + +// NOLINTBEGIN(bugprone-easily-swappable-parameters) +int +__futex_timedwait(volatile int *addr, int val, int priv, int clk, const struct timespec *at) +{ + int op = FUTEX_WAIT_BITSET | (priv ? FUTEX_PRIVATE : 0); + + if (clk == CLOCK_REALTIME) + { + op |= FUTEX_CLOCK_REALTIME; + } + + long r = __syscall6(SYS_futex, (long)addr, op, val, (long)at, 0, FUTEX_BITSET_MATCH_ANY); + if (r == -EAGAIN) + { + return 0; /* value already differs: condition holds, see above */ + } + return syscall_ret(r); +} +// NOLINTEND(bugprone-easily-swappable-parameters) + +int +__futex_wake(volatile int *addr, int cnt, int priv) +{ + if (cnt < 0) + { + cnt = 0x7fffffff; /* wake-all sentinel, see the file comment */ + } + return syscall_ret(__futex(addr, FUTEX_WAKE | (priv ? FUTEX_PRIVATE : 0), cnt, 0)); +} diff --git a/src/thread/pthread_impl.h b/src/thread/pthread_impl.h new file mode 100644 index 0000000..3bd9e6b --- /dev/null +++ b/src/thread/pthread_impl.h @@ -0,0 +1,109 @@ +#ifndef VLIBC_THREAD_PTHREAD_IMPL_H +#define VLIBC_THREAD_PTHREAD_IMPL_H + +/* + * vlibc — internal threading substrate (todo 44). + * + * Everything the pthread layer (#45 and later) needs that is NOT itself a + * public pthread_* API: the TCB accessor, the thread-pointer installer, the + * new-thread TLS block builder, the futex seam, and the shared static-TLS + * size symbol. The public pthread_* functions are built by #45 on top of + * exactly these primitives; nothing here is exported from the shared + * library (every function is hidden, and __vlibc_static_tls_size — the one + * symbol with default visibility — is a data object, not an API). + * + * Ownership notes for the follow-up todos: + * + * - #45 defines pthread_t and the public pthread_self(); its + * implementation simply returns __pthread_self(). musl's + * cancellation-aware __syscall_cp/syscall_cp.s seam is NOT provided + * here: cancellation points are #45's concept, and a syscall_cp here + * would have to reference a __cancel symbol that does not exist yet. + * - #46-#48 use __futex_wait/__futex_timedwait/__futex_wake and the + * atomic.h extension for their lock words. + * - #45's pthread_create maps a fresh stack + TLS block, calls + * __vlibc_tls_setup(base) to copy the template and get the TP, and + * installs it in the new thread via __set_thread_area(). + * + * TP discipline (authoritative layout in src/start/tcb.h): the thread + * pointer points AT the TCB (ELF TLS variant II), slot 0 holds the TCB's + * own address, and static TLS data live at negative offsets. Every + * primitive here respects that contract and never re-derives it. + */ + +#include + +#include "../internal/atomic.h" +#include "../internal/futex.h" +#include "../internal/libc.h" +#include "../start/tcb.h" + +/* + * The names below live in the implementation-reserved namespace (the + * __pthread_self/__set_thread_area pair by musl parity); the waiver covers + * exactly those, nothing else. + */ +// NOLINTBEGIN(bugprone-reserved-identifier) + +/* + * Internal TCB accessor: returns the calling thread's TCB address, which + * is the thread pointer itself (slot 0 of the TCB holds exactly that + * value; __builtin_thread_pointer() reads %fs:0). Distinct and non-null + * for every thread whose FS base was installed by init_main_tls() or + * __set_thread_area(). The PUBLIC pthread_self() is #45's symbol; this + * one stays hidden so the two can never collide at the ABI level. + */ +hidden void * +__pthread_self(void); + +/* + * Install the calling thread's thread pointer via arch_prctl(ARCH_SET_FS). + * Must run IN the thread whose FS base is being set (the arch_prctl + * operates on the current task). Returns the raw syscall result: 0 on + * success, a negative errno on failure — the caller decides how to report + * it (there is no errno write here). Implemented in assembly + * (src/thread/x86_64/__set_thread_area.s), after musl's file of the same + * name. + */ +hidden long +__set_thread_area(void *tp); + +/* + * Initialize a fresh TCB at tp exactly as the authoritative tcb.h layout + * dictates: self pointer at slot 0, errno slot zeroed, and the per-thread + * fenv reservation reset to the x86 defaults (x87 control word 0x037F, + * MXCSR 0x1F80) — the same values init_main_tls() writes for the main + * thread, so #42's fenv functions (which read/write the slot through the + * fixed offsets) start from a defined state in every thread. + */ +hidden void +__vlibc_tcb_init(void *tp); + +/* + * Build a new thread's static TLS block at base: copy the main + * executable's PT_TLS image from the stored template, zero the .tbss + * tail, initialize the TCB, and return the thread pointer. base must be a + * page-aligned region of at least round_up(__vlibc_static_tls_size, + * align) + VLIBC_TCB_SIZE bytes; the caller (#45's pthread_create) owns + * the mapping (stack + guard + TLS in one block, munmap'd at thread + * exit). The placement formula is exactly the one the linker bakes into + * every %fs-relative TLS offset — see tcb.h, and never round the + * alignment differently. + */ +hidden void * +__vlibc_tls_setup(void *base); + +/* + * Size in bytes of the main executable's PT_TLS (p_memsz, i.e. .tdata + + * .tbss) for the STATIC no-ld.so case. Default visibility ON PURPOSE: + * this is exported ABI. init_main_tls() (src/start/tls.c) initializes it + * from the auxv program headers at startup; #62's dynamic loader is the + * sole runtime updater (it grows it on each successful dlopen), and #45 + * reads it when sizing new-thread TLS blocks. In a static build there is + * no dlopen, so the startup value is the final one. + */ +extern size_t __vlibc_static_tls_size; + +// NOLINTEND(bugprone-reserved-identifier) + +#endif /* VLIBC_THREAD_PTHREAD_IMPL_H */ diff --git a/src/thread/pthread_self.c b/src/thread/pthread_self.c new file mode 100644 index 0000000..383b4a9 --- /dev/null +++ b/src/thread/pthread_self.c @@ -0,0 +1,25 @@ +#include + +#include "pthread_impl.h" + +/* + * vlibc — internal thread self-access (todo 44). + * + * The TCB lives at the thread pointer itself (variant II; the authoritative + * layout in src/start/tcb.h), and slot 0 of the TCB holds exactly that + * address because __builtin_thread_pointer() reads %fs:0. __pthread_self() + * is therefore a single load: it returns the calling thread's TCB address, + * distinct and non-null for every thread whose FS base was installed by + * init_main_tls() (main thread) or __set_thread_area() (new threads). + * + * This symbol is INTERNAL and hidden. The public pthread_self() belongs to + * todo 45 and is its own exported symbol; its implementation delegates to + * this accessor once pthread_t is defined there. Keeping the two names + * separate means this substrate cannot collide with #45's public ABI. + */ + +void * +__pthread_self(void) +{ + return __builtin_thread_pointer(); +} diff --git a/src/thread/tls.c b/src/thread/tls.c new file mode 100644 index 0000000..bccc3a6 --- /dev/null +++ b/src/thread/tls.c @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "pthread_impl.h" + +/* + * vlibc — new-thread TLS block builder (todo 44). + * + * The counterpart of init_main_tls() (src/start/tls.c) for THREADS: the + * caller (#45's pthread_create) maps a fresh block, this file turns it into + * a working per-thread TLS area. Two halves: + * + * - __vlibc_tcb_init(tp): fill the TCB fields at a fresh thread pointer. + * The fenv reservation is reset to the x86 defaults (x87 control word + * 0x037F, MXCSR 0x1F80) so #42's fenv functions observe a defined + * state in every thread — the same values init_main_tls() writes for + * the main thread. All offsets are the authoritative constants from + * src/start/tcb.h, never re-derived. + * + * - __vlibc_tls_setup(base): place the TLS image + TCB in base per the + * linker's placement contract (tp = base + round_up(memsz, align), + * with align EXACT — see tcb.h) and return the TP. + * + * The image source is the stored PRIMAL template (the executable's + * mapped .tdata at p_vaddr, recorded by init_main_tls in + * __vlibc_static_tls_template), NOT the main thread's live TLS block: + * copying the live block would leak the main thread's mutations into + * every new thread. The .tbss tail is zeroed explicitly. + * + * Thread pointer installation is deliberately NOT done here: + * __set_thread_area() must run in the new thread itself, so #45 calls it + * from the thread's entry point after __vlibc_tls_setup() returns. + */ + +/* + * Round v up to a multiple of a. a must be a power of two, which the ELF + * p_align of a PT_TLS segment (and the no-TLS fallback of 16) always is. + */ +static size_t +round_up(size_t v, size_t a) +{ + return (v + a - 1) & ~(a - 1); +} + +void +__vlibc_tcb_init(void *tp) +{ + char *t = tp; + + ((uintptr_t *)t)[VLIBC_TCB_SELF_OFF / sizeof(uintptr_t)] = (uintptr_t)tp; + *(int *)(t + VLIBC_TCB_ERRNO_OFF) = 0; + *(uint16_t *)(t + VLIBC_TCB_FENV_X87CW_OFF) = 0x037F; /* x87 reset default */ + *(uint32_t *)(t + VLIBC_TCB_FENV_MXCSR_OFF) = 0x1F80; /* MXCSR reset default */ +} + +/* + * memcpy/memset from a raw template address: the insecureAPI check and the + * integer-to-pointer pessimization warning do not apply to a libc copying + * its own TLS image (the template is a stored address by design; boundary + * checks are the placement contract in tcb.h, not memcpy_s). The whole + * function is waived. + */ +// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,performance-no-int-to-ptr) +void * +__vlibc_tls_setup(void *base) +{ + char *block = base; + char *tp = block + round_up(__vlibc_static_tls_size, __vlibc_static_tls_align); + + if (__vlibc_static_tls_filesz > 0) + { + memcpy(block, (const void *)__vlibc_static_tls_template, __vlibc_static_tls_filesz); + } + if (__vlibc_static_tls_size > __vlibc_static_tls_filesz) + { + memset(block + __vlibc_static_tls_filesz, 0, + __vlibc_static_tls_size - __vlibc_static_tls_filesz); + } + __vlibc_tcb_init(tp); + return tp; +} +// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,performance-no-int-to-ptr) diff --git a/src/thread/x86_64/__set_thread_area.s b/src/thread/x86_64/__set_thread_area.s new file mode 100644 index 0000000..8bd91f5 --- /dev/null +++ b/src/thread/x86_64/__set_thread_area.s @@ -0,0 +1,34 @@ +/* + * vlibc — x86_64 thread-pointer installer (todo 44). + * + * __set_thread_area(tp): install tp as the calling thread's FS base via + * arch_prctl(ARCH_SET_FS). After this, %fs-relative TLS offsets resolve + * against tp exactly as the linker baked them (see src/start/tcb.h for the + * placement contract), and __builtin_thread_pointer() / %fs:0 returns tp. + * + * Must be executed IN the thread whose FS base is being set: arch_prctl + * operates on the current task, so pthread_create (#45) calls this from the + * new thread's entry point, never from the parent. + * + * Return convention: the raw syscall result is left in %eax — 0 on success, + * a negative errno on failure. No errno write happens here; the caller maps + * the result however its context needs (this mirrors musl's + * src/thread/x86_64/__set_thread_area.s, which also returns raw). + * + * arch_prctl codes (kernel UAPI asm/prctl.h): ARCH_SET_FS 0x1002, + * SYS_arch_prctl 158. Kept as literals because there is no internal header + * for them yet; src/start/tls.c defines the same values locally. + */ + +.text +.global __set_thread_area +.hidden __set_thread_area +.type __set_thread_area,@function +__set_thread_area: + mov %rdi,%rsi + movl $0x1002,%edi + movl $158,%eax + syscall + ret +.size __set_thread_area,.-__set_thread_area +.section .note.GNU-stack,"",@progbits