Files

161 lines
4.6 KiB
Go
Raw Permalink Normal View History

2026-09-25 21:46:40 +02:00
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
//go:build freebsd && amd64
package debug
import (
"encoding/binary"
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
// GetRegs reads the general-purpose registers of the stopped debuggee and
// converts the FreeBSD struct reg into the portable layout.
func (s *Session) GetRegs() (Regs, error) {
var ur unix.Reg
if err := unix.PtraceGetRegs(s.pid, &ur); err != nil {
return Regs{}, fmt.Errorf("debug: PT_GETREGS: %w", err)
}
return Regs{
R15: uint64(ur.R15),
R14: uint64(ur.R14),
R13: uint64(ur.R13),
R12: uint64(ur.R12),
R11: uint64(ur.R11),
R10: uint64(ur.R10),
R9: uint64(ur.R9),
R8: uint64(ur.R8),
RDI: uint64(ur.Rdi),
RSI: uint64(ur.Rsi),
RBP: uint64(ur.Rbp),
RBX: uint64(ur.Rbx),
RDX: uint64(ur.Rdx),
RCX: uint64(ur.Rcx),
RAX: uint64(ur.Rax),
RIP: uint64(ur.Rip),
CS: uint64(ur.Cs),
RFLAGS: uint64(ur.Rflags),
RSP: uint64(ur.Rsp),
SS: uint64(ur.Ss),
FS: uint64(ur.Fs),
GS: uint64(ur.Gs),
DS: uint64(ur.Ds),
ES: uint64(ur.Es),
}, nil
}
// SetRegs writes the general-purpose registers of the stopped debuggee.
func (s *Session) SetRegs(regs *Regs) error {
// Read-modify-write keeps the fields FreeBSD owns (trapno, err) intact.
var ur unix.Reg
if err := unix.PtraceGetRegs(s.pid, &ur); err != nil {
return fmt.Errorf("debug: PT_GETREGS: %w", err)
}
ur.R15 = int64(regs.R15)
ur.R14 = int64(regs.R14)
ur.R13 = int64(regs.R13)
ur.R12 = int64(regs.R12)
ur.R11 = int64(regs.R11)
ur.R10 = int64(regs.R10)
ur.R9 = int64(regs.R9)
ur.R8 = int64(regs.R8)
ur.Rdi = int64(regs.RDI)
ur.Rsi = int64(regs.RSI)
ur.Rbp = int64(regs.RBP)
ur.Rbx = int64(regs.RBX)
ur.Rdx = int64(regs.RDX)
ur.Rcx = int64(regs.RCX)
ur.Rax = int64(regs.RAX)
ur.Rip = int64(regs.RIP)
ur.Cs = int64(regs.CS)
ur.Rflags = int64(regs.RFLAGS)
ur.Rsp = int64(regs.RSP)
ur.Ss = int64(regs.SS)
return unix.PtraceSetRegs(s.pid, &ur)
}
// FPRegs holds the x87 FPU and SSE (XMM) register state, the FXSAVE image
// the FreeBSD struct fpreg mirrors: XMM0-15 at the same offsets.
type FPRegs struct {
XMM [16][16]byte // XMM0-15
}
// GetFPRegs retrieves the FPU/SSE register state via PT_GETFPREGS. The
// FreeBSD struct fpreg mirrors the FXSAVE image: the x87 environment and
// stack in Env/Acc, XMM0-15 in Xacc.
func (s *Session) GetFPRegs() (FPRegs, error) {
var fp FPRegs
var fr unix.FpReg
if err := unix.PtraceGetFpRegs(s.pid, &fr); err != nil {
return fp, fmt.Errorf("debug: PT_GETFPREGS: %w", err)
}
for i := range 16 {
copy(fp.XMM[i][:], fr.Xacc[i][:])
}
return fp, nil
}
// VectorRegs holds the YMM register state.
type VectorRegs struct {
YMM [16][32]byte // YMM0-15 (full 256-bit values)
}
// The XSAVE area the PT_GETXSTATE request returns follows the architectural
// layout (Intel SDM vol 1, "XSAVE"): the 512-byte legacy FXSAVE image (x87
// state in 0-159, XMM0-15 in 160-511), then the 64-byte xsave header whose
// first 8 bytes are xstate_bv, then one component per set feature bit, each
// 64-byte aligned. The YMM high halves are the first extended component,
// at offset 576; XFEATURE_STATE_BIT_AVX is bit 2 of xstate_bv.
const (
xsaveXMMOffset = 160
xsaveHeaderOffset = 512
xsaveBVOffset = xsaveHeaderOffset
ymmOffset = xsaveHeaderOffset + 64 // 576
ymmSize = 256 // 16 registers, 16 bytes each
xfeatureMaskYMM = 1 << 2
xstateMaxBuffer = 4096 // PT_GETXSTATE_INFO bounds the size far below this
)
// GetVectorRegs retrieves the YMM registers via PT_GETXSTATE. The low
// (XMM) halves always come from the legacy image; the high halves are
// copied only when xstate_bv reports the AVX state, and read as zero
// otherwise. When the request fails the FP image still provides correct
// XMM halves, so that is the fallback.
func (s *Session) GetVectorRegs() (VectorRegs, error) {
var v VectorRegs
buf := make([]byte, xstateMaxBuffer)
n, _, errno := unix.Syscall6(
unix.SYS_PTRACE,
uintptr(unix.PT_GETXSTATE),
uintptr(s.pid),
0,
uintptr(unsafe.Pointer(&buf[0])),
0, 0,
)
if errno != 0 {
fp, err := s.GetFPRegs()
if err != nil {
return v, err
}
for i := range 16 {
copy(v.YMM[i][:16], fp.XMM[i][:])
}
return v, nil
}
for i := range 16 {
copy(v.YMM[i][:16], buf[xsaveXMMOffset+16*i:xsaveXMMOffset+16*i+16])
}
if int(n) >= ymmOffset+ymmSize {
if binary.LittleEndian.Uint64(buf[xsaveBVOffset:xsaveBVOffset+8])&xfeatureMaskYMM != 0 {
for i := range 16 {
copy(v.YMM[i][16:], buf[ymmOffset+16*i:ymmOffset+16*i+16])
}
}
}
return v, nil
}