mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Save/restore floating point state in amd64 signal frames.
PiperOrigin-RevId: 429178859
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -397,7 +397,6 @@ func New(arch Arch) Context {
|
||||
State{
|
||||
fpState: fpu.NewState(),
|
||||
},
|
||||
[]fpu.State(nil),
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("unknown architecture %v", arch))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user