fix(debug): target the traced thread and keep the kill from ever blocking
The Go runtime can migrate the debuggee's target-mode goroutine off the process leader before PTRACE_TRACEME, which left the trace relation on a thread the session never addressed: its stops starved the waits on the leader, and a kill sequence that resumed nothing and then blocked in Wait4 hung the whole package. The debuggee now reports the traced thread in the launch handshake and parks with a thread-directed stop, every ptrace request and wait addresses that thread, a SIGURG arriving on a single-step resumes it as a single-step again instead of letting the tracee run uncontrolled, a resume rejected with ESRCH lifts a group-stop with SIGCONT and retries once, and Kill resumes, kills and reaps through non-blocking waits so it returns for a tracee in any state. Assisted-by: GLM 5.3
This commit is contained in:
1 parent
409c8b348d
commit
b5d6f1b46a
14 files changed
+265
-80
No files matched your search
@@ -36,7 +36,7 @@ const (
|
||||
// read, or the next hit on a different slot would still see this slot's bit
|
||||
// set and report this slot's address again.
|
||||
func archWatchpointAddr(s *Session, siAddr uint64) uint64 {
|
||||
dr6, err := ptracePeekUser(s.pid, dr6Off)
|
||||
dr6, err := ptracePeekUser(s.tid, dr6Off)
|
||||
if err != nil {
|
||||
return siAddr
|
||||
}
|
||||
@@ -46,10 +46,10 @@ func archWatchpointAddr(s *Session, siAddr uint64) uint64 {
|
||||
}
|
||||
// Best effort: the write-back only fails for a debuggee that died, in
|
||||
// which case no further watchpoint can fire anyway.
|
||||
_ = ptracePokeUser(s.pid, dr6Off, dr6&^0xF)
|
||||
_ = ptracePokeUser(s.tid, dr6Off, dr6&^0xF)
|
||||
for slot := range 4 {
|
||||
if status&(1<<slot) != 0 {
|
||||
addr, err := ptracePeekUser(s.pid, drOffset+uintptr(slot*8))
|
||||
addr, err := ptracePeekUser(s.tid, drOffset+uintptr(slot*8))
|
||||
if err == nil && addr != 0 {
|
||||
return addr
|
||||
}
|
||||
@@ -112,11 +112,11 @@ func (s *Session) SetWatchpoint(slot int, addr uint64, typ WatchpointType, size
|
||||
return fmt.Errorf("debug: watchpoint size must be 1, 2, 4, or 8")
|
||||
}
|
||||
|
||||
if err := ptracePokeUser(s.pid, drOffset+uintptr(slot*8), addr); err != nil {
|
||||
if err := ptracePokeUser(s.tid, drOffset+uintptr(slot*8), addr); err != nil {
|
||||
return fmt.Errorf("debug: set DR%d: %w", slot, err)
|
||||
}
|
||||
|
||||
dr7, err := ptracePeekUser(s.pid, dr7Off)
|
||||
dr7, err := ptracePeekUser(s.tid, dr7Off)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debug: read DR7: %w", err)
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (s *Session) SetWatchpoint(slot int, addr uint64, typ WatchpointType, size
|
||||
mask := ^((uint64(1) << (2 * slot)) | (uint64(3) << (16 + 4*slot)) | (uint64(3) << (18 + 4*slot)))
|
||||
dr7 = (dr7 & mask) | enableBit | rwBits | lenField
|
||||
|
||||
if err := ptracePokeUser(s.pid, dr7Off, dr7); err != nil {
|
||||
if err := ptracePokeUser(s.tid, dr7Off, dr7); err != nil {
|
||||
return fmt.Errorf("debug: set DR7: %w", err)
|
||||
}
|
||||
s.wpSlots[slot] = true
|
||||
@@ -143,17 +143,17 @@ func (s *Session) ClearWatchpoint(slot int) error {
|
||||
if !s.wpSlots[slot] {
|
||||
return fmt.Errorf("debug: watchpoint slot %d is not in use", slot)
|
||||
}
|
||||
dr7, err := ptracePeekUser(s.pid, dr7Off)
|
||||
dr7, err := ptracePeekUser(s.tid, dr7Off)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dr7 &^= uint64(1) << (2 * slot)
|
||||
if err := ptracePokeUser(s.pid, dr7Off, dr7); err != nil {
|
||||
if err := ptracePokeUser(s.tid, dr7Off, dr7); err != nil {
|
||||
return err
|
||||
}
|
||||
// Zero the address register too: a stale address in a disabled slot
|
||||
// turns any sticky DR6 bit into a misattributed watchpoint report.
|
||||
if err := ptracePokeUser(s.pid, drOffset+uintptr(slot*8), 0); err != nil {
|
||||
if err := ptracePokeUser(s.tid, drOffset+uintptr(slot*8), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
s.wpSlots[slot] = false
|
||||
|
||||
Reference in new issue
Block a user