116 lines
3.0 KiB
Go
116 lines
3.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
|
||
|
|
//go:build freebsd && riscv64
|
||
|
|
|
||
|
|
package debug
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"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. Sstatus rides
|
||
|
|
// the kernel's struct but the portable surface carries the GPRs and PC.
|
||
|
|
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{
|
||
|
|
PC: ur.Sepc,
|
||
|
|
Ra: ur.Ra,
|
||
|
|
Sp: ur.Sp,
|
||
|
|
Gp: ur.Gp,
|
||
|
|
Tp: ur.Tp,
|
||
|
|
T0: ur.T[0],
|
||
|
|
T1: ur.T[1],
|
||
|
|
T2: ur.T[2],
|
||
|
|
S0: ur.S[0],
|
||
|
|
S1: ur.S[1],
|
||
|
|
A0: ur.A[0],
|
||
|
|
A1: ur.A[1],
|
||
|
|
A2: ur.A[2],
|
||
|
|
A3: ur.A[3],
|
||
|
|
A4: ur.A[4],
|
||
|
|
A5: ur.A[5],
|
||
|
|
A6: ur.A[6],
|
||
|
|
A7: ur.A[7],
|
||
|
|
S2: ur.S[2],
|
||
|
|
S3: ur.S[3],
|
||
|
|
S4: ur.S[4],
|
||
|
|
S5: ur.S[5],
|
||
|
|
S6: ur.S[6],
|
||
|
|
S7: ur.S[7],
|
||
|
|
S8: ur.S[8],
|
||
|
|
S9: ur.S[9],
|
||
|
|
S10: ur.S[10],
|
||
|
|
S11: ur.S[11],
|
||
|
|
T3: ur.T[3],
|
||
|
|
T4: ur.T[4],
|
||
|
|
T5: ur.T[5],
|
||
|
|
T6: ur.T[6],
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetRegs writes the general-purpose registers of the stopped debuggee.
|
||
|
|
// Read-modify-write keeps sstatus, which the kernel owns, intact.
|
||
|
|
func (s *Session) SetRegs(regs *Regs) error {
|
||
|
|
var ur unix.Reg
|
||
|
|
if err := unix.PtraceGetRegs(s.pid, &ur); err != nil {
|
||
|
|
return fmt.Errorf("debug: PT_GETREGS: %w", err)
|
||
|
|
}
|
||
|
|
ur.Sepc = regs.PC
|
||
|
|
ur.Ra = regs.Ra
|
||
|
|
ur.Sp = regs.Sp
|
||
|
|
ur.Gp = regs.Gp
|
||
|
|
ur.Tp = regs.Tp
|
||
|
|
ur.T = [7]uint64{regs.T0, regs.T1, regs.T2, regs.T3, regs.T4, regs.T5, regs.T6}
|
||
|
|
ur.S = [12]uint64{regs.S0, regs.S1, regs.S2, regs.S3, regs.S4, regs.S5,
|
||
|
|
regs.S6, regs.S7, regs.S8, regs.S9, regs.S10, regs.S11}
|
||
|
|
ur.A = [8]uint64{regs.A0, regs.A1, regs.A2, regs.A3, regs.A4, regs.A5, regs.A6, regs.A7}
|
||
|
|
return unix.PtraceSetRegs(s.pid, &ur)
|
||
|
|
}
|
||
|
|
|
||
|
|
// FPRegs holds the RISC-V FP register state (32 64-bit FP registers plus
|
||
|
|
// fcsr).
|
||
|
|
type FPRegs struct {
|
||
|
|
F [32]uint64 // F0-F31 (64-bit FP registers)
|
||
|
|
FCSR uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetFPRegs retrieves the FP register state via PT_GETFPREGS. The FreeBSD
|
||
|
|
// struct fpreg carries each 64-bit FP register in a 128-bit slot (fp_x is
|
||
|
|
// the flat [64]-word area the x/sys type renders as [32][2]); the low word
|
||
|
|
// holds the register, and FCSR rides the tail.
|
||
|
|
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 32 {
|
||
|
|
fp.F[i] = fr.X[i][0]
|
||
|
|
}
|
||
|
|
fp.FCSR = uint32(fr.Fcsr)
|
||
|
|
return fp, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// VectorRegs holds the FP register state shown by the regs command
|
||
|
|
// (riscv64 has 32 64-bit FP registers and fcsr).
|
||
|
|
type VectorRegs struct {
|
||
|
|
F [32]uint64
|
||
|
|
FCSR uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetVectorRegs retrieves the FP registers.
|
||
|
|
func (s *Session) GetVectorRegs() (VectorRegs, error) {
|
||
|
|
fp, err := s.GetFPRegs()
|
||
|
|
if err != nil {
|
||
|
|
return VectorRegs{}, err
|
||
|
|
}
|
||
|
|
return VectorRegs{F: fp.F, FCSR: fp.FCSR}, nil
|
||
|
|
}
|