feat(start): _start, __libc_start_main, exit/atexit/environ
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — x86_64 _start (todo 3).
|
||||||
|
*
|
||||||
|
* The kernel enters the program here with the initial stack holding, in
|
||||||
|
* order from %rsp: argc, argv[0..argc-1], a NULL argv terminator, the
|
||||||
|
* environment strings, a NULL envp terminator, and the auxiliary vector.
|
||||||
|
* The kernel leaves %rsp 16-byte aligned at entry.
|
||||||
|
*
|
||||||
|
* _start forwards (main, argc, argv, envp) to __libc_start_main, matching
|
||||||
|
* vlibc's startup signature (src/start/start.h). The frame pointer is
|
||||||
|
* zeroed first so the outermost frame terminates backtraces, and %rsp is
|
||||||
|
* 16-byte aligned at the call site per the SysV AMD64 ABI. __libc_start_main
|
||||||
|
* never returns (it ends in exit), so the hlt loop below is unreachable; it
|
||||||
|
* exists only to satisfy the assembler and to fault loudly (hlt in user
|
||||||
|
* mode raises #GP) if a broken build ever falls through.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.text
|
||||||
|
.global _start
|
||||||
|
.type _start, @function
|
||||||
|
_start:
|
||||||
|
xor %ebp, %ebp # outermost frame: zero frame pointer
|
||||||
|
mov %rsp, %r8 # keep the raw initial stack for envp
|
||||||
|
pop %rsi # rsi = argc
|
||||||
|
mov %rsp, %rdx # rdx = argv
|
||||||
|
lea 16(%r8, %rsi, 8), %rcx # rcx = envp = initial rsp + 8 + 8*(argc+1)
|
||||||
|
lea main(%rip), %rdi # rdi = &main
|
||||||
|
and $-16, %rsp # ABI: rsp % 16 == 0 at the call site
|
||||||
|
call __libc_start_main # _Noreturn: routes main's result into exit
|
||||||
|
1: hlt
|
||||||
|
jmp 1b
|
||||||
|
.size _start, . - _start
|
||||||
|
|
||||||
|
.section .note.GNU-stack, "", @progbits
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — startup test (todo 3).
|
||||||
|
*
|
||||||
|
* Exercises the static program-startup path end to end. This test is
|
||||||
|
* COMPILED INTO A STATIC VLIBC BINARY (harness commands in
|
||||||
|
* .omo/evidence/task-3-full-posix.log): glibc is never linked, so every
|
||||||
|
* exit()/atexit()/environ reference resolves to vlibc's own symbols via
|
||||||
|
* crt1.s and the src/start objects. Running it under the host libc would
|
||||||
|
* silently test glibc, not vlibc — the static link is the test.
|
||||||
|
*
|
||||||
|
* Modes (argv[1]):
|
||||||
|
* (none) atexit LIFO: register A, B, C on the atexit list, Q on the
|
||||||
|
* at_quick_exit list, then return 42 from main. exit() must run
|
||||||
|
* C, B, A in that order (reverse registration), must NOT run Q,
|
||||||
|
* and the process must exit with status 42.
|
||||||
|
* -q quick_exit(5): the at_quick_exit handler runs, the atexit
|
||||||
|
* handler must NOT. Status must be 5.
|
||||||
|
* -z _exit(7): neither handler list runs at all. Status 7.
|
||||||
|
* -e argv/env plumbing: prints argc and argv, checks
|
||||||
|
* VLIBC_TEST_VAR=hello in environ. Status 0 on match.
|
||||||
|
* -t main-thread TLS + errno: a file-scope __thread int set in main
|
||||||
|
* must read back through a separate function (exercises the
|
||||||
|
* PT_TLS copy path), and errno must round-trip through the TCB
|
||||||
|
* slot (proves the bootstrap ran before the first libc call).
|
||||||
|
* -x handler-list limits: 32 registrations fit, the 33rd returns
|
||||||
|
* -1, and atexit() during exit is gracefully refused (-1).
|
||||||
|
*
|
||||||
|
* No host headers: -Iinclude shadows GCC's internal headers (see
|
||||||
|
* tests/test_strerror.c). <stddef.h>/<errno.h> below are vlibc's own.
|
||||||
|
* All diagnostics go through raw SYS_write.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manual declarations of the startup surface under test: <stdlib.h> and
|
||||||
|
* <unistd.h> are later todos; consumers today declare exactly these names,
|
||||||
|
* matching src/start/start.h.
|
||||||
|
*/
|
||||||
|
[[noreturn]] void
|
||||||
|
exit(int status);
|
||||||
|
[[noreturn]] void
|
||||||
|
_Exit(int status);
|
||||||
|
[[noreturn]] void
|
||||||
|
_exit(int status);
|
||||||
|
[[noreturn]] void
|
||||||
|
quick_exit(int status);
|
||||||
|
int
|
||||||
|
atexit(void (*func)(void));
|
||||||
|
int
|
||||||
|
at_quick_exit(void (*func)(void));
|
||||||
|
extern char **environ;
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||||
|
static void
|
||||||
|
say(int fd, const char *s)
|
||||||
|
{
|
||||||
|
long n = 0;
|
||||||
|
|
||||||
|
while (s[n] != '\0')
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, fd, (long)s, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write v in decimal to fd. */
|
||||||
|
static void
|
||||||
|
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
char buf[24];
|
||||||
|
int i = (int)sizeof(buf);
|
||||||
|
|
||||||
|
buf[--i] = '\0';
|
||||||
|
do
|
||||||
|
{
|
||||||
|
buf[--i] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
} while (v != 0);
|
||||||
|
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check(int cond, const char *what)
|
||||||
|
{
|
||||||
|
if (!cond)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: ");
|
||||||
|
say(2, what);
|
||||||
|
say(2, "\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True when s is exactly lit. Hand-rolled: this test keeps deps minimal. */
|
||||||
|
static int
|
||||||
|
str_is(const char *s, const char *lit)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (lit[i] != '\0' && s[i] == lit[i])
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return lit[i] == '\0' && s[i] == '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True when environ holds an entry spelling "name=want". */
|
||||||
|
static int // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
env_matches(const char *name, const char *want)
|
||||||
|
{
|
||||||
|
for (char **e = environ; e != 0 && *e != 0; e++)
|
||||||
|
{
|
||||||
|
const char *s = *e;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (name[i] != '\0' && s[i] == name[i])
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (name[i] == '\0' && s[i] == '=')
|
||||||
|
{
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
while (want[j] != '\0' && s[i + 1 + j] == want[j])
|
||||||
|
{
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (want[j] == '\0' && s[i + 1 + j] == '\0')
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exit handlers. Each prints one distinguishing marker line. */
|
||||||
|
static void
|
||||||
|
hA(void)
|
||||||
|
{
|
||||||
|
say(1, "A\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hB(void)
|
||||||
|
{
|
||||||
|
say(1, "B\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hC(void)
|
||||||
|
{
|
||||||
|
say(1, "C\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hQ(void)
|
||||||
|
{
|
||||||
|
say(1, "Q\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
hNoop(void)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/* Registered as one of the 32 in -x mode: registration during exit must fail. */
|
||||||
|
static void
|
||||||
|
hRegisterDuringExit(void)
|
||||||
|
{
|
||||||
|
if (atexit(hA) == 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: atexit() during exit accepted (want graceful -1)\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File-scope __thread objects: live in PT_TLS, every access is %fs-relative
|
||||||
|
* with a link-time offset. tls_preinit tests the .tdata image copy (nonzero
|
||||||
|
* initial value), tls_counter tests the .tbss zeroing plus the write/read
|
||||||
|
* round trip. The reads go through noinline helpers on opaque pointers:
|
||||||
|
* a direct read of a static TLS object whose initializer the compiler can
|
||||||
|
* see gets constant-folded, which would delete the variable (and with it
|
||||||
|
* the PT_TLS image) and silently untest the copy path. The address-of and
|
||||||
|
* the helper-force a real TLS relocation and a real memory load.
|
||||||
|
*/
|
||||||
|
static __thread int tls_preinit = 42;
|
||||||
|
static __thread int tls_counter;
|
||||||
|
|
||||||
|
static int __attribute__((noinline))
|
||||||
|
tls_deref(int *p)
|
||||||
|
{
|
||||||
|
/* Opaque barrier: prevents interprocedural folding of *p to 42, which
|
||||||
|
* would delete the TLS image (see the block comment above). */
|
||||||
|
__asm__ volatile("" : "+r"(p));
|
||||||
|
return *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __attribute__((noinline))
|
||||||
|
tls_readback(void)
|
||||||
|
{
|
||||||
|
return tls_counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scenario 1: atexit LIFO. exit() must run the LAST registered handler
|
||||||
|
* first and skip the at_quick_exit list entirely.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
lifo_scenario(void)
|
||||||
|
{
|
||||||
|
atexit(hA);
|
||||||
|
atexit(hB);
|
||||||
|
atexit(hC);
|
||||||
|
at_quick_exit(hQ); /* must NOT run: no "Q" on stdout */
|
||||||
|
return 42; /* routed into exit(42) by __libc_start_main */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenario 2: quick_exit runs ONLY at_quick_exit handlers. */
|
||||||
|
static int
|
||||||
|
quick_scenario(void)
|
||||||
|
{
|
||||||
|
atexit(hA); /* must NOT run: no "A" on stdout */
|
||||||
|
at_quick_exit(hQ);
|
||||||
|
quick_exit(5);
|
||||||
|
return 1; /* unreachable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenario 3: _exit runs NO handlers at all. */
|
||||||
|
static int
|
||||||
|
raw_exit_scenario(void)
|
||||||
|
{
|
||||||
|
atexit(hA);
|
||||||
|
at_quick_exit(hQ);
|
||||||
|
_exit(7);
|
||||||
|
return 1; /* unreachable */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenario 4: argc/argv/environ plumbing from the kernel stack. */
|
||||||
|
static int
|
||||||
|
env_scenario(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 3)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: argc != 3 (harness must pass -e plus one argument)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "argc=");
|
||||||
|
say_dec(1, (unsigned long)argc);
|
||||||
|
say(1, "\nargv0=");
|
||||||
|
say(1, argv[0]);
|
||||||
|
say(1, "\nargv1=");
|
||||||
|
say(1, argv[1]);
|
||||||
|
say(1, "\nargv2=");
|
||||||
|
say(1, argv[2]);
|
||||||
|
say(1, "\n");
|
||||||
|
check(str_is(argv[1], "-e"), "argv[1] is the -e mode flag");
|
||||||
|
check(str_is(argv[2], "extraarg"), "argv[2] == \"extraarg\"");
|
||||||
|
check(env_matches("VLIBC_TEST_VAR", "hello"), "environ contains VLIBC_TEST_VAR=hello");
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenario 5: main-thread TLS image + TCB errno slot. */
|
||||||
|
static int
|
||||||
|
tls_scenario(void)
|
||||||
|
{
|
||||||
|
check(tls_deref(&tls_preinit) == 42, "initialized TLS image copied (.tdata value 42)");
|
||||||
|
check(tls_counter == 0, "TLS BSS tail zeroed (.tbss value 0)");
|
||||||
|
tls_counter = 777;
|
||||||
|
check(tls_readback() == 777, "__thread round trip across a function call");
|
||||||
|
errno = 123;
|
||||||
|
check(errno == 123, "errno round trip through the TCB slot");
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenario 6: fixed handler-list limits and the exiting guard. */
|
||||||
|
static int
|
||||||
|
limit_scenario(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 31; i++)
|
||||||
|
{
|
||||||
|
if (atexit(hNoop) != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: atexit slot accounting broke before the list was full\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (atexit(hRegisterDuringExit) != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: 32nd atexit registration rejected (list should just fit)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
check(atexit(hA) == -1, "33rd atexit() returns -1 (fixed list full)");
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc >= 2)
|
||||||
|
{
|
||||||
|
if (argv[1][0] == '-' && argv[1][1] == 'q')
|
||||||
|
{
|
||||||
|
return quick_scenario();
|
||||||
|
}
|
||||||
|
if (argv[1][0] == '-' && argv[1][1] == 'z')
|
||||||
|
{
|
||||||
|
return raw_exit_scenario();
|
||||||
|
}
|
||||||
|
if (argv[1][0] == '-' && argv[1][1] == 'e')
|
||||||
|
{
|
||||||
|
return env_scenario(argc, argv);
|
||||||
|
}
|
||||||
|
if (argv[1][0] == '-' && argv[1][1] == 't')
|
||||||
|
{
|
||||||
|
return tls_scenario();
|
||||||
|
}
|
||||||
|
if (argv[1][0] == '-' && argv[1][1] == 'x')
|
||||||
|
{
|
||||||
|
return limit_scenario();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lifo_scenario();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user