mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
page: a dropped unit's pages are forgotten again, not left numbered with no directory entry
The translator numbers the pages it claims in the page table it is handed, and that table comes back with the result; their directory entries are written only when the unit is installed. A dropped unit left them numbered and unpublished. The next lift there saw an old page and wrote no entry for it either, so every block running onto such a page (an instruction across the boundary) failed its page check against the empty entry, asked to be translated again, and got the same check: a miss loop for ever. That was the Undertale freeze at its first frame with the drop-on-retire path (76fb3b09): the dropped unit at 0x76645fa0 numbered 0x76646, and 0x76645ffd then missed 440 times and on. Withfe7057c6a unit is dropped only across an execve (whose table is the new program's) or a retire log overflow, so this closes the overflow case. retire-during-lift now puts the last block of every page across the boundary. On a build dropping on every retire it hung 2 runs of 2 (14-16 drops, missing at 0xf748ffd and 0xf757ffd); with this, 6 of 6 pass with 4-8 drops each. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_ea9f71488b6e448da93d6c2dabf4879c
This commit is contained in:
1 parent
064a344a9f
commit
5892193eeb
2 files changed
+35
-10
No files matched your search
@@ -12931,12 +12931,29 @@ pub extern "C" fn gex_dispatch_miss(_ctx: i32, pc: i64) -> i32 {
|
||||
.collect()
|
||||
})
|
||||
};
|
||||
if retired_since.is_none()
|
||||
|| jitout::EXEC_EPOCH.load(std::sync::atomic::Ordering::Acquire) != epoch.1
|
||||
{
|
||||
let execd = jitout::EXEC_EPOCH.load(std::sync::atomic::Ordering::Acquire) != epoch.1;
|
||||
if retired_since.is_none() || execd {
|
||||
for a in &fresh {
|
||||
h.jit.translated.remove(a);
|
||||
}
|
||||
// And the pages it numbered. The translator gives a page its base
|
||||
// in the page table it was handed, which came back with the
|
||||
// result; the directory entry is written only when the unit is
|
||||
// installed. Kept numbered with the entry never written, the next
|
||||
// lift there sees an old page, writes no entry for it either, and
|
||||
// every block of it that runs into that page (an instruction
|
||||
// across the boundary) fails its page check against the empty
|
||||
// entry, asks to be translated again, and gets the same check: a
|
||||
// miss loop for ever (Undertale, frozen at its first frame, when
|
||||
// any retire dropped the unit). After an `execve` the table is
|
||||
// the new program's, and nothing of this unit is in it.
|
||||
if !execd {
|
||||
for (p, b) in &unit.dir_entries {
|
||||
if h.jit.page_table.lookup(*p) == Some(*b) {
|
||||
h.jit.page_table.forget(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
static DROPPED: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
let n = DROPPED.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
|
||||
if tracing() || n <= 4 || n.is_power_of_two() {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
/* Code retired while other code is being translated. One thread runs a long
|
||||
* chain of small blocks (8,000 of them, each `add eax, 1; jmp next`), which
|
||||
* chain of small blocks (8,000 of them, each `add eax, 1; jmp next`, the last
|
||||
* of each page running onto the next one), which
|
||||
* takes many dispatch misses and long lifts to translate; another keeps
|
||||
* flipping a separate code page (a function it also calls) to
|
||||
* read-write-execute and back, rewriting a byte with the value it already
|
||||
* holds. Every flip retires that page's translations (`retire_code`), and in
|
||||
* the page the flips land while the chain is being lifted outside the kernel
|
||||
* lock (docs/kernel-locking.md, step 5). The chain must add up every round
|
||||
* and the run must end: dropping a unit lifted across a retire once froze
|
||||
* Undertale.
|
||||
* and the run must end. A unit dropped for a retire once left the pages it
|
||||
* had numbered with no directory entry, and a block running onto one of them
|
||||
* then failed its page check for ever: Undertale froze at its first frame, and
|
||||
* this froze the same way (2 runs of 2, a build dropping on every retire).
|
||||
*
|
||||
* Not named *smc*: run.sh would run it under --fjit as well, which livelocks
|
||||
* natively once a chain page is retired (a separate bug).
|
||||
@@ -26,6 +29,10 @@
|
||||
#define BLOCKS 8000
|
||||
#define STRIDE 16
|
||||
#define ROUNDS 20
|
||||
/* Where the first block starts: 13 bytes into every 16, so the last block of
|
||||
each page has its `add` in the page's last three bytes and its `jmp` on the
|
||||
next page. */
|
||||
#define FIRST 13
|
||||
#ifndef FLIP_US
|
||||
#define FLIP_US 200
|
||||
#endif
|
||||
@@ -80,20 +87,21 @@ static void *flipper(void *arg) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
size_t size = (2 + BLOCKS * STRIDE + 1 + 4095) & ~(size_t)4095;
|
||||
size_t size = (FIRST + BLOCKS * STRIDE + 1 + 4095) & ~(size_t)4095;
|
||||
unsigned char *code = map_code(size);
|
||||
flip_page = map_code(4096);
|
||||
if (!code || !flip_page) return 2;
|
||||
memset(code, 0xcc, size);
|
||||
code[0] = 0x31; code[1] = 0xc0; /* xor eax, eax */
|
||||
code[0] = 0x31; code[1] = 0xc0; /* xor eax, eax */
|
||||
code[2] = 0xeb; code[3] = FIRST - 4; /* jmp to the first block */
|
||||
for (int i = 0; i < BLOCKS; i++) {
|
||||
unsigned char *b = code + 2 + i * STRIDE;
|
||||
unsigned char *b = code + FIRST + i * STRIDE;
|
||||
b[0] = 0x83; b[1] = 0xc0; b[2] = 0x01; /* add eax, 1 */
|
||||
int32_t rel = STRIDE - 8; /* jmp to the next block */
|
||||
b[3] = 0xe9;
|
||||
memcpy(b + 4, &rel, 4);
|
||||
}
|
||||
code[2 + BLOCKS * STRIDE] = 0xc3; /* ret */
|
||||
code[FIRST + BLOCKS * STRIDE] = 0xc3; /* ret */
|
||||
memset(flip_page, 0xcc, 4096);
|
||||
memcpy(flip_page, "\xb8\x07\x00\x00\x00\xc3", 6); /* mov eax, 7; ret */
|
||||
if (mprotect(code, size, PROT_READ | PROT_EXEC) != 0) return 2;
|
||||
|
||||
Reference in new issue
Block a user