feat(start): _start, __libc_start_main, exit/atexit/environ

This commit is contained in:
2026-09-03 19:01:51 -04:00
parent 2480c63fbd
commit 558cf8fd12
8 changed files with 983 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
#ifndef VLIBC_INTERNAL_START_TCB_H
#define VLIBC_INTERNAL_START_TCB_H
/*
* vlibc — AUTHORITATIVE x86_64 TCB / thread-pointer layout (todo 3).
*
* This header defines the thread control block (TCB) layout ONCE, for the
* whole library. Later layers reference these constants and NEVER redefine
* them:
*
* #44 threading substrate: clones this layout for every new thread and
* initializes the fenv slot per thread.
* #45 sizes new-thread TLS from the static TLS block arithmetic below.
* #62 ld.so: performs the dynamic-case equivalent of this bootstrap (the
* FS-already-set guard in tls.c skips this code in that case).
* #42 fenv: reads/writes the reserved fenv slot through the fixed
* offsets below; the slot is per-thread lazy storage for the x87
* control word and MXCSR.
*
* ABI model — ELF TLS variant II on x86_64:
*
* The thread pointer (TP) is delivered by the %fs segment and points
* DIRECTLY at the TCB (this is what "variant II" means; variant I, used by
* e.g. 32-bit x86, points at the end of the TLS data instead). Static TLS
* data live at NEGATIVE offsets from TP (%fs:N with N < 0, the LE model);
* the TCB fields live at positive offsets. Slot 0 is required to hold the
* TCB's own address because __builtin_thread_pointer() reads %fs:0 — that
* is the entire mechanism by which C code recovers TP from a register.
*
* TCB field map (offsets are byte offsets from TP, i.e. from %fs:0):
*
* TP + 0 VLIBC_TCB_SELF_OFF TCB self pointer (TP itself).
* TP + 8 VLIBC_TCB_ERRNO_OFF per-thread errno slot: 8 bytes, errno
* occupies the low 4. This offset is OWNED
* by src/internal/errno.h (task 1 fixed it
* as an ABI constant) and is consumed
* here, never re-derived.
* TP + 16 VLIBC_TCB_FENV_OFF per-thread fenv reservation, 8 bytes:
* +16 VLIBC_TCB_FENV_X87CW_OFF uint16 x87 control word,
* reset default 0x037F (double
* precision, round-to-nearest)
* +18 (pad, reserved)
* +20 VLIBC_TCB_FENV_MXCSR_OFF uint32 MXCSR, reset default
* 0x1F80 (all exceptions masked)
* TP + 24 end of the fixed layout; VLIBC_TCB_SIZE = 24.
*
* The layout grows FORWARD from TP if later layers need more TCB fields;
* the fixed slots above are never renumbered. A DTV pointer is deliberately
* NOT reserved: the static TLS model needs no DTV (every access is
* %fs-relative with a link-time offset; there is no __tls_get_addr). Should
* #62's dynamic TLS ever require a DTV, add it at TP + VLIBC_TCB_SIZE —
* glibc's variant-II convention keeps the DTV at a negative offset relative
* to a separately allocated block; vlibc deliberately keeps the simpler
* grow-upward model documented here.
*
* Static TLS placement contract (tls.c implements this; the linker bakes
* its TPOFF arithmetic against the same formula, verified empirically with
* GNU ld):
*
* - tp = base + round_up(p_memsz, p_align), with base page-aligned.
* - the PT_TLS image is copied to [base, base + p_filesz), the BSS tail
* [base + p_filesz, base + p_memsz) is zeroed, and the TCB occupies
* [tp, tp + VLIBC_TCB_SIZE).
* - TP alignment equals p_align EXACTLY (never bumped to 16): the linker
* computes tpoff = var_offset - round_up(p_memsz, p_align), so any
* other rounding breaks every %fs offset when p_memsz is not a
* multiple of the runtime's alignment. When the program has no PT_TLS
* at all, p_align is taken as 16 and the block is TCB-only.
*
* TCB fields only need 8-byte alignment, and the linker guarantees
* p_align >= the alignment of every TLS object, so the exact-p_align
* rule is always sufficient.
*/
#include "../internal/errno.h" /* VLIBC_TCB_ERRNO_OFF */
#define VLIBC_TCB_SELF_OFF 0
/* VLIBC_TCB_ERRNO_OFF (8) is consumed from src/internal/errno.h. */
#define VLIBC_TCB_FENV_OFF 16
#define VLIBC_TCB_FENV_X87CW_OFF (VLIBC_TCB_FENV_OFF + 0)
#define VLIBC_TCB_FENV_MXCSR_OFF (VLIBC_TCB_FENV_OFF + 4)
#define VLIBC_TCB_SIZE 24
#endif /* VLIBC_INTERNAL_START_TCB_H */