fix(debug): hardware watchpoints, signal stops and breakpoint restore
Assisted-by: GLM 5.3
This commit is contained in:
@@ -8,10 +8,47 @@ package debug
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Hardware watchpoint support for LoongArch via debug registers.
|
||||
// Uses PTRACE_POKEUSER/PEEKUSER to access HW watchpoint registers.
|
||||
// Hardware watchpoint support via the NT_LOONGARCH_HW_WATCH regset.
|
||||
//
|
||||
// The kernel's PTRACE_POKEUSER on loong64 accepts only the user_pt_regs
|
||||
// indices 0-34 (GPRs, orig_a0, era, badv, per
|
||||
// arch/loongarch/include/uapi/asm/ptrace.h), so there is no debug-register
|
||||
// window to poke. The real interface is PTRACE_GETREGSET/SETREGSET on
|
||||
// NT_LOONGARCH_HW_WATCH (0xa06, include/uapi/linux/elf.h) with struct
|
||||
// user_watch_state_v2 (arch/loongarch/include/uapi/asm/ptrace.h): a dbg_info
|
||||
// word followed by 14 slots of {addr u64, mask u64, ctrl u32, pad u32}.
|
||||
// hw_break_get puts the slot count in the low byte of dbg_info
|
||||
// (arch/loongarch/kernel/ptrace.c, ptrace_hbp_get_resource_info) and
|
||||
// hw_break_set ignores dbg_info, reading addr, mask and ctrl per slot.
|
||||
|
||||
const ntLoongHWWatch = 0xa06
|
||||
|
||||
// loongWatchState mirrors the kernel's struct user_watch_state_v2.
|
||||
type loongWatchState struct {
|
||||
DbgInfo uint64
|
||||
DbgRegs [14]loongWatchReg
|
||||
}
|
||||
|
||||
type loongWatchReg struct {
|
||||
Addr uint64
|
||||
Mask uint64
|
||||
Ctrl uint32
|
||||
Pad uint32
|
||||
}
|
||||
|
||||
// Control word bit layout (arch/loongarch/include/asm/hw_breakpoint.h):
|
||||
// bits 1-4 privilege enables (CTRL_PLV3_ENABLE, 0x10, covers user mode),
|
||||
// bits 8-9 access type (LOAD 1<<0, STORE 1<<1), bits 10-11 length
|
||||
// (0=8 bytes, 1=4, 2=2, 3=1, inverted like the hardware FWP cfg).
|
||||
const (
|
||||
loongCtrlPLV3Enable = 0x10
|
||||
loongTypeLoad = 1 << 8
|
||||
loongTypeStore = 2 << 8
|
||||
loongLenShift = 10
|
||||
)
|
||||
|
||||
// WatchpointType selects what triggers the watchpoint.
|
||||
type WatchpointType int
|
||||
@@ -21,10 +58,16 @@ const (
|
||||
WatchRead WatchpointType = 3
|
||||
)
|
||||
|
||||
const maxWatchpoints = 4
|
||||
// maxWatchpoints reports the slot capacity of the regset struct; the number
|
||||
// the hardware actually provides is read from dbg_info at arm time.
|
||||
func maxWatchpoints() int { return len(loongWatchState{}.DbgRegs) }
|
||||
|
||||
// archWatchpointAddr resolves the address of the watchpoint that fired:
|
||||
// the loongarch kernel already reports the accessed address as si_addr.
|
||||
func archWatchpointAddr(s *Session, siAddr uint64) uint64 { return siAddr }
|
||||
|
||||
func (s *Session) FindFreeWatchpointSlot() int {
|
||||
for i := range maxWatchpoints {
|
||||
for i := range maxWatchpoints() {
|
||||
if !s.wpSlots[i] {
|
||||
return i
|
||||
}
|
||||
@@ -33,53 +76,56 @@ func (s *Session) FindFreeWatchpointSlot() int {
|
||||
}
|
||||
|
||||
func (s *Session) IsWatchpointSlotUsed(slot int) bool {
|
||||
if slot < 0 || slot >= maxWatchpoints {
|
||||
if slot < 0 || slot >= maxWatchpoints() {
|
||||
return false
|
||||
}
|
||||
return s.wpSlots[slot]
|
||||
}
|
||||
|
||||
// SetWatchpoint installs a hardware watchpoint.
|
||||
// SetWatchpoint installs a hardware watchpoint on the given address.
|
||||
func (s *Session) SetWatchpoint(slot int, addr uint64, typ WatchpointType, size int) error {
|
||||
if slot < 0 || slot >= maxWatchpoints {
|
||||
return fmt.Errorf("debug: watchpoint slot must be 0-%d", maxWatchpoints-1)
|
||||
if slot < 0 || slot >= maxWatchpoints() {
|
||||
return fmt.Errorf("debug: watchpoint slot must be 0-%d", maxWatchpoints()-1)
|
||||
}
|
||||
if s.wpSlots[slot] {
|
||||
return fmt.Errorf("debug: watchpoint slot %d already in use", slot)
|
||||
}
|
||||
if size != 1 && size != 2 && size != 4 && size != 8 {
|
||||
|
||||
var ctrlType uint32
|
||||
switch typ {
|
||||
case WatchWrite:
|
||||
ctrlType = loongTypeStore
|
||||
case WatchRead:
|
||||
ctrlType = loongTypeLoad | loongTypeStore
|
||||
}
|
||||
var lenBits uint32
|
||||
switch size {
|
||||
case 1:
|
||||
lenBits = 3
|
||||
case 2:
|
||||
lenBits = 2
|
||||
case 4:
|
||||
lenBits = 1
|
||||
case 8:
|
||||
lenBits = 0
|
||||
default:
|
||||
return fmt.Errorf("debug: watchpoint size must be 1, 2, 4, or 8")
|
||||
}
|
||||
|
||||
// LoongArch debug registers: DBGWVR (watchpoint value) and DBGWCR (watchpoint control).
|
||||
// Accessed via PTRACE_POKEUSER at architecture-specific offsets.
|
||||
if err := ptracePokeUser(s.pid, uintptr(0x1000+slot*8), addr); err != nil {
|
||||
return fmt.Errorf("debug: set watchpoint address: %w", err)
|
||||
state, err := s.getLoongWatchState()
|
||||
if err != nil {
|
||||
return fmt.Errorf("debug: read watchpoint state: %w", err)
|
||||
}
|
||||
if uint64(slot) >= state.DbgInfo&0xff {
|
||||
return fmt.Errorf("debug: slot %d exceeds available watchpoints (%d)", slot, state.DbgInfo&0xff)
|
||||
}
|
||||
|
||||
// DBGWCR: enable + type + size.
|
||||
var wcr uint64 = 1 // enable
|
||||
switch typ {
|
||||
case WatchWrite:
|
||||
wcr |= 1 << 3 // store
|
||||
case WatchRead:
|
||||
wcr |= 3 << 3 // load+store
|
||||
}
|
||||
var sizeBits uint64
|
||||
switch size {
|
||||
case 1:
|
||||
sizeBits = 0
|
||||
case 2:
|
||||
sizeBits = 1
|
||||
case 4:
|
||||
sizeBits = 2
|
||||
case 8:
|
||||
sizeBits = 3
|
||||
}
|
||||
wcr |= sizeBits << 5
|
||||
state.DbgRegs[slot].Addr = addr
|
||||
state.DbgRegs[slot].Mask = 0
|
||||
state.DbgRegs[slot].Ctrl = loongCtrlPLV3Enable | ctrlType | lenBits<<loongLenShift
|
||||
|
||||
if err := ptracePokeUser(s.pid, uintptr(0x1001+slot*8), wcr); err != nil {
|
||||
return fmt.Errorf("debug: set watchpoint control: %w", err)
|
||||
if err := s.setLoongWatchState(state); err != nil {
|
||||
return fmt.Errorf("debug: set watchpoint: %w", err)
|
||||
}
|
||||
|
||||
s.wpSlots[slot] = true
|
||||
@@ -87,14 +133,21 @@ func (s *Session) SetWatchpoint(slot int, addr uint64, typ WatchpointType, size
|
||||
}
|
||||
|
||||
func (s *Session) ClearWatchpoint(slot int) error {
|
||||
if slot < 0 || slot >= maxWatchpoints {
|
||||
return fmt.Errorf("debug: watchpoint slot must be 0-%d", maxWatchpoints-1)
|
||||
if slot < 0 || slot >= maxWatchpoints() {
|
||||
return fmt.Errorf("debug: watchpoint slot must be 0-%d", maxWatchpoints()-1)
|
||||
}
|
||||
if !s.wpSlots[slot] {
|
||||
return fmt.Errorf("debug: watchpoint slot %d is not in use", slot)
|
||||
}
|
||||
|
||||
if err := ptracePokeUser(s.pid, uintptr(0x1001+slot*8), 0); err != nil {
|
||||
state, err := s.getLoongWatchState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.DbgRegs[slot].Addr = 0
|
||||
state.DbgRegs[slot].Mask = 0
|
||||
state.DbgRegs[slot].Ctrl = 0
|
||||
if err := s.setLoongWatchState(state); err != nil {
|
||||
return err
|
||||
}
|
||||
s.wpSlots[slot] = false
|
||||
@@ -102,7 +155,7 @@ func (s *Session) ClearWatchpoint(slot int) error {
|
||||
}
|
||||
|
||||
func (s *Session) ClearAllWatchpoints() error {
|
||||
for slot := 0; slot < maxWatchpoints; slot++ {
|
||||
for slot := range maxWatchpoints() {
|
||||
if s.wpSlots[slot] {
|
||||
if err := s.ClearWatchpoint(slot); err != nil {
|
||||
return err
|
||||
@@ -112,14 +165,37 @@ func (s *Session) ClearAllWatchpoints() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ptracePokeUser(pid int, offset uintptr, val uint64) error {
|
||||
const ptracePokeuser = 6
|
||||
func (s *Session) getLoongWatchState() (*loongWatchState, error) {
|
||||
var state loongWatchState
|
||||
iovec := syscall.Iovec{
|
||||
Base: (*byte)(unsafe.Pointer(&state)),
|
||||
Len: uint64(unsafe.Sizeof(state)),
|
||||
}
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PTRACE,
|
||||
uintptr(ptracePokeuser),
|
||||
uintptr(pid),
|
||||
offset,
|
||||
uintptr(val),
|
||||
uintptr(syscall.PTRACE_GETREGSET),
|
||||
uintptr(s.pid),
|
||||
uintptr(ntLoongHWWatch),
|
||||
uintptr(unsafe.Pointer(&iovec)),
|
||||
0, 0,
|
||||
)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
func (s *Session) setLoongWatchState(state *loongWatchState) error {
|
||||
iovec := syscall.Iovec{
|
||||
Base: (*byte)(unsafe.Pointer(state)),
|
||||
Len: uint64(unsafe.Sizeof(*state)),
|
||||
}
|
||||
_, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PTRACE,
|
||||
uintptr(syscall.PTRACE_SETREGSET),
|
||||
uintptr(s.pid),
|
||||
uintptr(ntLoongHWWatch),
|
||||
uintptr(unsafe.Pointer(&iovec)),
|
||||
0, 0,
|
||||
)
|
||||
if errno != 0 {
|
||||
@@ -127,18 +203,3 @@ func ptracePokeUser(pid int, offset uintptr, val uint64) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ptracePeekUser(pid int, offset uintptr) (uint64, error) {
|
||||
const ptracePeekuser = 3
|
||||
val, _, errno := syscall.Syscall6(
|
||||
syscall.SYS_PTRACE,
|
||||
uintptr(ptracePeekuser),
|
||||
uintptr(pid),
|
||||
offset,
|
||||
0, 0, 0,
|
||||
)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
return uint64(val), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user