mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
platform/kvm: rewriting bluepill() without signal handler trampolining
Implement bluepill() without relying on a signal handler trampoline by directly switching stacks and execution contexts. Here, it is implemented just for x86_64. The arm64 part will be implemented separately. Here are benchmark results before and after this change: Before: BenchmarkWorldSwitchToUserRoundtrip-8 255940 4407 ns/op After: BenchmarkWorldSwitchToUserRoundtrip-8 307773 3765 ns/op Suggested-by: Jamie Liu <jamieliu@google.com> PiperOrigin-RevId: 664989586
This commit is contained in:
@@ -106,6 +106,7 @@ analyzers:
|
|||||||
- "\\[amd64\\].*sysret: RET without writing to 8-byte"
|
- "\\[amd64\\].*sysret: RET without writing to 8-byte"
|
||||||
- "\\[amd64\\].*iret: RET without writing to 8-byte"
|
- "\\[amd64\\].*iret: RET without writing to 8-byte"
|
||||||
- "\\[amd64\\].*exception: use of .* points beyond argument frame"
|
- "\\[amd64\\].*exception: use of .* points beyond argument frame"
|
||||||
|
- "\\[amd64\\].*retjmp: use of .* points beyond argument frame"
|
||||||
- "\\[arm64\\].*HaltEl1ExceptionAndResume: unknown variable"
|
- "\\[arm64\\].*HaltEl1ExceptionAndResume: unknown variable"
|
||||||
- "\\[arm64\\].*HaltEl1ExceptionAndResume: use of .* points beyond argument frame"
|
- "\\[arm64\\].*HaltEl1ExceptionAndResume: use of .* points beyond argument frame"
|
||||||
assign:
|
assign:
|
||||||
@@ -193,6 +194,7 @@ analyzers:
|
|||||||
- pkg/sentry/pgalloc/pgalloc_unsafe.go # Special case.
|
- pkg/sentry/pgalloc/pgalloc_unsafe.go # Special case.
|
||||||
- pkg/sentry/platform/kvm/bluepill_unsafe.go # Special case.
|
- pkg/sentry/platform/kvm/bluepill_unsafe.go # Special case.
|
||||||
- pkg/sentry/platform/kvm/machine_unsafe.go # Special case.
|
- pkg/sentry/platform/kvm/machine_unsafe.go # Special case.
|
||||||
|
- pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go # Special case.
|
||||||
- pkg/sentry/platform/pgalloc/pgalloc_unsafe.go # Special case.
|
- pkg/sentry/platform/pgalloc/pgalloc_unsafe.go # Special case.
|
||||||
- pkg/sentry/platform/systrap/stub_unsafe.go # Special case.
|
- pkg/sentry/platform/systrap/stub_unsafe.go # Special case.
|
||||||
- pkg/sentry/platform/systrap/syscall_thread_unsafe.go # Special case.
|
- pkg/sentry/platform/systrap/syscall_thread_unsafe.go # Special case.
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ go_library(
|
|||||||
"//pkg/sentry/platform",
|
"//pkg/sentry/platform",
|
||||||
"//pkg/sentry/platform/interrupt",
|
"//pkg/sentry/platform/interrupt",
|
||||||
"//pkg/sentry/time",
|
"//pkg/sentry/time",
|
||||||
|
"//pkg/sigframe",
|
||||||
"//pkg/sighandling",
|
"//pkg/sighandling",
|
||||||
"//pkg/sync",
|
"//pkg/sync",
|
||||||
"@org_golang_x_sys//unix:go_default_library",
|
"@org_golang_x_sys//unix:go_default_library",
|
||||||
|
|||||||
@@ -15,17 +15,11 @@
|
|||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/ring0"
|
"gvisor.dev/gvisor/pkg/ring0"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
"gvisor.dev/gvisor/pkg/sighandling"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// bluepill enters guest mode.
|
|
||||||
func bluepill(*vCPU)
|
|
||||||
|
|
||||||
// sighandler is the signal entry point.
|
// sighandler is the signal entry point.
|
||||||
func sighandler()
|
func sighandler()
|
||||||
|
|
||||||
@@ -100,11 +94,6 @@ func (c *vCPU) die(context *arch.SignalContext64, msg string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Install the handler.
|
|
||||||
if err := sighandling.ReplaceSignalHandler(bluepillSignal, addrOfSighandler(), &savedHandler); err != nil {
|
|
||||||
panic(fmt.Sprintf("Unable to set handler for signal %d: %v", bluepillSignal, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the address for the trampoline.
|
// Extract the address for the trampoline.
|
||||||
dieTrampolineAddr = addrOfDieTrampoline()
|
dieTrampolineAddr = addrOfDieTrampoline()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ var (
|
|||||||
bluepillSignal = unix.SIGSEGV
|
bluepillSignal = unix.SIGSEGV
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func bluepillArchVCPU(context *arch.SignalContext64) *vCPU {
|
||||||
|
return vCPUPtr(uintptr(context.Rax))
|
||||||
|
}
|
||||||
|
|
||||||
// bluepillArchEnter is called during bluepillEnter.
|
// bluepillArchEnter is called during bluepillEnter.
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
|
|||||||
@@ -40,69 +40,6 @@
|
|||||||
// System call definitions.
|
// System call definitions.
|
||||||
#define SYS_MMAP 9
|
#define SYS_MMAP 9
|
||||||
|
|
||||||
// See bluepill.go.
|
|
||||||
TEXT ·bluepill(SB),NOSPLIT|NOFRAME,$0
|
|
||||||
begin:
|
|
||||||
MOVQ arg+0(FP), AX
|
|
||||||
LEAQ VCPU_CPU(AX), BX
|
|
||||||
|
|
||||||
// The gorountine stack will be changed in guest which renders
|
|
||||||
// the frame pointer outdated and misleads perf tools.
|
|
||||||
// Disconnect the frame-chain with the zeroed frame pointer
|
|
||||||
// when it is saved in the frame in bluepillHandler().
|
|
||||||
MOVQ BP, CX
|
|
||||||
MOVQ $0, BP
|
|
||||||
BYTE CLI;
|
|
||||||
MOVQ CX, BP
|
|
||||||
check_vcpu:
|
|
||||||
MOVQ ENTRY_CPU_SELF(GS), CX
|
|
||||||
CMPQ BX, CX
|
|
||||||
JE right_vCPU
|
|
||||||
wrong_vcpu:
|
|
||||||
CALL ·redpill(SB)
|
|
||||||
JMP begin
|
|
||||||
right_vCPU:
|
|
||||||
RET
|
|
||||||
|
|
||||||
// sighandler: see bluepill.go for documentation.
|
|
||||||
//
|
|
||||||
// The arguments are the following:
|
|
||||||
//
|
|
||||||
// DI - The signal number.
|
|
||||||
// SI - Pointer to siginfo_t structure.
|
|
||||||
// DX - Pointer to ucontext structure.
|
|
||||||
//
|
|
||||||
TEXT ·sighandler(SB),NOSPLIT|NOFRAME,$0
|
|
||||||
// Check if the signal is from the kernel.
|
|
||||||
MOVQ $0x80, CX
|
|
||||||
CMPL CX, 0x8(SI)
|
|
||||||
JNE fallback
|
|
||||||
|
|
||||||
// Check if RIP is disable interrupts.
|
|
||||||
MOVQ CONTEXT_RIP(DX), CX
|
|
||||||
CMPQ CX, $0x0
|
|
||||||
JE fallback
|
|
||||||
CMPB 0(CX), CLI
|
|
||||||
JNE fallback
|
|
||||||
|
|
||||||
// Call the bluepillHandler.
|
|
||||||
PUSHQ DX // First argument (context).
|
|
||||||
CALL ·bluepillHandler(SB) // Call the handler.
|
|
||||||
POPQ DX // Discard the argument.
|
|
||||||
RET
|
|
||||||
|
|
||||||
fallback:
|
|
||||||
// Jump to the previous signal handler.
|
|
||||||
XORQ CX, CX
|
|
||||||
MOVQ ·savedHandler(SB), AX
|
|
||||||
JMP AX
|
|
||||||
|
|
||||||
// func addrOfSighandler() uintptr
|
|
||||||
TEXT ·addrOfSighandler(SB), $0-8
|
|
||||||
MOVQ $·sighandler(SB), AX
|
|
||||||
MOVQ AX, ret+0(FP)
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·sigsysHandler(SB),NOSPLIT|NOFRAME,$0
|
TEXT ·sigsysHandler(SB),NOSPLIT|NOFRAME,$0
|
||||||
// Check if the signal is from the kernel.
|
// Check if the signal is from the kernel.
|
||||||
MOVQ $1, CX
|
MOVQ $1, CX
|
||||||
@@ -122,6 +59,17 @@ fallback:
|
|||||||
MOVQ ·savedSigsysHandler(SB), AX
|
MOVQ ·savedSigsysHandler(SB), AX
|
||||||
JMP AX
|
JMP AX
|
||||||
|
|
||||||
|
TEXT ·rflags(SB), $8-8
|
||||||
|
PUSHFQ
|
||||||
|
POPQ AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·addrOfBluepillUserHandler(SB), $0-8
|
||||||
|
MOVQ $·bluepillUserHandler(SB), AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
// func addrOfSighandler() uintptr
|
// func addrOfSighandler() uintptr
|
||||||
TEXT ·addrOfSigsysHandler(SB), $0-8
|
TEXT ·addrOfSigsysHandler(SB), $0-8
|
||||||
MOVQ $·sigsysHandler(SB), AX
|
MOVQ $·sigsysHandler(SB), AX
|
||||||
@@ -139,3 +87,8 @@ TEXT ·addrOfDieTrampoline(SB), $0-8
|
|||||||
MOVQ $·dieTrampoline(SB), AX
|
MOVQ $·dieTrampoline(SB), AX
|
||||||
MOVQ AX, ret+0(FP)
|
MOVQ AX, ret+0(FP)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
TEXT ·currentCPU(SB), $0-8
|
||||||
|
MOVQ ENTRY_CPU_SELF(GS), AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|||||||
@@ -18,11 +18,16 @@
|
|||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"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/ring0"
|
"gvisor.dev/gvisor/pkg/ring0"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
|
"gvisor.dev/gvisor/pkg/sigframe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dieArchSetup initializes the state for dieTrampoline.
|
// dieArchSetup initializes the state for dieTrampoline.
|
||||||
@@ -141,3 +146,70 @@ func bluepillReadyStopGuest(c *vCPU) bool {
|
|||||||
func bluepillArchHandleExit(c *vCPU, context unsafe.Pointer) {
|
func bluepillArchHandleExit(c *vCPU, context unsafe.Pointer) {
|
||||||
c.die(bluepillArchContext(context), "unknown")
|
c.die(bluepillArchContext(context), "unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addrOfBluepillUserHandler() uintptr
|
||||||
|
func rflags() uint64
|
||||||
|
|
||||||
|
const _RFLAGS_IF = 1 << 9
|
||||||
|
|
||||||
|
func currentCPU() *vCPU
|
||||||
|
|
||||||
|
// bluepill enters guest mode.
|
||||||
|
//
|
||||||
|
//go:nosplit
|
||||||
|
func bluepill(c *vCPU) {
|
||||||
|
// Interrupts are always disabled in the VM.
|
||||||
|
if rflags()&_RFLAGS_IF == 0 {
|
||||||
|
if currentCPU() == c {
|
||||||
|
// Already in the vm.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Wrong vCPU, switch to the right one.
|
||||||
|
redpill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block all signals.
|
||||||
|
sigmask := linux.SignalSet(^uint64(0))
|
||||||
|
c.bluepillSigframe.MContext.Rax = uint64(uintptr(unsafe.Pointer(c)))
|
||||||
|
if err := sigframe.CallWithSignalFrame(
|
||||||
|
c.bluepillStack, addrOfBluepillUserHandler(),
|
||||||
|
c.bluepillSigframe, c.bluepillSigframeFPState, &sigmask); err != nil {
|
||||||
|
throw("failed to swallow the bluepill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +checkescape:all
|
||||||
|
//
|
||||||
|
//go:nosplit
|
||||||
|
func bluepillUserHandler(frame uintptr) {
|
||||||
|
// Sanitize the registers; interrupts must always be disabled.
|
||||||
|
context := bluepillArchContext(unsafe.Pointer(frame))
|
||||||
|
c := vCPUPtr(uintptr(context.Rax))
|
||||||
|
bluepillHandler(unsafe.Pointer(frame))
|
||||||
|
sigframe.Sigreturn(c.bluepillSigframe)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:nosplit
|
||||||
|
func (c *vCPU) initBluepillHandler() error {
|
||||||
|
stackSize := uintptr(hostarch.PageSize)
|
||||||
|
|
||||||
|
maxFPUSizeUint, fpuAlignmentUint := cpuid.HostFeatureSet().ExtendedStateSize()
|
||||||
|
fpuAlignment := uintptr(fpuAlignmentUint)
|
||||||
|
maxFPUSize := uintptr(maxFPUSizeUint)
|
||||||
|
|
||||||
|
fpuOffset := (unsafe.Sizeof(arch.UContext64{}) + fpuAlignment - 1) / fpuAlignment * fpuAlignment
|
||||||
|
mappingSize, _ := hostarch.PageRoundUp(stackSize + maxFPUSize + fpuOffset)
|
||||||
|
|
||||||
|
addr, _, errno := unix.Syscall6(unix.SYS_MMAP, 0, mappingSize,
|
||||||
|
uintptr(unix.PROT_READ|unix.PROT_WRITE),
|
||||||
|
uintptr(unix.MAP_PRIVATE|unix.MAP_ANONYMOUS),
|
||||||
|
0, 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return fmt.Errorf("mmap failed: %d", errno)
|
||||||
|
}
|
||||||
|
c.bluepillStack = addr + stackSize
|
||||||
|
c.bluepillSigframe = (*arch.UContext64)(unsafe.Pointer(addr + stackSize))
|
||||||
|
c.bluepillSigframeFPState = addr + stackSize + fpuOffset
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,9 +18,12 @@
|
|||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/ring0"
|
"gvisor.dev/gvisor/pkg/ring0"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
|
"gvisor.dev/gvisor/pkg/sighandling"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,6 +31,11 @@ var (
|
|||||||
bluepillSignal = unix.SIGILL
|
bluepillSignal = unix.SIGILL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// bluepill enters guest mode.
|
||||||
|
//
|
||||||
|
//go:nosplit
|
||||||
|
func bluepill(c *vCPU)
|
||||||
|
|
||||||
// getTLS returns the value of TPIDR_EL0 register.
|
// getTLS returns the value of TPIDR_EL0 register.
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
@@ -130,3 +138,10 @@ func (c *vCPU) KernelException(vector ring0.Vector) {
|
|||||||
//go:nosplit
|
//go:nosplit
|
||||||
func (c *vCPU) hltSanityCheck() {
|
func (c *vCPU) hltSanityCheck() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Install the handler.
|
||||||
|
if err := sighandling.ReplaceSignalHandler(bluepillSignal, addrOfSighandler(), &savedHandler); err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to set handler for signal %d: %v", bluepillSignal, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ TEXT ·setTLS(SB),NOSPLIT,$0-8
|
|||||||
// See bluepill.go.
|
// See bluepill.go.
|
||||||
TEXT ·bluepill(SB),NOSPLIT,$0
|
TEXT ·bluepill(SB),NOSPLIT,$0
|
||||||
begin:
|
begin:
|
||||||
MOVD arg+0(FP), R8
|
MOVD c+0(FP), R8
|
||||||
MOVD $VCPU_CPU(R8), R9
|
MOVD $VCPU_CPU(R8), R9
|
||||||
ORR $0xffff000000000000, R9, R9
|
ORR $0xffff000000000000, R9, R9
|
||||||
// Trigger sigill.
|
// Trigger sigill.
|
||||||
|
|||||||
@@ -510,6 +510,7 @@ func BenchmarkKernelSyscall(b *testing.B) {
|
|||||||
applicationTest(b, true, testutil.AddrOfGetpid(), func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
|
applicationTest(b, true, testutil.AddrOfGetpid(), func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
|
||||||
// iteration does not include machine.Get() / machine.Put().
|
// iteration does not include machine.Get() / machine.Put().
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
bluepill(c)
|
||||||
testutil.Getpid()
|
testutil.Getpid()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/hostarch"
|
"gvisor.dev/gvisor/pkg/hostarch"
|
||||||
"gvisor.dev/gvisor/pkg/ring0"
|
"gvisor.dev/gvisor/pkg/ring0"
|
||||||
"gvisor.dev/gvisor/pkg/ring0/pagetables"
|
"gvisor.dev/gvisor/pkg/ring0/pagetables"
|
||||||
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||||
ktime "gvisor.dev/gvisor/pkg/sentry/time"
|
ktime "gvisor.dev/gvisor/pkg/sentry/time"
|
||||||
)
|
)
|
||||||
@@ -77,6 +78,10 @@ type vCPUArchState struct {
|
|||||||
//
|
//
|
||||||
// This starts above fixedKernelPCID.
|
// This starts above fixedKernelPCID.
|
||||||
PCIDs *pagetables.PCIDs
|
PCIDs *pagetables.PCIDs
|
||||||
|
|
||||||
|
bluepillStack uintptr
|
||||||
|
bluepillSigframe *arch.UContext64
|
||||||
|
bluepillSigframeFPState uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -95,6 +100,10 @@ const (
|
|||||||
|
|
||||||
// initArchState initializes architecture-specific state.
|
// initArchState initializes architecture-specific state.
|
||||||
func (c *vCPU) initArchState() error {
|
func (c *vCPU) initArchState() error {
|
||||||
|
if err := c.initBluepillHandler(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kernelSystemRegs systemRegs
|
kernelSystemRegs systemRegs
|
||||||
kernelUserRegs userRegs
|
kernelUserRegs userRegs
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
load("//tools:defs.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
package(default_applicable_licenses = ["//:license"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "sigframe",
|
||||||
|
srcs = [
|
||||||
|
"sigframe.go",
|
||||||
|
"sigframe_amd64.s",
|
||||||
|
"sigframe_amd64_unsafe.go",
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/abi/linux",
|
||||||
|
"//pkg/sentry/arch",
|
||||||
|
"@org_golang_x_sys//unix:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "sigframe_test",
|
||||||
|
srcs = [
|
||||||
|
"sigframe_amd64_test.go",
|
||||||
|
"sigframe_amd64_test.s",
|
||||||
|
"sigframe_test.go",
|
||||||
|
],
|
||||||
|
library = ":sigframe",
|
||||||
|
deps = [
|
||||||
|
"//pkg/abi/linux",
|
||||||
|
"//pkg/hostarch",
|
||||||
|
"//pkg/sentry/arch",
|
||||||
|
"@org_golang_x_sys//unix:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2024 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 sigframe implements a mechanism to create a signal frame on the
|
||||||
|
// stack and execute a user-defined callback function within that context. The
|
||||||
|
// callback function can use the `sigreturn` system call to resuming the
|
||||||
|
// current thread with the state from the signal frame. This functionality can
|
||||||
|
// be helpful in scenarios where you need to simulate a signal handler-like
|
||||||
|
// behavior without triggering a signal.
|
||||||
|
package sigframe
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
#include "funcdata.h"
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
#define SIGMCTX 40 // +checkoffset arch UContext64.MContext
|
||||||
|
#define SIGCTX_R8 SIGMCTX+0 // +checkoffset arch SignalContext64.R8
|
||||||
|
#define SIGCTX_R9 SIGMCTX+8 // +checkoffset arch SignalContext64.R9
|
||||||
|
#define SIGCTX_R10 SIGMCTX+16 // +checkoffset arch SignalContext64.R10
|
||||||
|
#define SIGCTX_R11 SIGMCTX+24 // +checkoffset arch SignalContext64.R11
|
||||||
|
#define SIGCTX_R12 SIGMCTX+32 // +checkoffset arch SignalContext64.R12
|
||||||
|
#define SIGCTX_R13 SIGMCTX+40 // +checkoffset arch SignalContext64.R13
|
||||||
|
#define SIGCTX_R14 SIGMCTX+48 // +checkoffset arch SignalContext64.R14
|
||||||
|
#define SIGCTX_R15 SIGMCTX+56 // +checkoffset arch SignalContext64.R15
|
||||||
|
#define SIGCTX_RDI SIGMCTX+64 // +checkoffset arch SignalContext64.Rdi
|
||||||
|
#define SIGCTX_RSI SIGMCTX+72 // +checkoffset arch SignalContext64.Rsi
|
||||||
|
#define SIGCTX_RBP SIGMCTX+80 // +checkoffset arch SignalContext64.Rbp
|
||||||
|
#define SIGCTX_RBX SIGMCTX+88 // +checkoffset arch SignalContext64.Rbx
|
||||||
|
#define SIGCTX_RDX SIGMCTX+96 // +checkoffset arch SignalContext64.Rdx
|
||||||
|
#define SIGCTX_RAX SIGMCTX+104 // +checkoffset arch SignalContext64.Rax
|
||||||
|
#define SIGCTX_RCX SIGMCTX+112 // +checkoffset arch SignalContext64.Rcx
|
||||||
|
#define SIGCTX_RSP SIGMCTX+120 // +checkoffset arch SignalContext64.Rsp
|
||||||
|
#define SIGCTX_RIP SIGMCTX+128 // +checkoffset arch SignalContext64.Rip
|
||||||
|
#define SIGCTX_FL SIGMCTX+136 // +checkoffset arch SignalContext64.Eflags
|
||||||
|
#define SIGCTX_CS SIGMCTX+144 // +checkoffset arch SignalContext64.Cs
|
||||||
|
#define SIGCTX_GS SIGMCTX+146 // +checkoffset arch SignalContext64.Gs
|
||||||
|
#define SIGCTX_FS SIGMCTX+148 // +checkoffset arch SignalContext64.Fs
|
||||||
|
#define SIGCTX_SS SIGMCTX+150 // +checkoffset arch SignalContext64.Ss
|
||||||
|
#define SIGCTX_ERR SIGMCTX+152 // +checkoffset arch SignalContext64.Err
|
||||||
|
#define SIGCTX_TRAPNO SIGMCTX+160 // +checkoffset arch SignalContext64.Trapno
|
||||||
|
#define SIGCTX_MASK SIGMCTX+168 // +checkoffset arch SignalContext64.Oldmask
|
||||||
|
#define SIGCTX_CR2 SIGMCTX+176 // +checkoffset arch SignalContext64.Cr2
|
||||||
|
#define SIGCTX_FPSTATE SIGMCTX+184 // +checkoffset arch SignalContext64.Fpstate
|
||||||
|
|
||||||
|
// Callee-Save: RBX, RBP, and R12-R15 are preserved across function calls.
|
||||||
|
// Caller-Save: RAX, RCX, RDX, RSI, RDI, and R8-R11 are free to be used by
|
||||||
|
// the called function and may be overwritten.
|
||||||
|
// retjmp has to be updated when the stack frame size is changed.
|
||||||
|
TEXT ·callWithSignalFrame(SB),NOSPLIT,$8-32
|
||||||
|
MOVQ stack+0(FP), DI
|
||||||
|
MOVQ handler+8(FP), AX
|
||||||
|
MOVQ sigframe+16(FP), R8
|
||||||
|
|
||||||
|
MOVQ fpstate+24(FP), R9
|
||||||
|
MOVQ R9, SIGCTX_FPSTATE(R8)
|
||||||
|
|
||||||
|
MOVQ BX, SIGCTX_RBX(R8)
|
||||||
|
MOVQ BP, SIGCTX_RBP(R8)
|
||||||
|
MOVQ R12, SIGCTX_R12(R8)
|
||||||
|
MOVQ R13, SIGCTX_R13(R8)
|
||||||
|
MOVQ R14, SIGCTX_R14(R8)
|
||||||
|
MOVQ R15, SIGCTX_R15(R8)
|
||||||
|
PUSHFQ
|
||||||
|
POPQ R9
|
||||||
|
MOVQ R9, SIGCTX_FL(R8)
|
||||||
|
MOVQ SP, SIGCTX_RSP(R8)
|
||||||
|
MOVQ $·retjmp(SB),R9
|
||||||
|
MOVQ R9, SIGCTX_RIP(R8)
|
||||||
|
MOVQ $0x33, SIGCTX_CS(R8)
|
||||||
|
MOVQ $0x2b, SIGCTX_SS(R8)
|
||||||
|
MOVQ DI, SP
|
||||||
|
PUSHQ $0x0
|
||||||
|
MOVQ SP, BP
|
||||||
|
PUSHQ R8 // Save CPU.
|
||||||
|
CALL AX
|
||||||
|
|
||||||
|
#define __NR_rt_sigreturn 15 // +checkconst unix SYS_RT_SIGRETURN
|
||||||
|
TEXT ·Sigreturn(SB),NOSPLIT,$0-8
|
||||||
|
MOVQ sigframeAddr+0(FP), SP
|
||||||
|
MOVQ $__NR_rt_sigreturn, AX
|
||||||
|
SYSCALL
|
||||||
|
|
||||||
|
// retjmp is the return sequence from callWithSignalFrame. It is used to set
|
||||||
|
// RIP on a signal frame.
|
||||||
|
TEXT ·retjmp(SB),NOSPLIT,$0-0
|
||||||
|
MOVQ 8(SP), BP
|
||||||
|
ADDQ $0x10,SP
|
||||||
|
RET
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2024 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 amd64
|
||||||
|
// +build amd64
|
||||||
|
|
||||||
|
package sigframe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||||
|
"gvisor.dev/gvisor/pkg/hostarch"
|
||||||
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserSignalHandler(t *testing.T) {
|
||||||
|
addr, _, _ := unix.Syscall6(unix.SYS_MMAP, 0, hostarch.PageSize*4, uintptr(unix.PROT_READ|unix.PROT_WRITE),
|
||||||
|
uintptr(unix.MAP_PRIVATE|unix.MAP_ANONYMOUS),
|
||||||
|
0, 0)
|
||||||
|
set := linux.MakeSignalSet(linux.SIGURG)
|
||||||
|
sigframe := (*arch.UContext64)(unsafe.Pointer(addr + hostarch.PageSize))
|
||||||
|
CallWithSignalFrame(addr+hostarch.PageSize, addrOfUserHandler(), sigframe, 0, &set)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUserSigHandler(b *testing.B) {
|
||||||
|
addr, _, _ := unix.Syscall6(unix.SYS_MMAP, 0, hostarch.PageSize*4, uintptr(unix.PROT_READ|unix.PROT_WRITE),
|
||||||
|
uintptr(unix.MAP_PRIVATE|unix.MAP_ANONYMOUS),
|
||||||
|
0, 0)
|
||||||
|
set := linux.MakeSignalSet(linux.SIGURG)
|
||||||
|
sigframe := (*arch.UContext64)(unsafe.Pointer(addr + hostarch.PageSize))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CallWithSignalFrame(addr+hostarch.PageSize, addrOfUserHandler(), sigframe, 0, &set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addrOfUserHandler() uintptr
|
||||||
|
|
||||||
|
func userHandler(sigframe *arch.UContext64) {
|
||||||
|
Sigreturn(sigframe)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
#include "funcdata.h"
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// ADDR_OF_FUNC defines a function named 'name' that returns the address of
|
||||||
|
// 'symbol'.
|
||||||
|
#define ADDR_OF_FUNC(name, symbol) \
|
||||||
|
TEXT name,$0-8; \
|
||||||
|
MOVQ $symbol, AX; \
|
||||||
|
MOVQ AX, ret+0(FP); \
|
||||||
|
RET
|
||||||
|
|
||||||
|
ADDR_OF_FUNC(·addrOfUserHandler(SB), ·userHandler(SB));
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2024 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 amd64
|
||||||
|
// +build amd64
|
||||||
|
|
||||||
|
package sigframe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||||
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func callWithSignalFrame(stack uintptr, handler uintptr, sigframe *arch.UContext64, fpstate uintptr)
|
||||||
|
|
||||||
|
//go:linkname throw runtime.throw
|
||||||
|
func throw(s string)
|
||||||
|
|
||||||
|
// CallWithSignalFrame sets up a signal frame on the stack and executes a
|
||||||
|
// user-defined callback function within that context.
|
||||||
|
//
|
||||||
|
// Caller-save registers can be used for passing arguments to the handler.
|
||||||
|
// These registers must be pre-set within the signal frame.
|
||||||
|
//
|
||||||
|
//go:nosplit
|
||||||
|
func CallWithSignalFrame(stack uintptr, handlerAddr uintptr, sigframe *arch.UContext64, fpstate uintptr, sigmask *linux.SignalSet) error {
|
||||||
|
_, _, errno := unix.RawSyscall6(
|
||||||
|
unix.SYS_RT_SIGPROCMASK, linux.SIG_BLOCK,
|
||||||
|
uintptr(unsafe.Pointer(sigmask)),
|
||||||
|
uintptr(unsafe.Pointer(&sigframe.Sigset)),
|
||||||
|
linux.SignalSetSize,
|
||||||
|
0, 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
callWithSignalFrame(stack, handlerAddr, sigframe, fpstate)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigreturn restores the thread state from the signal frame.
|
||||||
|
func Sigreturn(sigframeAddr *arch.UContext64)
|
||||||
|
|
||||||
|
func retjmp()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2024 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 sigframe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDummy(t *testing.T) {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user