Skip to content

Commit e554292

Browse files
Sunrisepeaksunrisepeak
andauthored
0.16.1 --- the thread-local image is placed where the linker measured it (#33)
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. Co-authored-by: sunrisepeak <sunbreaks@163.com>
1 parent d1b2850 commit e554292

3 files changed

Lines changed: 55 additions & 17 deletions

File tree

‎README.md‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ for Linux, written on the kernel's own system-call interface.
88
openkal = "0.15.0"
99

1010
[target.'cfg(os = "linux")'.dependencies]
11-
openkal-linux = "0.16.0"
11+
openkal-linux = "0.16.1"
1212
```
1313

1414
## 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
8585
(`pthread_getattr_np`), because in that arrangement the runtime is what owns the
8686
stacks and is what a program above openkal would ask for itself.
8787

88+
## A defect this version repairs
89+
90+
0.16.0 added the storage this implementation keeps for the region a context
91+
stands on, and that storage made a latent defect of `src/tls.h` visible: the
92+
thread-local segment's alignment was clamped up to sixteen where the block's
93+
SIZE is computed, and the size has to be the one the linker measured every
94+
variable's offset against. On a segment stating `p_align = 8, p_memsz = 56` the
95+
linker laid the variables out at `tp - 56` while the region was built 64 bytes
96+
deep, so the image of the program's thread-local storage sat eight bytes below
97+
the variables that name it. A program whose thread-local variables are
98+
initialised then reads another variable's bytes, and a C++ program's
99+
`thread_local` object can find its guard byte non-zero and never be constructed
100+
--- which is how openkal-llvm-runtime's own probe reported it.
101+
102+
The clamp now applies to the ALLOCATION, which wants sixteen, and not to the
103+
size, which is the linker's.
104+
88105
## Conformance
89106

90107
The suite lives in the specification package and is the same suite every

‎mcpp.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-linux"
4-
version = "0.16.0"
4+
version = "0.16.1"
55
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."
66
license = "Apache-2.0"
77

‎src/tls.h‎

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,26 @@ inline void describe_tls(okl_ulong phdr, okl_ulong phent, okl_ulong phnum) {
5353
im.data = reinterpret_cast<const unsigned char*>(p->vaddr);
5454
im.filesz = static_cast<okl_uptr>(p->filesz);
5555
im.memsz = static_cast<okl_uptr>(p->memsz);
56-
im.align = p->align < 16 ? 16 : static_cast<okl_uptr>(p->align);
56+
// THE ALIGNMENT IS KEPT AS THE LOADER STATED IT, AND THE CLAMP THAT
57+
// USED TO BE HERE WAS HALF OF A DEFECT.
58+
//
59+
// Every thread-local address is `tp + st_value - tls_size', where
60+
// `tls_size' is the block the LINKER laid out --- `round_up(p_memsz,
61+
// p_align)' --- and a region of any other size puts the image it holds
62+
// at the wrong offset within it. Clamping the alignment up to sixteen
63+
// HERE lost the number the size has to be computed from, and the image
64+
// then sat below the variables that name it: measured 2026-10-03 on a
65+
// segment stating `p_align = 8, p_memsz = 56', whose variables the
66+
// linker put at `tp - 56' while the region was built 64 bytes deep.
67+
//
68+
// Two four-byte thread-local variables, each initialised with a
69+
// different value, read each other's bytes; and openkal-llvm-runtime's
70+
// own probe reported it as a `thread_local' whose constructor never
71+
// ran, because its guard byte had moved onto a non-zero neighbour.
72+
//
73+
// The clamp belongs to `make_tls', which applies it to the ALLOCATION
74+
// and not to the size.
75+
im.align = static_cast<okl_uptr>(p->align);
5776
return;
5877
}
5978
}
@@ -91,22 +110,24 @@ inline void describe_self() {
91110
inline tls_block make_tls(void* (*alloc)(okl_uptr, okl_uptr)) {
92111
describe_self();
93112
const auto& im = image();
94-
// TWO ALIGNMENTS, BECAUSE TWO DIFFERENT QUESTIONS ARE ASKED OF ONE NUMBER.
113+
// TWO ALIGNMENTS, BECAUSE TWO DIFFERENT QUESTIONS ARE ASKED OF ONE NUMBER,
114+
// AND ASKING BOTH OF ONE NUMBER WAS A DEFECT.
95115
//
96-
// The linker measures every offset backwards from `round_up(memsz, p_align)'
97-
// --- the size of the segment as it laid it out --- and the region must be
98-
// that size or the image it holds sits at the wrong offset within it. The
99-
// ALLOCATION, separately, is asked to be at least sixteen-byte aligned,
100-
// because the region is handed to code the compiler emitted and a smaller
101-
// alignment is a promise the allocator was never asked for.
116+
// The SIZE is the linker's: `round_up(p_memsz, p_align)', the block every
117+
// thread-local offset was measured backwards from, so that the image copied
118+
// to the start of the region is where the variables' addresses say it is.
119+
// The ALLOCATION is asked for at least sixteen bytes of alignment, because
120+
// the region is handed to code the compiler emitted and a smaller alignment
121+
// is a promise the allocator was never asked for.
102122
//
103-
// They were one number, clamped up to sixteen, and the clamp is what made
104-
// the two disagree: a program whose thread-local variables are all four- or
105-
// eight-byte aligned has a segment aligned to that, the linker rounds its
106-
// size to it, and a region rounded to sixteen instead puts every declared
107-
// value a few bytes away from the variable that was declared with it. It is
108-
// invisible until a variable is declared with a NON-ZERO value --- measured
109-
// 2026-10-03, where the two differ by exactly the clamp.
123+
// They were one number, clamped up to sixteen. On a segment stating
124+
// `p_align = 8, p_memsz = 56' --- measured 2026-10-03 with two initialised
125+
// thread-local variables --- the linker laid the variables out at `tp - 56'
126+
// and the clamp built a region 64 bytes deep, so the image sat eight bytes
127+
// below them and each variable read the other's bytes. openkal-linux 0.16.0
128+
// shipped that: the specification package's own kit test passed, because a
129+
// four-byte variable of this implementation's own was all the storage there
130+
// was, and it took this round's twenty-four to move anything into the way.
110131
const okl_uptr laid_out = im.align ? im.align : 1;
111132
const okl_uptr align = laid_out < 16 ? 16 : laid_out;
112133
tls_block b{};

0 commit comments

Comments
 (0)