proc/native: support watchpoints on linux/arm64 (#2667)
This commit is contained in:
parent
99f03597c3
commit
ea18bc6d6d
@ -16,9 +16,8 @@ Tests skipped by each supported backend:
|
||||
* 4 not implemented
|
||||
* linux/386/pie skipped = 1
|
||||
* 1 broken
|
||||
* linux/arm64 skipped = 4
|
||||
* linux/arm64 skipped = 1
|
||||
* 1 broken - cgo stacktraces
|
||||
* 3 not implemented
|
||||
* pie skipped = 2
|
||||
* 2 upstream issue - https://github.com/golang/go/issues/29322
|
||||
* windows skipped = 2
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// +build !amd64
|
||||
//go:build (linux && 386) || (darwin && arm64)
|
||||
// +build linux,386 darwin,arm64
|
||||
|
||||
package native
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package native
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@ -42,3 +43,148 @@ func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@ -5378,7 +5378,6 @@ func TestVariablesWithExternalLinking(t *testing.T) {
|
||||
func TestWatchpointsBasic(t *testing.T) {
|
||||
skipOn(t, "not implemented", "freebsd")
|
||||
skipOn(t, "not implemented", "386")
|
||||
skipOn(t, "not implemented", "linux", "arm64")
|
||||
protest.AllowRecording(t)
|
||||
|
||||
position1 := 19
|
||||
@ -5437,7 +5436,6 @@ func TestWatchpointsBasic(t *testing.T) {
|
||||
func TestWatchpointCounts(t *testing.T) {
|
||||
skipOn(t, "not implemented", "freebsd")
|
||||
skipOn(t, "not implemented", "386")
|
||||
skipOn(t, "not implemented", "linux", "arm64")
|
||||
protest.AllowRecording(t)
|
||||
|
||||
withTestProcess("databpcountstest", t, func(p *proc.Target, fixture protest.Fixture) {
|
||||
@ -5552,7 +5550,6 @@ func TestDwrapStartLocation(t *testing.T) {
|
||||
func TestWatchpointStack(t *testing.T) {
|
||||
skipOn(t, "not implemented", "freebsd")
|
||||
skipOn(t, "not implemented", "386")
|
||||
skipOn(t, "not implemented", "linux", "arm64")
|
||||
protest.AllowRecording(t)
|
||||
|
||||
position1 := 17
|
||||
|
||||
Loading…
Reference in New Issue
Block a user