From d4364d0496b3867b193d63a9b2757c4e8ca8a87b Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 20 Jul 2017 21:04:00 +0200 Subject: [PATCH] proc/core: support floating point registers (#912) Updates #794 --- _fixtures/fputest/fputest.go | 11 ++- pkg/proc/core/core.go | 73 +++++++++++++++-- pkg/proc/core/core_test.go | 99 ++++++++++++++++++++++-- pkg/proc/core/linux_amd64_core.go | 90 +++++++-------------- pkg/proc/native/ptrace_linux.go | 29 ++----- pkg/proc/native/registers_linux_amd64.go | 50 +----------- pkg/proc/registers.go | 95 +++++++++++++++++++++++ 7 files changed, 296 insertions(+), 151 deletions(-) diff --git a/_fixtures/fputest/fputest.go b/_fixtures/fputest/fputest.go index aed3d3c5..dbf20f2a 100644 --- a/_fixtures/fputest/fputest.go +++ b/_fixtures/fputest/fputest.go @@ -1,6 +1,9 @@ package main -import "runtime" +import ( + "os" + "runtime" +) func fputestsetup(f64a, f64b, f64c, f64d float64, f32a, f32b, f32c, f32d float32) @@ -15,5 +18,9 @@ func main() { var f32d float32 = 1.8 fputestsetup(f64a, f64b, f64c, f64d, f32a, f32b, f32c, f32d) - runtime.Breakpoint() + if len(os.Args) < 2 || os.Args[1] != "panic" { + runtime.Breakpoint() + } else { + panic("booom!") + } } diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index fbca5624..ce34a9d0 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -145,14 +145,15 @@ type Process struct { bi proc.BinaryInfo core *Core breakpoints map[uint64]*proc.Breakpoint - currentThread *LinuxPrStatus + currentThread *Thread selectedGoroutine *proc.G allGCache []*proc.G } type Thread struct { - th *LinuxPrStatus - p *Process + th *LinuxPrStatus + fpregs []proc.Register + p *Process } var ErrWriteCore = errors.New("can not to core process") @@ -169,6 +170,9 @@ func OpenCore(corePath, exePath string) (*Process, error) { breakpoints: make(map[uint64]*proc.Breakpoint), bi: proc.NewBinaryInfo("linux", "amd64"), } + for _, thread := range core.Threads { + thread.p = p + } var wg sync.WaitGroup p.bi.LoadBinaryInfo(exePath, &wg) @@ -221,8 +225,11 @@ func (t *Thread) ThreadID() int { } func (t *Thread) Registers(floatingPoint bool) (proc.Registers, error) { - //TODO(aarzilli): handle floating point registers - return &t.th.Reg, nil + r := &Registers{&t.th.Reg, nil} + if floatingPoint { + r.fpregs = t.fpregs + } + return r, nil } func (t *Thread) Arch() proc.Arch { @@ -274,7 +281,7 @@ func (p *Process) ManualStopRequested() bool { } func (p *Process) CurrentThread() proc.Thread { - return &Thread{p.currentThread, p} + return p.currentThread } func (p *Process) Detach(bool) error { @@ -340,12 +347,62 @@ func (p *Process) SwitchThread(tid int) error { func (p *Process) ThreadList() []proc.Thread { r := make([]proc.Thread, 0, len(p.core.Threads)) for _, v := range p.core.Threads { - r = append(r, &Thread{v, p}) + r = append(r, v) } return r } func (p *Process) FindThread(threadID int) (proc.Thread, bool) { t, ok := p.core.Threads[threadID] - return &Thread{t, p}, ok + return t, ok +} + +type Registers struct { + *LinuxCoreRegisters + fpregs []proc.Register +} + +func (r *Registers) Slice() []proc.Register { + var regs = []struct { + k string + v uint64 + }{ + {"Rip", r.Rip}, + {"Rsp", r.Rsp}, + {"Rax", r.Rax}, + {"Rbx", r.Rbx}, + {"Rcx", r.Rcx}, + {"Rdx", r.Rdx}, + {"Rdi", r.Rdi}, + {"Rsi", r.Rsi}, + {"Rbp", r.Rbp}, + {"R8", r.R8}, + {"R9", r.R9}, + {"R10", r.R10}, + {"R11", r.R11}, + {"R12", r.R12}, + {"R13", r.R13}, + {"R14", r.R14}, + {"R15", r.R15}, + {"Orig_rax", r.Orig_rax}, + {"Cs", r.Cs}, + {"Eflags", r.Eflags}, + {"Ss", r.Ss}, + {"Fs_base", r.Fs_base}, + {"Gs_base", r.Gs_base}, + {"Ds", r.Ds}, + {"Es", r.Es}, + {"Fs", r.Fs}, + {"Gs", r.Gs}, + } + out := make([]proc.Register, 0, len(regs)) + for _, reg := range regs { + if reg.k == "Eflags" { + out = proc.AppendEflagReg(out, reg.k, reg.v) + } else { + out = proc.AppendQwordReg(out, reg.k, reg.v) + } + } + out = append(out, r.fpregs...) + return out } diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 7918ac0c..5682dc64 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -125,26 +125,23 @@ func TestSplicedReader(t *testing.T) { } } -func TestCore(t *testing.T) { - if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { - return - } +func withCoreFile(t *testing.T, name, args string) *Process { // This is all very fragile and won't work on hosts with non-default core patterns. // Might be better to check in the binary and core? tempDir, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } - fix := test.BuildFixture("panic") - bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v", tempDir, fix.Path) + fix := test.BuildFixture(name) + bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args) exec.Command("bash", "-c", bashCmd).Run() cores, err := filepath.Glob(path.Join(tempDir, "core*")) switch { case err != nil || len(cores) > 1: t.Fatalf("Got %v, wanted one file named core* in %v", cores, tempDir) case len(cores) == 0: - t.Logf("core file was not produced, could not run test") - return + t.Skipf("core file was not produced, could not run test") + return nil } corePath := cores[0] @@ -156,6 +153,15 @@ func TestCore(t *testing.T) { t.Errorf("read apport log: %q, %v", apport, err) t.Fatalf("ReadCore() failed: %v", err) } + return p +} + +func TestCore(t *testing.T) { + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { + return + } + p := withCoreFile(t, "panic", "") + gs, err := proc.GoroutinesInfo(p) if err != nil || len(gs) == 0 { t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) @@ -207,3 +213,80 @@ func TestCore(t *testing.T) { t.Logf("%s = %s", reg.Name, reg.Value) } } + +func TestCoreFpRegisters(t *testing.T) { + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { + return + } + p := withCoreFile(t, "fputest/", "panic") + + gs, err := proc.GoroutinesInfo(p) + if err != nil || len(gs) == 0 { + t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) + } + + var regs proc.Registers + for _, thread := range p.ThreadList() { + frames, err := proc.ThreadStacktrace(thread, 10) + if err != nil { + t.Errorf("ThreadStacktrace for %x = %v", thread.ThreadID(), err) + continue + } + for i := range frames { + if frames[i].Current.Fn == nil { + continue + } + if frames[i].Current.Fn.Name == "runtime.crash" { + regs, err = thread.Registers(true) + if err != nil { + t.Fatalf("Could not get registers for thread %x, %v", thread.ThreadID(), err) + } + break + } + } + if regs != nil { + break + } + } + + regtests := []struct{ name, value string }{ + {"ST(0)", "0x3fffe666660000000000"}, + {"ST(1)", "0x3fffd9999a0000000000"}, + {"ST(2)", "0x3fffcccccd0000000000"}, + {"ST(3)", "0x3fffc000000000000000"}, + {"ST(4)", "0x3fffb333333333333000"}, + {"ST(5)", "0x3fffa666666666666800"}, + {"ST(6)", "0x3fff9999999999999800"}, + {"ST(7)", "0x3fff8cccccccccccd000"}, + // Unlike TestClientServer_FpRegisters in service/test/integration2_test + // we can not test the value of XMM0, it probably has been reused by + // something between the panic and the time we get the core dump. + {"XMM1", "0x3ff66666666666663ff4cccccccccccd"}, + {"XMM2", "0x3fe666663fd9999a3fcccccd3fc00000"}, + {"XMM3", "0x3ff199999999999a3ff3333333333333"}, + {"XMM4", "0x3ff4cccccccccccd3ff6666666666666"}, + {"XMM5", "0x3fcccccd3fc000003fe666663fd9999a"}, + {"XMM6", "0x4004cccccccccccc4003333333333334"}, + {"XMM7", "0x40026666666666664002666666666666"}, + {"XMM8", "0x4059999a404ccccd4059999a404ccccd"}, + } + + for _, reg := range regs.Slice() { + t.Logf("%s = %s", reg.Name, reg.Value) + } + + for _, regtest := range regtests { + found := false + for _, reg := range regs.Slice() { + if reg.Name == regtest.name { + found = true + if !strings.HasPrefix(reg.Value, regtest.value) { + t.Fatalf("register %s expected %q got %q", reg.Name, regtest.value, reg.Value) + } + } + } + if !found { + t.Fatalf("register %s not found: %v", regtest.name, regs) + } + } +} diff --git a/pkg/proc/core/linux_amd64_core.go b/pkg/proc/core/linux_amd64_core.go index 4de8da45..1073dcc3 100644 --- a/pkg/proc/core/linux_amd64_core.go +++ b/pkg/proc/core/linux_amd64_core.go @@ -54,7 +54,8 @@ type LinuxCoreTimeval struct { Usec int64 } -const NT_FILE elf.NType = 0x46494c45 // "FILE". +const NT_FILE elf.NType = 0x46494c45 // "FILE". +const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area. func (r *LinuxCoreRegisters) PC() uint64 { return r.Rip @@ -241,50 +242,6 @@ func (r *LinuxCoreRegisters) SetPC(proc.Thread, uint64) error { return errors.New("not supported") } -func (r *LinuxCoreRegisters) Slice() []proc.Register { - var regs = []struct { - k string - v uint64 - }{ - {"Rip", r.Rip}, - {"Rsp", r.Rsp}, - {"Rax", r.Rax}, - {"Rbx", r.Rbx}, - {"Rcx", r.Rcx}, - {"Rdx", r.Rdx}, - {"Rdi", r.Rdi}, - {"Rsi", r.Rsi}, - {"Rbp", r.Rbp}, - {"R8", r.R8}, - {"R9", r.R9}, - {"R10", r.R10}, - {"R11", r.R11}, - {"R12", r.R12}, - {"R13", r.R13}, - {"R14", r.R14}, - {"R15", r.R15}, - {"Orig_rax", r.Orig_rax}, - {"Cs", r.Cs}, - {"Eflags", r.Eflags}, - {"Ss", r.Ss}, - {"Fs_base", r.Fs_base}, - {"Gs_base", r.Gs_base}, - {"Ds", r.Ds}, - {"Es", r.Es}, - {"Fs", r.Fs}, - {"Gs", r.Gs}, - } - out := make([]proc.Register, 0, len(regs)) - for _, reg := range regs { - if reg.k == "Eflags" { - out = proc.AppendEflagReg(out, reg.k, reg.v) - } else { - out = proc.AppendQwordReg(out, reg.k, reg.v) - } - } - return out -} - // readCore reads a core file from corePath corresponding to the executable at // exePath. For details on the Linux ELF core format, see: // http://www.gabriel.urdhr.fr/2015/05/29/core-file/, @@ -292,7 +249,7 @@ func (r *LinuxCoreRegisters) Slice() []proc.Register { // elf_core_dump in http://lxr.free-electrons.com/source/fs/binfmt_elf.c, // and, if absolutely desperate, readelf.c from the binutils source. func readCore(corePath, exePath string) (*Core, error) { - core, err := elf.Open(corePath) + coreFile, err := elf.Open(corePath) if err != nil { return nil, err } @@ -301,37 +258,42 @@ func readCore(corePath, exePath string) (*Core, error) { return nil, err } - if core.Type != elf.ET_CORE { - return nil, fmt.Errorf("%v is not a core file", core) + if coreFile.Type != elf.ET_CORE { + return nil, fmt.Errorf("%v is not a core file", coreFile) } - notes, err := readNotes(core) + notes, err := readNotes(coreFile) if err != nil { return nil, err } - memory := buildMemory(core, exe, notes) + memory := buildMemory(coreFile, exe, notes) - threads := map[int]*LinuxPrStatus{} - pid := 0 + core := &Core{ + MemoryReader: memory, + Threads: map[int]*Thread{}, + } + + var lastThread *Thread for _, note := range notes { switch note.Type { case elf.NT_PRSTATUS: t := note.Desc.(*LinuxPrStatus) - threads[int(t.Pid)] = t + lastThread = &Thread{t, nil, nil} + core.Threads[int(t.Pid)] = lastThread + case NT_X86_XSTATE: + if lastThread != nil { + lastThread.fpregs = note.Desc.(*proc.LinuxX86Xstate).Decode() + } case elf.NT_PRPSINFO: - pid = int(note.Desc.(*LinuxPrPsInfo).Pid) + core.Pid = int(note.Desc.(*LinuxPrPsInfo).Pid) } } - return &Core{ - MemoryReader: memory, - Threads: threads, - Pid: pid, - }, nil + return core, nil } type Core struct { proc.MemoryReader - Threads map[int]*LinuxPrStatus + Threads map[int]*Thread Pid int } @@ -341,7 +303,7 @@ type Core struct { // - NT_PRPSINFO: Information about a process, including PID and signal. Desc is a LinuxPrPsInfo. // - NT_PRSTATUS: Information about a thread, including base registers, state, etc. Desc is a LinuxPrStatus. // - NT_FPREGSET (Not implemented): x87 floating point registers. -// - NT_X86_XSTATE (Not implemented): Other registers, including AVX and such. +// - NT_X86_XSTATE: Other registers, including AVX and such. type Note struct { Type elf.NType Name string @@ -428,6 +390,12 @@ func readNote(r io.ReadSeeker) (*Note, error) { data.entries = append(data.entries, entry) } note.Desc = data + case NT_X86_XSTATE: + var fpregs proc.LinuxX86Xstate + if err := proc.LinuxX86XstateRead(desc, true, &fpregs); err != nil { + return nil, err + } + note.Desc = &fpregs } if err := skipPadding(r, 4); err != nil { return nil, fmt.Errorf("aligning after desc: %v", err) diff --git a/pkg/proc/native/ptrace_linux.go b/pkg/proc/native/ptrace_linux.go index fb7687a4..ed7ffba9 100644 --- a/pkg/proc/native/ptrace_linux.go +++ b/pkg/proc/native/ptrace_linux.go @@ -1,11 +1,12 @@ package native import ( - "encoding/binary" "syscall" "unsafe" sys "golang.org/x/sys/unix" + + "github.com/derekparker/delve/pkg/proc" ) // PtraceAttach executes the sys.PtraceAttach call. @@ -56,7 +57,7 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) { // See amd64_linux_fetch_inferior_registers in gdb/amd64-linux-nat.c.html // and amd64_supply_xsave in gdb/amd64-tdep.c.html // and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture -func PtraceGetRegset(tid int) (regset PtraceXsave, err error) { +func PtraceGetRegset(tid int) (regset proc.LinuxX86Xstate, err error) { _, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(®set.PtraceFpRegs)), 0, 0) if err == syscall.Errno(0) { err = nil @@ -71,26 +72,6 @@ func PtraceGetRegset(tid int) (regset PtraceXsave, err error) { err = nil } - if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= iov.Len { - return - } - xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN] - xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8]) - xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16]) - - if xcomp_bv&(1<<63) != 0 { - // compact format not supported - return - } - - if xstate_bv&(1<<2) == 0 { - // AVX state not present - return - } - - avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:iov.Len] - regset.AvxState = true - copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)]) - - return + err = proc.LinuxX86XstateRead(xstateargs[:iov.Len], false, ®set) + return regset, err } diff --git a/pkg/proc/native/registers_linux_amd64.go b/pkg/proc/native/registers_linux_amd64.go index a5955780..b86659de 100644 --- a/pkg/proc/native/registers_linux_amd64.go +++ b/pkg/proc/native/registers_linux_amd64.go @@ -1,8 +1,6 @@ package native import ( - "fmt" - "golang.org/x/arch/x86/x86asm" sys "golang.org/x/sys/unix" @@ -273,27 +271,6 @@ func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) { return r, nil } -// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h -type PtraceFpRegs struct { - Cwd uint16 - Swd uint16 - Ftw uint16 - Fop uint16 - Rip uint64 - Rdp uint64 - Mxcsr uint32 - MxcrMask uint32 - StSpace [32]uint32 - XmmSpace [256]byte - padding [24]uint32 -} - -type PtraceXsave struct { - PtraceFpRegs - AvxState bool // contains AVX state - YmmSpace [256]byte -} - const ( _X86_XSTATE_MAX_SIZE = 2688 _NT_X86_XSTATE = 0x202 @@ -305,31 +282,8 @@ const ( ) func (thread *Thread) fpRegisters() (regs []proc.Register, err error) { - var fpregs PtraceXsave + var fpregs proc.LinuxX86Xstate thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) }) - - // x87 registers - regs = proc.AppendWordReg(regs, "CW", fpregs.Cwd) - regs = proc.AppendWordReg(regs, "SW", fpregs.Swd) - regs = proc.AppendWordReg(regs, "TW", fpregs.Ftw) - regs = proc.AppendWordReg(regs, "FOP", fpregs.Fop) - regs = proc.AppendQwordReg(regs, "FIP", fpregs.Rip) - regs = proc.AppendQwordReg(regs, "FDP", fpregs.Rdp) - - for i := 0; i < len(fpregs.StSpace); i += 4 { - regs = proc.AppendX87Reg(regs, i/4, uint16(fpregs.StSpace[i+2]), uint64(fpregs.StSpace[i+1])<<32|uint64(fpregs.StSpace[i])) - } - - // SSE registers - regs = proc.AppendMxcsrReg(regs, "MXCSR", uint64(fpregs.Mxcsr)) - regs = proc.AppendDwordReg(regs, "MXCSR_MASK", fpregs.MxcrMask) - - for i := 0; i < len(fpregs.XmmSpace); i += 16 { - regs = proc.AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), fpregs.XmmSpace[i:i+16]) - if fpregs.AvxState { - regs = proc.AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), fpregs.YmmSpace[i:i+16]) - } - } - + regs = fpregs.Decode() return } diff --git a/pkg/proc/registers.go b/pkg/proc/registers.go index b95e2bd2..f70e23c0 100644 --- a/pkg/proc/registers.go +++ b/pkg/proc/registers.go @@ -232,3 +232,98 @@ func (descr flagRegisterDescr) Describe(reg uint64, bitsize int) string { } return fmt.Sprintf("%#0*x\t[%s]", bitsize/4, reg, strings.Join(r, " ")) } + +// tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h +type PtraceFpRegs struct { + Cwd uint16 + Swd uint16 + Ftw uint16 + Fop uint16 + Rip uint64 + Rdp uint64 + Mxcsr uint32 + MxcrMask uint32 + StSpace [32]uint32 + XmmSpace [256]byte + Padding [24]uint32 +} + +// LinuxX86Xstate represents amd64 XSAVE area. See Section 13.1 (and +// following) of Intel® 64 and IA-32 Architectures Software Developer’s +// Manual, Volume 1: Basic Architecture. +type LinuxX86Xstate struct { + PtraceFpRegs + AvxState bool // contains AVX state + YmmSpace [256]byte +} + +// Decode decodes an XSAVE area to a list of name/value pairs of registers. +func (xsave *LinuxX86Xstate) Decode() (regs []Register) { + // x87 registers + regs = AppendWordReg(regs, "CW", xsave.Cwd) + regs = AppendWordReg(regs, "SW", xsave.Swd) + regs = AppendWordReg(regs, "TW", xsave.Ftw) + regs = AppendWordReg(regs, "FOP", xsave.Fop) + regs = AppendQwordReg(regs, "FIP", xsave.Rip) + regs = AppendQwordReg(regs, "FDP", xsave.Rdp) + + for i := 0; i < len(xsave.StSpace); i += 4 { + regs = AppendX87Reg(regs, i/4, uint16(xsave.StSpace[i+2]), uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i])) + } + + // SSE registers + regs = AppendMxcsrReg(regs, "MXCSR", uint64(xsave.Mxcsr)) + regs = AppendDwordReg(regs, "MXCSR_MASK", xsave.MxcrMask) + + for i := 0; i < len(xsave.XmmSpace); i += 16 { + regs = AppendSSEReg(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16]) + if xsave.AvxState { + regs = AppendSSEReg(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16]) + } + } + + return +} + +const ( + _XSAVE_HEADER_START = 512 + _XSAVE_HEADER_LEN = 64 + _XSAVE_EXTENDED_REGION_START = 576 + _XSAVE_SSE_REGION_LEN = 416 +) + +// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset. +// If readLegacy is true regset.PtraceFpRegs will be filled with the +// contents of the legacy region of the XSAVE area. +// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures +// Software Developer’s Manual, Volume 1: Basic Architecture. +func LinuxX86XstateRead(xstateargs []byte, readLegacy bool, regset *LinuxX86Xstate) error { + if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) { + return nil + } + if readLegacy { + rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START]) + if err := binary.Read(rdr, binary.LittleEndian, ®set.PtraceFpRegs); err != nil { + return err + } + } + xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN] + xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8]) + xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16]) + + if xcomp_bv&(1<<63) != 0 { + // compact format not supported + return nil + } + + if xstate_bv&(1<<2) == 0 { + // AVX state not present + return nil + } + + avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:] + regset.AvxState = true + copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)]) + + return nil +}