|
33 | 33 | #ifdef OPENKAL_OPENSBI_STANDALONE |
34 | 34 |
|
35 | 35 | #include <openkal/abort.h> |
| 36 | +#include <openkal/memory.h> |
36 | 37 |
|
37 | 38 | extern "C" { |
38 | 39 |
|
@@ -70,6 +71,88 @@ void run_initialisers() { |
70 | 71 |
|
71 | 72 | } // namespace |
72 | 73 |
|
| 74 | +// ⭐⭐ THE THREAD POINTER, WHICH IS THE OTHER THING A KERNEL WOULD HAVE DONE. |
| 75 | +// |
| 76 | +// openkal-linux's start object establishes it too, and for the same reason: the |
| 77 | +// register that names the current context's thread-local storage is set by |
| 78 | +// whoever creates the context, and where a program carries no loader, that is |
| 79 | +// the implementation. |
| 80 | +// |
| 81 | +// ⚠️ WHAT IT LOOKS LIKE WHEN IT IS MISSING IS NOT "NO THREAD-LOCAL STORAGE". |
| 82 | +// |
| 83 | +// Measured 2026-08-23. A bare-metal `import std;` program started, printed, and |
| 84 | +// faulted at the first `throw`: |
| 85 | +// |
| 86 | +// fault_load epc = __cxa_throw tval = 0x80048008 |
| 87 | +// cxa_exception.cpp:284 globals->uncaughtExceptions += 1 |
| 88 | +// |
| 89 | +// `__cxa_get_globals` reads a thread_local, `tp` held whatever it held at |
| 90 | +// reset, and the load went somewhere in the firmware's own memory. Nothing |
| 91 | +// about the message says "thread pointer": it names an exception function and |
| 92 | +// an address, and both look like memory corruption. |
| 93 | +// |
| 94 | +// ⚠️ AND THE C LIBRARY DOES NOT COVER THIS. openkal-musl keeps ITS OWN thread |
| 95 | +// pointer in a variable rather than in the register --- that is what lets it |
| 96 | +// run where the register means nothing --- so musl's startup succeeding says |
| 97 | +// nothing about whether the TOOLCHAIN's thread-locals work. Two mechanisms, |
| 98 | +// and only one of them was established. |
| 99 | +// ⚠️ FROM THE LINKER SCRIPT, NOT FROM THE PROGRAM HEADERS. |
| 100 | +// |
| 101 | +// The obvious source is PT_TLS, reached through `__ehdr_start`. That requires |
| 102 | +// the ELF header to lie inside a loaded segment, which requires it to be at the |
| 103 | +// lowest loaded address --- and firmware jumps to the lowest loaded address, so |
| 104 | +// the header would be executed. Measured: the machine hangs on `\x7fELF`. |
| 105 | +// |
| 106 | +// So the script states the three measurements, exactly as it already states |
| 107 | +// where the stack and the heap are, and for the same reason: they are facts |
| 108 | +// about the image's layout and the image is the program's. A program whose |
| 109 | +// script does not define them gets no thread-local storage established, which |
| 110 | +// is correct for one whose toolchain put its thread-locals somewhere else. |
| 111 | +extern "C" { |
| 112 | +[[gnu::weak]] extern unsigned char __tls_start[]; |
| 113 | +[[gnu::weak]] extern unsigned char __tls_filesz[]; |
| 114 | +[[gnu::weak]] extern unsigned char __tls_memsz[]; |
| 115 | +} |
| 116 | + |
| 117 | +namespace { |
| 118 | + |
| 119 | +kal_uintptr round_up(kal_uintptr n, kal_uintptr to) { return (n + to - 1) & ~(to - 1); } |
| 120 | + |
| 121 | +// Variant I with no gap: on this architecture `tp` addresses the first byte of |
| 122 | +// the block and every offset the linker computed is positive from there. That |
| 123 | +// is the psABI's statement, and musl's own header for this architecture repeats |
| 124 | +// it as `TLS_ABOVE_TP` with `GAP_ABOVE_TP 0` --- which is where this was taken |
| 125 | +// from rather than from memory. |
| 126 | +void establish_thread_pointer() { |
| 127 | + // ⚠️ These symbols carry their VALUE in their ADDRESS. A linker script |
| 128 | + // assignment defines an absolute symbol; there is no object to load from, |
| 129 | + // and reading one as if there were gives whatever lies at that address. |
| 130 | + const auto filesz = reinterpret_cast<kal_uintptr>(__tls_filesz); |
| 131 | + const auto memsz = reinterpret_cast<kal_uintptr>(__tls_memsz); |
| 132 | + if (memsz == 0) return; |
| 133 | + |
| 134 | + // 16 rather than the segment's own alignment, which the script does not |
| 135 | + // state: over-aligning a block is always safe, and every offset the linker |
| 136 | + // computed stays valid because they are measured from the block's start. |
| 137 | + const kal_uintptr align = 16; |
| 138 | + // The extra room is for what a C library places beside the block. musl puts |
| 139 | + // its own descriptor below `tp`; this allocation is never returned, so being |
| 140 | + // generous costs nothing that is later wanted. |
| 141 | + const kal_uintptr bytes = round_up(memsz, align) + 128; |
| 142 | + auto* p = static_cast<unsigned char*>(kal_alloc(bytes, align)); |
| 143 | + if (p == nullptr) { |
| 144 | + static const char m[] = |
| 145 | + "openkal-opensbi: no memory for this context's thread-local storage"; |
| 146 | + kal_abort(m, sizeof m - 1); |
| 147 | + } |
| 148 | + for (kal_uintptr i = 0; i < bytes; ++i) p[i] = 0; |
| 149 | + for (kal_uintptr i = 0; i < filesz; ++i) p[i] = __tls_start[i]; |
| 150 | + |
| 151 | + __asm__ __volatile__("mv tp, %0" :: "r"(p)); |
| 152 | +} |
| 153 | + |
| 154 | +} // namespace |
| 155 | + |
73 | 156 | // ⭐ WHAT crtbegin WOULD HAVE SUPPLIED, AND WHY ITS ABSENCE IS A LINK ERROR |
74 | 157 | // ABOUT A RELOCATION RANGE RATHER THAN ABOUT A MISSING NAME. |
75 | 158 | // |
@@ -114,6 +197,10 @@ extern "C" [[noreturn]] void __okb_start_c(void) { |
114 | 197 | // disagreeing. |
115 | 198 | static char* nothing = nullptr; |
116 | 199 |
|
| 200 | + // Before anything with thread-local state runs, which includes the C |
| 201 | + // library's own initialisation. |
| 202 | + establish_thread_pointer(); |
| 203 | + |
117 | 204 | if (__libc_start_main != nullptr) { |
118 | 205 | __libc_start_main(main, 0, ¬hing, nullptr, nullptr, nullptr); |
119 | 206 | // A C library's hand-over does not return. Reaching here means one did, |
|
0 commit comments