Move the code that manages floating-point state to a separate package

This change is inspired by Adin's cl/355256448.

PiperOrigin-RevId: 364695931
This commit is contained in:
Andrei Vagin
2021-03-23 18:46:37 -07:00
committed by gVisor bot
parent 960155cdaa
commit 56a9a13976
32 changed files with 528 additions and 421 deletions
+1
View File
@@ -80,6 +80,7 @@ go_library(
"//pkg/ring0/pagetables",
"//pkg/safecopy",
"//pkg/sentry/arch",
"//pkg/sentry/arch/fpu",
"//pkg/usermem",
],
)
+2 -1
View File
@@ -17,6 +17,7 @@ package ring0
import (
"gvisor.dev/gvisor/pkg/ring0/pagetables"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
)
// Kernel is a global kernel object.
@@ -96,7 +97,7 @@ type SwitchOpts struct {
// FloatingPointState is a byte pointer where floating point state is
// saved and restored.
FloatingPointState *byte
FloatingPointState *fpu.State
// PageTables are the application page tables.
PageTables *pagetables.PageTables
+1
View File
@@ -35,6 +35,7 @@ go_binary(
"//pkg/cpuid",
"//pkg/ring0/pagetables",
"//pkg/sentry/arch",
"//pkg/sentry/arch/fpu",
"//pkg/usermem",
],
)
+6 -6
View File
@@ -239,17 +239,17 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
regs.Ss = uint64(Udata) // Ditto.
// Perform the switch.
swapgs() // GS will be swapped on return.
WriteFS(uintptr(regs.Fs_base)) // escapes: no. Set application FS.
WriteGS(uintptr(regs.Gs_base)) // escapes: no. Set application GS.
LoadFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy in floating point.
swapgs() // GS will be swapped on return.
WriteFS(uintptr(regs.Fs_base)) // escapes: no. Set application FS.
WriteGS(uintptr(regs.Gs_base)) // escapes: no. Set application GS.
LoadFloatingPoint(switchOpts.FloatingPointState.BytePointer()) // escapes: no. Copy in floating point.
if switchOpts.FullRestore {
vector = iret(c, regs, uintptr(userCR3))
} else {
vector = sysret(c, regs, uintptr(userCR3))
}
SaveFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy out floating point.
WriteFS(uintptr(c.registers.Fs_base)) // escapes: no. Restore kernel FS.
SaveFloatingPoint(switchOpts.FloatingPointState.BytePointer()) // escapes: no. Copy out floating point.
WriteFS(uintptr(c.registers.Fs_base)) // escapes: no. Restore kernel FS.
return
}
+2 -2
View File
@@ -62,7 +62,7 @@ func IsCanonical(addr uint64) bool {
//go:nosplit
func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
storeAppASID(uintptr(switchOpts.UserASID))
storeEl0Fpstate(switchOpts.FloatingPointState)
storeEl0Fpstate(switchOpts.FloatingPointState.BytePointer())
if switchOpts.Flush {
FlushTlbByASID(uintptr(switchOpts.UserASID))
@@ -82,7 +82,7 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
fpDisableTrap = CPACREL1()
if fpDisableTrap != 0 {
SaveFloatingPoint(switchOpts.FloatingPointState)
SaveFloatingPoint(switchOpts.FloatingPointState.BytePointer())
}
vector = c.vecCode
+1 -2
View File
@@ -9,7 +9,6 @@ go_library(
"arch.go",
"arch_aarch64.go",
"arch_amd64.go",
"arch_amd64.s",
"arch_arm64.go",
"arch_state_x86.go",
"arch_x86.go",
@@ -36,8 +35,8 @@ go_library(
"//pkg/log",
"//pkg/marshal",
"//pkg/marshal/primitive",
"//pkg/sentry/arch/fpu",
"//pkg/sentry/limits",
"//pkg/sync",
"//pkg/syserror",
"//pkg/usermem",
"@org_golang_x_sys//unix:go_default_library",
+2 -32
View File
@@ -24,6 +24,7 @@ import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -50,12 +51,6 @@ func (a Arch) String() string {
}
}
// FloatingPointData is a generic type, and will always be passed as a pointer.
// We rely on the individual arch implementations to meet all the necessary
// requirements. For example, on x86 the region must be 16-byte aligned and 512
// bytes in size.
type FloatingPointData byte
// Context provides architecture-dependent information for a specific thread.
//
// NOTE(b/34169503): Currently we use uintptr here to refer to a generic native
@@ -187,7 +182,7 @@ type Context interface {
ClearSingleStep()
// FloatingPointData will be passed to underlying save routines.
FloatingPointData() *FloatingPointData
FloatingPointData() *fpu.State
// NewMmapLayout returns a layout for a new MM, where MinAddr for the
// returned layout must be no lower than min, and MaxAddr for the returned
@@ -221,16 +216,6 @@ type Context interface {
// number of bytes read.
PtraceSetRegs(src io.Reader) (int, error)
// PtraceGetFPRegs implements ptrace(PTRACE_GETFPREGS) by writing the
// floating-point registers represented by this Context to addr in dst and
// returning the number of bytes written.
PtraceGetFPRegs(dst io.Writer) (int, error)
// PtraceSetFPRegs implements ptrace(PTRACE_SETFPREGS) by reading
// floating-point registers from src into this Context and returning the
// number of bytes read.
PtraceSetFPRegs(src io.Reader) (int, error)
// PtraceGetRegSet implements ptrace(PTRACE_GETREGSET) by writing the
// register set given by architecture-defined value regset from this
// Context to dst and returning the number of bytes written, which must be
@@ -365,18 +350,3 @@ func (a SyscallArgument) SizeT() uint {
func (a SyscallArgument) ModeT() uint {
return uint(uint16(a.Value))
}
// ErrFloatingPoint indicates a failed restore due to unusable floating point
// state.
type ErrFloatingPoint struct {
// supported is the supported floating point state.
supported uint64
// saved is the saved floating point state.
saved uint64
}
// Error returns a sensible description of the restore error.
func (e ErrFloatingPoint) Error() string {
return fmt.Sprintf("floating point state contains unsupported features; supported: %#x saved: %#x", e.supported, e.saved)
}
+9 -62
View File
@@ -23,6 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
"gvisor.dev/gvisor/pkg/syserror"
)
@@ -40,65 +41,11 @@ type Registers struct {
const (
// SyscallWidth is the width of insturctions.
SyscallWidth = 4
// fpsimdMagic is the magic number which is used in fpsimd_context.
fpsimdMagic = 0x46508001
// fpsimdContextSize is the size of fpsimd_context.
fpsimdContextSize = 0x210
)
// ARMTrapFlag is the mask for the trap flag.
const ARMTrapFlag = uint64(1) << 21
// aarch64FPState is aarch64 floating point state.
type aarch64FPState []byte
// initAarch64FPState sets up initial state.
//
// Related code in Linux kernel: fpsimd_flush_thread().
// FPCR = FPCR_RM_RN (0x0 << 22).
//
// Currently, aarch64FPState is only a space of 0x210 length for fpstate.
// The fp head is useless in sentry/ptrace/kvm.
//
func initAarch64FPState(data aarch64FPState) {
}
func newAarch64FPStateSlice() []byte {
return alignedBytes(4096, 16)[:fpsimdContextSize]
}
// newAarch64FPState returns an initialized floating point state.
//
// The returned state is large enough to store all floating point state
// supported by host, even if the app won't use much of it due to a restricted
// FeatureSet.
func newAarch64FPState() aarch64FPState {
f := aarch64FPState(newAarch64FPStateSlice())
initAarch64FPState(f)
return f
}
// fork creates and returns an identical copy of the aarch64 floating point state.
func (f aarch64FPState) fork() aarch64FPState {
n := aarch64FPState(newAarch64FPStateSlice())
copy(n, f)
return n
}
// FloatingPointData returns the raw data pointer.
func (f aarch64FPState) FloatingPointData() *FloatingPointData {
return (*FloatingPointData)(&f[0])
}
// NewFloatingPointData returns a new floating point data blob.
//
// This is primarily for use in tests.
func NewFloatingPointData() *FloatingPointData {
return (*FloatingPointData)(&(newAarch64FPState()[0]))
}
// State contains the common architecture bits for aarch64 (the build tag of this
// file ensures it's only built on aarch64).
//
@@ -108,7 +55,7 @@ type State struct {
Regs Registers
// Our floating point state.
aarch64FPState `state:"wait"`
fpState fpu.State `state:"wait"`
// FeatureSet is a pointer to the currently active feature set.
FeatureSet *cpuid.FeatureSet
@@ -162,10 +109,10 @@ func (s State) Proto() *rpb.Registers {
// Fork creates and returns an identical copy of the state.
func (s *State) Fork() State {
return State{
Regs: s.Regs,
aarch64FPState: s.aarch64FPState.fork(),
FeatureSet: s.FeatureSet,
OrigR0: s.OrigR0,
Regs: s.Regs,
fpState: s.fpState.Fork(),
FeatureSet: s.FeatureSet,
OrigR0: s.OrigR0,
}
}
@@ -318,10 +265,10 @@ func New(arch Arch, fs *cpuid.FeatureSet) Context {
case ARM64:
return &context64{
State{
aarch64FPState: newAarch64FPState(),
FeatureSet: fs,
fpState: fpu.NewState(),
FeatureSet: fs,
},
[]aarch64FPState(nil),
[]fpu.State(nil),
}
}
panic(fmt.Sprintf("unknown architecture %v", arch))
+9 -4
View File
@@ -25,6 +25,7 @@ import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -105,7 +106,7 @@ const (
// +stateify savable
type context64 struct {
State
sigFPState []x86FPState // fpstate to be restored on sigreturn.
sigFPState []fpu.State // fpstate to be restored on sigreturn.
}
// Arch implements Context.Arch.
@@ -113,14 +114,18 @@ func (c *context64) Arch() Arch {
return AMD64
}
func (c *context64) copySigFPState() []x86FPState {
var sigfps []x86FPState
func (c *context64) copySigFPState() []fpu.State {
var sigfps []fpu.State
for _, s := range c.sigFPState {
sigfps = append(sigfps, s.fork())
sigfps = append(sigfps, s.Fork())
}
return sigfps
}
func (c *context64) FloatingPointData() *fpu.State {
return &c.State.fpState
}
// Fork returns an exact copy of this context.
func (c *context64) Fork() Context {
return &context64{
+9 -4
View File
@@ -24,6 +24,7 @@ import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -79,7 +80,7 @@ const (
// +stateify savable
type context64 struct {
State
sigFPState []aarch64FPState // fpstate to be restored on sigreturn.
sigFPState []fpu.State // fpstate to be restored on sigreturn.
}
// Arch implements Context.Arch.
@@ -87,10 +88,10 @@ func (c *context64) Arch() Arch {
return ARM64
}
func (c *context64) copySigFPState() []aarch64FPState {
var sigfps []aarch64FPState
func (c *context64) copySigFPState() []fpu.State {
var sigfps []fpu.State
for _, s := range c.sigFPState {
sigfps = append(sigfps, s.fork())
sigfps = append(sigfps, s.Fork())
}
return sigfps
}
@@ -286,3 +287,7 @@ func (c *context64) PtracePokeUser(addr, data uintptr) error {
// TODO(gvisor.dev/issue/1239): Full ptrace supporting for Arm64.
return nil
}
func (c *context64) FloatingPointData() *fpu.State {
return &c.State.fpState
}
+1 -53
View File
@@ -16,59 +16,7 @@
package arch
import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/usermem"
)
// XSTATE_BV does not exist if FXSAVE is used, but FXSAVE implicitly saves x87
// and SSE state, so this is the equivalent XSTATE_BV value.
const fxsaveBV uint64 = cpuid.XSAVEFeatureX87 | cpuid.XSAVEFeatureSSE
// afterLoadFPState is invoked by afterLoad.
func (s *State) afterLoadFPState() {
old := s.x86FPState
// Recreate the slice. This is done to ensure that it is aligned
// appropriately in memory, and large enough to accommodate any new
// state that may be saved by the new CPU. Even if extraneous new state
// is saved, the state we care about is guaranteed to be a subset of
// new state. Later optimizations can use less space when using a
// smaller state component bitmap. Intel SDM Volume 1 Chapter 13 has
// more info.
s.x86FPState = newX86FPState()
// x86FPState always contains all the FP state supported by the host.
// We may have come from a newer machine that supports additional state
// which we cannot restore.
//
// The x86 FP state areas are backwards compatible, so we can simply
// truncate the additional floating point state.
//
// Applications should not depend on the truncated state because it
// should relate only to features that were not exposed in the app
// FeatureSet. However, because we do not *prevent* them from using
// this state, we must verify here that there is no in-use state
// (according to XSTATE_BV) which we do not support.
if len(s.x86FPState) < len(old) {
// What do we support?
supportedBV := fxsaveBV
if fs := cpuid.HostFeatureSet(); fs.UseXsave() {
supportedBV = fs.ValidXCR0Mask()
}
// What was in use?
savedBV := fxsaveBV
if len(old) >= xstateBVOffset+8 {
savedBV = usermem.ByteOrder.Uint64(old[xstateBVOffset:])
}
// Supported features must be a superset of saved features.
if savedBV&^supportedBV != 0 {
panic(ErrFloatingPoint{supported: supportedBV, saved: savedBV})
}
}
// Copy to the new, aligned location.
copy(s.x86FPState, old)
s.fpState.AfterLoad()
}
+8 -209
View File
@@ -24,10 +24,9 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
// Registers represents the CPU registers for this architecture.
@@ -111,57 +110,6 @@ var (
X86TrapFlag uint64 = (1 << 8)
)
// x86FPState is x86 floating point state.
type x86FPState []byte
// initX86FPState (defined in asm files) sets up initial state.
func initX86FPState(data *FloatingPointData, useXsave bool)
func newX86FPStateSlice() []byte {
size, align := cpuid.HostFeatureSet().ExtendedStateSize()
capacity := size
// Always use at least 4096 bytes.
//
// For the KVM platform, this state is a fixed 4096 bytes, so make sure
// that the underlying array is at _least_ that size otherwise we will
// corrupt random memory. This is not a pleasant thing to debug.
if capacity < 4096 {
capacity = 4096
}
return alignedBytes(capacity, align)[:size]
}
// newX86FPState returns an initialized floating point state.
//
// The returned state is large enough to store all floating point state
// supported by host, even if the app won't use much of it due to a restricted
// FeatureSet. Since they may still be able to see state not advertised by
// CPUID we must ensure it does not contain any sentry state.
func newX86FPState() x86FPState {
f := x86FPState(newX86FPStateSlice())
initX86FPState(f.FloatingPointData(), cpuid.HostFeatureSet().UseXsave())
return f
}
// fork creates and returns an identical copy of the x86 floating point state.
func (f x86FPState) fork() x86FPState {
n := x86FPState(newX86FPStateSlice())
copy(n, f)
return n
}
// FloatingPointData returns the raw data pointer.
func (f x86FPState) FloatingPointData() *FloatingPointData {
return (*FloatingPointData)(&f[0])
}
// NewFloatingPointData returns a new floating point data blob.
//
// This is primarily for use in tests.
func NewFloatingPointData() *FloatingPointData {
return (*FloatingPointData)(&(newX86FPState()[0]))
}
// Proto returns a protobuf representation of the system registers in State.
func (s State) Proto() *rpb.Registers {
regs := &rpb.AMD64Registers{
@@ -200,7 +148,7 @@ func (s State) Proto() *rpb.Registers {
func (s *State) Fork() State {
return State{
Regs: s.Regs,
x86FPState: s.x86FPState.fork(),
fpState: s.fpState.Fork(),
FeatureSet: s.FeatureSet,
}
}
@@ -393,149 +341,6 @@ func isValidSegmentBase(reg uint64) bool {
return reg < uint64(maxAddr64)
}
// 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.
const ptraceFPRegsSize = 512
// PtraceGetFPRegs implements Context.PtraceGetFPRegs.
func (s *State) PtraceGetFPRegs(dst io.Writer) (int, error) {
return dst.Write(s.x86FPState[:ptraceFPRegsSize])
}
// PtraceSetFPRegs implements Context.PtraceSetFPRegs.
func (s *State) PtraceSetFPRegs(src io.Reader) (int, error) {
var f [ptraceFPRegsSize]byte
n, err := io.ReadFull(src, f[:])
if err != nil {
return 0, err
}
// Force reserved bits in MXCSR to 0. This is consistent with Linux.
sanitizeMXCSR(x86FPState(f[:]))
// N.B. this only copies the beginning of the FP state, which
// corresponds to the FXSAVE area.
copy(s.x86FPState, f[:])
return n, nil
}
const (
// mxcsrOffset is the offset in bytes of the MXCSR field from the start of
// the FXSAVE area. (Intel SDM Vol. 1, Table 10-2 "Format of an FXSAVE
// Area")
mxcsrOffset = 24
// mxcsrMaskOffset is the offset in bytes of the MXCSR_MASK field from the
// start of the FXSAVE area.
mxcsrMaskOffset = 28
)
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 x86FPState) {
mxcsr := usermem.ByteOrder.Uint32(f[mxcsrOffset:])
initMXCSRMask.Do(func() {
temp := x86FPState(alignedBytes(uint(ptraceFPRegsSize), 16))
initX86FPState(temp.FloatingPointData(), false /* useXsave */)
mxcsrMask = usermem.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
usermem.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr)
}
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
// XSAVE header (64 bytes). Equivalently, minXstateBytes is GDB's
// X86_XSTATE_SSE_SIZE.
minXstateBytes = 512 + 64
// userXstateXCR0Offset is the offset in bytes of the USER_XSTATE_XCR0_WORD
// field in Linux's struct user_xstateregs, which is the type manipulated
// by ptrace(PTRACE_GET/SETREGSET, NT_X86_XSTATE). Equivalently,
// userXstateXCR0Offset is GDB's I386_LINUX_XSAVE_XCR0_OFFSET.
userXstateXCR0Offset = 464
// xstateBVOffset is the offset in bytes of the XSTATE_BV field in an x86
// XSAVE area.
xstateBVOffset = 512
// xsaveHeaderZeroedOffset and xsaveHeaderZeroedBytes indicate parts of the
// XSAVE header that we coerce to zero: "Bytes 15:8 of the XSAVE header is
// a state-component bitmap called XCOMP_BV. ... Bytes 63:16 of the XSAVE
// header are reserved." - Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header".
// Linux ignores XCOMP_BV, but it's able to recover from XRSTOR #GP
// exceptions resulting from invalid values; we aren't. Linux also never
// uses the compacted format when doing XSAVE and doesn't even define the
// compaction extensions to XSAVE as a CPU feature, so for simplicity we
// assume no one is using them.
xsaveHeaderZeroedOffset = 512 + 8
xsaveHeaderZeroedBytes = 64 - 8
)
func (s *State) ptraceGetXstateRegs(dst io.Writer, maxlen int) (int, error) {
// N.B. s.x86FPState may contain more state than the application
// expects. We only copy the subset that would be in their XSAVE area.
ess, _ := s.FeatureSet.ExtendedStateSize()
f := make([]byte, ess)
copy(f, s.x86FPState)
// "The XSAVE feature set does not use bytes 511:416; bytes 463:416 are
// reserved." - Intel SDM Vol 1., Section 13.4.1 "Legacy Region of an XSAVE
// Area". Linux uses the first 8 bytes of this area to store the OS XSTATE
// mask. GDB relies on this: see
// gdb/x86-linux-nat.c:x86_linux_read_description().
usermem.ByteOrder.PutUint64(f[userXstateXCR0Offset:], s.FeatureSet.ValidXCR0Mask())
if len(f) > maxlen {
f = f[:maxlen]
}
return dst.Write(f)
}
func (s *State) ptraceSetXstateRegs(src io.Reader, maxlen int) (int, error) {
// Allow users to pass an xstate register set smaller than ours (they can
// mask bits out of XSTATE_BV), as long as it's at least minXstateBytes.
// Also allow users to pass a register set larger than ours; anything after
// their ExtendedStateSize will be ignored. (I think Linux technically
// permits setting a register set smaller than minXstateBytes, but it has
// the same silent truncation behavior in kernel/ptrace.c:ptrace_regset().)
if maxlen < minXstateBytes {
return 0, unix.EFAULT
}
ess, _ := s.FeatureSet.ExtendedStateSize()
if maxlen > int(ess) {
maxlen = int(ess)
}
f := make([]byte, maxlen)
if _, err := io.ReadFull(src, f); err != nil {
return 0, err
}
// Force reserved bits in MXCSR to 0. This is consistent with Linux.
sanitizeMXCSR(x86FPState(f))
// Users can't enable *more* XCR0 bits than what we, and the CPU, support.
xstateBV := usermem.ByteOrder.Uint64(f[xstateBVOffset:])
xstateBV &= s.FeatureSet.ValidXCR0Mask()
usermem.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.x86FPState, f), nil
}
// Register sets defined in include/uapi/linux/elf.h.
const (
_NT_PRSTATUS = 1
@@ -552,12 +357,9 @@ func (s *State) PtraceGetRegSet(regset uintptr, dst io.Writer, maxlen int) (int,
}
return s.PtraceGetRegs(dst)
case _NT_PRFPREG:
if maxlen < ptraceFPRegsSize {
return 0, syserror.EFAULT
}
return s.PtraceGetFPRegs(dst)
return s.fpState.PtraceGetFPRegs(dst, maxlen)
case _NT_X86_XSTATE:
return s.ptraceGetXstateRegs(dst, maxlen)
return s.fpState.PtraceGetXstateRegs(dst, maxlen, s.FeatureSet)
default:
return 0, syserror.EINVAL
}
@@ -572,12 +374,9 @@ func (s *State) PtraceSetRegSet(regset uintptr, src io.Reader, maxlen int) (int,
}
return s.PtraceSetRegs(src)
case _NT_PRFPREG:
if maxlen < ptraceFPRegsSize {
return 0, syserror.EFAULT
}
return s.PtraceSetFPRegs(src)
return s.fpState.PtraceSetFPRegs(src, maxlen)
case _NT_X86_XSTATE:
return s.ptraceSetXstateRegs(src, maxlen)
return s.fpState.PtraceSetXstateRegs(src, maxlen, s.FeatureSet)
default:
return 0, syserror.EINVAL
}
@@ -609,10 +408,10 @@ func New(arch Arch, fs *cpuid.FeatureSet) Context {
case AMD64:
return &context64{
State{
x86FPState: newX86FPState(),
fpState: fpu.NewState(),
FeatureSet: fs,
},
[]x86FPState(nil),
[]fpu.State(nil),
}
}
panic(fmt.Sprintf("unknown architecture %v", arch))
+2 -1
View File
@@ -18,6 +18,7 @@ package arch
import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
)
// State contains the common architecture bits for X86 (the build tag of this
@@ -29,7 +30,7 @@ type State struct {
Regs Registers
// Our floating point state.
x86FPState `state:"wait"`
fpState fpu.State `state:"wait"`
// FeatureSet is a pointer to the currently active feature set.
FeatureSet *cpuid.FeatureSet
+21
View File
@@ -0,0 +1,21 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "fpu",
srcs = [
"fpu.go",
"fpu_amd64.go",
"fpu_amd64.s",
"fpu_arm64.go",
],
visibility = ["//:sandbox"],
deps = [
"//pkg/cpuid",
"//pkg/sync",
"//pkg/syserror",
"//pkg/usermem",
"@org_golang_x_sys//unix:go_default_library",
],
)
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2021 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.
// Package fpu provides basic floating point helpers.
package fpu
import (
"fmt"
"reflect"
)
// State represents floating point state.
//
// This is a simple byte slice, but may have architecture-specific methods
// attached to it.
type State []byte
// ErrLoadingState indicates a failed restore due to unusable floating point
// state.
type ErrLoadingState struct {
// supported is the supported floating point state.
supportedFeatures uint64
// saved is the saved floating point state.
savedFeatures uint64
}
// Error returns a sensible description of the restore error.
func (e ErrLoadingState) Error() string {
return fmt.Sprintf("floating point state contains unsupported features; supported: %#x saved: %#x", e.supportedFeatures, e.savedFeatures)
}
// alignedBytes returns a slice of size bytes, aligned in memory to the given
// alignment. This is used because we require certain structures to be aligned
// in a specific way (for example, the X86 floating point data).
func alignedBytes(size, alignment uint) []byte {
data := make([]byte, size+alignment-1)
offset := uint(reflect.ValueOf(data).Index(0).Addr().Pointer() % uintptr(alignment))
if offset == 0 {
return data[:size:size]
}
return data[alignment-offset:][:size:size]
}
+280
View File
@@ -0,0 +1,280 @@
// Copyright 2021 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.
// +build amd64 i386
package fpu
import (
"io"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/usermem"
)
// initX86FPState (defined in asm files) sets up initial state.
func initX86FPState(data *byte, useXsave bool)
func newX86FPStateSlice() State {
size, align := cpuid.HostFeatureSet().ExtendedStateSize()
capacity := size
// Always use at least 4096 bytes.
//
// For the KVM platform, this state is a fixed 4096 bytes, so make sure
// that the underlying array is at _least_ that size otherwise we will
// corrupt random memory. This is not a pleasant thing to debug.
if capacity < 4096 {
capacity = 4096
}
return alignedBytes(capacity, align)[:size]
}
// NewState returns an initialized floating point state.
//
// The returned state is large enough to store all floating point state
// supported by host, even if the app won't use much of it due to a restricted
// FeatureSet. Since they may still be able to see state not advertised by
// CPUID we must ensure it does not contain any sentry state.
func NewState() State {
f := newX86FPStateSlice()
initX86FPState(&f[0], cpuid.HostFeatureSet().UseXsave())
return f
}
// Fork creates and returns an identical copy of the x86 floating point state.
func (s *State) Fork() State {
n := newX86FPStateSlice()
copy(n, *s)
return n
}
// 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.
const ptraceFPRegsSize = 512
// PtraceGetFPRegs implements Context.PtraceGetFPRegs.
func (s *State) PtraceGetFPRegs(dst io.Writer, maxlen int) (int, error) {
if maxlen < ptraceFPRegsSize {
return 0, syserror.EFAULT
}
return dst.Write((*s)[:ptraceFPRegsSize])
}
// PtraceSetFPRegs implements Context.PtraceSetFPRegs.
func (s *State) PtraceSetFPRegs(src io.Reader, maxlen int) (int, error) {
if maxlen < ptraceFPRegsSize {
return 0, syserror.EFAULT
}
var f [ptraceFPRegsSize]byte
n, err := io.ReadFull(src, f[:])
if err != nil {
return 0, err
}
// Force reserved bits in MXCSR to 0. This is consistent with Linux.
sanitizeMXCSR(State(f[:]))
// N.B. this only copies the beginning of the FP state, which
// corresponds to the FXSAVE area.
copy(*s, f[:])
return n, nil
}
const (
// mxcsrOffset is the offset in bytes of the MXCSR field from the start of
// the FXSAVE area. (Intel SDM Vol. 1, Table 10-2 "Format of an FXSAVE
// Area")
mxcsrOffset = 24
// mxcsrMaskOffset is the offset in bytes of the MXCSR_MASK field from the
// start of the FXSAVE area.
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
// XSAVE header (64 bytes). Equivalently, minXstateBytes is GDB's
// X86_XSTATE_SSE_SIZE.
minXstateBytes = 512 + 64
// userXstateXCR0Offset is the offset in bytes of the USER_XSTATE_XCR0_WORD
// field in Linux's struct user_xstateregs, which is the type manipulated
// by ptrace(PTRACE_GET/SETREGSET, NT_X86_XSTATE). Equivalently,
// userXstateXCR0Offset is GDB's I386_LINUX_XSAVE_XCR0_OFFSET.
userXstateXCR0Offset = 464
// xstateBVOffset is the offset in bytes of the XSTATE_BV field in an x86
// XSAVE area.
xstateBVOffset = 512
// xsaveHeaderZeroedOffset and xsaveHeaderZeroedBytes indicate parts of the
// XSAVE header that we coerce to zero: "Bytes 15:8 of the XSAVE header is
// a state-component bitmap called XCOMP_BV. ... Bytes 63:16 of the XSAVE
// header are reserved." - Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header".
// Linux ignores XCOMP_BV, but it's able to recover from XRSTOR #GP
// exceptions resulting from invalid values; we aren't. Linux also never
// uses the compacted format when doing XSAVE and doesn't even define the
// compaction extensions to XSAVE as a CPU feature, so for simplicity we
// assume no one is using them.
xsaveHeaderZeroedOffset = 512 + 8
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 := usermem.ByteOrder.Uint32(f[mxcsrOffset:])
initMXCSRMask.Do(func() {
temp := State(alignedBytes(uint(ptraceFPRegsSize), 16))
initX86FPState(&temp[0], false /* useXsave */)
mxcsrMask = usermem.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
usermem.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.
func (s *State) PtraceGetXstateRegs(dst io.Writer, maxlen int, featureSet *cpuid.FeatureSet) (int, error) {
// N.B. s.x86FPState may contain more state than the application
// expects. We only copy the subset that would be in their XSAVE area.
ess, _ := featureSet.ExtendedStateSize()
f := make([]byte, ess)
copy(f, *s)
// "The XSAVE feature set does not use bytes 511:416; bytes 463:416 are
// reserved." - Intel SDM Vol 1., Section 13.4.1 "Legacy Region of an XSAVE
// Area". Linux uses the first 8 bytes of this area to store the OS XSTATE
// mask. GDB relies on this: see
// gdb/x86-linux-nat.c:x86_linux_read_description().
usermem.ByteOrder.PutUint64(f[userXstateXCR0Offset:], featureSet.ValidXCR0Mask())
if len(f) > maxlen {
f = f[:maxlen]
}
return dst.Write(f)
}
// PtraceSetXstateRegs implements ptrace(PTRACE_SETREGS, NT_X86_XSTATE) by
// reading floating point registers from src and returning the number of bytes
// read, which must be less than or equal to maxlen.
func (s *State) PtraceSetXstateRegs(src io.Reader, maxlen int, featureSet *cpuid.FeatureSet) (int, error) {
// Allow users to pass an xstate register set smaller than ours (they can
// mask bits out of XSTATE_BV), as long as it's at least minXstateBytes.
// Also allow users to pass a register set larger than ours; anything after
// their ExtendedStateSize will be ignored. (I think Linux technically
// permits setting a register set smaller than minXstateBytes, but it has
// the same silent truncation behavior in kernel/ptrace.c:ptrace_regset().)
if maxlen < minXstateBytes {
return 0, unix.EFAULT
}
ess, _ := featureSet.ExtendedStateSize()
if maxlen > int(ess) {
maxlen = int(ess)
}
f := make([]byte, maxlen)
if _, err := io.ReadFull(src, f); err != nil {
return 0, err
}
// 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 := usermem.ByteOrder.Uint64(f[xstateBVOffset:])
xstateBV &= featureSet.ValidXCR0Mask()
usermem.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
}
// BytePointer returns a pointer to the first byte of the state.
//
//go:nosplit
func (s *State) BytePointer() *byte {
return &(*s)[0]
}
// XSTATE_BV does not exist if FXSAVE is used, but FXSAVE implicitly saves x87
// and SSE state, so this is the equivalent XSTATE_BV value.
const fxsaveBV uint64 = cpuid.XSAVEFeatureX87 | cpuid.XSAVEFeatureSSE
// AfterLoad converts the loaded state to the format that compatible with the
// current processor.
func (s *State) AfterLoad() {
old := *s
// Recreate the slice. This is done to ensure that it is aligned
// appropriately in memory, and large enough to accommodate any new
// state that may be saved by the new CPU. Even if extraneous new state
// is saved, the state we care about is guaranteed to be a subset of
// new state. Later optimizations can use less space when using a
// smaller state component bitmap. Intel SDM Volume 1 Chapter 13 has
// more info.
*s = NewState()
// x86FPState always contains all the FP state supported by the host.
// We may have come from a newer machine that supports additional state
// which we cannot restore.
//
// The x86 FP state areas are backwards compatible, so we can simply
// truncate the additional floating point state.
//
// Applications should not depend on the truncated state because it
// should relate only to features that were not exposed in the app
// FeatureSet. However, because we do not *prevent* them from using
// this state, we must verify here that there is no in-use state
// (according to XSTATE_BV) which we do not support.
if len(*s) < len(old) {
// What do we support?
supportedBV := fxsaveBV
if fs := cpuid.HostFeatureSet(); fs.UseXsave() {
supportedBV = fs.ValidXCR0Mask()
}
// What was in use?
savedBV := fxsaveBV
if len(old) >= xstateBVOffset+8 {
savedBV = usermem.ByteOrder.Uint64(old[xstateBVOffset:])
}
// Supported features must be a superset of saved features.
if savedBV&^supportedBV != 0 {
panic(ErrLoadingState{supportedFeatures: supportedBV, savedFeatures: savedBV})
}
}
// Copy to the new, aligned location.
copy(*s, old)
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2020 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.
// +build arm64
package fpu
const (
// fpsimdMagic is the magic number which is used in fpsimd_context.
fpsimdMagic = 0x46508001
// fpsimdContextSize is the size of fpsimd_context.
fpsimdContextSize = 0x210
)
// initAarch64FPState sets up initial state.
//
// Related code in Linux kernel: fpsimd_flush_thread().
// FPCR = FPCR_RM_RN (0x0 << 22).
//
// Currently, aarch64FPState is only a space of 0x210 length for fpstate.
// The fp head is useless in sentry/ptrace/kvm.
//
func initAarch64FPState(data *State) {
}
func newAarch64FPStateSlice() []byte {
return alignedBytes(4096, 16)[:fpsimdContextSize]
}
// NewState returns an initialized floating point state.
//
// The returned state is large enough to store all floating point state
// supported by host, even if the app won't use much of it due to a restricted
// FeatureSet.
func NewState() State {
f := State(newAarch64FPStateSlice())
initAarch64FPState(&f)
return f
}
// Fork creates and returns an identical copy of the aarch64 floating point state.
func (s *State) Fork() State {
n := State(newAarch64FPStateSlice())
copy(n, *s)
return n
}
// BytePointer returns a pointer to the first byte of the state.
func (s *State) BytePointer() *byte {
return &(*s)[0]
}
+5 -4
View File
@@ -23,6 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -98,7 +99,7 @@ func (c *context64) NewSignalStack() NativeSignalStack {
const _FP_XSTATE_MAGIC2_SIZE = 4
func (c *context64) fpuFrameSize() (size int, useXsave bool) {
size = len(c.x86FPState)
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
@@ -226,10 +227,10 @@ func (c *context64) SignalSetup(st *Stack, act *SignalAct, info *SignalInfo, alt
c.Regs.Ss = userDS
// Save the thread's floating point state.
c.sigFPState = append(c.sigFPState, c.x86FPState)
c.sigFPState = append(c.sigFPState, c.fpState)
// Signal handler gets a clean floating point state.
c.x86FPState = newX86FPState()
c.fpState = fpu.NewState()
return nil
}
@@ -273,7 +274,7 @@ func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, SignalSt
// Restore floating point state.
l := len(c.sigFPState)
if l > 0 {
c.x86FPState = c.sigFPState[l-1]
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
+4 -3
View File
@@ -20,6 +20,7 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/arch/fpu"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -139,9 +140,9 @@ func (c *context64) SignalSetup(st *Stack, act *SignalAct, info *SignalInfo, alt
c.Regs.Regs[30] = uint64(act.Restorer)
// Save the thread's floating point state.
c.sigFPState = append(c.sigFPState, c.aarch64FPState)
c.sigFPState = append(c.sigFPState, c.fpState)
// Signal handler gets a clean floating point state.
c.aarch64FPState = newAarch64FPState()
c.fpState = fpu.NewState()
return nil
}
@@ -166,7 +167,7 @@ func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, SignalSt
// Restore floating point state.
l := len(c.sigFPState)
if l > 0 {
c.aarch64FPState = c.sigFPState[l-1]
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

Some files were not shown because too many files have changed in this diff Show More