feat(start): _start, __libc_start_main, exit/atexit/environ
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
#include "../internal/libc.h"
|
||||
#include "start.h"
|
||||
|
||||
/*
|
||||
* vlibc — environ (todo 3).
|
||||
*
|
||||
* POSIX exposes the process environment as the global `environ`: a pointer
|
||||
* to the array of "name=value" strings, terminated by a NULL pointer. The
|
||||
* kernel places the initial environment on the stack at exec time; crt1.s
|
||||
* forwards the envp pointer to __libc_start_main, which stores it here
|
||||
* before main runs, so consumers see a fully initialized environment from
|
||||
* the first line of main.
|
||||
*
|
||||
* environ is the single storage object. __environ is an internal hidden
|
||||
* alias of that same storage: library code (getenv/setenv, todo 13) goes
|
||||
* through the internal name so the public symbol is never referenced from
|
||||
* inside the library, and setenv mutating environ is visible through
|
||||
* __environ and vice versa. A plain exported mutable global is exactly what
|
||||
* POSIX requires (setenv replaces the array pointer).
|
||||
*
|
||||
* exit() deliberately does not touch environ: the strings and pointer array
|
||||
* live on the kernel-provided initial stack, not the heap — there is
|
||||
* nothing to free.
|
||||
*/
|
||||
|
||||
char **environ = 0;
|
||||
|
||||
/*
|
||||
* Internal alias of environ: same storage, hidden visibility. The alias is
|
||||
* what makes the two spellings indistinguishable to setenv/getenv.
|
||||
*/
|
||||
hidden extern __typeof(environ) __environ
|
||||
__attribute__((__alias__("environ"))); // NOLINT(bugprone-reserved-identifier)
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "../internal/syscall.h"
|
||||
#include "start.h"
|
||||
|
||||
/*
|
||||
* vlibc — exit/_Exit/_exit/quick_exit/atexit/at_quick_exit (todo 3).
|
||||
*
|
||||
* Handler storage is a FIXED pre-allocated array (VLIBC_EXIT_HANDLERS_MAX
|
||||
* entries): exit must work when malloc cannot, and the allocator is a later
|
||||
* todo — no allocation happens here, ever. The atexit list and the
|
||||
* at_quick_exit list are STRICTLY SEPARATE per POSIX: exit() runs only the
|
||||
* atexit list, quick_exit() only the at_quick_exit list, never both.
|
||||
*
|
||||
* Order: both lists run in reverse registration order (LIFO) — the handler
|
||||
* registered last runs first (C11 7.22.4.4, POSIX exit()).
|
||||
*
|
||||
* Registration once termination has begun is undefined behavior per POSIX.
|
||||
* Rather than corrupt the list or crash, atexit/at_quick_exit gracefully
|
||||
* refuse: the terminating flag makes them return -1. Registering a 33rd
|
||||
* handler (list full) also returns -1, the POSIX failure convention. A
|
||||
* handler calling exit() again is likewise UB; the count-based loops below
|
||||
* simply keep running the remaining handlers — deterministic, no crash.
|
||||
*
|
||||
* Termination always goes through SYS_exit_group, so every thread of the
|
||||
* process dies — the correct semantics once threading lands (#44).
|
||||
*
|
||||
* exit() is required to flush all open streams and remove tmpfile()
|
||||
* creations before terminating. No streams exist yet: the stdio flush hook
|
||||
* belongs to the stdio todo (#28) and slots in at the marker below.
|
||||
*/
|
||||
|
||||
#define VLIBC_EXIT_HANDLERS_MAX 32
|
||||
|
||||
struct exit_handler_list
|
||||
{
|
||||
void (*fns[VLIBC_EXIT_HANDLERS_MAX])(void);
|
||||
int count;
|
||||
};
|
||||
|
||||
static struct exit_handler_list atexit_list;
|
||||
static struct exit_handler_list quick_list;
|
||||
static int terminating;
|
||||
|
||||
int
|
||||
atexit(void (*func)(void))
|
||||
{
|
||||
if (func == 0 || terminating || atexit_list.count >= VLIBC_EXIT_HANDLERS_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
atexit_list.fns[atexit_list.count] = func;
|
||||
atexit_list.count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
at_quick_exit(void (*func)(void))
|
||||
{
|
||||
if (func == 0 || terminating || quick_list.count >= VLIBC_EXIT_HANDLERS_MAX)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
quick_list.fns[quick_list.count] = func;
|
||||
quick_list.count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
exit(int status)
|
||||
{
|
||||
terminating = 1;
|
||||
while (atexit_list.count > 0)
|
||||
{
|
||||
atexit_list.count--;
|
||||
atexit_list.fns[atexit_list.count]();
|
||||
}
|
||||
/* stdio hook (#28): flush + close all streams, remove tmpfile files. */
|
||||
_exit(status);
|
||||
}
|
||||
|
||||
void
|
||||
quick_exit(int status)
|
||||
{
|
||||
terminating = 1;
|
||||
while (quick_list.count > 0)
|
||||
{
|
||||
quick_list.count--;
|
||||
quick_list.fns[quick_list.count]();
|
||||
}
|
||||
_exit(status);
|
||||
}
|
||||
|
||||
void
|
||||
_exit(int status)
|
||||
{
|
||||
__syscall1(SYS_exit_group, (long)status);
|
||||
/*
|
||||
* exit_group never returns for a valid status; the loop is defensive
|
||||
* (and satisfies the noreturn contract without unreachable-code UB).
|
||||
*/
|
||||
for (;;)
|
||||
{
|
||||
__syscall1(SYS_exit, (long)status);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_Exit(int status)
|
||||
{
|
||||
_exit(status);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "start.h"
|
||||
|
||||
/*
|
||||
* vlibc — __libc_start_main (todo 3).
|
||||
*
|
||||
* Called by crt1.s's _start with (main, argc, argv, envp) after the kernel
|
||||
* has placed argc/argv/envp/auxv on the initial stack. In order:
|
||||
*
|
||||
* 1. install environ from the kernel-provided envp — before main runs, so
|
||||
* consumers see the environment from the first line of main;
|
||||
* 2. bootstrap main-thread TLS (static case; init_main_tls skips itself
|
||||
* when the FS thread pointer is already set, the dynamic-loader #62
|
||||
* case);
|
||||
* 3. call main(argc, argv);
|
||||
* 4. route main's return value into exit().
|
||||
*
|
||||
* Never returns. The signature is vlibc's own; crt1.s and the future
|
||||
* dynamic loader (#62) must match it.
|
||||
*/
|
||||
|
||||
void
|
||||
__libc_start_main(
|
||||
int (*main_fn)(int, char **, char **), int argc, char **argv,
|
||||
char **envp) // NOLINT(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
|
||||
{
|
||||
environ = envp;
|
||||
init_main_tls(envp);
|
||||
exit(main_fn(argc, argv, envp));
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef VLIBC_INTERNAL_START_H
|
||||
#define VLIBC_INTERNAL_START_H
|
||||
|
||||
/*
|
||||
* vlibc — program startup / termination surface (todo 3).
|
||||
*
|
||||
* The canonical prototypes of the startup ABI. These names are mandated by
|
||||
* the C standard and POSIX, not invented by vlibc: exit/_Exit/atexit/
|
||||
* at_quick_exit belong to <stdlib.h> and _exit to <unistd.h>, environ is
|
||||
* the POSIX process environment global, and __libc_start_main is the CRT
|
||||
* ABI entry point that crt1.s calls. Once <stdlib.h>/<unistd.h> land (later
|
||||
* todos) their declarations move there; internal code keeps including this
|
||||
* header until then, and consumers declare the names themselves.
|
||||
*
|
||||
* The standard mandates several names in the implementation- or
|
||||
* standard-reserved namespace (_Exit, _exit, at_quick_exit,
|
||||
* __libc_start_main, __environ); the NOLINT waiver below covers exactly
|
||||
* those mandated names, nothing else.
|
||||
*/
|
||||
|
||||
#include "../internal/libc.h"
|
||||
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
|
||||
|
||||
/* Termination (src/start/exit.c). Never return. */
|
||||
void
|
||||
exit(int status) __attribute__((__noreturn__));
|
||||
void
|
||||
_Exit(int status) __attribute__((__noreturn__));
|
||||
void
|
||||
_exit(int status) __attribute__((__noreturn__));
|
||||
void
|
||||
quick_exit(int status) __attribute__((__noreturn__));
|
||||
|
||||
/* Handler registration (src/start/exit.c). 0 on success, -1 on failure. */
|
||||
int
|
||||
atexit(void (*func)(void));
|
||||
int
|
||||
at_quick_exit(void (*func)(void));
|
||||
|
||||
/*
|
||||
* CRT ABI entry point (src/start/libc_start_main.c), called by crt1.s with
|
||||
* (main, argc, argv, envp). Never returns: it routes main's result into
|
||||
* exit(). The future dynamic loader (#62) must use the same signature.
|
||||
*/
|
||||
void
|
||||
__libc_start_main(int (*main_fn)(int, char **, char **), int argc, char **argv, char **envp)
|
||||
__attribute__((__noreturn__));
|
||||
|
||||
/* The POSIX process environment (src/start/environ.c). */
|
||||
extern char **environ;
|
||||
|
||||
/* Internal alias of the same storage, hidden visibility (src/start/environ.c). */
|
||||
extern char **__environ;
|
||||
|
||||
// NOLINTEND(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
|
||||
|
||||
/*
|
||||
* Main-thread TLS bootstrap (src/start/tls.c): parse the auxv for the
|
||||
* program headers, find PT_TLS, allocate the static TLS block + TCB, copy
|
||||
* the image, and install the FS thread pointer via arch_prctl(ARCH_SET_FS).
|
||||
* Skips itself when FS is already set (the dynamic-loader case, #62).
|
||||
*/
|
||||
hidden void
|
||||
init_main_tls(char **envp);
|
||||
|
||||
#endif /* VLIBC_INTERNAL_START_H */
|
||||
@@ -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 */
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
#include "start.h"
|
||||
#include "tcb.h"
|
||||
|
||||
/*
|
||||
* vlibc — main-thread TLS bootstrap (todo 3).
|
||||
*
|
||||
* Makes the TCB-slot errno (and any other TCB state) work from the very
|
||||
* first libc call in a STATICALLY linked program. The dynamic case is
|
||||
* explicitly out of scope here: when the FS thread pointer is already set
|
||||
* (the dynamic loader #62 installed it before transferring control), this
|
||||
* function detects that and returns without touching anything.
|
||||
*
|
||||
* Mechanism:
|
||||
* - The kernel puts the auxiliary vector on the initial stack right after
|
||||
* envp's NULL terminator; AT_PHDR/AT_PHENT/AT_PHNUM locate the ELF
|
||||
* program headers, which are searched for PT_TLS.
|
||||
* - One anonymous private mmap holds the whole static TLS area: the TLS
|
||||
* image at the base, the TCB at the top. The placement formula (tp =
|
||||
* base + round_up(p_memsz, p_align)) is the exact arithmetic the linker
|
||||
* baked into every %fs-relative offset; see tcb.h for the contract.
|
||||
* - The image is copied from memory at p_vaddr: for a static non-PIE
|
||||
* executable GNU ld lays PT_TLS out inside the RW PT_LOAD, so the
|
||||
* template is already mapped (verified empirically). When no PT_LOAD
|
||||
* covers the segment — a layout this project does not produce — the
|
||||
* file fallback reads the segment from /proc/self/exe via pread.
|
||||
* - The TCB's self pointer, errno slot, and fenv defaults are written,
|
||||
* then arch_prctl(ARCH_SET_FS) installs TP. From that instruction on,
|
||||
* __builtin_thread_pointer() (which reads %fs:0) returns TP and errno
|
||||
* works.
|
||||
*
|
||||
* No errno is touched before arch_prctl: errno lives in the TCB, which does
|
||||
* not exist until this function creates it — a pre-TLS errno read would
|
||||
* dereference %fs:0 with the kernel's post-exec FS base of 0 and fault on
|
||||
* the unmapped NULL page. Failures therefore terminate hard (exit 127 via
|
||||
* exit_group) instead of reporting through errno.
|
||||
*/
|
||||
|
||||
/* arch_prctl codes (kernel UAPI asm/prctl.h). */
|
||||
#define ARCH_SET_FS 0x1002
|
||||
#define ARCH_GET_FS 0x1003
|
||||
|
||||
/* auxv a_type values (kernel UAPI asm/auxvec.h). AT_RANDOM is deliberately
|
||||
* not consumed: nothing in this todo needs a per-process random seed. */
|
||||
#define AT_NULL 0
|
||||
#define AT_PHDR 3
|
||||
#define AT_PHENT 4
|
||||
#define AT_PHNUM 5
|
||||
|
||||
/* ELF program header p_type values (kernel UAPI linux/elf.h). */
|
||||
#define PT_LOAD 1
|
||||
#define PT_TLS 7
|
||||
|
||||
/* mmap/openat/pread constants (kernel UAPI). */
|
||||
#define PROT_READ 0x1
|
||||
#define PROT_WRITE 0x2
|
||||
#define MAP_PRIVATE 0x2
|
||||
#define MAP_ANONYMOUS 0x20
|
||||
#define AT_FDCWD (-100)
|
||||
#define O_RDONLY 0
|
||||
#define O_CLOEXEC 0x80000
|
||||
|
||||
/* ELF64 program header, exactly as laid out in the file/memory image. */
|
||||
struct elf64_phdr
|
||||
{
|
||||
uint32_t p_type;
|
||||
uint32_t p_flags;
|
||||
uint64_t p_offset;
|
||||
uint64_t p_vaddr;
|
||||
uint64_t p_paddr;
|
||||
uint64_t p_filesz;
|
||||
uint64_t p_memsz;
|
||||
uint64_t p_align;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
static size_t
|
||||
round_up(size_t v, size_t a)
|
||||
{
|
||||
return (v + a - 1) & ~(a - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hard-stop for unrecoverable bootstrap failures (mmap ENOMEM, a TLS image
|
||||
* that is neither mapped nor readable). exit 127 — errno is unavailable
|
||||
* here, because the TCB that would hold it does not exist yet.
|
||||
*/
|
||||
static _Noreturn void
|
||||
tls_fail(void)
|
||||
{
|
||||
__syscall1(SYS_exit_group, 127);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/*
|
||||
* The helpers below take structurally similar out-parameters (auxv and ELF
|
||||
* layout values), so the easily-swappable-parameters check is waived for
|
||||
* this section: these are kernel/ELF ABI shapes, not caller-facing APIs.
|
||||
*/
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
|
||||
/*
|
||||
* Walk the auxiliary vector on the initial stack: envp points at the first
|
||||
* environment string, the auxv follows envp's NULL terminator as pairs of
|
||||
* (type, value) longs, ending at AT_NULL. The kernel wrote these on the
|
||||
* stack at exec time, so the ORIGINAL envp (never the possibly-mutated
|
||||
* environ global) must be used.
|
||||
*/
|
||||
static void
|
||||
find_auxv(char **envp, uintptr_t *phdr, uintptr_t *phent, uintptr_t *phnum)
|
||||
{
|
||||
while (*envp != 0)
|
||||
{
|
||||
envp++;
|
||||
}
|
||||
envp++; /* step over the NULL terminator */
|
||||
for (uintptr_t *aux = (uintptr_t *)envp; aux[0] != AT_NULL; aux += 2)
|
||||
{
|
||||
if (aux[0] == AT_PHDR)
|
||||
{
|
||||
*phdr = aux[1];
|
||||
}
|
||||
else if (aux[0] == AT_PHENT)
|
||||
{
|
||||
*phent = aux[1];
|
||||
}
|
||||
else if (aux[0] == AT_PHNUM)
|
||||
{
|
||||
*phnum = aux[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Search the program headers for PT_TLS and check whether the image is
|
||||
* already mapped (some PT_LOAD covers [p_vaddr, p_vaddr + p_filesz)).
|
||||
* Returns 1 when PT_TLS exists, filling the out parameters.
|
||||
*/
|
||||
static int
|
||||
find_tls(uintptr_t phdr, uintptr_t phent, uintptr_t phnum, uint64_t *offset, uint64_t *vaddr,
|
||||
size_t *filesz, size_t *memsz, size_t *align, int *mapped)
|
||||
{
|
||||
int found = 0;
|
||||
uint64_t lo = 0;
|
||||
uint64_t hi = 0;
|
||||
|
||||
for (uintptr_t i = 0; i < phnum; i++)
|
||||
{
|
||||
const struct elf64_phdr *p = (const struct elf64_phdr *)(phdr + i * phent);
|
||||
if (p->p_type == PT_TLS)
|
||||
{
|
||||
*offset = p->p_offset;
|
||||
*vaddr = p->p_vaddr;
|
||||
*filesz = (size_t)p->p_filesz;
|
||||
*memsz = (size_t)p->p_memsz;
|
||||
*align = (size_t)p->p_align;
|
||||
lo = p->p_vaddr;
|
||||
hi = p->p_vaddr + p->p_filesz;
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
*mapped = 0;
|
||||
if (found)
|
||||
{
|
||||
for (uintptr_t i = 0; i < phnum; i++)
|
||||
{
|
||||
const struct elf64_phdr *p = (const struct elf64_phdr *)(phdr + i * phent);
|
||||
if (p->p_type == PT_LOAD && p->p_vaddr <= lo && hi <= p->p_vaddr + p->p_memsz)
|
||||
{
|
||||
*mapped = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy the initial TLS image (the .tdata template; .tbss is zeroed by the
|
||||
* caller). Primary path: memcpy from the mapped image. Fallback: pread the
|
||||
* segment out of /proc/self/exe — immune to chdir, works even for deleted
|
||||
* executables, no host libc involved.
|
||||
*/
|
||||
static void
|
||||
copy_tls_image(char *dst, uint64_t offset, uint64_t vaddr, size_t filesz, int mapped)
|
||||
{
|
||||
if (mapped)
|
||||
{
|
||||
memcpy(dst, (const void *)vaddr, filesz);
|
||||
return;
|
||||
}
|
||||
|
||||
long fd = __syscall3(SYS_openat, AT_FDCWD, (long)"/proc/self/exe", O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
{
|
||||
tls_fail();
|
||||
}
|
||||
size_t done = 0;
|
||||
while (done < filesz)
|
||||
{
|
||||
long r = __syscall4(SYS_pread64, fd, (long)(dst + done), (long)(filesz - done),
|
||||
(long)(offset + done));
|
||||
if (r <= 0)
|
||||
{
|
||||
tls_fail();
|
||||
}
|
||||
done += (size_t)r;
|
||||
}
|
||||
__syscall1(SYS_close, fd);
|
||||
}
|
||||
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
void
|
||||
init_main_tls(char **envp)
|
||||
{
|
||||
uintptr_t fs_base = 0;
|
||||
uintptr_t phdr = 0, phent = 0, phnum = 0;
|
||||
uint64_t offset = 0, vaddr = 0;
|
||||
size_t filesz = 0, memsz = 0, align = 16;
|
||||
int mapped = 0;
|
||||
size_t block, total;
|
||||
uintptr_t base, tp;
|
||||
char *image;
|
||||
|
||||
/*
|
||||
* Static-case guard. arch_prctl(ARCH_GET_FS) is used instead of
|
||||
* __builtin_thread_pointer() because the latter dereferences %fs:0,
|
||||
* and with the kernel's post-exec FS base of 0 that read faults on the
|
||||
* unmapped NULL page. A non-zero FS base means the dynamic loader (#62)
|
||||
* already installed a TCB: leave it alone.
|
||||
*/
|
||||
__syscall2(SYS_arch_prctl, ARCH_GET_FS, (long)&fs_base);
|
||||
if (fs_base != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
find_auxv(envp, &phdr, &phent, &phnum);
|
||||
if (phdr != 0 && phent != 0 &&
|
||||
!find_tls(phdr, phent, phnum, &offset, &vaddr, &filesz, &memsz, &align, &mapped))
|
||||
{
|
||||
/* No TLS in this program: TCB-only block, arbitrary alignment. */
|
||||
filesz = 0;
|
||||
memsz = 0;
|
||||
align = 16;
|
||||
}
|
||||
|
||||
/*
|
||||
* Single allocation: TLS image at the base, TCB at tp. tp uses exactly
|
||||
* p_align (never bumped): the linker bakes tpoff against
|
||||
* round_up(p_memsz, p_align) and any other rounding breaks the
|
||||
* %fs-relative offsets (see tcb.h).
|
||||
*/
|
||||
block = round_up(memsz, align);
|
||||
total = block + VLIBC_TCB_SIZE;
|
||||
|
||||
base = (uintptr_t)__syscall6(SYS_mmap, 0, (long)total, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if ((long)base < 0 && (long)base > -4096)
|
||||
{
|
||||
/* ENOMEM and friends: no errno possible yet (the TCB IS errno). */
|
||||
tls_fail();
|
||||
}
|
||||
|
||||
tp = base + block;
|
||||
image = (char *)base;
|
||||
|
||||
if (filesz > 0)
|
||||
{
|
||||
copy_tls_image(image, offset, vaddr, filesz, mapped);
|
||||
}
|
||||
if (memsz > filesz)
|
||||
{
|
||||
memset(image + filesz, 0, memsz - filesz);
|
||||
}
|
||||
|
||||
/* Build the TCB at TP (authoritative layout in tcb.h). */
|
||||
((uintptr_t *)tp)[VLIBC_TCB_SELF_OFF / sizeof(uintptr_t)] = tp;
|
||||
*(int *)(tp + VLIBC_TCB_ERRNO_OFF) = 0;
|
||||
*(uint16_t *)(tp + VLIBC_TCB_FENV_X87CW_OFF) = 0x037F; /* x87 reset default */
|
||||
*(uint32_t *)(tp + VLIBC_TCB_FENV_MXCSR_OFF) = 0x1F80; /* MXCSR reset default */
|
||||
|
||||
/* From here on the TCB (and errno) is live. Install the thread pointer. */
|
||||
__syscall2(SYS_arch_prctl, ARCH_SET_FS, (long)tp);
|
||||
}
|
||||
Reference in New Issue
Block a user