feat(debug): port the debugger to FreeBSD
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user