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
+62 -14
View File
@@ -71,7 +71,12 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
case "step", "s":
n := 1
if len(parts) > 1 {
n, _ = strconv.Atoi(parts[1])
v, err := strconv.Atoi(parts[1])
if err != nil || v < 0 {
fmt.Printf("invalid count: %s\n", parts[1])
continue
}
n = v
}
for range n {
if s.Exited() {
@@ -82,8 +87,13 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
fmt.Println(err)
break
}
if sig := s.LastSignal(); sig != 0 {
regs, _ := s.GetRegs()
fmt.Printf("stopped on signal %v at %#x\n", sig, regs.GetPC())
break
}
}
if !s.Exited() {
if !s.Exited() && s.LastSignal() == 0 {
regs, _ := s.GetRegs()
pc := regs.GetPC()
text, _, _ := s.Disassemble(pc)
@@ -106,16 +116,16 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
}
if err := s.Continue(); err != nil {
fmt.Println(err)
bm.Clear(afterAddr)
clearNextBp(bm, afterAddr)
continue
}
if s.Exited() {
bm.Clear(afterAddr)
clearNextBp(bm, afterAddr)
fmt.Println("debuggee exited")
continue
}
if sig := s.LastSignal(); sig != 0 {
bm.Clear(afterAddr)
clearNextBp(bm, afterAddr)
regs, _ := s.GetRegs()
fmt.Printf("stopped on signal %v at %#x\n", sig, regs.GetPC())
continue
@@ -125,12 +135,17 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
// snapshot, and a stale SetRegs would clobber live state.
regs, _ = s.GetRegs()
bm.HandleTrap(&regs)
bm.Clear(afterAddr)
clearNextBp(bm, afterAddr)
} else {
if err := s.Step(); err != nil {
fmt.Println(err)
continue
}
if sig := s.LastSignal(); sig != 0 {
regs, _ := s.GetRegs()
fmt.Printf("stopped on signal %v at %#x\n", sig, regs.GetPC())
continue
}
}
if !s.Exited() {
regs, _ := s.GetRegs()
@@ -156,16 +171,16 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
}
if err := s.Continue(); err != nil {
fmt.Println(err)
bm.Clear(retAddr)
clearNextBp(bm, retAddr)
continue
}
if s.Exited() {
bm.Clear(retAddr)
clearNextBp(bm, retAddr)
fmt.Println("debuggee exited")
continue
}
if sig := s.LastSignal(); sig != 0 {
bm.Clear(retAddr)
clearNextBp(bm, retAddr)
regs, _ := s.GetRegs()
fmt.Printf("stopped on signal %v at %#x\n", sig, regs.GetPC())
continue
@@ -174,7 +189,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
// does: HandleTrap must see the PC the trap left behind.
regs, _ = s.GetRegs()
bm.HandleTrap(&regs)
bm.Clear(retAddr)
clearNextBp(bm, retAddr)
if s.Exited() {
fmt.Println("debuggee exited")
} else {
@@ -214,6 +229,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
break
}
regs, _ := s.GetRegs()
trapPC := regs.GetPC()
if bp := bm.HandleTrap(&regs); bp != nil {
// Execute the instruction under the restored breakpoint
// so the next continue cannot re-trap on the same
@@ -229,6 +245,13 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
fmt.Printf("breakpoint hit: %s (func+%#x)\n", name, bp.Addr-codeBase-uint64(funcOffset))
break
}
// The trap matched no breakpoint of ours. When the PC still
// stands on the trapping instruction, resuming would re-execute
// it and trap forever, so surface the stop instead of spinning.
if TrapStray(s, reason, trapPC) {
fmt.Printf("SIGTRAP at %#x matches no breakpoint; the PC did not advance\n", trapPC-uint64(breakpointPCAdjust))
break
}
}
case "break", "b":
@@ -326,6 +349,10 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
length := 64
if len(parts) > 1 {
addr, _ = resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
if addr == 0 {
fmt.Printf("unknown address: %s\n", parts[1])
continue
}
}
if len(parts) > 2 {
// A malformed or non-positive length would panic
@@ -399,9 +426,13 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
case "disas", "u":
n := 5
if len(parts) > 1 {
n, _ = strconv.Atoi(parts[1])
if n <= 0 {
n = 5
v, err := strconv.Atoi(parts[1])
if err != nil {
fmt.Printf("invalid count: %s\n", parts[1])
continue
}
if v > 0 {
n = v
}
}
regs, _ := s.GetRegs()
@@ -496,10 +527,18 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
typ = WatchRead
case "w":
typ = WatchWrite
default:
fmt.Printf("unknown watchpoint type: %s (want r or w)\n", parts[2])
continue
}
}
if len(parts) > 3 {
size, _ = strconv.Atoi(parts[3])
v, err := strconv.Atoi(parts[3])
if err != nil || v <= 0 {
fmt.Printf("invalid size: %s\n", parts[3])
continue
}
size = v
}
slot := s.FindFreeWatchpointSlot()
if slot < 0 {
@@ -543,6 +582,15 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
s.Kill()
}
// clearNextBp removes one of the temporary breakpoints the next and finish
// commands plant, reporting a failure instead of silently leaving the trap
// instruction behind in the debuggee.
func clearNextBp(bm *Breakpoints, addr uint64) {
if err := bm.Clear(addr); err != nil {
fmt.Printf("cannot remove temporary breakpoint at %#x: %v\n", addr, err)
}
}
func hexDump(addr uint64, data []byte) {
for i := 0; i < len(data); i += 16 {
end := min(i+16, len(data))