diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 78e7ecd..a7182e1 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 112 +#define MONTAUK_BUILD_NUMBER 115 diff --git a/kernel/src/Hal/IDT.cpp b/kernel/src/Hal/IDT.cpp index beeddfa..4bdb01e 100644 --- a/kernel/src/Hal/IDT.cpp +++ b/kernel/src/Hal/IDT.cpp @@ -141,7 +141,7 @@ namespace Hal { rep.timestampTick = Timekeeping::GetTicks(); CrashReport::AddReport(rep); - Sched::SpawnCrashPad(); + Sched::SpawnCrashPad(proc->pid); Sched::ExitProcess(); __builtin_unreachable(); diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 37721d4..1e9b349 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -1388,11 +1388,57 @@ namespace Sched { return &processTable[slot]; } - int SpawnCrashPad() { + int SpawnCrashPad(int crasherPid) { // Spawn crashpad as a child of init (slot 0), not the crashing process. - // We can't use the current process context since we're in ExitProcess. + // The crasher is still alive here (we run before its ExitProcess), but + // it is about to die, so we must not parent the crashpad to it. static constexpr const char* crashpadPath = "0:/apps/crashpad/crashpad.elf"; - return Spawn(crashpadPath, nullptr); + + // If the crashing process had a redirected console (i.e. it was running + // under terminal.elf in a console session), hand the crashpad a write + // handle to that same out-stream so it can surface a textual crash + // report to the console user when no desktop is running. + Process* crasher = GetProcessByPid(crasherPid); + int crasherSlot = Ipc::SlotForPid(crasherPid); + bool inheritRedir = crasher && crasher->redirected && + crasher->ioOutHandle >= 0 && crasherSlot >= 0; + + Ipc::Object* outObj = nullptr; + Ipc::HandleType outType = Ipc::HandleType::None; + uint32_t outRights = 0; + if (inheritRedir) { + if (!Ipc::SnapshotHandleForSlot(crasherSlot, crasher->ioOutHandle, + outType, outObj, outRights) || + outType != Ipc::HandleType::Stream || outObj == nullptr) { + inheritRedir = false; + } + } + + // Deferred start when inheriting, so we can wire ioOutHandle before _start. + int childPid = Spawn(crashpadPath, nullptr, /*startReady=*/!inheritRedir); + if (childPid < 0 || !inheritRedir) return childPid; + + Process* child = GetProcessByPid(childPid); + int childSlot = Ipc::SlotForPid(childPid); + if (child && childSlot >= 0) { + // Installing with RightWrite retains the stream (writerRefs++/refs++), + // so it outlives the crasher's imminent ExitProcess teardown. The + // snapshot above and this install run back-to-back with no blocking + // call between, while the crasher's handle still pins the stream. + int h = Ipc::InstallHandleForSlot(childSlot, outObj, Ipc::HandleType::Stream, + Ipc::RightWrite | Ipc::RightWait | Ipc::RightDup); + if (h >= 0) { + child->ioOutHandle = h; + child->redirected = true; + child->parentPid = crasherPid; + child->termCols = crasher->termCols; + child->termRows = crasher->termRows; + } + } + + // Always start it (even if wiring failed -> it just falls back to silent). + StartProcess(childPid); + return childPid; } } diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index b05431d..48811f4 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -190,9 +190,11 @@ namespace Sched { // Find a process by PID (returns nullptr if not found or not alive) Process* GetProcessByPid(int pid); - // Spawn a crashpad process with the given crash file path as argument. - // Called by the exception handler when a process faults. - int SpawnCrashPad(); + // Spawn the crashpad to report on a faulted process (pid `crasherPid`). + // Called by the exception handler when a process faults. If the crasher had + // a redirected console, the crashpad inherits its out-stream so it can print + // a textual report to a console session with no desktop. + int SpawnCrashPad(int crasherPid); // Get a pointer to slot i in the process table (for enumeration) Process* GetProcessSlot(int slot); diff --git a/programs/src/crashpad/main.cpp b/programs/src/crashpad/main.cpp index 1f3b4cc..6c70ce0 100644 --- a/programs/src/crashpad/main.cpp +++ b/programs/src/crashpad/main.cpp @@ -1,6 +1,6 @@ /* * main.cpp - * MontaukOS Crashpad — displays crash reports in a popup window + * MontaukOS Crashpad - renders process crash information * Copyright (c) 2026 Daniel Hammer */ @@ -13,6 +13,20 @@ using namespace gui; +static bool is_desktop_running() { + static montauk::abi::ProcInfo procs[256]; + int count = montauk::proclist(procs, 256); + if (count <= 0) return false; + + int self_pid = montauk::getpid(); + for (int i = 0; i < count; i++) { + if (montauk::streq(procs[i].name, "0:/os/desktop.elf")) { + return true; + } + } + return false; +} + static constexpr int WINDOW_W = 460; static constexpr int WINDOW_H = 480; static constexpr int PADDING = 12; @@ -201,6 +215,42 @@ static void render() { g_win.present(); } +// ==== Console (no-desktop) crash report ==== +// When no desktop is running, the crashpad is spawned with its stdout +// redirected to the same out-stream as the crashed process (wired up by the +// kernel in SpawnCrashPad), so montauk::print() lands in the console terminal. +static void emit(const char* s) { montauk::print(s); } + +static void emit_kv_hex(const char* key, uint64_t val) { + char buf[32]; + emit(" "); + emit(key); + emit(": "); + format_hex16(buf, val); + emit(buf); + emit("\n"); +} + +static void render_console() { + char num[16]; + emit("\n\033[1;31m*** Process crashed ***\033[0m\n"); + emit("Process: "); + emit(g_report.processName); + emit(" (pid "); + format_decimal(num, g_report.pid); + emit(num); + emit(")\n"); + emit("Exception: "); + emit(g_report.exceptionName); + emit("\n"); + emit_kv_hex("RIP", g_report.instructionPointer); + emit_kv_hex("RSP", g_report.stackPointer); + emit_kv_hex("RFLAGS", g_report.flags); + if (g_report.exceptionVector == 0x0E) + emit_kv_hex("CR2 (fault addr)", g_report.faultingAddress); + emit("\n"); +} + extern "C" void _start() { fonts::init(); @@ -208,42 +258,48 @@ extern "C" void _start() { int available = montauk::crash_report(&g_report); if (available <= 0) montauk::exit(1); - if (!g_win.create("Process Crashed", WINDOW_W, WINDOW_H)) montauk::exit(1); + if (is_desktop_running()) { + if (!g_win.create("Process Crashed", WINDOW_W, WINDOW_H)) montauk::exit(1); - render(); + render(); - while (true) { - montauk::abi::WinEvent ev; - int r = g_win.poll(&ev); - if (r < 0) break; - if (r == 0) { - montauk::sleep_ms(16); - continue; - } - - if (ev.type == 3) break; // close - - if (ev.type == 0 && ev.key.pressed) { - if (ev.key.scancode == 0x01) break; // Escape - if (ev.key.scancode == 0x51) { - // Page Down: scroll down - g_scroll += 60; - render(); + while (true) { + montauk::abi::WinEvent ev; + int r = g_win.poll(&ev); + if (r < 0) break; + if (r == 0) { + montauk::sleep_ms(16); + continue; } - if (ev.key.scancode == 0x49) { - // Page Up: scroll up - g_scroll -= 60; - if (g_scroll < 0) g_scroll = 0; + + if (ev.type == 3) break; // close + + if (ev.type == 0 && ev.key.pressed) { + if (ev.key.scancode == 0x01) break; // Escape + if (ev.key.scancode == 0x51) { + // Page Down: scroll down + g_scroll += 60; + render(); + } + if (ev.key.scancode == 0x49) { + // Page Up: scroll up + g_scroll -= 60; + if (g_scroll < 0) g_scroll = 0; + render(); + } + } + + if (ev.type == 2) { + // Resize render(); } } - if (ev.type == 2) { - // Resize - render(); - } + g_win.destroy(); + } else { + // No desktop: print a textual report to the redirected console. + render_console(); } - g_win.destroy(); montauk::exit(0); }