pkg/proc: fix and improve freebsd register handling (#3019)

* pkg/proc: convert freebsd ptrace code to cgo

There is little point in having cgo call a custom C function, when the same
can be done directly from cgo (with less code and effort). Split the amd64
specific code into ptrace_freebsd_amd64.go. Also avoid mixing C.ptrace()
with syscall.SYS_PTRACE.

This will make further changes easier - no functional change intended.

* pkg/proc: check return values of ptrace calls on freebsd

The return values of the PT_GETNUMLWPS and PT_GETLWPLIST ptrace calls were
previously unchecked. While these should not fail, panic instead of using
-1 with slice allocation/handling.

* pkg/proc: return *amd64util.AMD64Xstate from freebsd ptraceGetRegset

Return a pointer to a struct, rather than a struct - this simplifies the
code in both the caller and the ptraceGetRegset function, while also avoiding
struct copying.

* pkg/proc: fix floating point register setting on freebsd

The original code could never work - PT_SETREGS on freebsd does not
take an iovec, nor does it set FP registers. Furthermore, the xsave
bytes were not stored in the amd64util.AMD64Xstate struct.

Updates #3001

* pkg/proc: re-enable function call injection on freebsd

Floating point registers can now be set and restored correctly.

This is a partial revert of 51090f003bace1f8cc37b8480ffdb6f6cc91fa5a.

Fixes #3001

* pkg/proc: deduplicate register setting code on freebsd
This commit is contained in:
Joel Sing 2022-06-01 09:04:36 +10:00 committed by GitHub
parent 5b88e45ca9
commit 9c5777e762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 194 additions and 175 deletions

@ -1,6 +1,10 @@
package fbsdutil
import (
"fmt"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/regnum"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/amd64util"
)
@ -178,3 +182,71 @@ func (r *AMD64Registers) Copy() (proc.Registers, error) {
}
return &rr, nil
}
func (r *AMD64Registers) SetReg(regNum uint64, reg *op.DwarfRegister) (bool, error) {
var p *int64
switch regNum {
case regnum.AMD64_Rax:
p = &r.Regs.Rax
case regnum.AMD64_Rbx:
p = &r.Regs.Rbx
case regnum.AMD64_Rcx:
p = &r.Regs.Rcx
case regnum.AMD64_Rdx:
p = &r.Regs.Rdx
case regnum.AMD64_Rsi:
p = &r.Regs.Rsi
case regnum.AMD64_Rdi:
p = &r.Regs.Rdi
case regnum.AMD64_Rbp:
p = &r.Regs.Rbp
case regnum.AMD64_Rsp:
p = &r.Regs.Rsp
case regnum.AMD64_R8:
p = &r.Regs.R8
case regnum.AMD64_R9:
p = &r.Regs.R9
case regnum.AMD64_R10:
p = &r.Regs.R10
case regnum.AMD64_R11:
p = &r.Regs.R11
case regnum.AMD64_R12:
p = &r.Regs.R12
case regnum.AMD64_R13:
p = &r.Regs.R13
case regnum.AMD64_R14:
p = &r.Regs.R14
case regnum.AMD64_R15:
p = &r.Regs.R15
case regnum.AMD64_Rip:
p = &r.Regs.Rip
case regnum.AMD64_Rflags:
p = &r.Regs.Rflags
}
if p != nil {
if reg.Bytes != nil && len(reg.Bytes) != 8 {
return false, fmt.Errorf("wrong number of bytes for register %s (%d)", regnum.AMD64ToName(regNum), len(reg.Bytes))
}
*p = int64(reg.Uint64Val)
return false, nil
}
if r.loadFpRegs != nil {
if err := r.loadFpRegs(r); err != nil {
return false, err
}
r.loadFpRegs = nil
}
if regNum < regnum.AMD64_XMM0 || regNum > regnum.AMD64_XMM0+15 {
return false, fmt.Errorf("can not set %s", regnum.AMD64ToName(regNum))
}
reg.FillBytes()
if err := r.Fpregset.SetXmmRegister(int(regNum-regnum.AMD64_XMM0), reg.Bytes); err != nil {
return false, err
}
return true, nil
}

