/* * 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). / below are vlibc's own. * All diagnostics go through raw SYS_write. */ #include #include #include "../src/internal/syscall.h" /* * Manual declarations of the startup surface under test: and * 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(); }