fix(debug): handle mapping edges, stray traps and dying debuggees

Assisted-by: GLM 5.3
This commit is contained in:
2026-10-02 00:40:54 +02:00
parent dd1782c538
commit f57377abb9
19 changed files with 726 additions and 97 deletions
+30 -6
View File
@@ -7,7 +7,11 @@ package debug
import "strings"
import "fmt"
import (
"cmp"
"fmt"
"slices"
)
// Breakpoint is one software breakpoint in the debuggee.
type Breakpoint struct {
@@ -149,15 +153,16 @@ func (bm *Breakpoints) SetWithCond(addr uint64, label string, cond *Condition) (
return bp, nil
}
// Info returns a formatted list of all breakpoints.
// Info returns a formatted list of all breakpoints, ordered by address so
// the numbering is stable across calls (map iteration order is not).
func (bm *Breakpoints) Info() string {
if len(bm.bps) == 0 {
return "no breakpoints set\n"
}
var result strings.Builder
i := 0
for _, bp := range bm.bps {
i++
bps := bm.All()
slices.SortFunc(bps, func(a, b *Breakpoint) int { return cmp.Compare(a.Addr, b.Addr) })
for i, bp := range bps {
status := "enabled"
if !bp.Enabled {
status = "disabled"
@@ -170,7 +175,7 @@ func (bm *Breakpoints) Info() string {
if bp.Cond != nil {
cond = " if " + bp.Cond.String()
}
result.WriteString(fmt.Sprintf(" %d: %s at %#x [%s, %d hits]%s\n", i, label, bp.Addr, status, bp.hits, cond))
result.WriteString(fmt.Sprintf(" %d: %s at %#x [%s, %d hits]%s\n", i+1, label, bp.Addr, status, bp.hits, cond))
}
return result.String()
}
@@ -238,6 +243,25 @@ func (bm *Breakpoints) All() []*Breakpoint {
// Hits returns how many times the breakpoint has been hit.
func (bp *Breakpoint) Hits() int { return bp.hits }
// TrapStray reports whether a stop is a breakpoint-class trap that matches
// no breakpoint of ours and cannot be resumed: the PC still stands on the
// trapping instruction (the kernel's own BRK, EBREAK or break, on an
// architecture that reports the trap in place), so the next resume would
// re-execute it and trap forever. trapPC is the PC the stop reported,
// before any HandleTrap rewinding; reason is the stop's StopInfo class.
// The debuggee's SIGSTOP barriers also stop without PC movement, and they
// never carry the breakpoint class, so they are unaffected.
func TrapStray(s *Session, reason StopReason, trapPC uint64) bool {
if reason != StopBreakpoint {
return false
}
after, err := s.GetRegs()
if err != nil {
return false
}
return after.GetPC() <= trapPC-uint64(breakpointPCAdjust)
}
func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
// On amd64 the kernel reports the trap with RIP past the INT3; on the
// other supported architectures the PC still stands on the trap