Files
gasm-sdk/debug/watchpoint_linux_arm64.go
T

182 lines
3.8 KiB
Go
Raw Normal View History

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && arm64
package debug
import (
"fmt"
"syscall"
"unsafe"
)
// Hardware watchpoint support via arm64 debug registers (DBGWVR/DBGWCR).
// Accessed via PTRACE_SETREGSET with NT_ARM_HW_BREAK.
// WatchpointType selects what triggers the watchpoint.
type WatchpointType int
const (
WatchWrite WatchpointType = 1
WatchRead WatchpointType = 3
)
// wpSlots tracks watchpoint slot occupancy.
var wpSlots [16]bool // arm64 supports up to 16 watchpoints
const maxWatchpoints = 16
// hwBreakState mirrors the kernel's struct user_hwdebug_state.
type hwBreakState struct {
DbgInfo uint32
_pad [4]byte
DbgRegs [16]hwBreakReg
}
type hwBreakReg struct {
Addr uint64
Ctrl uint64
}
const (
ntArmHWBreak = 0x403 // NT_ARM_HW_BREAK
)
func (s *Session) FindFreeWatchpointSlot() int {
for i := range maxWatchpoints {
if !wpSlots[i] {
return i
}
}
return -1
}
func (s *Session) IsWatchpointSlotUsed(slot int) bool {
if slot < 0 || slot >= maxWatchpoints {
return false
}
return wpSlots[slot]
}
// 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 wpSlots[slot] {
return fmt.Errorf("debug: watchpoint slot %d already in use", slot)
}
state, err := s.getHWBreakState()
if err != nil {
return fmt.Errorf("debug: read watchpoint state: %w", err)
}
if uint32(slot) >= state.DbgInfo {
return fmt.Errorf("debug: slot %d exceeds available watchpoints (%d)", slot, state.DbgInfo)
}
state.DbgRegs[slot].Addr = addr
ctrl := uint64(1) // enable
switch typ {
case WatchWrite:
ctrl |= 1 << 3 // store only
case WatchRead:
ctrl |= 3 << 3 // load+store
}
var bas uint64
switch size {
case 1:
bas = 0x01
case 2:
bas = 0x03
case 4:
bas = 0x0F
case 8:
bas = 0xFF
default:
return fmt.Errorf("debug: watchpoint size must be 1, 2, 4, or 8")
}
ctrl |= bas << 5
state.DbgRegs[slot].Ctrl = ctrl
if err := s.setHWBreakState(state); err != nil {
return fmt.Errorf("debug: set watchpoint: %w", err)
}
wpSlots[slot] = true
return nil
}
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 !wpSlots[slot] {
return fmt.Errorf("debug: watchpoint slot %d is not in use", slot)
}
state, err := s.getHWBreakState()
if err != nil {
return err
}
state.DbgRegs[slot].Addr = 0
state.DbgRegs[slot].Ctrl = 0
if err := s.setHWBreakState(state); err != nil {
return err
}
wpSlots[slot] = false
return nil
}
func (s *Session) ClearAllWatchpoints() error {
for slot := 0; slot < maxWatchpoints; slot++ {
if wpSlots[slot] {
if err := s.ClearWatchpoint(slot); err != nil {
return err
}
}
}
return nil
}
func (s *Session) getHWBreakState() (*hwBreakState, error) {
var state hwBreakState
iovec := syscall.Iovec{
Base: (*byte)(unsafe.Pointer(&state)),
Len: uint64(unsafe.Sizeof(state)),
}
_, _, errno := syscall.Syscall6(
syscall.SYS_PTRACE,
uintptr(syscall.PTRACE_GETREGSET),
uintptr(s.pid),
uintptr(ntArmHWBreak),
uintptr(unsafe.Pointer(&iovec)),
0, 0,
)
if errno != 0 {
return nil, errno
}
return &state, nil
}
func (s *Session) setHWBreakState(state *hwBreakState) 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(ntArmHWBreak),
uintptr(unsafe.Pointer(&iovec)),
0, 0,
)
if errno != 0 {
return errno
}
return nil
}