From ab7d87bcde31c1681ea09c30534dde840a58d6fa Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 22:28:34 +0200 Subject: rsnix v0.5.2: fault diagnostics + real-hardware scroll-crash hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real-hardware scroll crash (white bands + instant freeze, QEMU-clean) was invisible because the only trace was a single serial line on machines with no serial console. This makes every crash self-identifying and hardens the real-hardware failure surfaces: - Every CPU exception vector 0-31 now has an IDT entry (macro-generated stubs; 8/14 keep their IST stacks). A fault in the shell prints a white-on-red band to the framebuffer via gop::dump_lines (raw fb writes bypassing the vt, fully clamped) plus a parseable serial line: '!!! vec=N err=… rip=… cr2=… rsp=… rflags=… task=N "name"', then halts — instead of converging to a cryptic double fault. Background-task faults abort the task as before. - Boot log now reports the authoritative post-SetMode geometry: 'gop: fb base=… size=… pitch=… WxH bpp=… bgr=…', so the next real machine's report identifies whether pitch*height exceeds the mapping. - Framebuffer writes (clear, push_rect, shift_up_n bulk push, cursor overlay) are clamped to the firmware's framebuffer_size: a pitch- padded mode degrades to a truncated view instead of faulting past the mapping. - The GOP framebuffer region is excluded from the page allocator (and subtracted from the heap's conventional-region pick) even when the firmware lists it as ConventionalMemory — kernel allocations can never overwrite the display again. The GOP protocol is located and peeked before the memory map is captured so the exclusion is known at heap-init time. - Shell gains a hidden 'fault' command (deliberate unmapped write) and 'make test-fault' verifies the dump: vec=14, cr2=0x400000000000, task=0, halt — no triple fault. All other suites re-pass. - CONTEXT.md: fault-dump format documented for real-hardware reports. --- scripts/test-fault.sh | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100755 scripts/test-fault.sh (limited to 'scripts') diff --git a/scripts/test-fault.sh b/scripts/test-fault.sh new file mode 100755 index 0000000..49ba791 --- /dev/null +++ b/scripts/test-fault.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# scripts/test-fault.sh — fault-diagnostics verification. +# +# Real hardware has no serial console, so a kernel crash previously left +# only a frozen screen ("unresponsive"). This test proves the fault-dump +# path: every CPU exception now prints a white-on-red band to the +# framebuffer *and* a parseable serial line (vector, error, RIP, CR2, +# task) before halting — so the next real-hardware failure identifies +# itself. The `fault` command deliberately writes to an unmapped +# address; the machine must halt with a full dump, not triple-fault. +# +# Usage: ./scripts/test-fault.sh +# Env: OVMF, QEMU (same defaults as other test scripts) +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +cd "$PROJECT_DIR" + +OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}" +QEMU="${QEMU:-qemu-system-x86_64}" +ESP_DIR="$PROJECT_DIR/esp" +QPID=/tmp/rsdos-fault-qpid +SOCK=/tmp/rsdos-fault-qmon.sock +LOG=/tmp/rsdos-fault-serial.log +QLOG=/tmp/rsdos-fault-qemu.log + +trap 'kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true' EXIT + +fail() { + echo "FAIL: $1" + [ -f "$LOG" ] && tail -40 "$LOG" + exit 1 +} + +wait_log() { + local i=0 + while [ "$i" -lt $((${3:-60} * 10)) ]; do + grep -q "$2" "$1" 2>/dev/null && return 0 + sleep 0.1 + i=$((i + 1)) + done + return 1 +} + +echo "==> 1/5 build kernel (debug)" +cargo build --package rsdos --target x86_64-unknown-uefi + +echo "==> 2/5 deploy to ESP" +mkdir -p "$ESP_DIR/EFI/BOOT" +cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI" + +echo "==> 3/5 boot QEMU (headless)" +kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true +sleep 1 +rm -f "$SOCK" "$LOG" +"$QEMU" -bios "$OVMF" \ + -drive file=fat:rw:"$ESP_DIR",format=raw \ + -serial file:"$LOG" \ + -monitor unix:"$SOCK",server,nowait \ + -m 256M -machine q35 -cpu qemu64 -no-reboot -display none \ + >"$QLOG" 2>&1 & +echo $! >"$QPID" + +wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" + +echo "==> 4/5 boot diagnostics present" +grep -aq "gop: fb base=" "$LOG" || fail "fb geometry diagnostic missing" +grep -aq "gop: mode .* selected" "$LOG" || fail "mode-selection diagnostic missing" + +echo "==> 5/5 inject a page fault (fault command)" +python3 "$SCRIPT_DIR/sendkeys.py" --sock "$SOCK" \ + "clear" \ + "fault" +sleep 2 + +# The dump must appear on serial: vector 14, the CR2 we wrote, RIP, and +# the shell task identity — then the machine halts (never triple-faults). +grep -aq "!!! page fault vec=14 .*rip=" "$LOG" || fail "page-fault dump line missing" +grep -aq "cr2=0x400000000000" "$LOG" || fail "CR2 not reported (faulting address)" +grep -aq "task=0 \"shell\"" "$LOG" || fail "faulting task not identified" +grep -aq "!!! halting" "$LOG" || fail "no halt message" + +# The screen dump itself is exercised in-kernel (dump_lines); the serial +# path sharing the same format line is verified above. +echo "" +echo "PASS — fault dump works (vec, err, rip, cr2, task → screen + serial)" +echo " every CPU exception now halts with a diagnosable dump, not a freeze" -- cgit v1.2.3