@ -5,16 +5,13 @@ package native
//#include <sys/ptrace.h>
//
// #include <stdlib.h>
// #include "ptrace_freebsd_amd64.h"
//
import "C"
import (
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc/amd64util"
)
// ptraceAttach executes the sys.PtraceAttach call.
@ -42,9 +39,15 @@ func ptraceSingleStep(id int) error {
// Get a list of the thread ids of a process
func ptraceGetLwpList(pid int) (tids []int32) {
num_lwps, _ := C.ptrace_get_num_lwps(C.int(pid))
tids = make([]int32, num_lwps)
n, _ := C.ptrace_get_lwp_list(C.int(pid), (*C.int)(unsafe.Pointer(&tids[0])), C.size_t(num_lwps))
numLWPS := C.ptrace(C.PT_GETNUMLWPS, C.pid_t(pid), C.caddr_t(unsafe.Pointer(uintptr(0))), C.int(0))
if numLWPS < 0 {
panic("PT_GETNUMLWPS failed")
}
tids = make([]int32, numLWPS)
n := C.ptrace(C.PT_GETLWPLIST, C.pid_t(pid), C.caddr_t(unsafe.Pointer(&tids[0])), C.int(numLWPS))
if n < 0 {
panic("PT_GETLWPLIST failed")
}
return tids[0:n]
}
@ -54,20 +57,6 @@ func ptraceGetLwpInfo(wpid int) (info sys.PtraceLwpInfoStruct, err error) {
return info, err
}
func ptraceGetRegset(id int) (regset amd64util.AMD64Xstate, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(id), uintptr(unsafe.Pointer(&regset.AMD64PtraceFpRegs)), 0, 0, 0)
if err == syscall.Errno(0) || err == syscall.ENODEV {
var xsave_len C.size_t
xsave, _ := C.ptrace_get_xsave(C.int(id), &xsave_len)
defer C.free(unsafe.Pointer(xsave))
if xsave != nil {
xsave_sl := C.GoBytes(unsafe.Pointer(xsave), C.int(xsave_len))
err = amd64util.AMD64XstateRead(xsave_sl, false, &regset)
}
}
return
}
// id may be a PID or an LWPID
func ptraceReadData(id int, addr uintptr, data []byte) (n int, err error) {
return sys.PtraceIO(sys.PIOD_READ_D, id, addr, data, len(data))

@ -1,73 +0,0 @@
#include <sys/types.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ptrace_freebsd_amd64.h"
/* Returns the number of kernel threads associated with the traced process. */
int ptrace_get_num_lwps(int pid) {
int ret;
errno = 0;
ret = ptrace(PT_GETNUMLWPS, (pid_t)pid, 0, 0);
return (ret);
}
/*
* Fetches the list of LWPs for a given process into tids. Returns the number
* of LWP entries filled in. Sets errno on return.
*/
int ptrace_get_lwp_list(int pid, int *tids, size_t len) {
int ret;
errno = 0;
ret = ptrace(PT_GETLWPLIST, (pid_t)pid, (caddr_t)tids, len);
return (ret);
}
/*
* Returns a pointer to the X86 XSAVE data, or NULL on failure. Returns the
* length of the buffer in the len argument. Must be freed when no longer in
* use. Modifies errno.
*/
unsigned char* ptrace_get_xsave(int tid, size_t *len) {
static ssize_t xsave_len = 0;
static int getxstate_info_errno = 0;
unsigned char *buf;
int err;
if (xsave_len == 0) {
/* Haven't tried to set the size yet */
struct ptrace_xstate_info info;
err = ptrace(PT_GETXSTATE_INFO, (pid_t)tid,
(caddr_t)&info, sizeof(info));
if (err == 0)
xsave_len = info.xsave_len;
else {
xsave_len = -1;
getxstate_info_errno = errno;
}
}
if (xsave_len < 0) {
/* Not supported on this system */
errno = getxstate_info_errno;
return (NULL);
}
buf = malloc(xsave_len);
if (buf == NULL) {
errno;
return (NULL);
}
err = ptrace(PT_GETXSTATE, (pid_t)tid, (caddr_t)buf, xsave_len);
if (err == 0) {
errno = 0;
*len = xsave_len;
return (buf);
} else {
free(buf);
return (NULL);
}
}

@ -0,0 +1,89 @@
package native
/*
#include <sys/types.h>
#include <sys/ptrace.h>
*/
import "C"
import (
"fmt"
"unsafe"
"github.com/go-delve/delve/pkg/proc/amd64util"
)
var (
xsaveLen int
xsaveErr error
)
func ptraceGetXsaveLen(tid int) (int, error) {
var info C.struct_ptrace_xstate_info
ret, err := C.ptrace(C.PT_GETXSTATE_INFO, C.pid_t(tid), C.caddr_t(unsafe.Pointer(&info)), C.int(unsafe.Sizeof(info)))
if ret == 0 {
xsaveLen = int(info.xsave_len)
} else {
xsaveLen, xsaveErr = -1, err
return xsaveLen, fmt.Errorf("failed to get xstate info: %v", err)
}
return xsaveLen, nil
}
func ptraceXsaveLen(tid int) (int, error) {
if xsaveLen > 0 {
return xsaveLen, nil
}
if xsaveLen < 0 {
return xsaveLen, fmt.Errorf("failed to get xstate info: %v", xsaveErr)
}
return ptraceGetXsaveLen(tid)
}
// ptraceGetXsave gets the X86 XSAVE data for the given tid.
func ptraceGetXsave(tid int) ([]byte, error) {
len, err := ptraceXsaveLen(tid)
if err != nil {
return nil, err
}
xsaveBuf := make([]byte, len)
ret, err := C.ptrace(C.PT_GETXSTATE, C.pid_t(tid), C.caddr_t(unsafe.Pointer(&xsaveBuf[0])), C.int(len))
if ret != 0 {
return nil, fmt.Errorf("failed to get xstate: %v", err)
}
return xsaveBuf, nil
}
// ptraceSetXsave sets the X86 XSAVE data for the given tid.
func ptraceSetXsave(tid int, xsaveBuf []byte) error {
ret, err := C.ptrace(C.PT_SETXSTATE, C.pid_t(tid), C.caddr_t(unsafe.Pointer(&xsaveBuf[0])), C.int(len(xsaveBuf)))
if ret != 0 {
return fmt.Errorf("failed to set xstate: %v", err)
}
return nil
}
func ptraceGetRegset(id int) (*amd64util.AMD64Xstate, error) {
var regset amd64util.AMD64Xstate
ret, err := C.ptrace(C.PT_GETFPREGS, C.pid_t(id), C.caddr_t(unsafe.Pointer(&regset.AMD64PtraceFpRegs)), C.int(0))
if ret != 0 {
return nil, fmt.Errorf("failed to get FP registers: %v", err)
}
regset.Xsave, err = ptraceGetXsave(id)
if err != nil {
return nil, err
}
err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset)
return &regset, err
}
func ptraceSetRegset(id int, regset *amd64util.AMD64Xstate) error {
ret, err := C.ptrace(C.PT_SETFPREGS, C.pid_t(id), C.caddr_t(unsafe.Pointer(&regset.AMD64PtraceFpRegs)), C.int(0))
if ret != 0 {
return fmt.Errorf("failed to set FP registers: %v", err)
}
if regset.Xsave != nil {
return ptraceSetXsave(id, regset.Xsave)
}
return nil
}

