Skip to content

Commit 96e9861

Browse files
committed
feat: 线程指针也要有人建立,而它缺席时的报错不提线程指针
openkal-linux 的 start 对象会建立它,理由一样:命名当前上下文线程局部存储的 那个寄存器由创建上下文的人设置,而程序不带加载器时,那个人就是实现。 ⚠️ 它缺席时看起来不像「没有线程局部存储」。实测 2026-08-23:裸机 import std 的程序启动了、打印了,在第一次 throw 时挂掉: fault_load epc = __cxa_throw tval = 0x80048008 cxa_exception.cpp:284 globals->uncaughtExceptions += 1 __cxa_get_globals 读的是一个 thread_local,tp 里是复位时留下的东西,那次读落 进了固件自己的内存。这条消息里没有一个字提到线程指针:它报的是一个异常函数和 一个地址,两样看起来都像内存损坏。 ⚠️ 而 C 库盖不住这件事。openkal-musl 把**它自己的**线程指针放在变量里而不是 寄存器里 —— 那正是它能跑在「寄存器没有意义」的地方的原因 —— 所以 musl 启动 成功完全说明不了**工具链的**线程局部存储能不能用。两套机制,只建立了一套。 ⚠️ 三个尺寸从链接脚本来,不从程序头来:走程序头要 __ehdr_start,那要求 ELF 头 落在某个已加载段里,而那要求它在最低的加载地址上 —— 固件跳的就是最低加载地址, 于是头会被当成指令执行。实测:机器停在 \x7fELF 上。 同时:堆的位置也改成可由链接脚本给(见上一条),以及补上 __dso_handle。
1 parent 589272d commit 96e9861

2 files changed

Lines changed: 91 additions & 1 deletion

File tree

src/kal.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ namespace {
143143
struct region { unsigned char* base; kal_uintptr size; };
144144

145145
region heap_region() {
146-
if (__heap_start != nullptr && __heap_end > __heap_start)
146+
// ⚠️ `+` on each: these are arrays, and comparing two arrays directly is
147+
// deprecated in this dialect because it compares addresses while reading
148+
// like a comparison of contents. Decaying them says which was meant.
149+
if (+__heap_start != nullptr && +__heap_end > +__heap_start)
147150
return { __heap_start, (kal_uintptr)(__heap_end - __heap_start) };
148151
return { g_heap, sizeof g_heap };
149152
}

src/start.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#ifdef OPENKAL_OPENSBI_STANDALONE
3434

3535
#include <openkal/abort.h>
36+
#include <openkal/memory.h>
3637

3738
extern "C" {
3839

@@ -70,6 +71,88 @@ void run_initialisers() {
7071

7172
} // namespace
7273

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+
73156
// ⭐ WHAT crtbegin WOULD HAVE SUPPLIED, AND WHY ITS ABSENCE IS A LINK ERROR
74157
// ABOUT A RELOCATION RANGE RATHER THAN ABOUT A MISSING NAME.
75158
//
@@ -114,6 +197,10 @@ extern "C" [[noreturn]] void __okb_start_c(void) {
114197
// disagreeing.
115198
static char* nothing = nullptr;
116199

200+
// Before anything with thread-local state runs, which includes the C
201+
// library's own initialisation.
202+
establish_thread_pointer();
203+
117204
if (__libc_start_main != nullptr) {
118205
__libc_start_main(main, 0, &nothing, nullptr, nullptr, nullptr);
119206
// A C library's hand-over does not return. Reaching here means one did,

0 commit comments

Comments
 (0)