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