debugger: reachable from the console, and named on the crash screen

Two things, both about the debugger being available at the moment you
actually need it rather than once a shell exists.

**Ctrl-G on the console runs every view.** No filesystem, no /dev, no
shell -- it is the last thing main() sets up before entering the scheduler,
so it answers from early in boot, which is exactly when boot problems
happen. It sits beside the older Ctrl-P, and the difference between them is
the interesting part: procdump() takes no lock at all, deliberately,
because a lock is a way to hang while reporting a hang. The views cannot
follow that rule -- a free list read torn is worse than not read -- so they
take locks, and a lock must never be taken from an interrupt handler that
may have interrupted its holder. The key therefore *requests* and a kernel
thread *performs*. A scheduler too wedged to run a thread will not answer
Ctrl-G; Ctrl-P still will, and that division is the honest one.

**PSOGM says where the report went.** Its closing line used to be an
addr2line command, which the call chain no longer needs -- it prints names
now. What replaced it is the thing nobody would guess:

    This report is in /proc/kmsg after a restart.
    schdbg, or Ctrl-G on the console, asks more.
    PSOGM  --  Purple Screen of Guru Meditation

"Restart and read it" beats "photograph this screen" by a wide margin, and
until the log outlived the kernel it was not true. Both panic.c and
graphics.md had claimed in writing that the screen was the only artefact of
a crash; neither says so now. The QR code stays, because it is still the
only channel that works when the machine will not come back at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-14 01:29:30 +02:00
commit 415df0fd4d
7 changed files with 142 additions and 16 deletions

View file

@ -2,6 +2,7 @@
#include "types.h"
#include "defs.h"
#include "klog.h"
#include "dbg.h"
#include "vt.h"
#include "kbd.h" /* KEY_VT_SWITCH -- the terminal-switch chords */
#include "gfx.h"
@ -462,6 +463,19 @@ consoleintr(int (*getc)(void))
continue;
}
// Ctrl-G: every debug view, on the console.
//
// The same thing `sysrq g` and `schdbg all` produce, reachable with no
// filesystem mounted and no shell running -- which is the point. It only
// asks; a kernel thread does the work, because the views take locks and
// this is an interrupt handler. See dbg_request_dump().
if (c == C('G')) {
release(&input_lock);
dbg_request_dump();
acquire(&input_lock);
continue;
}
if (v->tio.c_lflag & ICANON) {
if (c == v->tio.c_cc[VERASE] || c == C('H')) {
erase_one(v);

View file

@ -217,6 +217,12 @@ kmain(uint32 fb_addr)
// until mpmain() below enters the scheduler.
netinit();
if (boot_verbose) cprintf("verbose boot: netinit done\n");
// The debugger's own thread, for the same reason netinit's comes after
// sinit: kthread_create needs a usable process table. From here on Ctrl-G
// on the console answers, with no filesystem mounted and nothing in
// userspace started -- which is the whole point of putting it this early.
dbg_thread_start();
if (boot_verbose) cprintf("verbose boot: dbg thread started\n");
mpmain();
}

View file

@ -497,16 +497,25 @@ paint(char *reason, addr_t *pcs, int npcs)
trace_line(line, sizeof(line), i, pcs[i]);
gfx_text_at(vx - 3 * cw, y, line, PSOGM_FG);
y += ch;
if (y > sh - ch * 5)
if (y > sh - ch * 6)
break;
}
// Two closing lines, bottom left: what to do with the numbers above, and
// what this screen is called, so it can be searched for by name. Shortened
// from the full command line, which at 62 characters ran under the QR card.
gfx_text_at(left, sh - ch * 3,
"Resolve: addr2line -e kernel <address>", PSOGM_DIM);
gfx_text_at(left, sh - ch * 3 + ch + 4,
// Three closing lines, bottom left. The first used to be an addr2line
// command, which the call chain above no longer needs -- it prints names.
//
// What replaces it is the thing nobody would guess: **this screen is no
// longer the only copy.** The log survives into the next boot (see
// include/klog.h), so the right instruction on a crash screen is now
// "restart and read it" rather than "photograph this".
//
// Kept under about 45 characters each: the QR card is on the right, and the
// 62-character command line this replaces used to run underneath it.
gfx_text_at(left, sh - ch * 4,
"This report is in /proc/kmsg after a restart.", PSOGM_DIM);
gfx_text_at(left, sh - ch * 4 + ch + 4,
"schdbg, or Ctrl-G on the console, asks more.", PSOGM_DIM);
gfx_text_at(left, sh - ch * 4 + (ch + 4) * 2,
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
}
@ -542,6 +551,14 @@ report_serial(char *reason, addr_t *pcs, int npcs)
cprintf("\n Resolve with: addr2line -e build/kernel/kernel <address>\n");
else
cprintf("\n For a line number: addr2line -e build/kernel/kernel <address>\n");
// Where to go next. Worth printing even though the machine is about to stop,
// because this text is also what ends up in the log -- and the log is the
// thing being pointed at.
cprintf(" This report is in /proc/kmsg after a restart.\n");
cprintf(" Then: schdbg whose <addr> -- who owns a page\n");
cprintf(" schdbg watch <addr> -- who writes to one\n");
cprintf(" Ctrl-G on the console -- every view, no shell needed\n");
cprintf(" Reason: %s\n", reason);
cprintf(" System halted.\n");
}

View file

@ -328,6 +328,56 @@ fill(const char *cmd)
return sb.len;
}
// --- the console key --------------------------------------------------------
//
// Ctrl-G runs the debugger with no filesystem, no shell and no client program,
// which is what makes it available *during boot* rather than once userspace is
// up. docs/plan/debugger.md calls this level 0, and it is the level that
// matters when the thing you are debugging is the boot.
//
// It cannot do the work in the interrupt handler, though, and the reason is
// worth stating because the neighbouring Ctrl-P does exactly that. procdump()
// reads the process table with no lock held, deliberately: a lock is a way to
// hang while reporting a hang. The views cannot follow that rule -- they read
// free lists and socket tables that are meaningless torn -- so they take locks,
// and taking a lock from an interrupt handler deadlocks against whatever was
// interrupted while holding it.
//
// So the key *requests* and a kernel thread *performs*. The cost is that a
// scheduler too wedged to run a thread will not answer; Ctrl-P still does, and
// that division is the honest one.
static int dbg_wanted;
void
dbg_request_dump(void)
{
dbg_wanted = 1;
wakeup(&dbg_wanted);
}
static void
dbg_thread(void)
{
for (;;) {
acquire(&dbglock);
while (!dbg_wanted)
sleep(&dbg_wanted, &dbglock);
dbg_wanted = 0;
release(&dbglock);
cprintf("\n--- debug views, requested from the console ---\n");
dbg_dump_all();
}
}
void
dbg_thread_start(void)
{
if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; }
if (kthread_create(dbg_thread, "dbgd") < 0)
cprintf("dbg: no kernel thread; Ctrl-G will not work\n");
}
void
dbg_dump_all(void)
{