331 lines
8.5 KiB
C
331 lines
8.5 KiB
C
/*
|
|
* 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();
|
|
}
|