Clean up context decoupling experiment.

This change removes code branches and variables only used in coupled-context
mode.

PiperOrigin-RevId: 529776383
This commit is contained in:
Konstantin Bogomolov
2023-05-05 11:55:50 -07:00
committed by gVisor bot
parent 05deaf0b0c
commit d7f590dd00
19 changed files with 112 additions and 558 deletions
-2
View File
@@ -44,8 +44,6 @@ go_template_instance(
go_library(
name = "systrap",
srcs = [
"context_decoupling_disable.go",
"context_decoupling_enable.go",
"context_list.go",
"context_queue.go",
"filters.go",
@@ -1,20 +0,0 @@
// Copyright 2023 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.
//go:build context_decoupling_disabled
// +build context_decoupling_disabled
package systrap
var contextDecouplingExp bool = false
@@ -1,27 +0,0 @@
// Copyright 2023 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.
//go:build !context_decoupling_disabled
// +build !context_decoupling_disabled
package systrap
// contextDecouplingExp is a global flag that enables thread decoupling mode.
// In this mode thread contexts are able to migrate between systrap user
// process threads. This also allows and enables the following experimental
// optimizations:
// - Ability to run M contexts using N threads, where M > N.
// - Reduce synchronization overhead between sentry threads and user
// threads when switching contexts in and out of the sentry.
var contextDecouplingExp bool = true
@@ -163,13 +163,6 @@ func (sc *sharedContext) threadID() uint32 {
return atomic.LoadUint32(&sc.shared.ThreadID)
}
func (sc *sharedContext) setThreadID(threadID uint32) {
if contextDecouplingExp {
panic("context decoupled systrap should never explicitly set ThreadID")
}
atomic.StoreUint32(&sc.shared.ThreadID, threadID)
}
// EnableSentryFastPath indicates that the polling mode is enabled for the
// Sentry. It has to be called before putting the context into the context queue.
// This function is used if contextDecouplingExp=true because the fastpath
@@ -287,6 +280,13 @@ func (q *fastPathDispatcher) deactivateSubprocess(s *subprocess) {
q.subprocessList.Remove(s)
}
// deep_sleep_timeout is the timeout after which we stops polling and fall asleep.
//
// The value is 40µs for 2GHz CPU. This timeout matches the sentry<->stub round
// trip in the pure deep sleep case.
const deepSleepTimeout = uint64(80000)
const handshakeTimeout = uint64(1000)
// loop is processing contexts in the queue. Only one instance of it can be
// running, because it has exclusive access to the list.
//
+9 -19
View File
@@ -141,14 +141,12 @@ func stubInit() {
mapLen, _ = hostarch.PageRoundUp(mapLen + sysmsg.PerThreadMemSize*(maxSystemThreads+1))
// Allocate context queue region
if contextDecouplingExp {
stubContextQueueRegion = mapLen
stubContextQueueRegionLen, _ = hostarch.PageRoundUp(unsafe.Sizeof(contextQueue{}))
mapLen += stubContextQueueRegionLen
stubContextQueueRegion = mapLen
stubContextQueueRegionLen, _ = hostarch.PageRoundUp(unsafe.Sizeof(contextQueue{}))
mapLen += stubContextQueueRegionLen
stubSpinningThreadQueueAddr = mapLen
mapLen += sysmsg.SpinningQueueMemSize
}
stubSpinningThreadQueueAddr = mapLen
mapLen += sysmsg.SpinningQueueMemSize
// Allocate thread context region
stubContextRegion = mapLen
@@ -220,23 +218,16 @@ func stubInit() {
// Initialize stub globals
p := (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_deep_sleep_timeout)))
*p = deepSleepTimeout
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_handshake_timeout)))
*p = handshakeTimeout
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_context_region)))
*p = uint64(stubContextRegion)
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_stub_start)))
*p = uint64(stubStart)
archState := (*sysmsg.ArchState)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_arch_state)))
archState.Init()
exp := (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_context_decoupling_exp)))
if contextDecouplingExp {
*exp = 1
contextQueue := (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_context_queue_addr)))
*contextQueue = uint64(stubContextQueueRegion)
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_spinning_queue_addr)))
*p = uint64(stubSpinningThreadQueueAddr)
}
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_context_queue_addr)))
*p = uint64(stubContextQueueRegion)
p = (*uint64)(unsafe.Pointer(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_spinning_queue_addr)))
*p = uint64(stubSpinningThreadQueueAddr)
prepareSeccompRules(stubSysmsgStart, stubSysmsgRules, stubSysmsgRulesLen)
@@ -253,5 +244,4 @@ func stubInit() {
stubEnd = stubStart + mapLen + uintptr(gap)
log.Debugf("stubStart %x stubSysmsgStart %x stubSysmsgStack %x, stubContextQueue %x, stubThreadContextRegion %x, mapLen %x", stubStart, stubSysmsgStart, stubSysmsgStack, stubContextQueueRegion, stubContextRegion, mapLen)
log.Debugf(archState.String())
log.Debugf("contextDecouplingExp=%t", contextDecouplingExp)
}
+55 -145
View File
@@ -315,14 +315,12 @@ func newSubprocess(create func() (*thread, error), memoryFile *pgalloc.MemoryFil
sp.mapPrivateRegions()
// Create the initial sysmsg thread.
if contextDecouplingExp {
atomic.AddUint32(&sp.contextQueue.numActiveThreads, 1)
if _, err := sp.createSysmsgThread(nil, nil, nil); err != nil {
atomic.AddUint32(&sp.contextQueue.numActiveThreads, ^uint32(0))
return nil, err
}
sp.numSysmsgThreads++
atomic.AddUint32(&sp.contextQueue.numActiveThreads, 1)
if err := sp.createSysmsgThread(); err != nil {
atomic.AddUint32(&sp.contextQueue.numActiveThreads, ^uint32(0))
return nil, err
}
sp.numSysmsgThreads++
return sp, nil
}
@@ -343,27 +341,25 @@ func (s *subprocess) mapSharedRegions() {
Dir: pgalloc.TopDown,
}
if contextDecouplingExp {
// Map shared regions into the sentry.
contextQueueFR, contextQueue := mmapContextQueueForSentry(s.memoryFile, opts)
contextQueue.init()
// Map shared regions into the sentry.
contextQueueFR, contextQueue := mmapContextQueueForSentry(s.memoryFile, opts)
contextQueue.init()
// Map thread context region into the syscall thread.
_, err := s.syscallThread.syscall(
unix.SYS_MMAP,
arch.SyscallArgument{Value: uintptr(stubContextQueueRegion)},
arch.SyscallArgument{Value: uintptr(contextQueueFR.Length())},
arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)},
arch.SyscallArgument{Value: uintptr(unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED)},
arch.SyscallArgument{Value: uintptr(s.memoryFile.FD())},
arch.SyscallArgument{Value: uintptr(contextQueueFR.Start)})
if err != nil {
panic(fmt.Sprintf("failed to mmap context queue region into syscall thread: %v", err))
}
s.contextQueue = contextQueue
// Map thread context region into the syscall thread.
_, err := s.syscallThread.syscall(
unix.SYS_MMAP,
arch.SyscallArgument{Value: uintptr(stubContextQueueRegion)},
arch.SyscallArgument{Value: uintptr(contextQueueFR.Length())},
arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)},
arch.SyscallArgument{Value: uintptr(unix.MAP_SHARED | unix.MAP_FILE | unix.MAP_FIXED)},
arch.SyscallArgument{Value: uintptr(s.memoryFile.FD())},
arch.SyscallArgument{Value: uintptr(contextQueueFR.Start)})
if err != nil {
panic(fmt.Sprintf("failed to mmap context queue region into syscall thread: %v", err))
}
s.contextQueue = contextQueue
// Map thread context region into the sentry.
threadContextFR, err := s.memoryFile.Allocate(uint64(stubContextRegionLen), opts)
if err != nil {
@@ -396,18 +392,16 @@ func (s *subprocess) mapSharedRegions() {
}
func (s *subprocess) mapPrivateRegions() {
if contextDecouplingExp {
_, err := s.syscallThread.syscall(
unix.SYS_MMAP,
arch.SyscallArgument{Value: uintptr(stubSpinningThreadQueueAddr)},
arch.SyscallArgument{Value: uintptr(sysmsg.SpinningQueueMemSize)},
arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)},
arch.SyscallArgument{Value: uintptr(unix.MAP_PRIVATE | unix.MAP_ANONYMOUS | unix.MAP_FIXED)},
arch.SyscallArgument{Value: 0},
arch.SyscallArgument{Value: 0})
if err != nil {
panic(fmt.Sprintf("failed to mmap spinning queue region into syscall thread: %v", err))
}
_, err := s.syscallThread.syscall(
unix.SYS_MMAP,
arch.SyscallArgument{Value: uintptr(stubSpinningThreadQueueAddr)},
arch.SyscallArgument{Value: uintptr(sysmsg.SpinningQueueMemSize)},
arch.SyscallArgument{Value: uintptr(unix.PROT_READ | unix.PROT_WRITE)},
arch.SyscallArgument{Value: uintptr(unix.MAP_PRIVATE | unix.MAP_ANONYMOUS | unix.MAP_FIXED)},
arch.SyscallArgument{Value: 0},
arch.SyscallArgument{Value: 0})
if err != nil {
panic(fmt.Sprintf("failed to mmap spinning queue region into syscall thread: %v", err))
}
}
@@ -724,14 +718,8 @@ func (s *subprocess) decAwakeContexts() {
// The second return value is true if a syscall instruction can be replaced on
// a function call.
func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool, shouldPatchSyscall bool, err error) {
// Get sysmsg thread bound to the context; no-op if contextDecoupling is on.
regs := &ac.StateData().Regs
sysThread, err := s.getSysmsgThread(regs, c, ac)
if err != nil {
return false, false, err
}
// Reset necessary registers.
regs := &ac.StateData().Regs
s.resetSysemuRegs(regs)
ctx := c.sharedContext
ctx.shared.Regs = regs.PtraceRegs
@@ -749,49 +737,26 @@ func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool
c.interrupt.Disable()
}()
if contextDecouplingExp {
restoreFPState(nil, ctx, 0, c, ac)
restoreFPState(ctx, c, ac)
// Place the context onto the context queue.
if ctx.sleeping {
ctx.sleeping = false
s.incAwakeContexts()
}
stubFastPathEnabled := dispatcher.stubFastPathEnabled()
ctx.setState(sysmsg.ContextStateNone)
s.contextQueue.add(ctx, stubFastPathEnabled)
s.waitOnState(ctx, stubFastPathEnabled)
// Place the context onto the context queue.
if ctx.sleeping {
ctx.sleeping = false
s.incAwakeContexts()
}
stubFastPathEnabled := dispatcher.stubFastPathEnabled()
ctx.setState(sysmsg.ContextStateNone)
s.contextQueue.add(ctx, stubFastPathEnabled)
s.waitOnState(ctx, stubFastPathEnabled)
// Check if there's been an error.
threadID := ctx.threadID()
if threadID != invalidThreadID {
if sysThread, ok := s.sysmsgThreads[threadID]; ok && sysThread.msg.Err != 0 {
msg := sysThread.msg
panic(fmt.Sprintf("stub thread %d failed: err 0x%x line %d: %s", sysThread.thread.tid, msg.Err, msg.Line, msg))
}
log.Warningf("systrap: found unexpected ThreadContext.ThreadID field, expected %d found %d", invalidThreadID, threadID)
}
} else {
msg := sysThread.msg
t := sysThread.thread
restoreFPState(msg, ctx, sysThread.fpuStateToMsgOffset, c, ac)
msg.EnableSentryFastPath()
sysThread.waitEvent(sysmsg.ThreadStateDone, ctx)
// Check if there's been an error.
if msg.Err != 0 {
panic(fmt.Sprintf("stub thread %d failed: err %d line %d: %s", t.tid, msg.Err, msg.Line, msg))
}
if ctx.state() != sysmsg.ContextStateSyscallTrap {
var err error
sysThread.fpuStateToMsgOffset, err = msg.FPUStateOffset()
if err != nil {
return false, false, err
}
// Check if there's been an error.
threadID := ctx.threadID()
if threadID != invalidThreadID {
if sysThread, ok := s.sysmsgThreads[threadID]; ok && sysThread.msg.Err != 0 {
msg := sysThread.msg
panic(fmt.Sprintf("stub thread %d failed: err 0x%x line %d: %s", sysThread.thread.tid, msg.Err, msg.Line, msg))
}
log.Warningf("systrap: found unexpected ThreadContext.ThreadID field, expected %d found %d", invalidThreadID, threadID)
}
// Copy register state locally.
@@ -908,7 +873,7 @@ func (s *subprocess) kickSysmsgThread() bool {
s.numSysmsgThreads++
s.sysmsgThreadsMu.Unlock()
atomic.AddUint32(&s.contextQueue.numActiveThreads, 1)
if _, err := s.createSysmsgThread(nil, nil, nil); err != nil {
if err := s.createSysmsgThread(); err != nil {
log.Warningf("Unable to create a new stub thread: %s", err)
atomic.AddUint32(&s.contextQueue.numActiveThreads, ^uint32(0))
s.sysmsgThreadsMu.Lock()
@@ -982,37 +947,10 @@ func (s *subprocess) PullFullState(c *context, ac *arch.Context64) error {
if !c.sharedContext.isActiveInSubprocess(s) {
panic("Attempted to PullFullState for context that is not used in subprocess")
}
if contextDecouplingExp {
saveFPState(nil, c.sharedContext, 0, c, ac)
} else {
sysThread, err := s.getSysmsgThread(&ac.StateData().Regs, c, ac)
if err != nil {
return err
}
saveFPState(sysThread.msg, c.sharedContext, sysThread.fpuStateToMsgOffset, c, ac)
}
saveFPState(c.sharedContext, ac)
return nil
}
// getSysmsgThread returns a sysmsg thread for the specified context.
// (Unused if contextDecouplingExp=true).
func (s *subprocess) getSysmsgThread(tregs *arch.Registers, c *context, ac *arch.Context64) (*sysmsgThread, error) {
if contextDecouplingExp {
return nil, nil
}
sysThread := c.sysmsgThread
if sysThread != nil && sysThread.subproc != s {
// This can happen if a new address space
// has been created (e.g. fork).
sysThread.destroy()
sysThread = nil
}
if sysThread != nil {
return sysThread, nil
}
return s.createSysmsgThread(tregs, c, ac)
}
var sysmsgThreadPriority int
func initSysmsgThreadPriority() {
@@ -1025,16 +963,8 @@ func initSysmsgThreadPriority() {
}
// createSysmsgThread creates a new sysmsg thread.
// If contextDecouplingExp=false, the thread starts working on the given context.
// Otherwise the given function parameters are not used, and the thread starts
// processing any available context in the context queue.
func (s *subprocess) createSysmsgThread(tregs *arch.Registers, c *context, ac *arch.Context64) (*sysmsgThread, error) {
if contextDecouplingExp {
// We will not bind any specific context to this thread. We will still use
// tregs to setup the thread though.
tregs = &arch.Registers{}
}
// The thread starts processing any available context in the context queue.
func (s *subprocess) createSysmsgThread() error {
// Create a new seccomp process.
var r requestThread
r.thread = make(chan *thread)
@@ -1115,12 +1045,6 @@ func (s *subprocess) createSysmsgThread(tregs *arch.Registers, c *context, ac *a
sysThread.setMsg(sysmsg.StackAddrToMsg(sentryStackAddr))
sysThread.msg.Init(threadID)
if contextDecouplingExp {
sysThread.msg.Context = 0
} else {
c.sharedContext.setThreadID(threadID)
sysThread.msg.Context = uint64(stubContextRegion + uintptr(c.sharedContext.contextID)*sysmsg.AllocatedSizeofThreadContextStruct)
}
sysThread.msg.Self = uint64(sysmsgStackAddr + sysmsg.MsgOffsetFromSharedStack)
sysThread.msg.SyshandlerStack = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr()))
sysThread.msg.Syshandler = uint64(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_syshandler))
@@ -1152,6 +1076,7 @@ func (s *subprocess) createSysmsgThread(tregs *arch.Registers, c *context, ac *a
}
// Prepare to start the BPF process.
tregs := &arch.Registers{}
s.resetSysemuRegs(tregs)
setArchSpecificRegs(sysThread, tregs)
if err := p.setRegs(tregs); err != nil {
@@ -1167,26 +1092,11 @@ func (s *subprocess) createSysmsgThread(tregs *arch.Registers, c *context, ac *a
panic(fmt.Sprintf("can't detach new clone: %v", errno))
}
if !contextDecouplingExp {
sysThread.waitEvent(sysmsg.ThreadStateNone, c.sharedContext)
if msg := sysThread.msg; msg.Err != 0 {
panic(fmt.Sprintf("stub thread failed: %v (line %v)", msg.Err, msg.Line))
}
sysThread.fpuStateToMsgOffset, err = sysThread.msg.FPUStateOffset()
if err != nil {
sysThread.destroy()
return nil, err
}
c.sysmsgThread = sysThread
}
s.sysmsgThreadsMu.Lock()
s.sysmsgThreads[threadID] = sysThread
s.sysmsgThreadsMu.Unlock()
return sysThread, nil
return nil
}
// PreFork implements platform.AddressSpace.PreFork.
@@ -214,11 +214,9 @@ func restoreArchSpecificState(ctx *sysmsg.ThreadContext, ac *arch.Context64) {
}
func setArchSpecificRegs(sysThread *sysmsgThread, regs *arch.Registers) {
if contextDecouplingExp {
// Set the start function and initial stack.
regs.PtraceRegs.Rip = uint64(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_start))
regs.PtraceRegs.Rsp = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr()))
}
// Set the start function and initial stack.
regs.PtraceRegs.Rip = uint64(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_start))
regs.PtraceRegs.Rsp = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr()))
// Set gs_base; this is the only time we set it and we don't expect it to ever
// change for any thread.
@@ -229,12 +227,4 @@ func retrieveArchSpecificState(ctx *sysmsg.ThreadContext, ac *arch.Context64) {
}
func archSpecificSysmsgThreadInit(sysThread *sysmsgThread) {
// Send a fake event to stop the BPF process so that it enters the sighandler.
// If there is no coupled context we don't want that to happen because the
// thread needs to find a context first.
if !contextDecouplingExp {
if _, _, e := unix.RawSyscall(unix.SYS_TGKILL, uintptr(sysThread.thread.tgid), uintptr(sysThread.thread.tid), uintptr(unix.SIGSEGV)); e != 0 {
panic(fmt.Sprintf("tkill failed: %v", e))
}
}
}
@@ -192,13 +192,6 @@ func restoreArchSpecificState(ctx *sysmsg.ThreadContext, ac *arch.Context64) {
}
func setArchSpecificRegs(sysThread *sysmsgThread, regs *arch.Registers) {
if contextDecouplingExp {
// Set the start function and initial stack. On ARM __export_start does not
// actually get used because we send a signal to the thread upon startup
// right away (see archSpecificSysmsgThreadInit below).
regs.PtraceRegs.Pc = uint64(stubSysmsgStart + uintptr(sysmsg.Sighandler_blob_offset____export_start))
regs.PtraceRegs.Sp = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr()))
}
}
func retrieveArchSpecificState(ctx *sysmsg.ThreadContext, ac *arch.Context64) {
@@ -41,6 +41,12 @@ func afterFork()
//go:linkname afterForkInChild syscall.runtime_AfterForkInChild
func afterForkInChild()
//go:linkname cputicks runtime.cputicks
func cputicks() int64
// spinloop is implemented in assembly.
func spinloop()
// getThreadContextFromID returns a ThreadContext struct that corresponds to the
// given ID.
//
@@ -70,29 +76,16 @@ func mmapContextQueueForSentry(memoryFile *pgalloc.MemoryFile, opts pgalloc.Allo
return fr, (*contextQueue)(unsafe.Pointer(addr))
}
//go:nosplit
func isFPStateInContextRegion(ctx *sharedContext) bool {
// If context decoupling experiment is ON then both the sighandler and
// syshandler save FPState to the context region since contexts will move
// threads. Otherwise only syshandler will save FPState to the region.
return contextDecouplingExp || ctx.state() == sysmsg.ContextStateSyscallTrap
}
func saveFPState(msg *sysmsg.Msg, ctx *sharedContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
func saveFPState(ctx *sharedContext, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), archState.FpLen())
var src []byte
if isFPStateInContextRegion(ctx) {
src = ctx.shared.FPState[:]
} else {
src = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), archState.FpLen())
}
src := ctx.shared.FPState[:]
copy(dst, src)
}
// restoreFPStateDecoupledContext writes FPState from c to the thread context
// shared memory region if there is any need to do so.
func restoreFPState(msg *sysmsg.Msg, ctx *sharedContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
func restoreFPState(ctx *sharedContext, c *context, ac *arch.Context64) {
if !c.needRestoreFPState {
return
}
@@ -101,11 +94,6 @@ func restoreFPState(msg *sysmsg.Msg, ctx *sharedContext, fpuToMsgOffset uint64,
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), archState.FpLen())
var dst []byte
if isFPStateInContextRegion(ctx) {
dst = ctx.shared.FPState[:]
} else {
dst = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), archState.FpLen())
}
dst := ctx.shared.FPState[:]
copy(dst, src)
}
-2
View File
@@ -127,9 +127,7 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//pkg/abi/linux",
"//pkg/abi/linux/errno",
"//pkg/cpuid",
"//pkg/errors",
"//pkg/hostarch",
"//pkg/log",
"//pkg/sentry/platform/interrupt",
@@ -34,7 +34,6 @@
// TODO(b/271631387): These globals are shared between AMD64 and ARM64; move to
// sysmsg_lib.c.
struct arch_state __export_arch_state;
uint64_t __export_context_decoupling_exp;
uint64_t __export_stub_start;
long __syscall(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
@@ -160,18 +159,11 @@ static void set_fsbase(uint64_t fsbase) {
// specific to amd64.
struct thread_context *switch_context_amd64(
struct sysmsg *sysmsg, struct thread_context *ctx,
enum thread_state new_thread_state, enum context_state new_context_state) {
enum context_state new_context_state) {
struct thread_context *old_ctx = sysmsg->context;
for (;;) {
// TODO(b/271631387): Once stub code globals can be used between objects
// move this check into sysmsg_lib:switch_context().
if (__export_context_decoupling_exp) {
ctx = switch_context(sysmsg, ctx, new_context_state);
} else {
ctx->state = new_context_state;
wait_state(sysmsg, new_thread_state);
}
ctx = switch_context(sysmsg, ctx, new_context_state);
// After setting THREAD_STATE_NONE, syshandled can be interrupted by
// SIGCHLD. In this case, we consider that the current context contains
@@ -207,8 +199,7 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
if (sysmsg != sysmsg->self) panic(0xdeaddead);
int32_t thread_state = __atomic_load_n(&sysmsg->state, __ATOMIC_ACQUIRE);
if (__export_context_decoupling_exp &&
thread_state == THREAD_STATE_INITIALIZING) {
if (thread_state == THREAD_STATE_INITIALIZING) {
// This thread was interrupted before it even had a context.
return;
}
@@ -239,13 +230,9 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
ucontext->uc_mcontext.gregs[REG_RIP] < __export_stub_start) {
ctx->ptregs.fs_base = fs_base;
gregs_to_ptregs(ucontext, &ctx->ptregs);
if (__export_context_decoupling_exp) {
memcpy(ctx->fpstate, (uint8_t *)ucontext->uc_mcontext.fpregs,
__export_arch_state.fp_len);
} else {
sysmsg->fpstate =
(unsigned long)ucontext->uc_mcontext.fpregs - (unsigned long)sysmsg;
}
memcpy(ctx->fpstate, (uint8_t *)ucontext->uc_mcontext.fpregs,
__export_arch_state.fp_len);
__atomic_store_n(&ctx->fpstate_changed, 0, __ATOMIC_RELEASE);
}
@@ -332,13 +319,12 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
return;
}
ctx = switch_context_amd64(sysmsg, ctx, THREAD_STATE_EVENT, ctx_state);
ctx = switch_context_amd64(sysmsg, ctx, ctx_state);
if (fs_base != ctx->ptregs.fs_base) {
set_fsbase(ctx->ptregs.fs_base);
}
if (__export_context_decoupling_exp &&
__atomic_load_n(&ctx->fpstate_changed, __ATOMIC_ACQUIRE)) {
if (__atomic_load_n(&ctx->fpstate_changed, __ATOMIC_ACQUIRE)) {
prep_fpstate_for_sigframe(
ctx->fpstate, __export_arch_state.fp_len,
__export_arch_state.xsave_mode != XSAVE_MODE_FXSAVE);
@@ -366,7 +352,7 @@ void __syshandler() {
long fs_base = get_fsbase();
ctx->ptregs.fs_base = fs_base;
ctx = switch_context_amd64(sysmsg, ctx, THREAD_STATE_EVENT, ctx_state);
ctx = switch_context_amd64(sysmsg, ctx, ctx_state);
// switch_context_amd64 changed sysmsg->state to THREAD_STATE_NONE, so we can
// only resume the current process, all other actions are
// prohibited after this point.
@@ -384,8 +370,8 @@ void __export_start(struct sysmsg *sysmsg, void *_ucontext) {
}
#endif
struct thread_context *ctx = switch_context_amd64(
sysmsg, NULL, THREAD_STATE_EVENT, CONTEXT_STATE_INVALID);
struct thread_context *ctx =
switch_context_amd64(sysmsg, NULL, CONTEXT_STATE_INVALID);
restore_state(sysmsg, ctx, _ucontext);
}
@@ -32,7 +32,6 @@
// TODO(b/271631387): These globals are shared between AMD64 and ARM64; move to
// sysmsg_lib.c.
struct arch_state __export_arch_state;
uint64_t __export_context_decoupling_exp;
uint64_t __export_stub_start;
long __syscall(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
@@ -105,8 +104,7 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
uint32_t ctx_state = CONTEXT_STATE_INVALID;
struct thread_context *ctx = NULL, *old_ctx = NULL;
if (__export_context_decoupling_exp &&
thread_state == THREAD_STATE_INITIALIZING) {
if (thread_state == THREAD_STATE_INITIALIZING) {
// Find a new context and exit to restore it.
goto init;
}
@@ -129,11 +127,8 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
}
uint8_t *fpStatePointer =
(uint8_t *)&ucontext->uc_mcontext.__reserved + kSigframeMagicHeaderLen;
if (__export_context_decoupling_exp) {
memcpy(ctx->fpstate, fpStatePointer, __export_arch_state.fp_len);
} else {
sysmsg->fpstate = (uint64_t)(fpStatePointer) - (uint64_t)sysmsg;
}
memcpy(ctx->fpstate, fpStatePointer, __export_arch_state.fp_len);
ctx->tls = get_tls();
ctx->siginfo = *siginfo;
switch (signo) {
@@ -162,12 +157,7 @@ void __export_sighandler(int signo, siginfo_t *siginfo, void *_ucontext) {
init:
for (;;) {
if (__export_context_decoupling_exp) {
ctx = switch_context(sysmsg, ctx, ctx_state);
} else {
ctx->state = ctx_state;
wait_state(sysmsg, THREAD_STATE_EVENT);
}
ctx = switch_context(sysmsg, ctx, ctx_state);
if (__atomic_load_n(&ctx->interrupt, __ATOMIC_ACQUIRE) != 0) {
// This context got interrupted while it was waiting in the queue.
@@ -197,8 +187,7 @@ void restore_state(struct sysmsg *sysmsg, struct thread_context *ctx,
(struct fpsimd_context *)&ucontext->uc_mcontext.__reserved;
uint8_t *fpStatePointer = (uint8_t *)&fpctx->fpsr;
if (__export_context_decoupling_exp &&
__atomic_load_n(&ctx->fpstate_changed, __ATOMIC_ACQUIRE)) {
if (__atomic_load_n(&ctx->fpstate_changed, __ATOMIC_ACQUIRE)) {
memcpy(fpStatePointer, ctx->fpstate, __export_arch_state.fp_len);
}
ptregs_to_gregs(ucontext, &ctx->ptregs);
+1 -62
View File
@@ -26,8 +26,6 @@ import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/abi/linux/errno"
"gvisor.dev/gvisor/pkg/errors"
"gvisor.dev/gvisor/pkg/hostarch"
)
@@ -111,9 +109,6 @@ const (
// ThreadStateDone means that last event has been handled and the stub thread
// can be resumed.
ThreadStateDone
// ThreadStateEvent means that there is a new event which has to be handled by
// Sentry. (Used only if !contextDecoupledExp)
ThreadStateEvent
// ThreadStatePrep means that syshandler started filling the sysmsg struct.
ThreadStatePrep
// ThreadStateAsleep means that this thread fell asleep because there was not
@@ -164,27 +159,6 @@ type Msg struct {
Line int32
// Debug is a variable to use to get visibility into the stub from the sentry.
Debug uint64
// fpState is an offset relative to the sighandler stack to the fpState, stored
// by the sighandler.
fpState uint64
// The fast path is the mode when a thread is polling msg->state to
// wait for a required state instead of calling FUTEX_WAIT.
//
// If core tagging is supported by the kernel, the Sentry thread, and a
// stub thread share the same cookie and run on two associated
// hyper-threads. The thread which is polling msg->state calls the
// pause instruction, so the second thread gets almost the entire core
// to run its workload.
//
// If core tagging isn't supported, a polling thread calls sched_yield
// to let other processes to run.
// StubFastPath is set if a stub process uses the fast path to wait for
// events. Only the stub thread can set it before switching to the
// sentry, but the Sentry can clear it.
stubFastPath uint32
sentryFastPath uint32
AckedEvents uint32
// ThreadID is the ID of the sysmsg thread.
ThreadID uint32
}
@@ -292,9 +266,8 @@ type ThreadContext struct {
func (m *Msg) Init(threadID uint32) {
m.Err = 0
m.Line = -1
m.stubFastPath = 0
m.sentryFastPath = 1
m.ThreadID = threadID
m.Context = 0
}
// Init initializes the ThreadContext instance.
@@ -307,40 +280,6 @@ func (c *ThreadContext) Init(initialThreadID uint32) {
c.ThreadID = initialThreadID
}
// StubFastPath returns true if the stub thread in the polling mode.
func (m *Msg) StubFastPath() bool {
return atomic.LoadUint32(&m.stubFastPath) != 0
}
// DisableStubFastPath disables the polling mode for the stub thread.
func (m *Msg) DisableStubFastPath() {
atomic.StoreUint32(&m.stubFastPath, 0)
}
// EnableSentryFastPath enables the polling mode for the Sentry. It has to be
// called before switching controls to the stub process.
// This function is used if contextDecouplingExp=false because the fastpath
// is negotiated in Sysmsg.
func (m *Msg) EnableSentryFastPath() {
m.sentryFastPath = 1
}
// DisableSentryFastPath disables the polling mode for the Sentry.
// This function is used if contextDecouplingExp=false because the fastpath
// is negotiated in Sysmsg.
func (m *Msg) DisableSentryFastPath() {
atomic.StoreUint32(&m.sentryFastPath, 0)
}
// FPUStateOffset returns the offset of a saved FPU state to the msg.
func (m *Msg) FPUStateOffset() (uint64, error) {
offset := m.fpState
if int64(offset) > -MsgOffsetFromSharedStack && int64(offset) < 0 {
return offset, nil
}
return 0, errors.New(errno.EFAULT, fmt.Sprintf("FPU offset has been corrupted: %x", offset))
}
func (m *Msg) String() string {
var b strings.Builder
fmt.Fprintf(&b, "sysmsg.Msg{msg: %x state %d", m.Self, m.State)
+1 -10
View File
@@ -41,7 +41,6 @@ struct arch_state {
enum thread_state {
THREAD_STATE_NONE,
THREAD_STATE_DONE,
THREAD_STATE_EVENT,
THREAD_STATE_PREP,
THREAD_STATE_ASLEEP,
THREAD_STATE_INITIALIZING,
@@ -66,10 +65,6 @@ struct sysmsg {
int32_t err;
int32_t err_line;
uint64_t debug;
uint64_t fpstate;
uint32_t stub_fast_path;
uint32_t sentry_fast_path;
uint32_t acked_events;
uint32_t thread_id;
};
@@ -122,7 +117,6 @@ struct thread_context {
extern uint64_t __export_pr_sched_core;
extern uint64_t __export_deep_sleep_timeout;
extern struct arch_state __export_arch_state;
extern uint64_t __export_context_decoupling_exp;
struct context_queue;
extern struct context_queue *__export_context_queue_addr;
@@ -154,10 +148,7 @@ static void __panic(int err, long line) {
struct thread_context *ctx = sysmsg->context;
sysmsg->err = err;
sysmsg->err_line = line;
// Normally sentry waits on sysmsg->state.
__atomic_store_n(&sysmsg->state, THREAD_STATE_EVENT, __ATOMIC_RELEASE);
sys_futex(&sysmsg->state, FUTEX_WAKE, 1, NULL, NULL, 666);
// Under context-decoupling the sentry waits on ctx->state.
// Wake up the goroutine waiting on the current context.
__atomic_store_n(&ctx->state, CONTEXT_STATE_FAULT, __ATOMIC_RELEASE);
sys_futex(&ctx->state, FUTEX_WAKE, 1, NULL, NULL, 666);
// crash the stub process.
@@ -27,7 +27,6 @@
// __export_deep_sleep_timeout is the timeout after which the stub thread stops
// polling and fall asleep.
uint64_t __export_deep_sleep_timeout;
uint64_t __export_handshake_timeout;
// LINT.IfChange
#define MAX_GUEST_CONTEXTS (4095)
@@ -372,66 +371,6 @@ struct thread_context *switch_context(struct sysmsg *sysmsg,
return get_context(sysmsg);
}
int wait_state(struct sysmsg *sysmsg, enum thread_state new_thread_state) {
unsigned long handshake_timeout;
uint64_t acked_events_prev;
unsigned long start;
int ret, v, fast_path;
acked_events_prev = __atomic_load_n(&sysmsg->acked_events, __ATOMIC_SEQ_CST);
// stub_fast_path can be changed non-atomically before we change the state and
// wake up the Sentry.
sysmsg->stub_fast_path = 1;
__atomic_store_n(&sysmsg->state, new_thread_state, __ATOMIC_SEQ_CST);
fast_path = __atomic_load_n(&sysmsg->sentry_fast_path, __ATOMIC_SEQ_CST);
if (!fast_path) {
ret = sys_futex(&sysmsg->state, FUTEX_WAKE, 1, NULL, NULL, 0);
if (ret < 0) panic(ret);
}
v = __atomic_load_n(&sysmsg->state, __ATOMIC_ACQUIRE);
if (v == THREAD_STATE_DONE) goto out;
handshake_timeout = __export_handshake_timeout;
start = rdtsc();
while (1) {
v = __atomic_load_n(&sysmsg->state, __ATOMIC_ACQUIRE);
if (v == THREAD_STATE_DONE) goto out;
// The Sentry can change stub_fast_path to zero if it finds out that the
// user task has to sleep.
fast_path = __atomic_load_n(&sysmsg->stub_fast_path, __ATOMIC_ACQUIRE);
if (fast_path) {
unsigned long delta = rdtsc() - start;
if (delta > __export_deep_sleep_timeout) {
fast_path = 0;
__atomic_store_n(&sysmsg->stub_fast_path, 0, __ATOMIC_SEQ_CST);
}
if (handshake_timeout != 0) {
if (__atomic_load_n(&sysmsg->acked_events, __ATOMIC_SEQ_CST) !=
acked_events_prev) {
handshake_timeout = 0;
} else if (delta > handshake_timeout) {
__syscall(__NR_sched_yield, 0, 0, 0, 0, 0, 0);
handshake_timeout += __export_handshake_timeout;
continue;
}
}
}
if (fast_path) {
spinloop();
} else {
sys_futex(&sysmsg->state, FUTEX_WAIT, v, NULL, NULL, 0);
}
}
out:
__atomic_fetch_add(&sysmsg->acked_events, 1, __ATOMIC_SEQ_CST);
return v;
}
void verify_offsets() {
BUILD_BUG_ON(offsetof_sysmsg_self != offsetof(struct sysmsg, self));
BUILD_BUG_ON(offsetof_sysmsg_ret_addr != offsetof(struct sysmsg, ret_addr));
@@ -46,7 +46,7 @@
#define offsetof_thread_context_ptregs 0x8 + MAX_FPSTATE_LEN
#define kTHREAD_STATE_NONE 0
#define kTHREAD_STATE_INTERRUPT 4
#define kTHREAD_STATE_INTERRUPT 3
// LINT.ThenChange(sysmsg.h, sysmsg_lib.c)
+3 -21
View File
@@ -16,7 +16,6 @@ package systrap
import (
"fmt"
"sync/atomic"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -24,7 +23,6 @@ import (
"gvisor.dev/gvisor/pkg/seccomp"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/platform/interrupt"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
@@ -34,8 +32,7 @@ import (
// This type of thread is used to execute user processes.
type sysmsgThread struct {
// subproc is a link to the subprocess which is used to call native
// system calls and track when a sysmsg thread has to be recreated.
// Look at getSysmsgThread() for more details.
// system calls.
subproc *subprocess
// thread is a thread identifier.
@@ -55,7 +52,8 @@ type sysmsgThread struct {
fpuStateToMsgOffset uint64
}
// sysmsgStackAddr returns a sysmsg stack address in the thread address space.
// sysmsgPerThreadMemAddr returns a sysmsg stack address in the thread address
// space.
func (p *sysmsgThread) sysmsgPerThreadMemAddr() uintptr {
return stubSysmsgStack + sysmsg.PerThreadMemSize*uintptr(p.thread.sysmsgStackID)
}
@@ -117,22 +115,6 @@ func (p *sysmsgThread) mapPrivateStack(addr uintptr, size uintptr) error {
return err
}
func (p *sysmsgThread) waitEvent(switchToState sysmsg.ThreadState, interruptor interrupt.Receiver) {
msg := p.msg
wakeup := false
acked := atomic.LoadUint32(&msg.AckedEvents)
if switchToState != sysmsg.ThreadStateNone {
msg.State.Set(switchToState)
wakeup = msg.StubFastPath() == false
} else {
acked--
}
if errno := futexWaitForState(msg, sysmsg.ThreadStateEvent, wakeup, acked, interruptor); errno != 0 {
panic(fmt.Sprintf("error waiting for state: %v", errno))
}
}
func (p *sysmsgThread) Debugf(format string, v ...any) {
if !log.IsLogging(log.Debug) {
return
@@ -16,15 +16,11 @@ package systrap
import (
"fmt"
"runtime"
"sync/atomic"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/interrupt"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
@@ -78,82 +74,6 @@ func (p *sysmsgThread) init(sentryAddr, guestAddr uintptr) {
}
}
func futexWake(msg *sysmsg.Msg) syscall.Errno {
_, _, e := unix.RawSyscall6(unix.SYS_FUTEX, uintptr(unsafe.Pointer(&msg.State)), linux.FUTEX_WAKE, 1, 0, 0, 0)
return e
}
//go:linkname cputicks runtime.cputicks
func cputicks() int64
// spinloop is implemented in assembly.
func spinloop()
//go:linkname entersyscall runtime.entersyscall
func entersyscall()
//go:linkname exitsyscall runtime.exitsyscall
func exitsyscall()
// deep_sleep_timeout is the timeout after which we stops polling and fall asleep.
//
// The value is 40µs for 2GHz CPU. This timeout matches the sentry<->stub round
// trip in the pure deep sleep case.
const deepSleepTimeout = uint64(80000)
const handshakeTimeout = uint64(1000)
func futexWaitForState(msg *sysmsg.Msg, state sysmsg.ThreadState, wakeup bool, acked uint32, interruptor interrupt.Receiver) syscall.Errno {
slowPath := false
errno := syscall.Errno(0)
start := cputicks()
htimeout := handshakeTimeout
handshake := false
for {
curState := msg.State.Get()
if curState == state {
break
}
if wakeup {
if errno = futexWake(msg); errno != 0 {
break
}
wakeup = false
continue
}
if !slowPath {
delta := uint64(cputicks() - start)
if delta > deepSleepTimeout {
msg.DisableSentryFastPath()
slowPath = true
continue
}
if !handshake {
if acked != atomic.LoadUint32(&msg.AckedEvents) {
handshake = true
continue
}
if delta > htimeout {
htimeout += handshakeTimeout
runtime.Gosched()
}
}
}
if slowPath {
errno = msg.SleepOnState(curState, interruptor)
if errno != 0 {
break
}
} else {
spinloop()
}
}
atomic.AddUint32(&msg.AckedEvents, 1)
return errno
}
// sysmsgSigactions installs signal handles for signals which can be triggered
// by stubProcess and have to be handled by Sentry.
//
+6 -18
View File
@@ -136,10 +136,6 @@ type context struct {
// this is also only meaningful if lastFaultSP is non-nil.
lastFaultIP hostarch.Addr
// sysmsgThread is a sysmsg thread descriptor which is used to execute
// application code. (Note: Unused if contextDecouplingExp=true).
sysmsgThread *sysmsgThread
// needRestoreFPState indicates that the FPU state has been changed by
// the Sentry and has to be updated on the stub thread.
needRestoreFPState bool
@@ -278,9 +274,6 @@ func (c *context) Interrupt() {
// Release releases all platform resources used by the context.
func (c *context) Release() {
if c.sysmsgThread != nil {
c.sysmsgThread.destroy()
}
if c.sharedContext != nil {
c.sharedContext.release()
c.sharedContext = nil
@@ -289,18 +282,13 @@ func (c *context) Release() {
// PrepareSleep implements platform.Context.platform.PrepareSleep.
func (c *context) PrepareSleep() {
if contextDecouplingExp {
ctx := c.sharedContext
if ctx == nil {
return
}
if !ctx.sleeping {
ctx.sleeping = true
ctx.subprocess.decAwakeContexts()
}
ctx := c.sharedContext
if ctx == nil {
return
} else if c.sysmsgThread != nil {
c.sysmsgThread.msg.DisableStubFastPath()
}
if !ctx.sleeping {
ctx.sleeping = true
ctx.subprocess.decAwakeContexts()
}
}