From 4f4c9db5b84293c6a1cf22fb5ac1c06446d71b6c Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 3 Oct 2026 02:34:36 +0800 Subject: [PATCH] 0.16.1 --- the thread-local image is placed where the linker measured it 0.16.0 added this implementation's own storage for the region a context stands on, and that storage made a latent defect of `src/tls.h` visible. Nothing about the region is wrong; what is repaired is where the program's thread-local image is copied. EVERY THREAD-LOCAL ADDRESS IS `tp + st_value - tls_size', where `tls_size' is the block the LINKER laid out: `p_memsz' rounded up to the segment's alignment. `describe_tls' clamped that alignment up to sixteen before recording it, and the clamp therefore reached the SIZE, which must not have it. Measured 2026-10-03 on a segment stating `p_align = 8, p_memsz = 56': the linker put the variables at `tp - 56' and the region was built 64 bytes deep, so the image sat eight bytes below the variables that name it. WHAT THAT LOOKS LIKE FROM ABOVE. Two thread-local variables, each declared with a different value, read each other's bytes. And a C++ program's `thread_local` object --- whose guard byte lives in the same image --- can find that byte non-zero and never run its constructor at all: openkal-llvm-runtime's own probe reported exactly that, on the runner, as `a thread_local is constructed in a spawned thread: FAIL`. WHY IT SURFACED NOW, STATED RATHER THAN GLOSSED. The defect is as old as the clamp, and this implementation's own thread-local storage had been one four-byte variable, which sits at the very end of the segment and was copied correctly by luck. A twenty-four-byte one moved the image far enough to be read by the wrong variable. The specification package's own kit test therefore passed throughout, and the test that now observes this --- `tests/conformance_task_tls.cpp`, with a non-zero initialiser and a four-kilobyte block --- was added by 0.16.0 and did not catch it either, because the alignment it happened to be compiled with was sixteen. THE CLAMP NOW APPLIES TO THE ALLOCATION, WHICH WANTS SIXTEEN, AND NOT TO THE SIZE, WHICH IS THE LINKER'S. `describe_tls' keeps the segment's alignment as the loader stated it; `make_tls' rounds the size by that and asks the allocator for sixteen. Verified against a probe of two initialised thread-local variables, in the context the program started on and in a started one --- both now read their own values --- by the kit, by this package's tests, by the conformance suite, and by openkal-llvm-runtime's `examples/cxx', which is where the defect was seen. --- README.md | 19 ++++++++++++++++++- mcpp.toml | 2 +- src/tls.h | 51 ++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3c97023..2b300f5 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ for Linux, written on the kernel's own system-call interface. openkal = "0.15.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.16.0" +openkal-linux = "0.16.1" ``` ## Why it does not use a C library @@ -85,6 +85,23 @@ A program that carries a runtime of its own answers from that runtime instead (`pthread_getattr_np`), because in that arrangement the runtime is what owns the stacks and is what a program above openkal would ask for itself. +## A defect this version repairs + +0.16.0 added the storage this implementation keeps for the region a context +stands on, and that storage made a latent defect of `src/tls.h` visible: the +thread-local segment's alignment was clamped up to sixteen where the block's +SIZE is computed, and the size has to be the one the linker measured every +variable's offset against. On a segment stating `p_align = 8, p_memsz = 56` the +linker laid the variables out at `tp - 56` while the region was built 64 bytes +deep, so the image of the program's thread-local storage sat eight bytes below +the variables that name it. A program whose thread-local variables are +initialised then reads another variable's bytes, and a C++ program's +`thread_local` object can find its guard byte non-zero and never be constructed +--- which is how openkal-llvm-runtime's own probe reported it. + +The clamp now applies to the ALLOCATION, which wants sixteen, and not to the +size, which is the linker's. + ## Conformance The suite lives in the specification package and is the same suite every diff --git a/mcpp.toml b/mcpp.toml index 9541002..1b09646 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.16.0" +version = "0.16.1" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" diff --git a/src/tls.h b/src/tls.h index 9fc01b8..6ed9e87 100644 --- a/src/tls.h +++ b/src/tls.h @@ -53,7 +53,26 @@ inline void describe_tls(okl_ulong phdr, okl_ulong phent, okl_ulong phnum) { im.data = reinterpret_cast(p->vaddr); im.filesz = static_cast(p->filesz); im.memsz = static_cast(p->memsz); - im.align = p->align < 16 ? 16 : static_cast(p->align); + // THE ALIGNMENT IS KEPT AS THE LOADER STATED IT, AND THE CLAMP THAT + // USED TO BE HERE WAS HALF OF A DEFECT. + // + // Every thread-local address is `tp + st_value - tls_size', where + // `tls_size' is the block the LINKER laid out --- `round_up(p_memsz, + // p_align)' --- and a region of any other size puts the image it holds + // at the wrong offset within it. Clamping the alignment up to sixteen + // HERE lost the number the size has to be computed from, and the image + // then sat below the variables that name it: measured 2026-10-03 on a + // segment stating `p_align = 8, p_memsz = 56', whose variables the + // linker put at `tp - 56' while the region was built 64 bytes deep. + // + // Two four-byte thread-local variables, each initialised with a + // different value, read each other's bytes; and openkal-llvm-runtime's + // own probe reported it as a `thread_local' whose constructor never + // ran, because its guard byte had moved onto a non-zero neighbour. + // + // The clamp belongs to `make_tls', which applies it to the ALLOCATION + // and not to the size. + im.align = static_cast(p->align); return; } } @@ -91,22 +110,24 @@ inline void describe_self() { inline tls_block make_tls(void* (*alloc)(okl_uptr, okl_uptr)) { describe_self(); const auto& im = image(); - // TWO ALIGNMENTS, BECAUSE TWO DIFFERENT QUESTIONS ARE ASKED OF ONE NUMBER. + // TWO ALIGNMENTS, BECAUSE TWO DIFFERENT QUESTIONS ARE ASKED OF ONE NUMBER, + // AND ASKING BOTH OF ONE NUMBER WAS A DEFECT. // - // The linker measures every offset backwards from `round_up(memsz, p_align)' - // --- the size of the segment as it laid it out --- and the region must be - // that size or the image it holds sits at the wrong offset within it. The - // ALLOCATION, separately, is asked to be at least sixteen-byte aligned, - // because the region is handed to code the compiler emitted and a smaller - // alignment is a promise the allocator was never asked for. + // The SIZE is the linker's: `round_up(p_memsz, p_align)', the block every + // thread-local offset was measured backwards from, so that the image copied + // to the start of the region is where the variables' addresses say it is. + // The ALLOCATION is asked for at least sixteen bytes of alignment, because + // the region is handed to code the compiler emitted and a smaller alignment + // is a promise the allocator was never asked for. // - // They were one number, clamped up to sixteen, and the clamp is what made - // the two disagree: a program whose thread-local variables are all four- or - // eight-byte aligned has a segment aligned to that, the linker rounds its - // size to it, and a region rounded to sixteen instead puts every declared - // value a few bytes away from the variable that was declared with it. It is - // invisible until a variable is declared with a NON-ZERO value --- measured - // 2026-10-03, where the two differ by exactly the clamp. + // They were one number, clamped up to sixteen. On a segment stating + // `p_align = 8, p_memsz = 56' --- measured 2026-10-03 with two initialised + // thread-local variables --- the linker laid the variables out at `tp - 56' + // and the clamp built a region 64 bytes deep, so the image sat eight bytes + // below them and each variable read the other's bytes. openkal-linux 0.16.0 + // shipped that: the specification package's own kit test passed, because a + // four-byte variable of this implementation's own was all the storage there + // was, and it took this round's twenty-four to move anything into the way. const okl_uptr laid_out = im.align ? im.align : 1; const okl_uptr align = laid_out < 16 ? 16 : laid_out; tls_block b{};