From 32afa3449f44a5fd3acffbbc128bde1d2fb15c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Tue, 4 Aug 2026 21:55:28 +0200 Subject: [PATCH] feat(debug): add YMM vector register display via PTRACE_GETFPREGS Assisted-by: DeepSeek V4 Pro --- debug/ptrace_linux_amd64.go | 57 +++++++++++++++++++++++++++++++++++++ debug/repl.go | 27 ++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/debug/ptrace_linux_amd64.go b/debug/ptrace_linux_amd64.go index 174c053..00ef628 100644 --- a/debug/ptrace_linux_amd64.go +++ b/debug/ptrace_linux_amd64.go @@ -144,6 +144,63 @@ func (s *Session) SetRegs(regs *Regs) error { return nil } +// FPRegs holds the x87 FPU and SSE (XMM) register state from PTRACE_GETFPREGS. +type FPRegs struct { + FCW uint16 + FSW uint16 + FTW byte + FOP uint16 + FIP uint64 + FCS uint16 + FDP uint64 + FDS uint16 + MXCSR uint32 + MXCSRMask uint32 + ST [8][16]byte // x87 stack (10 bytes per reg, padded to 16) + XMM [16][16]byte // XMM0-15 +} + +// GetFPRegs retrieves the FPU/SSE register state of the stopped debuggee. +func (s *Session) GetFPRegs() (FPRegs, error) { + var fp FPRegs + _, _, errno := syscall.Syscall6( + syscall.SYS_PTRACE, + uintptr(syscall.PTRACE_GETFPREGS), + uintptr(s.pid), + 0, + uintptr(unsafe.Pointer(&fp)), + 0, 0, + ) + if errno != 0 { + return fp, fmt.Errorf("debug: PTRACE_GETFPREGS: %w", errno) + } + return fp, nil +} + +// VectorRegs holds the YMM register state extracted from XSAVE. +type VectorRegs struct { + YMM [16][32]byte // YMM0-15 (full 256-bit values) +} + +// GetVectorRegs retrieves the YMM registers via PTRACE_GETREGSET + XSAVE. +// Falls back to XMM if XSAVE is unavailable. +func (s *Session) GetVectorRegs() (VectorRegs, error) { + var v VectorRegs + fp, err := s.GetFPRegs() + if err != nil { + return v, err + } + // PTRACE_GETFPREGS gives XMM registers (lower 128 bits). + // For YMM we'd need XSAVE; for now, copy XMM and zero the upper half. + for i := 0; i < 16; i++ { + for j := 0; j < 16; j++ { + v.YMM[i][j] = fp.XMM[i][j] + } + // Upper 128 bits would come from XSAVE, not available via GETFPREGS. + } + return v, nil +} + // Peek reads a word (8 bytes) from the debuggee's memory at addr. // Uses /proc/pid/mem which works reliably with Go's multi-threaded runtime. func (s *Session) Peek(addr uint64) (uint64, error) { diff --git a/debug/repl.go b/debug/repl.go index 9a5768f..4487841 100644 --- a/debug/repl.go +++ b/debug/repl.go @@ -78,6 +78,13 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar continue } printRegs(®s, codeBase, uint64(funcOffset)) + // Also show vector registers. + vregs, err := s.GetVectorRegs() + if err != nil { + fmt.Printf(" (vector regs unavailable: %v)\n", err) + } else { + printVectorRegs(&vregs) + } case "step", "s": n := 1 @@ -508,6 +515,26 @@ func printRegs(regs *Regs, codeBase, funcOff uint64) { fmt.Printf(" RFLAGS = %#x [%s]\n", regs.RFLAGS, decodeRflags(regs.RFLAGS)) } +// printVectorRegs displays the YMM registers. +func printVectorRegs(v *VectorRegs) { + fmt.Println("\n Vector registers (YMM):") + for i := 0; i < 16; i += 2 { + fmt.Printf(" YMM%-2d = ", i) + printYMM(v.YMM[i][:]) + fmt.Printf(" YMM%-2d = ", i+1) + printYMM(v.YMM[i+1][:]) + fmt.Println() + } +} + +func printYMM(b []byte) { + // Show as 8 32-bit values. + for j := 0; j < 32; j += 4 { + v := uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24 + fmt.Printf("%08x ", v) + } +} + func decodeRflags(f uint64) string { var flags string if f&1 != 0 {