pkg/proc/native,pkg/proc/amd64util: xsave decoding cleanup (#3840)

- move the cpuid querying code to pkg/proc/native/cpuid since
  pkg/proc/native is the only package entitled to calling it
- add a type to describe the xstate_bv bitmap instead of using
  hardcoded constants everywhere
- use xcr0 instead of xstate_bv for the offset heuristic like gdb does
This commit is contained in:
Alessandro Arzilli 2024-11-21 13:06:51 +01:00 committed by GitHub
parent f83958b923
commit e0c80c8612
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 42 additions and 28 deletions

@ -18,6 +18,8 @@ type AMD64Xstate struct {
YmmSpace [256]byte YmmSpace [256]byte
Avx512State bool // contains AVX512 state Avx512State bool // contains AVX512 state
ZmmSpace [512]byte ZmmSpace [512]byte
zmmHi256offset int
} }
// AMD64PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h // AMD64PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
@ -72,15 +74,24 @@ func (xstate *AMD64Xstate) Decode() []proc.Register {
} }
const ( const (
_XSTATE_MAX_KNOWN_SIZE = 2969
_XSAVE_XMM_REGION_START = 160 _XSAVE_XMM_REGION_START = 160
_XSAVE_HEADER_START = 512 _XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64 _XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576 _XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416 _XSAVE_SSE_REGION_LEN = 416
_I386_LINUX_XSAVE_XCR0_OFFSET = 464
) )
// xstate_bv is a type representing the xcr0 and xstate_bv bitmaps as
// described in section 13.1 and 13.3 of the Intel® 64 and IA-32 Architectures
// Software Developers Manual, Volume 1
type xstate_bv uint64
func (s xstate_bv) hasAVX() bool { return s&(1<<2) != 0 }
func (s xstate_bv) hasZMM_Hi256() bool { return s&(1<<6) != 0 }
func (s xstate_bv) hasHi16_ZMM() bool { return s&(1<<7) != 0 } //lint:ignore U1000 future use
func (s xstate_bv) hasPKRU() bool { return s&(1<<9) != 0 }
// AMD64XstateRead reads a byte array containing an XSAVE area into regset. // AMD64XstateRead reads a byte array containing an XSAVE area into regset.
// If readLegacy is true regset.PtraceFpRegs will be filled with the // If readLegacy is true regset.PtraceFpRegs will be filled with the
// contents of the legacy region of the XSAVE area. // contents of the legacy region of the XSAVE area.
@ -97,17 +108,19 @@ func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate, xs
return err return err
} }
} }
xcr0 := xstate_bv(binary.LittleEndian.Uint64(xstateargs[_I386_LINUX_XSAVE_XCR0_OFFSET:][:8]))
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN] xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8]) xstate_bv := xstate_bv(binary.LittleEndian.Uint64(xsaveheader[0:8]))
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16]) xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
fmt.Printf("xcr0: %#x xstate_bv: %#x\n", xcr0, xstate_bv)
if xcomp_bv&(1<<63) != 0 { if xcomp_bv&(1<<63) != 0 {
// compact format not supported // compact format not supported
return nil return nil
} }
if xstate_bv&(1<<2) == 0 { if !xstate_bv.hasAVX() {
// AVX state not present
return nil return nil
} }
@ -115,15 +128,14 @@ func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate, xs
regset.AvxState = true regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)]) copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
if xstate_bv&(1<<6) == 0 { if !xstate_bv.hasZMM_Hi256() {
// AVX512 state not present
return nil return nil
} }
if xstateZMMHi256Offset == 0 { if xstateZMMHi256Offset == 0 {
// Guess ZMM_Hi256 component offset // Guess ZMM_Hi256 component offset
// ref: https://github.com/bminor/binutils-gdb/blob/df89bdf0baf106c3b0a9fae53e4e48607a7f3f87/gdb/i387-tdep.c#L916 // ref: https://github.com/bminor/binutils-gdb/blob/df89bdf0baf106c3b0a9fae53e4e48607a7f3f87/gdb/i387-tdep.c#L916
if xstate_bv&(1<<9) != 0 && len(xstateargs) == 2440 { if xcr0.hasPKRU() && len(xstateargs) == 2440 {
// AMD CPUs supporting PKRU // AMD CPUs supporting PKRU
xstateZMMHi256Offset = 896 xstateZMMHi256Offset = 896
} else { } else {
@ -132,11 +144,13 @@ func AMD64XstateRead(xstateargs []byte, readLegacy bool, regset *AMD64Xstate, xs
} }
} }
regset.zmmHi256offset = xstateZMMHi256Offset
avx512state := xstateargs[xstateZMMHi256Offset:] avx512state := xstateargs[xstateZMMHi256Offset:]
regset.Avx512State = true regset.Avx512State = true
copy(regset.ZmmSpace[:], avx512state[:len(regset.ZmmSpace)]) copy(regset.ZmmSpace[:], avx512state[:len(regset.ZmmSpace)])
// TODO(aarzilli): if xstate_bv&(1<<7) is set then xstateargs[1664:2688] // TODO(aarzilli): if xstate_bv.hasHi16_ZMM() is set then xstateargs[1664:2688]
// contains ZMM16 through ZMM31, those aren't just the higher 256bits, it's // contains ZMM16 through ZMM31, those aren't just the higher 256bits, it's
// the full register so each is 64 bytes (512bits) // the full register so each is 64 bytes (512bits)
@ -192,7 +206,7 @@ func (xstate *AMD64Xstate) SetXmmRegister(n int, value []byte) error {
// Copy bytes [32, 64) to Xsave area // Copy bytes [32, 64) to Xsave area
zmmval := rest zmmval := rest
zmmpos := AMD64XstateZMMHi256Offset() + (n * 32) zmmpos := xstate.zmmHi256offset + (n * 32) //TODO: change this!!!
if zmmpos >= len(xstate.Xsave) { if zmmpos >= len(xstate.Xsave) {
return fmt.Errorf("could not set XMM%d: bytes 32..%d not in XSAVE area", n, 32+len(zmmval)) return fmt.Errorf("could not set XMM%d: bytes 32..%d not in XSAVE area", n, 32+len(zmmval))
} }

@ -1,12 +0,0 @@
//go:build !amd64
package amd64util
func AMD64XstateMaxSize() int {
return _XSTATE_MAX_KNOWN_SIZE
}
func AMD64XstateZMMHi256Offset() int {
// AVX-512 not supported
return 0
}

@ -1,14 +1,20 @@
package amd64util //go:build amd64 || 386
package cpuid
import ( import (
"sync" "sync"
) )
const _XSTATE_MAX_KNOWN_SIZE = 2969
var xstateMaxSize int var xstateMaxSize int
var loadXstateMaxSizeOnce sync.Once var loadXstateMaxSizeOnce sync.Once
func cpuid(axIn, cxIn uint32) (axOut, bxOut, cxOut, dxOut uint32) func cpuid(axIn, cxIn uint32) (axOut, bxOut, cxOut, dxOut uint32)
// AMD64XstateMaxSize returns the maximum size of the xstate area.
func AMD64XstateMaxSize() int { func AMD64XstateMaxSize() int {
loadXstateMaxSizeOnce.Do(func() { loadXstateMaxSizeOnce.Do(func() {
// See Intel 64 and IA-32 Architecture Software Developer's Manual, Vol. 1 // See Intel 64 and IA-32 Architecture Software Developer's Manual, Vol. 1

@ -1,3 +1,5 @@
//go:build amd64 || 386
TEXT ·cpuid(SB),$0-24 TEXT ·cpuid(SB),$0-24
MOVL axIn+0(FP), AX MOVL axIn+0(FP), AX
MOVL cxIn+4(FP), CX MOVL cxIn+4(FP), CX
@ -7,3 +9,4 @@ TEXT ·cpuid(SB),$0-24
MOVL CX, cxOut+16(FP) MOVL CX, cxOut+16(FP)
MOVL DX, dxOut+20(FP) MOVL DX, dxOut+20(FP)
RET RET

@ -11,6 +11,7 @@ import (
"unsafe" "unsafe"
"github.com/go-delve/delve/pkg/proc/amd64util" "github.com/go-delve/delve/pkg/proc/amd64util"
"github.com/go-delve/delve/pkg/proc/native/cpuid"
) )
var ( var (
@ -73,7 +74,7 @@ func ptraceGetRegset(id int) (*amd64util.AMD64Xstate, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, amd64util.AMD64XstateZMMHi256Offset()) err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, cpuid.AMD64XstateZMMHi256Offset())
return &regset, err return &regset, err
} }

@ -8,6 +8,7 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc/amd64util" "github.com/go-delve/delve/pkg/proc/amd64util"
"github.com/go-delve/delve/pkg/proc/native/cpuid"
) )
// ptraceGetRegset returns floating point registers of the specified thread // ptraceGetRegset returns floating point registers of the specified thread
@ -22,7 +23,7 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
err = nil err = nil
} }
xstateargs := make([]byte, amd64util.AMD64XstateMaxSize()) xstateargs := make([]byte, cpuid.AMD64XstateMaxSize())
iov := sys.Iovec{Base: &xstateargs[0], Len: uint32(len(xstateargs))} iov := sys.Iovec{Base: &xstateargs[0], Len: uint32(len(xstateargs))}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) { if err != syscall.Errno(0) {
@ -38,7 +39,7 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
} }
regset.Xsave = xstateargs[:iov.Len] regset.Xsave = xstateargs[:iov.Len]
err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, amd64util.AMD64XstateZMMHi256Offset()) err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, cpuid.AMD64XstateZMMHi256Offset())
return return
} }

@ -7,6 +7,7 @@ import (
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc/amd64util" "github.com/go-delve/delve/pkg/proc/amd64util"
"github.com/go-delve/delve/pkg/proc/native/cpuid"
) )
// ptraceGetRegset returns floating point registers of the specified thread // ptraceGetRegset returns floating point registers of the specified thread
@ -21,7 +22,7 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
err = nil err = nil
} }
xstateargs := make([]byte, amd64util.AMD64XstateMaxSize()) xstateargs := make([]byte, cpuid.AMD64XstateMaxSize())
iov := sys.Iovec{Base: &xstateargs[0], Len: uint64(len(xstateargs))} iov := sys.Iovec{Base: &xstateargs[0], Len: uint64(len(xstateargs))}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0) _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) { if err != syscall.Errno(0) {
@ -37,6 +38,6 @@ func ptraceGetRegset(tid int) (regset amd64util.AMD64Xstate, err error) {
} }
regset.Xsave = xstateargs[:iov.Len] regset.Xsave = xstateargs[:iov.Len]
err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, amd64util.AMD64XstateZMMHi256Offset()) err = amd64util.AMD64XstateRead(regset.Xsave, false, &regset, cpuid.AMD64XstateZMMHi256Offset())
return return
} }