# DF-5475 — gdb_handle_exception spins forever when `gdb_tab == NULL` (RB_GDB with no gdb port): permanent all-CPU hang

`sys/cpu/x86_64/misc/x86_64-gdbstub.c`

## What

`getDebugChar()` returns **-1** when `gdb_tab == NULL`
(`sys/cpu/x86_64/misc/x86_64-gdbstub.c:156-162`). Every consumer of that
sentinel masks it with `& 0x7f`, turning "no gdb console exists" into the
data byte `0x7f`:

- `putpacket()` ack wait — `sys/cpu/x86_64/misc/x86_64-gdbstub.c:277`
  `while ((getDebugChar () & 0x7f) != '+');`
- `getpacket()` start-char wait — `x86_64-gdbstub.c:189`
- `getpacket()` body/checksum — `x86_64-gdbstub.c:200,211-212`

When the kernel is asked to use the GDB remote debugger
(`boothowto & RB_GDB`, set by `boot -g`, by `debug.enter_debugger=gdb`, or
by a debugger-on-panic path) but **no gdb console device was ever
attached** (no `flag 0x80` sio port, no dcons, no COM ports at all),
`kdb_trap()` still routes into the stub
(`sys/platform/pc64/x86_64/db_interface.c:105,179`) and:

1. the stub's very first `putpacket()` (the `T` stop-reply,
   `x86_64-gdbstub.c:531`) faults: in the shipped gcc 8.3 kernel the
   `.constprop` clone of `putpacket` **deleted the accessor NULL guards**,
   so the first character emit executes
   `gdb_tab->cn_putc(gdb_tab->cn_gdbprivate, …)` with `gdb_tab == NULL` →
   page fault at `putpacket+0x2c` reading `0x68`
   (`offsetof(struct consdev, cn_gdbprivate)`, `sys/sys/cons.h:72`), and
2. the fault re-enters `kdb_trap()` (`trap.c:1096`, `db_active != 0`) which
   re-routes to the stub, which faults identically — an **unterminating
   `Fatal trap 12` storm** (~35 KB/s on serial) while the other CPUs sit
   IPI-stopped (`db_interface.c:160-168`).

Even in a hypothetical build that keeps the guards, every wait loop masks
the `getDebugChar() == -1` sentinel into data (`-1 & 0x7f == 0x7f`,
`x86_64-gdbstub.c:189,200,277`), so `putpacket()`'s ack wait still can
never exit — same permanent hang, silent instead of loud.

No ddb prompt, no gdb packet on the wire, no dump, no auto-reboot: the
machine is permanently hung. Any later kernel panic (e.g. remotely
triggerable crash) on such a box becomes a permanent hang instead of the
usual dump-and-reboot.

## How to reproduce (guest is single-tenant; serial console is output-only)

No build is required — the stock kernel carries the stub
(`X86_64_GENERIC`, `optional ddb`).

1. `./run.sh`   — triggers `sysctl debug.enter_debugger=gdb` over ssh
   (production path: `sys/ddb/db_sysctl.c:57-74` sets `boothowto |=
   RB_GDB` then calls `Debugger()`).
2. Expected (baseline, vulnerable kernel): the ssh command never returns
   (exit 124 after the 60 s timeout), the guest stops answering ssh
   entirely, QEMU keeps running, and the serial log shows the kdb banner
   `CPU0 stopping CPUs:` / ` stopped` **followed by eternal silence**
   (no ddb banner, no `$` packet — `putDebugChar` produced no bytes).
3. `./run_control.sh` — same entry via `debug.enter_debugger=ddb`
   (differential control): serial shows the ddb banner + `db>` prompt,
   proving the trap entry works and only the gdb branch hangs silently.
   (Control run also wedges the guest at the `db>` prompt — the guest's
   serial console is `-serial file:` and accepts no input — recover with
   `dfbsd-qemu/vm.sh reset with-src`.)
4. Fix validation: `fix.diff` adds an early `if (gdb_tab == NULL) return;`
   to `gdb_handle_exception`. With the patched kernel the *same* trigger
   returns cleanly: the sysctl exits 0, `echo STILL_ALIVE` works, serial
   shows stopping/stopped/restarted. See `run.patched.log`.

## Files

- `run.sh` / `run_control.sh` / `run_patched.sh` — exact runnable commands
- `run.log` / `serial.baseline.txt` — baseline hang (RC 124, wedged guest)
- `run.control.log` / `serial.control.txt` — ddb differential control
- `run.patched.log` / `serial.patched.txt` — fix validation
- `fix.diff` — one-guard fix (never applied to the read-only `sys/` tree)
- `env.txt` — guest identity, symbols, CPU count
- `VERDICT.md`, `manifest.json`, `verdict.json`
