delve/pkg/proc/native/threads_linux_arm64.go

192 lines
6.0 KiB
Go
Raw Normal View History

package native
import (
"debug/elf"
"errors"
"fmt"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
)
func (thread *nativeThread) fpRegisters() ([]proc.Register, []byte, error) {
var err error
var arm_fpregs linutil.ARM64PtraceFpRegs
thread.dbp.execPtraceFunc(func() { arm_fpregs.Vregs, err = ptraceGetFpRegset(thread.ID) })
fpregs := arm_fpregs.Decode()
if err != nil {
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
}
return fpregs, arm_fpregs.Vregs, err
}
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
sr := savedRegs.(*linutil.ARM64Registers)
var restoreRegistersErr error
t.dbp.execPtraceFunc(func() {
restoreRegistersErr = ptraceSetGRegs(t.ID, sr.Regs)
if restoreRegistersErr != syscall.Errno(0) && restoreRegistersErr != nil {
return
}
if sr.Fpregset != nil {
iov := sys.Iovec{Base: &sr.Fpregset[0], Len: uint64(len(sr.Fpregset))}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), uintptr(elf.NT_FPREGSET), uintptr(unsafe.Pointer(&iov)), 0, 0)
}
})
if restoreRegistersErr == syscall.Errno(0) {
restoreRegistersErr = nil
}
return restoreRegistersErr
}
const (
_MAX_ARM64_WATCH = 16
_NT_ARM_HW_WATCH = 0x403
_TRAP_HWBKPT = 0x4
)
type watchpointState struct {
num uint8
debugVer uint8
words []uint64
}
func (wpstate *watchpointState) set(idx uint8, addr, ctrl uint64) {
wpstate.words[1+idx*2] = addr
wpstate.words[1+idx*2+1] = ctrl
}
// getWatchpoints reads the NT_ARM_HW_WATCH ptrace register set.
// The format of this register set is described by user_hwdebug_state in
// arch/arm64/include/uapi/asm/ptrace.h.
// It consists of one 64bit word containing:
// - 1byte number of watchpoints
// - 1byte debug architecture version (the 4 least significant bits of ID_AA64DFR0_EL1)
// - 6bytes padding
//
// Followed by 2 64bit words for each watchpoint, up to a maximum of 16
// watchpoints. The first word contains the address at which the watchpoint
// is set (DBGWVRn_EL1), the second word is the control register for the
// watchpoint (DBGWCRn_EL1), as described by
// ARM - Architecture Reference Manual Armv8, for A-profile architectures
// section D13.3.11
// where only the BAS, LSC, PAC and E fields are accessible.
func (t *nativeThread) getWatchpoints() (*watchpointState, error) {
words := make([]uint64, _MAX_ARM64_WATCH*2+1)
iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&words[0])), Len: uint64(len(words)) * uint64(unsafe.Sizeof(words[0]))}
var err error
t.dbp.execPtraceFunc(func() {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
})
if err != syscall.Errno(0) {
return nil, err
}
wpstate := &watchpointState{num: uint8(words[0] & 0xff), debugVer: uint8((words[0] >> 8) & 0xff), words: words}
if wpstate.num > _MAX_ARM64_WATCH {
// According to the specification this should never be more than 16 but
// the code here will not work if this limit ever gets relaxed.
wpstate.num = _MAX_ARM64_WATCH
}
// remove the watchpoint registers that don't exist from the words slice so
// that we won't try later to write them (which would cause an ENOSPC
// error).
wpstate.words = wpstate.words[:wpstate.num*2+1]
return wpstate, nil
}
// setWatchpoints saves the watchpoint state of the given thread.
// See (*nativeThread).getWatchpoints for a description of how this works.
func (t *nativeThread) setWatchpoints(wpstate *watchpointState) error {
iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(&(wpstate.words[0]))), Len: uint64(len(wpstate.words)) * uint64(unsafe.Sizeof(wpstate.words[0]))}
var err error
t.dbp.execPtraceFunc(func() {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGSET, uintptr(t.ID), _NT_ARM_HW_WATCH, uintptr(unsafe.Pointer(&iov)), 0, 0)
})
if err != syscall.Errno(0) {
return err
}
return nil
}
type ptraceSiginfoArm64 struct {
signo uint32
errno uint32
code uint32
addr uint64 // only valid if Signo is SIGTRAP, SIGFPE, SIGILL, SIGBUS or SIGEMT
pad [128]byte // the total size of siginfo_t on ARM64 is 128 bytes so this is more than enough padding for all the fields we don't care about
}
func (t *nativeThread) findHardwareBreakpoint() (*proc.Breakpoint, error) {
var siginfo ptraceSiginfoArm64
var err error
t.dbp.execPtraceFunc(func() {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETSIGINFO, uintptr(t.ID), 0, uintptr(unsafe.Pointer(&siginfo)), 0, 0)
})
if err != syscall.Errno(0) {
return nil, err
}
if siginfo.signo != uint32(sys.SIGTRAP) || (siginfo.code&0xffff) != _TRAP_HWBKPT {
return nil, nil
}
for _, bp := range t.dbp.Breakpoints().M {
if bp.WatchType != 0 && siginfo.addr >= bp.Addr && siginfo.addr < bp.Addr+uint64(bp.WatchType.Size()) {
return bp, nil
}
}
return nil, fmt.Errorf("could not find hardware breakpoint for address %#x", siginfo.addr)
}
func (t *nativeThread) writeHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
wpstate, err := t.getWatchpoints()
if err != nil {
return err
}
if idx >= wpstate.num {
return errors.New("hardware breakpoints exhausted")
}
const (
readBreakpoint = 0x1
writeBreakpoint = 0x2
lenBitOffset = 5
typeBitOffset = 3
privBitOffset = 1
)
var typ uint64
if wtype.Read() {
typ |= readBreakpoint
}
if wtype.Write() {
typ |= writeBreakpoint
}
len := uint64((1 << wtype.Size()) - 1) // arm wants the length expressed as address bitmask
priv := uint64(3)
ctrl := (len << lenBitOffset) | (typ << typeBitOffset) | (priv << privBitOffset) | 1
wpstate.set(idx, addr, ctrl)
return t.setWatchpoints(wpstate)
}
func (t *nativeThread) clearHardwareBreakpoint(addr uint64, wtype proc.WatchType, idx uint8) error {
wpstate, err := t.getWatchpoints()
if err != nil {
return err
}
if idx >= wpstate.num {
return errors.New("hardware breakpoints exhausted")
}
wpstate.set(idx, 0, 0)
return t.setWatchpoints(wpstate)
}