List the compound physics shape's children in the crash report for the 1.2.1 fault after the material lookup (#705)

On 1.2.1 the game asks a compound physics shape for its first child's material at main+0xe056d0 and reads the result
without a check. At the reported crash the shape had none of its 125 children left, although every child is filled
when the shape is built. The crash handler now prints that shape's live bits and each record's shape pointer and name,
which tells removed children from switched-off ones and names the object. It runs only after the crash.
--probe-kernel builds a fake shape in a crashing child and checks the listing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valiantsin DzerakhandClaude Opus 5.5 committed 2026-11-10 10:19:50 +03:00
1 parent 3d386fe37e
commit 4c4bd8c67a
2 files changed
+79

No files matched your search

+42
View File
@@ -10,6 +10,7 @@
#include "cfg.h"
#include "host.h"
#include "kernel.h"
#include "layout.h"
#include "platform/platform.h"
#define STACK 0x10000
@@ -744,7 +745,36 @@ static void test_platform(void) {
static uint64_t crash_host_words[6] = {0, 0, 0x5ca1ab1e0c0de001ull, 0, 0, 0};
static uint64_t crash_guest_words[6] = {0, 0, 0x5ca1ab1e0c0de002ull, 0, 0, 0};
// The compound shape note (main.c crash_compound_shape, 1.2.1): a shape of 3 records whose child 0 has no shape, child
// 1 has one but its live bit is clear, child 2 is live and filled.
static uint64_t crash_shape[0x1a0 / 8], crash_records[3 * 0x78 / 8], crash_child_shape;
static void crash_shape_fill(Cpu *c) {
uint8_t *s = (uint8_t *)crash_shape, *r = (uint8_t *)crash_records;
static const char *const names[3] = {"PartA", "PartB", "PartC"};
for (int i = 0; i < 3; i++) {
uint64_t name = (uint64_t)(uintptr_t)(r + i * 0x78 + 0x14);
memcpy(r + i * 0x78 + 8, &name, 8);
memcpy(r + i * 0x78 + 0x14, names[i], strlen(names[i]) + 1);
}
uint64_t child = (uint64_t)(uintptr_t)&crash_child_shape, vt = MOD_MAIN_BASE + 0x45e1218;
uint64_t rec = (uint64_t)(uintptr_t)crash_records;
uint32_t n = 3, bits = 0x5;
memcpy(r + 0x78, &child, 8);
memcpy(r + 2 * 0x78, &child, 8);
memcpy(s, &vt, 8);
memcpy(s + 0x78, &n, 4);
memcpy(s + 0x80, &rec, 8);
memcpy(s + 0x88, &bits, 4);
memcpy(s + 0x190, &n, 4);
c->x[20] = (uint64_t)(uintptr_t)crash_shape;
c->x[21] = 0x1000038;
c->x[30] = MOD_MAIN_BASE + 0xe056d4;
}
static void crash_child_main(Cpu *c) {
const char *mode = getenv(CRASH_CHILD_ENV);
if (mode && !strcmp(mode, "shape")) crash_shape_fill(c);
c->x[19] = (uint64_t)(uintptr_t)&crash_guest_words[2];
c->sp -= 0x100; // a frame on the guest stack, as a crashing function has
__asm__ volatile("movq %0, %%rbx\n\tmovq 0x10, %%rax" : : "r"(&crash_host_words[2]) : "rax", "rbx", "memory");
@@ -779,6 +809,18 @@ static void test_crash_report(void) {
"crash report: no memory commit line");
printf("crash report: exit code %d, host registers, code bytes, memory at rbx and x19, guest stack, commit %s\n", code,
failures ? "checked with failures" : "present");
if (rt_game_version != 121) return;
code = plat_run_self(CRASH_CHILD_ENV, "shape", out, sizeof out, 30000);
CHECK(report_line(out, " compound shape ", line, sizeof line) && strstr(line, "3 children, 3 records") &&
strstr(line, "live bits: 00000005"),
"crash report: no compound shape line (%s)", strstr(out, "compound shape") ? line : "none");
CHECK(report_line(out, "child 0:", line, sizeof line) && strstr(line, "shape 0 \"PartA\""),
"crash report: child 0 should have no shape (%s)", line);
CHECK(report_line(out, "child 1:", line, sizeof line) && strstr(line, "(off) \"PartB\""),
"crash report: child 1 should be off (%s)", line);
CHECK(report_line(out, "child 2:", line, sizeof line) && !strstr(line, "(off)") && strstr(line, "\"PartC\""),
"crash report: child 2 should be live (%s)", line);
printf("crash report, compound shape: exit code %d, %s\n", code, failures ? "checked with failures" : "children listed");
}
int probe_kernel(void) {
+37
View File
@@ -30,6 +30,7 @@
#include "cfg.h"
#include "host.h"
#include "layout.h"
#include "platform/platform.h"
#include "sha256.h"
@@ -284,6 +285,41 @@ static void crash_memory(const PlatCrash *x, const Cpu *c) {
(unsigned long long)s[i + 1], (unsigned long long)s[i + 2], (unsigned long long)s[i + 3]);
}
// 1.2.1: a fault right after a compound physics shape was asked for its first child's material (the call returning to
// main+0xe056d4 reads the result without a check). The shape (vtable main+0x45e1218, built by main+0x7359fc) is in x20
// there. It returns null when no child slot is both live (bitmap at +0x88) and filled (shape pointer at +0 of the
// 0x78-byte records at +0x80). Every child is filled when the shape is built, so the list tells children removed later
// (null shape) from children switched off (clear bit), and the names name the object. Runs only in the crash handler.
static void crash_compound_shape(const Cpu *c) {
if (!c || rt_game_version != 121 || c->x[30] != MOD_MAIN_BASE + 0xe056d4) return;
uint64_t shape = c->x[20];
if (!readable(shape, 0x1a0) || LD64(shape) != MOD_MAIN_BASE + 0x45e1218) {
rt_logf(" compound shape: x20 %#llx is not one\n", (unsigned long long)shape);
return;
}
uint32_t children = LD32(shape + 0x190), records = LD32(shape + 0x78);
uint64_t rec = LD64(shape + 0x80);
rt_logf(" compound shape %#llx (holder %#llx): %u children, %u records at %#llx, live bits:",
(unsigned long long)shape, (unsigned long long)(c->x[21] - 0x38), children, records,
(unsigned long long)rec);
uint32_t n = children > 0x400 ? 0x400 : children; // the bitmap holds 0x400 bits
for (uint32_t w = 0; w < (n + 31) / 32; w++) rt_logf(" %08x", LD32(shape + 0x88 + 4 * w));
rt_log_raw("\n", 1);
if (records > 0x400 || !readable(rec, (uint64_t)records * 0x78)) return;
for (uint32_t i = 0; i < records; i++) {
uint64_t r = rec + (uint64_t)i * 0x78, child = LD64(r), s = LD64(r + 8);
char name[33];
size_t k = 0;
for (; k < 32 && readable(s + k, 1); k++) {
name[k] = *(const char *)(uintptr_t)(s + k);
if (!name[k]) break;
}
name[k] = 0;
int live = LD32(shape + 0x88 + 4 * (i >> 5)) >> (i & 31) & 1;
rt_logf(" child %3u: shape %#llx%s \"%s\"\n", i, (unsigned long long)child, live ? "" : " (off)", name);
}
}
static void crash_handler(const PlatCrash *x) {
static volatile int32_t busy;
if (plat_xchg32(&busy, 1)) plat_exit(71);
@@ -349,6 +385,7 @@ static void crash_handler(const PlatCrash *x) {
rt_logf(" not on a guest thread\n");
}
crash_memory(x, c);
crash_compound_shape(c);
kern_free_cores_note();
fflush(stderr);
plat_exit(71);