debugger: who owns this page, and who just wrote to it
docs/plan/debugger.md D3 and D4, which finish the plan apart from the
network transport.
D3 -- `whose <addr>`. The views each speak for one subsystem, which is what
keeps them cheap and current, and it is exactly why none of them can see
the bug that matters: a page on the DMA arena's free list looks fine from
dma.c, the same page on kalloc's free list looks fine from kalloc.c, and
that it is on both is a fact no single subsystem holds.
# schdbg whose 0x3e239000
phys 0x000000003e239000 kernel va 0xffff80003e239000
dma arena, handed out
pcidma slot 2, 0 KB into a 4000 KB block given to pid 4 (vgpud)
mapped pid 4 (vgpud) at 0x000000004000d000 rwu vma[5] no_free
consistent: 1 owner, 1 mapping(s)
Claimants register the way views do -- one line in the file that owns the
data -- so the kernel image, the log region, the arena, the page allocator,
the pciuser DMA blocks and every page table each answer for themselves.
`mapped` is the one no allocator can answer: there is no reverse map, so
vm_walk_user() goes forward over every mapped page of every live process.
Two verdicts are shouted and everything else is reported as fact. More than
one owner means the second allocator to hand the page out is handing out
memory the first is still using -- the H2 bug, which would have printed as
three lines. Free-but-still-mapped means a page will be given away from
under the page tables that point at it. Deliberately *not* a finding: no
owner at all, because kalloc keeps a free list and no record of what it has
handed out, so an ordinary allocated page has nobody to speak for it and
saying otherwise would be the query crying wolf.
D4 -- watchpoints, on the four debug registers that had been sitting unused
since this kernel was written. Armed against the same framebuffer page and
then killing the driver:
watch 0: 0xffff80003e239000 written, 8 byte(s), by stosq+0x1f, pid 12 vgpud
#0 memset+0xba
#1 dma_alloc+0x2e2
#2 do_dma+0x22a
#3 pciuser_ioctl+0x13b
#4 sys_ioctl+0x3c7
#5 syscall+0xf6
The chain is the answer, not the rip. It reports and continues -- a data
watchpoint is a trap, delivered after the store completes, so returning
carries on with the program none the wiser, which is what lets this be used
on a machine that is still running.
Debug registers are per-CPU and there is no general IPI here, so the table
carries a generation number and each CPU reloads from its timer interrupt
when it notices the number has moved: one comparison per tick, one tick of
latency at worst. `watch` prints which CPUs have caught up, because "this
watchpoint is watching" and "this watchpoint is watching one CPU" must not
look the same.
Also fixed while testing: watch_init() was called from mpmain(), which runs
on every processor, so the command appeared in the list once per CPU.
dbg_register() now refuses a duplicate name outright, so the next init that
runs per-CPU fails loudly in the list rather than quietly doubling it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
1dd4ba0186
commit
113595f80b
17 changed files with 1067 additions and 19 deletions
|
|
@ -96,7 +96,7 @@ The galling ones. The work exists, is tested, and nothing uses it.
|
|||
| **`foundations.md`** | Item 1 built. Items 2 (a shareable memory object), 3 (`AF_UNIX` in the filesystem namespace), 4 and 5 open. **Wayland stops at item 2.** |
|
||||
| **`hybrid.md`** | All four mechanisms built. H1 and H2 done — the driver can be killed and comes back, which is the milestone the plan calls the one that proves the whole idea. H3 (a modeset API) and H4 (USB) open. |
|
||||
| **Schcom v0** | 715 lines that composite, over a wire format that is superseded. Frozen: it runs, nothing more is added, and it is kept only because it is the one thing in the tree that has ever put a client's pixels on screen. |
|
||||
| **`debugger.md`** | D0, D1 and D2 built and described in [../architecture/debugging.md](../architecture/debugging.md). D3 (`whose <addr>`, the cross-subsystem ownership query) and D4 (hardware watchpoints) open, and the network transport with them. |
|
||||
| **`debugger.md`** | D0 through D4 built and described in [../architecture/debugging.md](../architecture/debugging.md). Only the network transport is open — a userspace daemon bridging TCP to `/dev/dbg`, which is deliberately last. |
|
||||
|
||||
**The row the debugger spent.** Adding one requires closing one, and *A released
|
||||
PCI device* closed when H2 landed. It is worth saying plainly that the debugger
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ And one that is a tool rather than a capability:
|
|||
|
||||
| | |
|
||||
|---|---|
|
||||
| **[debugger.md](debugger.md)** | A debugger for this kernel — one that observes rather than halts, and answers in this kernel's own vocabulary. **Mostly built**: symbols, a log that outlives the kernel, and `/dev/dbg` are done and described in [../architecture/debugging.md](../architecture/debugging.md). |
|
||||
| **[debugger.md](debugger.md)** | A debugger for this kernel — one that observes rather than halts, and answers in this kernel's own vocabulary. **Built**, except the network transport: symbols, a log that outlives the kernel, `/dev/dbg`, the `whose` ownership query and hardware watchpoints are all described in [../architecture/debugging.md](../architecture/debugging.md). |
|
||||
|
||||
Two longer-horizon documents sit behind these: [schapi.md](schapi.md), the API
|
||||
layering and the compatibility promise, and [roadmap.md](roadmap.md), which holds
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
# A debugger for this kernel
|
||||
|
||||
> **D0, D1 and D2 are built.** Symbols, a log that outlives the kernel, and the
|
||||
> introspection registry behind `/dev/dbg` are described in
|
||||
> [../architecture/debugging.md](../architecture/debugging.md). What is left
|
||||
> here is D3, D4, and the network transport.
|
||||
> **Every milestone on this plan is built.** Symbols, a log that outlives the
|
||||
> kernel, the introspection registry behind `/dev/dbg`, the `whose` ownership
|
||||
> query and hardware watchpoints are all described in
|
||||
> [../architecture/debugging.md](../architecture/debugging.md).
|
||||
>
|
||||
> What is left here is **level 2, the network transport** — a userspace daemon
|
||||
> bridging TCP to `/dev/dbg`, deliberately outside the kernel. Nothing else.
|
||||
|
||||
Every bug in this project so far has been found the same way: add a `cprintf`,
|
||||
rebuild, boot, read the serial log, guess again. That loop is about ninety
|
||||
|
|
@ -151,8 +154,9 @@ happens the kernel does not change.
|
|||
| **D0** | **Symbols.** A compact sorted table emitted from `nm` at link time and linked into the image; `ksym_lookup(addr, &off)`. | **done** |
|
||||
| **D1** | **A log that survives.** A ring behind `cprintf`, timestamped, in a region the allocator never sees — so a panic's last words are readable on the next boot. `/proc/kmsg`. | **done** |
|
||||
| **D2** | **The introspection registry.** `dbg_register("dma", dma_dump)`. Seven views, `/dev/dbg`, `schdbg`, and `sysrq g`. | **done** |
|
||||
| **D3** | **`whose <addr>`** — the cross-subsystem ownership query above. | |
|
||||
| **D4** | **Watchpoints**, DR0–DR3, break-on-write, reporting rip and call chain and **continuing** rather than stopping. | |
|
||||
| **D3** | **`whose <addr>`** — the cross-subsystem ownership query above. | **done** |
|
||||
| **D4** | **Watchpoints**, DR0–DR3, break-on-write, reporting rip and call chain and **continuing** rather than stopping. | **done** |
|
||||
| **D5** | **The network transport**, level 2 above: a userspace daemon bridging TCP to `/dev/dbg`. Off unless a boot flag turns it on, loopback by default. | |
|
||||
|
||||
### Why D0 and D1 come before the interesting parts
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue