Breakpoints and watchpoints¶
The shared breakpoint grammar follows WinDbg. Code breakpoints use /1 (one-shot), /p <pid> (process scope), /t <ethread> (thread scope), /c <processor> (processor scope), and /w "<expr>" (conditional shorthand), followed by a target, optional pass count, if <expr>, and do "<commands>"; ba takes the same options and adds <access><size>. /c has no WinDbg equivalent.
Conditions use the normal expression grammar. Comparisons, bitwise operations, and short-circuiting !, &&, and || can be combined with parentheses. Write ranges explicitly (0 < @rax && @rax < 0n10) rather than as chained comparisons; chained equality (a == b == c) is refused for the same reason and names && as the fix. Multi-command actions must be quoted, like WinDbg, and a trailing gc continues after the action.
bp nt!KeBugCheckEx @rcx == 0x50 && (@rdx & 0xff) != 0
bu /1 /p 1234 mydriver!DriverEntry
bu mydriver.c:42 10 if @rcx != 0 do "r; gc"
bm mydriver!Dispatch*
ba w8 nt!KiBalanceSetManagerLastCheckTick
A kernel code breakpoint can target a non-resident page. KD records the site and writes the breakpoint when the page arrives. Until then, bl shows o (owed).
User-space code breakpoints require resident memory. If the page is absent, set the breakpoint after the code has run, or use ba e1 <address>, which requires no memory write.
A ba e1 stop can precede the instruction page fault, leaving no bytes to disassemble. Registers and the stack remain available. Use t to execute the fetch and bring the page in; p needs to decode the instruction first.
/p filters reported hits; it does not change how a breakpoint is installed. Kernel sites are always target-managed. User-space sites are patched through the selected process’s page tables. Shared physical pages can therefore trap other processes too; those hits are discarded but still incur debugger round trips.
/t filters the same way, against the Windows thread the stop belongs to. It takes what !thread takes: a thread id, an ETHREAD, or a KTHREAD. No target programs a breakpoint per thread. A software site is a byte in a page every thread shares, and a debug register belongs to a processor that any thread may be scheduled on, so every thread executing the site still traps and the debugger discards the hits belonging to other threads. Discarding costs a step-over and a resume per hit, so a filter on a site the whole system calls slows the session down. A hit whose thread cannot be resolved is reported rather than discarded, so a filter never loses a stop silently.
/c filters by processor instead, taking the number ~ lists. It is checked against the vCPU the stop was reported on, which costs nothing to know, so unlike /t it adds no walk. A processor the guest does not have is rejected when the breakpoint is set, because a filter that can never match is a breakpoint that silently never fires.
KD has a fixed 32-entry software-breakpoint table. A session killed with SIGKILL leaves its entries installed and can prevent later breakpoints at those addresses.
Attach reclaims entries that no live session owns, restores displaced instructions, and reports the count. A colliding install also reclaims stale entries and retries. bc * only clears the current session’s handles. Normal exit, SIGTERM, and SIGHUP release them.