@ -1,7 +0,0 @@
#include <stddef.h>
struct ptrace_lwpinfo;
unsigned char* ptrace_get_xsave(int tid, size_t *len);
int ptrace_get_lwp_list(int pid, int *tids, size_t len);
int ptrace_get_num_lwps(int pid);

@ -6,7 +6,6 @@ import (
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/regnum"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/amd64util"
"github.com/go-delve/delve/pkg/proc/fbsdutil"
@ -25,52 +24,17 @@ func (thread *nativeThread) setPC(pc uint64) error {
}
// SetReg changes the value of the specified register.
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) (err error) {
func (thread *nativeThread) SetReg(regNum uint64, reg *op.DwarfRegister) error {
ir, err := registers(thread)
if err != nil {
return err
}
r := ir.(*fbsdutil.AMD64Registers)
switch regNum {
case regnum.AMD64_Rax:
r.Regs.Rax = int64(reg.Uint64Val)
case regnum.AMD64_Rbx:
r.Regs.Rbx = int64(reg.Uint64Val)
case regnum.AMD64_Rcx:
r.Regs.Rcx = int64(reg.Uint64Val)
case regnum.AMD64_Rdx:
r.Regs.Rdx = int64(reg.Uint64Val)
case regnum.AMD64_Rsi:
r.Regs.Rsi = int64(reg.Uint64Val)
case regnum.AMD64_Rdi:
r.Regs.Rdi = int64(reg.Uint64Val)
case regnum.AMD64_Rbp:
r.Regs.Rbp = int64(reg.Uint64Val)
case regnum.AMD64_Rsp:
r.Regs.Rsp = int64(reg.Uint64Val)
case regnum.AMD64_R8:
r.Regs.R8 = int64(reg.Uint64Val)
case regnum.AMD64_R9:
r.Regs.R9 = int64(reg.Uint64Val)
case regnum.AMD64_R10:
r.Regs.R10 = int64(reg.Uint64Val)
case regnum.AMD64_R11:
r.Regs.R11 = int64(reg.Uint64Val)
case regnum.AMD64_R12:
r.Regs.R12 = int64(reg.Uint64Val)
case regnum.AMD64_R13:
r.Regs.R13 = int64(reg.Uint64Val)
case regnum.AMD64_R14:
r.Regs.R14 = int64(reg.Uint64Val)
case regnum.AMD64_R15:
r.Regs.R15 = int64(reg.Uint64Val)
case regnum.AMD64_Rip:
r.Regs.Rip = int64(reg.Uint64Val)
default:
return fmt.Errorf("changing register %d not implemented", regNum)
fpchanged, err := r.SetReg(regNum, reg)
if err != nil {
return err
}
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs)) })
return
return setRegisters(thread, r, fpchanged)
}
func registers(thread *nativeThread) (proc.Registers, error) {
@ -88,18 +52,14 @@ func registers(thread *nativeThread) (proc.Registers, error) {
return nil, err
}
r := fbsdutil.NewAMD64Registers(&regs, uint64(fsbase), func(r *fbsdutil.AMD64Registers) error {
var fpregset amd64util.AMD64Xstate
var floatLoadError error
r.Fpregs, fpregset, floatLoadError = thread.fpRegisters()
r.Fpregset = &fpregset
r.Fpregs, r.Fpregset, floatLoadError = thread.fpRegisters()
return floatLoadError
})
return r, nil
}
const _NT_X86_XSTATE = 0x202
func (thread *nativeThread) fpRegisters() (regs []proc.Register, fpregs amd64util.AMD64Xstate, err error) {
func (thread *nativeThread) fpRegisters() (regs []proc.Register, fpregs *amd64util.AMD64Xstate, err error) {
thread.dbp.execPtraceFunc(func() { fpregs, err = ptraceGetRegset(thread.ID) })
if err != nil {
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
@ -107,3 +67,16 @@ func (thread *nativeThread) fpRegisters() (regs []proc.Register, fpregs amd64uti
regs = fpregs.Decode()
return
}
func setRegisters(thread *nativeThread, r *fbsdutil.AMD64Registers, setFP bool) (err error) {
thread.dbp.execPtraceFunc(func() {
err = sys.PtraceSetRegs(thread.ID, (*sys.Reg)(r.Regs))
if err != nil {
return
}
if setFP && r.Fpregset != nil {
err = ptraceSetRegset(thread.ID, r.Fpregset)
}
})
return
}

@ -2,11 +2,10 @@ package native
// #include <sys/thr.h>
import "C"
import (
"fmt"
"github.com/go-delve/delve/pkg/proc/fbsdutil"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
@ -78,26 +77,7 @@ func (t *nativeThread) singleStep() (err error) {
func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error {
sr := savedRegs.(*fbsdutil.AMD64Registers)
var restoreRegistersErr error
t.dbp.execPtraceFunc(func() {
restoreRegistersErr = sys.PtraceSetRegs(t.ID, (*sys.Reg)(sr.Regs))
if restoreRegistersErr != nil {
return
}
if sr.Fpregset.Xsave != nil {
iov := sys.Iovec{Base: &sr.Fpregset.Xsave[0], Len: uint64(len(sr.Fpregset.Xsave))}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETREGS, uintptr(t.ID), uintptr(unsafe.Pointer(&iov)), 0, 0, 0)
return
}
_, _, restoreRegistersErr = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_SETFPREGS, uintptr(t.ID), uintptr(unsafe.Pointer(&sr.Fpregset.AMD64PtraceFpRegs)), 0, 0, 0)
return
})
if restoreRegistersErr == syscall.Errno(0) {
restoreRegistersErr = nil
}
return restoreRegistersErr
return setRegisters(t, sr, true)
}
func (t *nativeThread) WriteMemory(addr uint64, data []byte) (written int, err error) {

@ -268,7 +268,7 @@ func (t *Target) Valid() (bool, error) {
// Currently only non-recorded processes running on AMD64 support
// function calls.
func (t *Target) SupportsFunctionCalls() bool {
return (t.Process.BinInfo().Arch.Name == "amd64" && t.Process.BinInfo().GOOS != "freebsd") || t.Process.BinInfo().Arch.Name == "arm64"
return t.Process.BinInfo().Arch.Name == "amd64" || t.Process.BinInfo().Arch.Name == "arm64"
}
// ClearCaches clears internal caches that should not survive a restart.

@ -325,10 +325,6 @@ func MustSupportFunctionCalls(t *testing.T, testBackend string) {
t.Skip("this backend does not support function calls")
}
if runtime.GOOS == "freebsd" {
t.Skip("freebsd backend has problems with changing and restoring XMM registers")
}
if runtime.GOOS == "darwin" && os.Getenv("TRAVIS") == "true" && runtime.GOARCH == "amd64" {
t.Skip("function call injection tests are failing on macOS on Travis-CI (see #1802)")
}