debugger: D5, the network transport -- and the plan closes

schdbgd bridges TCP to the debugger, which is the one way in that does not
need you to be sitting at the machine. It is a bridge and nothing else: a line
in becomes a line written to /dev/dbgro and the answer comes back, so `nc` is a
complete client and there is no clever client to be broken on the day you need
this.

    # schdbgd &
    schdbgd: debugger on port 4444, loopback only
    $ nc 127.0.0.1 4444
    dma
    arena  0xffff80003da41000-0xffff80003ea41000  16384 KB total, ...

A kernel debugger plus a TCP socket is a remote root hole, so three things were
decided rather than discovered:

  - It does not start by itself. schdbgd.job ships Disabled. SchOS has no
    kernel command line, so the boot flag the plan asked for is the job
    manifest, which is the same thing in this system's own idiom.
  - It binds loopback; -a names an address and says so in a paragraph.
  - It cannot reach a view that changes state, enforced in the kernel.

That last one is /dev/dbgro: /dev/dbg on minor 1. The minor is the one piece of
per-open state a character device already sees, so which node you opened is the
permission -- nothing to latch, nothing to remember between the write and the
read, no way across. Which views are refused comes from dbg_register_rw() on
the registration itself, because a central list of dangerous views goes stale
in the direction where a new view is quietly treated as safe. Today that is
`watch <addr>`; `watch` with no argument is still the listing and still
answered.

Also: socket() now accepts IPPROTO_TCP with SOCK_STREAM and IPPROTO_UDP with
SOCK_DGRAM. They name the default rather than departing from it, and toybox's
nc writes them out -- so TCP was "Protocol not supported" on a system whose
whole network is TCP.

docs/plan/debugger.md retires into docs/architecture/debugging.md; 16 files
repointed. Every plan in docs/plan/ that was open is now closed, so the next
thing is Gate 0 itself: the survey, with the instrument built.

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 15:33:53 +02:00
commit bc39b73969
23 changed files with 491 additions and 293 deletions

View file

@ -6,7 +6,7 @@ struct sbuf;
// Asking the kernel a question.
//
// docs/plan/debugger.md milestone D2. The thing this exists to end is the
// docs/architecture/debugging.md milestone D2. The thing this exists to end is the
// ninety-second loop: add a cprintf, rebuild, boot, read the serial log, and
// learn the answer to exactly the one question you thought of before the
// rebuild.
@ -37,6 +37,31 @@ typedef void (*dbg_view_fn)(struct sbuf *out, const char *arg);
// and needs no initialisation of its own.
void dbg_register(const char *name, dbg_view_fn fn, const char *what);
// The same, for a view that **changes something**.
//
// Nearly every view only reads, and the few that do not are worth separating
// because of where this can now be reached from. `watch 0xffff...` arms a debug
// register; that is a legitimate thing for a debugger to do and an illegitimate
// thing for a stranger on a TCP socket to do, and milestone D5 put a TCP socket
// in front of it.
//
// The flag lives on the registration and not in a list somewhere, for the same
// reason the registration exists at all: a central list of which views are
// dangerous is a list that goes stale, and it goes stale in the direction where
// a new view is quietly treated as safe.
void dbg_register_rw(const char *name, dbg_view_fn fn, const char *what);
// Answer only the views that read.
//
// `/dev/dbgro` is the same device on minor 1. The minor is the one piece of
// per-open state a character device already sees, so *which node you opened* is
// the permission -- there is nothing to latch, nothing to remember between the
// write and the read, and no way to escalate from one descriptor to the other.
//
// `schdbgd` opens that one and never the other, so nothing reachable over the
// network can arm a watchpoint. See cmd/schdbgd.
int dbg_command_ro(const char *line, struct sbuf *out, int ro);
// --- whose <addr> -----------------------------------------------------------
//
// The query the views cannot answer between them: *who owns this page?*
@ -67,7 +92,7 @@ void dbg_register_whose(const char *name, dbg_whose_fn fn);
// Run one command line and render the answer. Returns the number of bytes
// written into `out`.
//
// The protocol is lines of text -- see docs/plan/debugger.md for why that and
// The protocol is lines of text -- see docs/architecture/debugging.md for why that and
// not a typed binary format. Commands:
//
// help what this kernel can be asked