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
46 lines
1.8 KiB
C
46 lines
1.8 KiB
C
#pragma once
|
|
|
|
#include "types.h"
|
|
|
|
// A bounded string builder, because there is no snprintf in this kernel.
|
|
//
|
|
// These lived inside kernel/dev/procfs.c, where they were written, and were
|
|
// static to it. The debugger's introspection registry needs the same thing --
|
|
// a subsystem rendering its own state into a caller's buffer, with no
|
|
// allocation and no way to run off the end -- and the choice was to write a
|
|
// second set or to move this one. See docs/architecture/debugging.md, D2.
|
|
//
|
|
// Nothing here can overflow: every writer checks cap, and a full buffer
|
|
// silently drops the rest. That is the right trade for something whose callers
|
|
// are a /proc read and a crash-time dump; a builder that can fail is a builder
|
|
// every caller has to check, and none of them would.
|
|
|
|
struct sbuf {
|
|
char *p;
|
|
int len;
|
|
int cap;
|
|
};
|
|
|
|
void sb_putc(struct sbuf *sb, char c);
|
|
void sb_puts(struct sbuf *sb, const char *s);
|
|
|
|
// A string in a fixed-width column, space-padded. Over-long values run past
|
|
// their column rather than being truncated: a misaligned row is easier to read
|
|
// than a mangled name.
|
|
void sb_pad(struct sbuf *sb, const char *s, int width);
|
|
|
|
// Four lowercase hex digits, zero-padded. PCI and USB identifiers are always
|
|
// written this way, and a lookup table keyed on "8086:100e" does not match
|
|
// "8086:100E" or "8086:10e".
|
|
void sb_puthex4(struct sbuf *sb, uint32 v);
|
|
|
|
void sb_putu(struct sbuf *sb, uint64 v);
|
|
|
|
// Zero-padded hex of a given width, for addresses.
|
|
void sb_puthex(struct sbuf *sb, uint64 v, int digits);
|
|
|
|
// A decimal number in a right-aligned column, so tables of sizes line up.
|
|
void sb_putu_pad(struct sbuf *sb, uint64 v, int width);
|
|
|
|
// "<label><value> <unit>\n" -- the shape almost every /proc line takes.
|
|
void sb_kv(struct sbuf *sb, const char *label, uint64 v, const char *unit);
|