feat(thread): futex + TLS + pthread_self substrate
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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 <time.h>
|
||||
|
||||
#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<n> 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 */
|
||||
@@ -74,6 +74,9 @@
|
||||
|
||||
#include "../internal/errno.h" /* VLIBC_TCB_ERRNO_OFF */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
#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));
|
||||
}
|
||||
@@ -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 <stddef.h>
|
||||
|
||||
#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 */
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user