fix(debug): hardware watchpoints, signal stops and breakpoint restore
Assisted-by: GLM 5.3
This commit is contained in:
@@ -8,10 +8,48 @@ package debug
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Hardware watchpoint support via x86-64 debug registers (DR0-DR3, DR7).
|
||||
|
||||
// The kernel translates PTRACE_POKEUSER/PEEKUSER offsets inside
|
||||
// [offsetof(struct user, u_debugreg[0]), u_debugreg[7]] to DR0-DR7
|
||||
// (arch/x86/kernel/ptrace.c, arch_ptrace). sys/user.h places u_debugreg at
|
||||
// 0x350: DR0-DR3 are 0x350/0x358/0x360/0x368, DR6 (status) is 0x380 and
|
||||
// DR7 (control) is 0x388. Offsets below 0x350 write user_regs_struct
|
||||
// fields (r15 at 0x0, r10 at 0x38), not debug registers.
|
||||
const (
|
||||
drOffset = 0x350 // offsetof(struct user, u_debugreg[0]), DR0
|
||||
dr6Off = 0x380 // offsetof(struct user, u_debugreg[6]), DR6
|
||||
dr7Off = 0x388 // offsetof(struct user, u_debugreg[7]), DR7
|
||||
)
|
||||
|
||||
// archWatchpointAddr resolves the address of the watchpoint that fired.
|
||||
// x86 delivers si_addr = the instruction pointer of the trapping access
|
||||
// (arch/x86/kernel/ptrace.c send_sigtrap passes regs->ip), so the watched
|
||||
// data address is recovered from DR6's slot bits (B0-B3, positive polarity
|
||||
// through PEEKUSER) and the matching DR0-DR3.
|
||||
func archWatchpointAddr(s *Session, siAddr uint64) uint64 {
|
||||
dr6, err := ptracePeekUser(s.pid, dr6Off)
|
||||
if err != nil {
|
||||
return siAddr
|
||||
}
|
||||
for slot := range 4 {
|
||||
if dr6&(1<<slot) != 0 {
|
||||
addr, err := ptracePeekUser(s.pid, drOffset+uintptr(slot*8))
|
||||
if err == nil && addr != 0 {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
}
|
||||
return siAddr
|
||||
}
|
||||
|
||||
// maxWatchpoints reports the number of hardware watchpoint slots the
|
||||
// architecture provides: four address registers, DR0-DR3.
|
||||
func maxWatchpoints() int { return 4 }
|
||||
|
||||
// WatchpointType selects what triggers the watchpoint.
|
||||
type WatchpointType int
|
||||
|
||||
@@ -62,23 +100,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")
|
||||
}
|
||||
|
||||
var drAddr uintptr
|
||||
switch slot {
|
||||
case 0:
|
||||
drAddr = 0x0
|
||||
case 1:
|
||||
drAddr = 0x8
|
||||
case 2:
|
||||
drAddr = 0x10
|
||||
case 3:
|
||||
drAddr = 0x18
|
||||
}
|
||||
|
||||
if err := ptracePokeUser(s.pid, drAddr, addr); err != nil {
|
||||
if err := ptracePokeUser(s.pid, drOffset+uintptr(slot*8), addr); err != nil {
|
||||
return fmt.Errorf("debug: set DR%d: %w", slot, err)
|
||||
}
|
||||
|
||||
dr7, err := ptracePeekUser(s.pid, 0x38)
|
||||
dr7, err := ptracePeekUser(s.pid, dr7Off)
|
||||
if err != nil {
|
||||
return fmt.Errorf("debug: read DR7: %w", err)
|
||||
}
|
||||
@@ -90,7 +116,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, 0x38, dr7); err != nil {
|
||||
if err := ptracePokeUser(s.pid, dr7Off, dr7); err != nil {
|
||||
return fmt.Errorf("debug: set DR7: %w", err)
|
||||
}
|
||||
s.wpSlots[slot] = true
|
||||
@@ -105,12 +131,12 @@ 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, 0x38)
|
||||
dr7, err := ptracePeekUser(s.pid, dr7Off)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dr7 &^= uint64(1) << (2 * slot)
|
||||
if err := ptracePokeUser(s.pid, 0x38, dr7); err != nil {
|
||||
if err := ptracePokeUser(s.pid, dr7Off, dr7); err != nil {
|
||||
return err
|
||||
}
|
||||
s.wpSlots[slot] = false
|
||||
@@ -119,7 +145,7 @@ func (s *Session) ClearWatchpoint(slot int) error {
|
||||
|
||||
// ClearAllWatchpoints removes all hardware watchpoints.
|
||||
func (s *Session) ClearAllWatchpoints() error {
|
||||
for slot := range 4 {
|
||||
for slot := range maxWatchpoints() {
|
||||
if s.wpSlots[slot] {
|
||||
if err := s.ClearWatchpoint(slot); err != nil {
|
||||
return err
|
||||
@@ -146,16 +172,21 @@ func ptracePokeUser(pid int, offset uintptr, val uint64) error {
|
||||
}
|
||||
|
||||
func ptracePeekUser(pid int, offset uintptr) (uint64, error) {
|
||||
// x86 PEEKUSR writes the word to the user-space pointer in data
|
||||
// (arch/x86/kernel/ptrace.c uses put_user); passing 0 there fails with
|
||||
// EFAULT, so the word is read through a real address.
|
||||
const ptracePeekuser = 3
|
||||
val, _, errno := syscall.Syscall6(
|
||||
var word uint64
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PTRACE,
|
||||
uintptr(ptracePeekuser),
|
||||
uintptr(pid),
|
||||
offset,
|
||||
0, 0, 0,
|
||||
uintptr(unsafe.Pointer(&word)),
|
||||
0, 0,
|
||||
)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return uint64(val), nil
|
||||
return word, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user