// Copyright (c) 2026 Petr BalvĂ­n (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 }