diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md index ed231acf..27cf1bf5 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -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 diff --git a/pkg/proc/native/hwbreak_other.go b/pkg/proc/native/hwbreak_other.go index d841cd9e..548f3336 100644 --- a/pkg/proc/native/hwbreak_other.go +++ b/pkg/proc/native/hwbreak_other.go @@ -1,4 +1,5 @@ -// +build !amd64 +//go:build (linux && 386) || (darwin && arm64) +// +build linux,386 darwin,arm64 package native diff --git a/pkg/proc/native/threads_linux_arm64.go b/pkg/proc/native/threads_linux_arm64.go index f2f4d290..ea0e7642 100644 --- a/pkg/proc/native/threads_linux_arm64.go +++ b/pkg/proc/native/threads_linux_arm64.go @@ -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) +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 32c1f719..7871add3 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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