Save/restore floating point state in amd64 signal frames.

PiperOrigin-RevId: 429178859
This commit is contained in:
Jamie Liu
2022-02-16 17:23:40 -08:00
committed by gVisor bot
parent d96b1860ba
commit 44644d9c19
10 changed files with 288 additions and 107 deletions
+7 -2
View File
@@ -148,7 +148,9 @@ type Context interface {
// stack is not going to be used).
//
// sigset is the signal mask before entering the signal handler.
SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error
//
// featureSet is the application CPU feature set.
SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error
// SignalRestore restores context after returning from a signal
// handler.
@@ -157,8 +159,11 @@ type Context interface {
//
// rt is true if SignalRestore is being entered from rt_sigreturn and
// false if SignalRestore is being entered from sigreturn.
//
// featureSet is the application CPU feature set.
//
// SignalRestore returns the thread's new signal mask.
SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error)
SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error)
// SingleStep returns true if single stepping is enabled.
SingleStep() bool
+1 -11
View File
@@ -106,7 +106,6 @@ const (
// +stateify savable
type context64 struct {
State
sigFPState []fpu.State // fpstate to be restored on sigreturn.
}
// Arch implements Context.Arch.
@@ -114,14 +113,6 @@ func (c *context64) Arch() Arch {
return AMD64
}
func (c *context64) copySigFPState() []fpu.State {
var sigfps []fpu.State
for _, s := range c.sigFPState {
sigfps = append(sigfps, s.Fork())
}
return sigfps
}
func (c *context64) FloatingPointData() *fpu.State {
return &c.State.fpState
}
@@ -129,8 +120,7 @@ func (c *context64) FloatingPointData() *fpu.State {
// Fork returns an exact copy of this context.
func (c *context64) Fork() Context {
return &context64{
State: c.State.Fork(),
sigFPState: c.copySigFPState(),
State: c.State.Fork(),
}
}
-1
View File
@@ -397,7 +397,6 @@ func New(arch Arch) Context {
State{
fpState: fpu.NewState(),
},
[]fpu.State(nil),
}
}
panic(fmt.Sprintf("unknown architecture %v", arch))
+57 -37
View File
@@ -63,6 +63,15 @@ func (s *State) Fork() State {
return n
}
// Reset resets s to its initial state.
func (s *State) Reset() {
f := *s
for i := range f {
f[i] = 0
}
initX86FPState(&f[0], cpuid.HostFeatureSet().UseXsave())
}
// ptraceFPRegsSize is the size in bytes of Linux's user_i387_struct, the type
// manipulated by PTRACE_GETFPREGS and PTRACE_SETFPREGS on x86. Equivalently,
// ptraceFPRegsSize is the size in bytes of the x86 FXSAVE area.
@@ -107,11 +116,6 @@ const (
mxcsrMaskOffset = 28
)
var (
mxcsrMask uint32
initMXCSRMask sync.Once
)
const (
// minXstateBytes is the minimum size in bytes of an x86 XSAVE area, equal
// to the size of the XSAVE legacy area (512 bytes) plus the size of the
@@ -142,28 +146,6 @@ const (
xsaveHeaderZeroedBytes = 64 - 8
)
// sanitizeMXCSR coerces reserved bits in the MXCSR field of f to 0. ("FXRSTOR
// generates a general-protection fault (#GP) in response to an attempt to set
// any of the reserved bits of the MXCSR register." - Intel SDM Vol. 1, Section
// 10.5.1.2 "SSE State")
func sanitizeMXCSR(f State) {
mxcsr := hostarch.ByteOrder.Uint32(f[mxcsrOffset:])
initMXCSRMask.Do(func() {
temp := State(alignedBytes(uint(ptraceFPRegsSize), 16))
initX86FPState(&temp[0], false /* useXsave */)
mxcsrMask = hostarch.ByteOrder.Uint32(temp[mxcsrMaskOffset:])
if mxcsrMask == 0 {
// "If the value of the MXCSR_MASK field is 00000000H, then the
// MXCSR_MASK value is the default value of 0000FFBFH." - Intel SDM
// Vol. 1, Section 11.6.6 "Guidelines for Writing to the MXCSR
// Register"
mxcsrMask = 0xffbf
}
})
mxcsr &= mxcsrMask
hostarch.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr)
}
// PtraceGetXstateRegs implements ptrace(PTRACE_GETREGS, NT_X86_XSTATE) by
// writing the floating point registers from this state to dst and returning the
// number of bytes written, which must be less than or equal to maxlen.
@@ -206,18 +188,56 @@ func (s *State) PtraceSetXstateRegs(src io.Reader, maxlen int, featureSet cpuid.
if _, err := io.ReadFull(src, f); err != nil {
return 0, err
}
n := copy(*s, f)
s.SanitizeUser(featureSet)
return n, nil
}
// SanitizeUser mutates s to ensure that restoring it is safe.
func (s *State) SanitizeUser(featureSet cpuid.FeatureSet) {
f := *s
// Force reserved bits in MXCSR to 0. This is consistent with Linux.
sanitizeMXCSR(State(f))
// Users can't enable *more* XCR0 bits than what we, and the CPU, support.
xstateBV := hostarch.ByteOrder.Uint64(f[xstateBVOffset:])
xstateBV &= featureSet.ValidXCR0Mask()
hostarch.ByteOrder.PutUint64(f[xstateBVOffset:], xstateBV)
// Force XCOMP_BV and reserved bytes in the XSAVE header to 0.
reserved := f[xsaveHeaderZeroedOffset : xsaveHeaderZeroedOffset+xsaveHeaderZeroedBytes]
for i := range reserved {
reserved[i] = 0
sanitizeMXCSR(f)
if len(f) >= minXstateBytes {
// Users can't enable *more* XCR0 bits than what we, and the CPU, support.
xstateBV := hostarch.ByteOrder.Uint64(f[xstateBVOffset:])
xstateBV &= featureSet.ValidXCR0Mask()
hostarch.ByteOrder.PutUint64(f[xstateBVOffset:], xstateBV)
// Force XCOMP_BV and reserved bytes in the XSAVE header to 0.
reserved := f[xsaveHeaderZeroedOffset : xsaveHeaderZeroedOffset+xsaveHeaderZeroedBytes]
for i := range reserved {
reserved[i] = 0
}
}
return copy(*s, f), nil
}
var (
mxcsrMask uint32
initMXCSRMask sync.Once
)
// sanitizeMXCSR coerces reserved bits in the MXCSR field of f to 0. ("FXRSTOR
// generates a general-protection fault (#GP) in response to an attempt to set
// any of the reserved bits of the MXCSR register." - Intel SDM Vol. 1, Section
// 10.5.1.2 "SSE State")
func sanitizeMXCSR(f State) {
mxcsr := hostarch.ByteOrder.Uint32(f[mxcsrOffset:])
initMXCSRMask.Do(func() {
temp := State(alignedBytes(uint(ptraceFPRegsSize), 16))
initX86FPState(&temp[0], false /* useXsave */)
mxcsrMask = hostarch.ByteOrder.Uint32(temp[mxcsrMaskOffset:])
if mxcsrMask == 0 {
// "If the value of the MXCSR_MASK field is 00000000H, then the
// MXCSR_MASK value is the default value of 0000FFBFH." - Intel SDM
// Vol. 1, Section 11.6.6 "Guidelines for Writing to the MXCSR
// Register"
mxcsrMask = 0xffbf
}
})
mxcsr &= mxcsrMask
hostarch.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr)
}
// SetMXCSR sets the MXCSR control/status register in the state.
+87 -52
View File
@@ -22,10 +22,11 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/usermem"
)
// SignalContext64 is equivalent to struct sigcontext, the type passed as the
@@ -59,7 +60,7 @@ type SignalContext64 struct {
Trapno uint64
Oldmask linux.SignalSet
Cr2 uint64
// Pointer to a struct _fpstate. See b/33003106#comment8.
// Pointer to a struct _fpstate.
Fpstate uint64
Reserved [8]uint64
}
@@ -82,28 +83,38 @@ type UContext64 struct {
Sigset linux.SignalSet
}
// From Linux 'arch/x86/include/uapi/asm/sigcontext.h' the following is the
// size of the magic cookie at the end of the xsave frame.
// FPSoftwareFrame is equivalent to struct _fpx_sw_bytes, the data stored by
// Linux in bytes 464:511 of the fxsave/xsave frame.
//
// NOTE(b/33003106#comment11): Currently we don't actually populate the fpstate
// on the signal stack.
const _FP_XSTATE_MAGIC2_SIZE = 4
func (c *context64) fpuFrameSize() (size int, useXsave bool) {
size = len(c.fpState)
if size > 512 {
// Make room for the magic cookie at the end of the xsave frame.
size += _FP_XSTATE_MAGIC2_SIZE
useXsave = true
}
return size, useXsave
// +marshal
type FPSoftwareFrame struct {
Magic1 uint32
ExtendedSize uint32
Xfeatures uint64
XstateSize uint32
Padding [7]uint32
}
// From Linux's arch/x86/include/uapi/asm/sigcontext.h.
const (
// Value of FPSoftwareFrame.Magic1.
_FP_XSTATE_MAGIC1 = 0x46505853
// Value written to the 4 bytes inserted by Linux after the fxsave/xsave
// area in the signal frame.
_FP_XSTATE_MAGIC2 = 0x46505845
_FP_XSTATE_MAGIC2_SIZE = 4
)
// From Linux's arch/x86/include/asm/fpu/types.h.
const (
// xsave features that are always enabled in signal frame fpstate.
_XFEATURE_MASK_FPSSE = 0x3
)
// SignalSetup implements Context.SignalSetup. (Compare to Linux's
// arch/x86/kernel/signal.c:__setup_rt_frame().)
func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error {
sp := st.Bottom
func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error {
// "The 128-byte area beyond the location pointed to by %rsp is considered
// to be reserved and shall not be modified by signal or interrupt
// handlers. ... leaf functions may use this area for their entire stack
@@ -112,23 +123,22 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig
//
// (But this doesn't apply if we're starting at the top of the signal
// stack, in which case there is no following stack frame.)
sp := st.Bottom
if !(alt.IsEnabled() && sp == alt.Top()) {
sp -= 128
}
// Allocate space for floating point state on the stack.
//
// This isn't strictly necessary because we don't actually populate
// the fpstate. However we do store the floating point state of the
// interrupted thread inside the sentry. Simply accounting for this
// space on the user stack naturally caps the amount of memory the
// sentry will allocate for this purpose.
fpSize, _ := c.fpuFrameSize()
sp = (sp - hostarch.Addr(fpSize)) & ^hostarch.Addr(63)
fpSize, fpAlign := featureSet.ExtendedStateSize()
if fpSize < 512 {
// We expect support for at least FXSAVE.
fpSize = 512
}
fpSize += _FP_XSTATE_MAGIC2_SIZE
fpStart := (sp - hostarch.Addr(fpSize)) & ^hostarch.Addr(fpAlign-1)
// Construct the UContext64 now since we need its size.
uc := &UContext64{
// No _UC_FP_XSTATE: see Fpstate above.
// No _UC_STRICT_RESTORE_SS: we don't allow SS changes.
Flags: _UC_SIGCONTEXT_SS,
Stack: *alt,
@@ -154,9 +164,13 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig
Cs: uint16(c.Regs.Cs),
Ss: uint16(c.Regs.Ss),
Oldmask: sigset,
Fpstate: uint64(fpStart),
},
Sigset: sigset,
}
if featureSet.UseXsave() {
uc.Flags |= _UC_FP_XSTATE
}
// TODO(gvisor.dev/issue/159): Set SignalContext64.Err, Trapno, and Cr2
// based on the fault that caused the signal. For now, leave Err and
@@ -171,21 +185,46 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig
ucSize := uc.SizeBytes()
// st.Arch.Width() is for the restorer address. sizeof(siginfo) == 128.
frameSize := int(st.Arch.Width()) + ucSize + 128
frameBottom := (sp-hostarch.Addr(frameSize)) & ^hostarch.Addr(15) - 8
sp = frameBottom + hostarch.Addr(frameSize)
st.Bottom = sp
frameStart := (fpStart-hostarch.Addr(frameSize)) & ^hostarch.Addr(15) - 8
frameEnd := frameStart + hostarch.Addr(frameSize)
// Prior to proceeding, figure out if the frame will exhaust the range
// for the signal stack. This is not allowed, and should immediately
// force signal delivery (reverting to the default handler).
if act.Flags&linux.SA_ONSTACK != 0 && alt.IsEnabled() && !alt.Contains(frameBottom) {
if act.Flags&linux.SA_ONSTACK != 0 && alt.IsEnabled() && !alt.Contains(frameStart) {
return unix.EFAULT
}
// Set up floating point state on the stack. Compare Linux's
// arch/x86/kernel/fpu/signal.c:copy_fpstate_to_sigframe().
if _, err := st.IO.CopyOut(context.Background(), fpStart, c.fpState[:464], usermem.IOOpts{}); err != nil {
return err
}
fpsw := FPSoftwareFrame{
Magic1: _FP_XSTATE_MAGIC1,
ExtendedSize: uint32(fpSize),
Xfeatures: _XFEATURE_MASK_FPSSE | featureSet.ValidXCR0Mask(),
XstateSize: uint32(fpSize) - _FP_XSTATE_MAGIC2_SIZE,
}
st.Bottom = fpStart + 512
if _, err := fpsw.CopyOut(st, StackBottomMagic); err != nil {
return err
}
if len(c.fpState) > 512 {
if _, err := st.IO.CopyOut(context.Background(), fpStart+512, c.fpState[512:], usermem.IOOpts{}); err != nil {
return err
}
}
st.Bottom = fpStart + hostarch.Addr(fpSize)
if _, err := primitive.CopyUint32Out(st, StackBottomMagic, _FP_XSTATE_MAGIC2); err != nil {
return err
}
// Adjust the code.
info.FixSignalCodeForUser()
// Set up the stack frame.
st.Bottom = frameEnd
if _, err := info.CopyOut(st, StackBottomMagic); err != nil {
return err
}
@@ -212,23 +251,21 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig
c.Regs.Rsi = uint64(infoAddr)
c.Regs.Rdx = uint64(ucAddr)
c.Regs.Rax = 0
c.Regs.Eflags &^= eflagsDF | eflagsRF | eflagsTF
c.Regs.Ds = userDS
c.Regs.Es = userDS
c.Regs.Cs = userCS
c.Regs.Ss = userDS
// Save the thread's floating point state.
c.sigFPState = append(c.sigFPState, c.fpState)
// Signal handler gets a clean floating point state.
c.fpState = fpu.NewState()
// Clear floating point registers.
c.fpState.Reset()
return nil
}
// SignalRestore implements Context.SignalRestore. (Compare to Linux's
// arch/x86/kernel/signal.c:sys_rt_sigreturn().)
func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error) {
func (c *context64) SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error) {
// Copy out the stack frame.
var uc UContext64
if _, err := uc.CopyIn(st, StackBottomMagic); err != nil {
@@ -262,20 +299,18 @@ func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.Si
// N.B. _UC_STRICT_RESTORE_SS not supported.
c.Regs.Orig_rax = math.MaxUint64
// Restore floating point state.
l := len(c.sigFPState)
if l > 0 {
c.fpState = c.sigFPState[l-1]
// NOTE(cl/133042258): State save requires that any slice
// elements from '[len:cap]' to be zero value.
c.sigFPState[l-1] = nil
c.sigFPState = c.sigFPState[0 : l-1]
// Restore floating point state. Compare Linux's
// arch/x86/kernel/fpu/signal.c:fpu__restore_sig().
if uc.MContext.Fpstate == 0 {
c.fpState.Reset()
} else {
// This might happen if sigreturn(2) calls are unbalanced with
// respect to signal handler entries. This is not expected so
// don't bother to do anything fancy with the floating point
// state.
log.Infof("sigreturn unable to restore application fpstate")
fpSize, _ := featureSet.ExtendedStateSize()
f := make([]byte, fpSize)
if _, err := st.IO.CopyIn(context.Background(), hostarch.Addr(uc.MContext.Fpstate), f, usermem.IOOpts{}); err != nil {
return 0, linux.SignalStack{}, err
}
copy(c.fpState, f)
c.fpState.SanitizeUser(featureSet)
}
return uc.Sigset, uc.Stack, nil
+3 -2
View File
@@ -20,6 +20,7 @@ package arch
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
@@ -73,7 +74,7 @@ type UContext64 struct {
}
// SignalSetup implements Context.SignalSetup.
func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error {
func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error {
sp := st.Bottom
// Construct the UContext64 now since we need its size.
@@ -138,7 +139,7 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig
}
// SignalRestore implements Context.SignalRestore.
func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error) {
func (c *context64) SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error) {
// Copy out the stack frame.
var uc UContext64
if _, err := uc.CopyIn(st, StackBottomMagic); err != nil {
+2 -2
View File
@@ -282,7 +282,7 @@ func (t *Task) deliverSignalToHandler(info *linux.SignalInfo, act linux.SigActio
act.Restorer = mm.VDSOSigReturn()
}
if err := t.Arch().SignalSetup(st, &act, info, &alt, mask); err != nil {
if err := t.Arch().SignalSetup(st, &act, info, &alt, mask, t.k.featureSet); err != nil {
return err
}
t.p.FullStateChanged()
@@ -304,7 +304,7 @@ var ctrlResume = &SyscallControl{ignoreReturn: true}
// rt is true).
func (t *Task) SignalReturn(rt bool) (*SyscallControl, error) {
st := t.Stack()
sigset, alt, err := t.Arch().SignalRestore(st, rt)
sigset, alt, err := t.Arch().SignalRestore(st, rt, t.k.featureSet)
if err != nil {
return nil, err
}
+4
View File
@@ -193,6 +193,10 @@ syscall_test(
test = "//test/syscalls/linux:fpsig_fork_test",
)
syscall_test(
test = "//test/syscalls/linux:fpsig_mut_test",
)
syscall_test(
test = "//test/syscalls/linux:fpsig_nested_test",
)
+16
View File
@@ -888,6 +888,22 @@ cc_binary(
],
)
cc_binary(
name = "fpsig_mut_test",
testonly = 1,
srcs = select_arch(
amd64 = ["fpsig_mut_amd64.cc"],
arm64 = [],
),
linkstatic = 1,
deps = [
gtest,
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
],
)
cc_binary(
name = "fpsig_nested_test",
testonly = 1,
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This program verifies that application floating point state is visible in
// signal frames, and that changes to said state is visible after the signal
// handler returns.
#include <sys/time.h>
#include <sys/ucontext.h>
#include "gtest/gtest.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
namespace gvisor {
namespace testing {
namespace {
#define GET_XMM(__var, __xmm) \
asm volatile("movq %%" #__xmm ", %0" : "=r"(__var))
#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var))
int pid;
int tid;
volatile uint64_t handlerxmm = ~0UL;
volatile uint64_t framexmm = ~0UL;
constexpr uint64_t kOldFPRegValue = 0xdeadbeeffacefeed;
constexpr uint64_t kNewFPRegValue = 0xfacefeedbaad1dea;
void sigusr1(int s, siginfo_t* siginfo, void* _uc) {
uint64_t val = SIGUSR1;
// Record the value of %xmm0 on entry and then clobber it.
GET_XMM(handlerxmm, xmm0);
SET_XMM(val, xmm0);
// Record the value of %xmm0 stored in _uc and then replace it.
ucontext_t* uc = reinterpret_cast<ucontext_t*>(_uc);
auto* uc_xmm0 = &uc->uc_mcontext.fpregs->_xmm[0];
framexmm = (static_cast<uint64_t>(uc_xmm0->element[1]) << 32) |
static_cast<uint64_t>(uc_xmm0->element[0]);
uc_xmm0->element[1] = static_cast<uint32_t>(kNewFPRegValue >> 32);
uc_xmm0->element[0] = static_cast<uint32_t>(kNewFPRegValue);
}
TEST(FPSigTest, StateInFrame) {
pid = getpid();
tid = gettid();
struct sigaction sa = {};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = sigusr1;
ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds());
// The amd64 ABI specifies that the XMM register set is caller-saved. This
// implies that if there is any function call between SET_XMM and GET_XMM the
// compiler might save/restore xmm0 implicitly. This defeats the entire
// purpose of the test which is to verify that fpstate is restored by
// sigreturn(2).
//
// This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented
// in inline assembly below.
//
// If the OS is broken and registers are clobbered by the signal, using tgkill
// to signal the current thread ensures that this is the clobbered thread.
SET_XMM(kOldFPRegValue, xmm0);
asm volatile(
"movl %[killnr], %%eax;"
"movl %[pid], %%edi;"
"movl %[tid], %%esi;"
"movl %[sig], %%edx;"
"syscall;"
:
: [killnr] "i"(__NR_tgkill), [pid] "rm"(pid), [tid] "rm"(tid),
[sig] "i"(SIGUSR1)
: "rax", "rdi", "rsi", "rdx",
// Clobbered by syscall.
"rcx", "r11");
uint64_t got;
GET_XMM(got, xmm0);
//
// The checks below verifies the following:
// - signal handlers must called with a clean fpu state.
// - sigreturn(2) must restore fpstate of the interrupted context.
//
EXPECT_EQ(handlerxmm, 0);
EXPECT_EQ(framexmm, kOldFPRegValue);
EXPECT_EQ(got, kNewFPRegValue);
}
} // namespace
} // namespace testing
} // namespace gvisor