enable kvm to support arm64

There are 4 jobs were finished in this package:
  	1, Virtual machine initialization.
	2, Bluepill implementation.
	3, Move ring0.Vectors() into the address with 11-bits alignment.
	4, Basic support for "SwitchToUser".
Signed-off-by: Bin Lu <bin.lu@arm.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/919 from lubinszARM:pr_kvm eedea52db451bf62722759009a9f14c54a69c55f
PiperOrigin-RevId: 285501256
This commit is contained in:
lubinszARM
2019-12-13 17:11:23 -08:00
committed by gVisor bot
parent ad80dcf470
commit 6b42453039
20 changed files with 1059 additions and 73 deletions
+9 -1
View File
@@ -12,18 +12,26 @@ go_library(
"bluepill_amd64.go",
"bluepill_amd64.s",
"bluepill_amd64_unsafe.go",
"bluepill_arm64.go",
"bluepill_arm64.s",
"bluepill_arm64_unsafe.go",
"bluepill_fault.go",
"bluepill_unsafe.go",
"context.go",
"filters.go",
"filters_amd64.go",
"filters_arm64.go",
"kvm.go",
"kvm_amd64.go",
"kvm_amd64_unsafe.go",
"kvm_arm64.go",
"kvm_arm64_unsafe.go",
"kvm_const.go",
"kvm_const_arm64.go",
"machine.go",
"machine_amd64.go",
"machine_amd64_unsafe.go",
"machine_arm64.go",
"machine_arm64_unsafe.go",
"machine_unsafe.go",
"physical_map.go",
"virtual_map.go",
+22 -2
View File
@@ -20,6 +20,7 @@ import (
"syscall"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
"gvisor.dev/gvisor/pkg/sentry/platform/safecopy"
)
@@ -36,6 +37,18 @@ func sighandler()
func dieTrampoline()
var (
// bounceSignal is the signal used for bouncing KVM.
//
// We use SIGCHLD because it is not masked by the runtime, and
// it will be ignored properly by other parts of the kernel.
bounceSignal = syscall.SIGCHLD
// bounceSignalMask has only bounceSignal set.
bounceSignalMask = uint64(1 << (uint64(bounceSignal) - 1))
// bounce is the interrupt vector used to return to the kernel.
bounce = uint32(ring0.VirtualizationException)
// savedHandler is a pointer to the previous handler.
//
// This is called by bluepillHandler.
@@ -45,6 +58,13 @@ var (
dieTrampolineAddr uintptr
)
// redpill invokes a syscall with -1.
//
//go:nosplit
func redpill() {
syscall.RawSyscall(^uintptr(0), 0, 0, 0)
}
// dieHandler is called by dieTrampoline.
//
//go:nosplit
@@ -73,8 +93,8 @@ func (c *vCPU) die(context *arch.SignalContext64, msg string) {
func init() {
// Install the handler.
if err := safecopy.ReplaceSignalHandler(syscall.SIGSEGV, reflect.ValueOf(sighandler).Pointer(), &savedHandler); err != nil {
panic(fmt.Sprintf("Unable to set handler for signal %d: %v", syscall.SIGSEGV, err))
if err := safecopy.ReplaceSignalHandler(bluepillSignal, reflect.ValueOf(sighandler).Pointer(), &savedHandler); err != nil {
panic(fmt.Sprintf("Unable to set handler for signal %d: %v", bluepillSignal, err))
}
// Extract the address for the trampoline.
+2 -18
View File
@@ -24,26 +24,10 @@ import (
)
var (
// bounceSignal is the signal used for bouncing KVM.
//
// We use SIGCHLD because it is not masked by the runtime, and
// it will be ignored properly by other parts of the kernel.
bounceSignal = syscall.SIGCHLD
// bounceSignalMask has only bounceSignal set.
bounceSignalMask = uint64(1 << (uint64(bounceSignal) - 1))
// bounce is the interrupt vector used to return to the kernel.
bounce = uint32(ring0.VirtualizationException)
// The action for bluepillSignal is changed by sigaction().
bluepillSignal = syscall.SIGSEGV
)
// redpill on amd64 invokes a syscall with -1.
//
//go:nosplit
func redpill() {
syscall.RawSyscall(^uintptr(0), 0, 0, 0)
}
// bluepillArchEnter is called during bluepillEnter.
//
//go:nosplit
@@ -23,13 +23,6 @@ import (
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
)
// bluepillArchContext returns the arch-specific context.
//
//go:nosplit
func bluepillArchContext(context unsafe.Pointer) *arch.SignalContext64 {
return &((*arch.UContext64)(context).MContext)
}
// dieArchSetup initializes the state for dieTrampoline.
//
// The amd64 dieTrampoline requires the vCPU to be set in BX, and the last RIP
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2019 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 kvm
import (
"syscall"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
)
var (
// The action for bluepillSignal is changed by sigaction().
bluepillSignal = syscall.SIGILL
)
// bluepillArchEnter is called during bluepillEnter.
//
//go:nosplit
func bluepillArchEnter(context *arch.SignalContext64) (c *vCPU) {
c = vCPUPtr(uintptr(context.Regs[8]))
regs := c.CPU.Registers()
regs.Regs = context.Regs
regs.Sp = context.Sp
regs.Pc = context.Pc
regs.Pstate = context.Pstate
regs.Pstate &^= uint64(ring0.KernelFlagsClear)
regs.Pstate |= ring0.KernelFlagsSet
return
}
// bluepillArchExit is called during bluepillEnter.
//
//go:nosplit
func bluepillArchExit(c *vCPU, context *arch.SignalContext64) {
regs := c.CPU.Registers()
context.Regs = regs.Regs
context.Sp = regs.Sp
context.Pc = regs.Pc
context.Pstate = regs.Pstate
context.Pstate &^= uint64(ring0.UserFlagsClear)
context.Pstate |= ring0.UserFlagsSet
}
// KernelSyscall handles kernel syscalls.
//
//go:nosplit
func (c *vCPU) KernelSyscall() {
regs := c.Registers()
if regs.Regs[8] != ^uint64(0) {
regs.Pc -= 4 // Rewind.
}
ring0.Halt()
}
// KernelException handles kernel exceptions.
//
//go:nosplit
func (c *vCPU) KernelException(vector ring0.Vector) {
regs := c.Registers()
if vector == ring0.Vector(bounce) {
regs.Pc = 0
}
ring0.Halt()
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2019 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 "textflag.h"
// VCPU_CPU is the location of the CPU in the vCPU struct.
//
// This is guaranteed to be zero.
#define VCPU_CPU 0x0
// CPU_SELF is the self reference in ring0's percpu.
//
// This is guaranteed to be zero.
#define CPU_SELF 0x0
// Context offsets.
//
// Only limited use of the context is done in the assembly stub below, most is
// done in the Go handlers.
#define SIGINFO_SIGNO 0x0
#define CONTEXT_PC 0x1B8
#define CONTEXT_R0 0xB8
// See bluepill.go.
TEXT ·bluepill(SB),NOSPLIT,$0
begin:
MOVD vcpu+0(FP), R8
MOVD $VCPU_CPU(R8), R9
ORR $0xffff000000000000, R9, R9
// Trigger sigill.
// In ring0.Start(), the value of R8 will be stored into tpidr_el1.
// When the context was loaded into vcpu successfully,
// we will check if the value of R10 and R9 are the same.
WORD $0xd538d08a // MRS TPIDR_EL1, R10
check_vcpu:
CMP R10, R9
BEQ right_vCPU
wrong_vcpu:
CALL ·redpill(SB)
B begin
right_vCPU:
RET
// sighandler: see bluepill.go for documentation.
//
// The arguments are the following:
//
// R0 - The signal number.
// R1 - Pointer to siginfo_t structure.
// R2 - Pointer to ucontext structure.
//
TEXT ·sighandler(SB),NOSPLIT,$0
// si_signo should be sigill.
MOVD SIGINFO_SIGNO(R1), R7
CMPW $4, R7
BNE fallback
MOVD CONTEXT_PC(R2), R7
CMPW $0, R7
BEQ fallback
MOVD R2, 8(RSP)
BL ·bluepillHandler(SB) // Call the handler.
RET
fallback:
// Jump to the previous signal handler.
MOVD ·savedHandler(SB), R7
B (R7)
// dieTrampoline: see bluepill.go, bluepill_arm64_unsafe.go for documentation.
TEXT ·dieTrampoline(SB),NOSPLIT,$0
// TODO(gvisor.dev/issue/1249): dieTrampoline supporting for Arm64.
MOVD R9, 8(RSP)
BL ·dieHandler(SB)
@@ -0,0 +1,28 @@
// Copyright 2019 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 kvm
import (
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
)
//go:nosplit
func dieArchSetup(c *vCPU, context *arch.SignalContext64, guestRegs *userRegs) {
// TODO(gvisor.dev/issue/1249): dieTrampoline supporting for Arm64.
}
@@ -23,6 +23,8 @@ import (
"sync/atomic"
"syscall"
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
)
//go:linkname throw runtime.throw
@@ -49,6 +51,13 @@ func uintptrValue(addr *byte) uintptr {
return (uintptr)(unsafe.Pointer(addr))
}
// bluepillArchContext returns the UContext64.
//
//go:nosplit
func bluepillArchContext(context unsafe.Pointer) *arch.SignalContext64 {
return &((*arch.UContext64)(context).MContext)
}
// bluepillHandler is called from the signal stub.
//
// The world may be stopped while this is executing, and it executes on the
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2019 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 kvm
import (
"syscall"
"gvisor.dev/gvisor/pkg/seccomp"
)
// SyscallFilters returns syscalls made exclusively by the KVM platform.
func (*KVM) SyscallFilters() seccomp.SyscallRules {
return seccomp.SyscallRules{
syscall.SYS_IOCTL: {},
syscall.SYS_MMAP: {},
syscall.SYS_RT_SIGSUSPEND: {},
syscall.SYS_RT_SIGTIMEDWAIT: {},
0xffffffffffffffff: {}, // KVM uses syscall -1 to transition to host.
}
}
+1 -4
View File
@@ -21,7 +21,6 @@ import (
"sync"
"syscall"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
@@ -56,9 +55,7 @@ func New(deviceFile *os.File) (*KVM, error) {
// Ensure global initialization is done.
globalOnce.Do(func() {
physicalInit()
globalErr = updateSystemValues(int(fd))
ring0.Init(cpuid.HostFeatureSet())
globalErr = updateGlobalOnce(int(fd))
})
if globalErr != nil {
return nil, globalErr
+9
View File
@@ -17,6 +17,7 @@
package kvm
import (
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
)
@@ -211,3 +212,11 @@ type cpuidEntries struct {
_ uint32
entries [_KVM_NR_CPUID_ENTRIES]cpuidEntry
}
// updateGlobalOnce does global initialization. It has to be called only once.
func updateGlobalOnce(fd int) error {
physicalInit()
err := updateSystemValues(int(fd))
ring0.Init(cpuid.HostFeatureSet())
return err
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2019 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 kvm
import (
"syscall"
)
// userMemoryRegion is a region of physical memory.
//
// This mirrors kvm_memory_region.
type userMemoryRegion struct {
slot uint32
flags uint32
guestPhysAddr uint64
memorySize uint64
userspaceAddr uint64
}
type kvmOneReg struct {
id uint64
addr uint64
}
const KVM_NR_SPSR = 5
type userFpsimdState struct {
vregs [64]uint64
fpsr uint32
fpcr uint32
reserved [2]uint32
}
type userRegs struct {
Regs syscall.PtraceRegs
sp_el1 uint64
elr_el1 uint64
spsr [KVM_NR_SPSR]uint64
fpRegs userFpsimdState
}
// runData is the run structure. This may be mapped for synchronous register
// access (although that doesn't appear to be supported by my kernel at least).
//
// This mirrors kvm_run.
type runData struct {
requestInterruptWindow uint8
_ [7]uint8
exitReason uint32
readyForInterruptInjection uint8
ifFlag uint8
_ [2]uint8
cr8 uint64
apicBase uint64
// This is the union data for exits. Interpretation depends entirely on
// the exitReason above (see vCPU code for more information).
data [32]uint64
}
// updateGlobalOnce does global initialization. It has to be called only once.
func updateGlobalOnce(fd int) error {
physicalInit()
err := updateSystemValues(int(fd))
updateVectorTable()
return err
}
@@ -0,0 +1,39 @@
// Copyright 2019 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 kvm
import (
"fmt"
"syscall"
)
var (
runDataSize int
)
func updateSystemValues(fd int) error {
// Extract the mmap size.
sz, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd), _KVM_GET_VCPU_MMAP_SIZE, 0)
if errno != 0 {
return fmt.Errorf("getting VCPU mmap size: %v", errno)
}
// Save the data.
runDataSize = int(sz)
// Success.
return nil
}
+3 -1
View File
@@ -49,11 +49,13 @@ const (
_KVM_EXIT_SHUTDOWN = 0x8
_KVM_EXIT_FAIL_ENTRY = 0x9
_KVM_EXIT_INTERNAL_ERROR = 0x11
_KVM_EXIT_SYSTEM_EVENT = 0x18
)
// KVM capability options.
const (
_KVM_CAP_MAX_VCPUS = 0x42
_KVM_CAP_MAX_VCPUS = 0x42
_KVM_CAP_ARM_VM_IPA_SIZE = 0xa5
)
// KVM limits.
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2019 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 kvm
// KVM ioctls for Arm64.
const (
_KVM_GET_ONE_REG = 0x4010aeab
_KVM_SET_ONE_REG = 0x4010aeac
_KVM_ARM_PREFERRED_TARGET = 0x8020aeaf
_KVM_ARM_VCPU_INIT = 0x4020aeae
_KVM_ARM64_REGS_PSTATE = 0x6030000000100042
_KVM_ARM64_REGS_SP_EL1 = 0x6030000000100044
_KVM_ARM64_REGS_R0 = 0x6030000000100000
_KVM_ARM64_REGS_R1 = 0x6030000000100002
_KVM_ARM64_REGS_R2 = 0x6030000000100004
_KVM_ARM64_REGS_R3 = 0x6030000000100006
_KVM_ARM64_REGS_R8 = 0x6030000000100010
_KVM_ARM64_REGS_R18 = 0x6030000000100024
_KVM_ARM64_REGS_PC = 0x6030000000100040
_KVM_ARM64_REGS_MAIR_EL1 = 0x603000000013c510
_KVM_ARM64_REGS_TCR_EL1 = 0x603000000013c102
_KVM_ARM64_REGS_TTBR0_EL1 = 0x603000000013c100
_KVM_ARM64_REGS_TTBR1_EL1 = 0x603000000013c101
_KVM_ARM64_REGS_SCTLR_EL1 = 0x603000000013c080
_KVM_ARM64_REGS_CPACR_EL1 = 0x603000000013c082
_KVM_ARM64_REGS_VBAR_EL1 = 0x603000000013c600
)
// Arm64: Architectural Feature Access Control Register EL1.
const (
_FPEN_NOTRAP = 0x3
_FPEN_SHIFT = 0x20
)
// Arm64: System Control Register EL1.
const (
_SCTLR_M = 1 << 0
_SCTLR_C = 1 << 2
_SCTLR_I = 1 << 12
)
// Arm64: Translation Control Register EL1.
const (
_TCR_IPS_40BITS = 2 << 32 // PA=40
_TCR_IPS_48BITS = 5 << 32 // PA=48
_TCR_T0SZ_OFFSET = 0
_TCR_T1SZ_OFFSET = 16
_TCR_IRGN0_SHIFT = 8
_TCR_IRGN1_SHIFT = 24
_TCR_ORGN0_SHIFT = 10
_TCR_ORGN1_SHIFT = 26
_TCR_SH0_SHIFT = 12
_TCR_SH1_SHIFT = 28
_TCR_TG0_SHIFT = 14
_TCR_TG1_SHIFT = 30
_TCR_T0SZ_VA48 = 64 - 48 // VA=48
_TCR_T1SZ_VA48 = 64 - 48 // VA=48
_TCR_ASID16 = 1 << 36
_TCR_TBI0 = 1 << 37
_TCR_TXSZ_VA48 = (_TCR_T0SZ_VA48 << _TCR_T0SZ_OFFSET) | (_TCR_T1SZ_VA48 << _TCR_T1SZ_OFFSET)
_TCR_TG0_4K = 0 << _TCR_TG0_SHIFT // 4K
_TCR_TG0_64K = 1 << _TCR_TG0_SHIFT // 64K
_TCR_TG1_4K = 2 << _TCR_TG1_SHIFT
_TCR_TG_FLAGS = _TCR_TG0_4K | _TCR_TG1_4K
_TCR_IRGN0_WBWA = 1 << _TCR_IRGN0_SHIFT
_TCR_IRGN1_WBWA = 1 << _TCR_IRGN1_SHIFT
_TCR_IRGN_WBWA = _TCR_IRGN0_WBWA | _TCR_IRGN1_WBWA
_TCR_ORGN0_WBWA = 1 << _TCR_ORGN0_SHIFT
_TCR_ORGN1_WBWA = 1 << _TCR_ORGN1_SHIFT
_TCR_ORGN_WBWA = _TCR_ORGN0_WBWA | _TCR_ORGN1_WBWA
_TCR_SHARED = (3 << _TCR_SH0_SHIFT) | (3 << _TCR_SH1_SHIFT)
_TCR_CACHE_FLAGS = _TCR_IRGN_WBWA | _TCR_ORGN_WBWA
)
// Arm64: Memory Attribute Indirection Register EL1.
const (
_MT_DEVICE_nGnRnE = 0
_MT_DEVICE_nGnRE = 1
_MT_DEVICE_GRE = 2
_MT_NORMAL_NC = 3
_MT_NORMAL = 4
_MT_NORMAL_WT = 5
_MT_EL1_INIT = (0 << _MT_DEVICE_nGnRnE) | (0x4 << _MT_DEVICE_nGnRE * 8) | (0xc << _MT_DEVICE_GRE * 8) | (0x44 << _MT_NORMAL_NC * 8) | (0xff << _MT_NORMAL * 8) | (0xbb << _MT_NORMAL_WT * 8)
)
const (
_KVM_ARM_VCPU_POWER_OFF = 0 // CPU is started in OFF state
_KVM_ARM_VCPU_PSCI_0_2 = 2 // CPU uses PSCI v0.2
)
// Arm64: Exception Syndrome Register EL1.
const (
_ESR_ELx_FSC = 0x3F
_ESR_SEGV_MAPERR_L0 = 0x4
_ESR_SEGV_MAPERR_L1 = 0x5
_ESR_SEGV_MAPERR_L2 = 0x6
_ESR_SEGV_MAPERR_L3 = 0x7
_ESR_SEGV_ACCERR_L1 = 0x9
_ESR_SEGV_ACCERR_L2 = 0xa
_ESR_SEGV_ACCERR_L3 = 0xb
_ESR_SEGV_PEMERR_L1 = 0xd
_ESR_SEGV_PEMERR_L2 = 0xe
_ESR_SEGV_PEMERR_L3 = 0xf
)
@@ -135,3 +135,43 @@ func (c *vCPU) setSignalMask() error {
}
return nil
}
// setUserRegisters sets user registers in the vCPU.
func (c *vCPU) setUserRegisters(uregs *userRegs) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_REGS,
uintptr(unsafe.Pointer(uregs))); errno != 0 {
return fmt.Errorf("error setting user registers: %v", errno)
}
return nil
}
// getUserRegisters reloads user registers in the vCPU.
//
// This is safe to call from a nosplit context.
//
//go:nosplit
func (c *vCPU) getUserRegisters(uregs *userRegs) syscall.Errno {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_GET_REGS,
uintptr(unsafe.Pointer(uregs))); errno != 0 {
return errno
}
return 0
}
// setSystemRegisters sets system registers.
func (c *vCPU) setSystemRegisters(sregs *systemRegs) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_SREGS,
uintptr(unsafe.Pointer(sregs))); errno != 0 {
return fmt.Errorf("error setting system registers: %v", errno)
}
return nil
}
+122
View File
@@ -12,8 +12,38 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build arm64
package kvm
import (
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0/pagetables"
"gvisor.dev/gvisor/pkg/sentry/usermem"
)
type vCPUArchState struct {
// PCIDs is the set of PCIDs for this vCPU.
//
// This starts above fixedKernelPCID.
PCIDs *pagetables.PCIDs
}
const (
// fixedKernelPCID is a fixed kernel PCID used for the kernel page
// tables. We must start allocating user PCIDs above this in order to
// avoid any conflict (see below).
fixedKernelPCID = 1
// poolPCIDs is the number of PCIDs to record in the database. As this
// grows, assignment can take longer, since it is a simple linear scan.
// Beyond a relatively small number, there are likely few perform
// benefits, since the TLB has likely long since lost any translations
// from more than a few PCIDs past.
poolPCIDs = 8
)
// Get all read-only physicalRegions.
func rdonlyRegionsForSetMem() (phyRegions []physicalRegion) {
var rdonlyRegions []region
@@ -59,3 +89,95 @@ func availableRegionsForSetMem() (phyRegions []physicalRegion) {
return phyRegions
}
// dropPageTables drops cached page table entries.
func (m *machine) dropPageTables(pt *pagetables.PageTables) {
m.mu.Lock()
defer m.mu.Unlock()
// Clear from all PCIDs.
for _, c := range m.vCPUs {
c.PCIDs.Drop(pt)
}
}
// nonCanonical generates a canonical address return.
//
//go:nosplit
func nonCanonical(addr uint64, signal int32, info *arch.SignalInfo) (usermem.AccessType, error) {
*info = arch.SignalInfo{
Signo: signal,
Code: arch.SignalInfoKernel,
}
info.SetAddr(addr) // Include address.
return usermem.NoAccess, platform.ErrContextSignal
}
// fault generates an appropriate fault return.
//
//go:nosplit
func (c *vCPU) fault(signal int32, info *arch.SignalInfo) (usermem.AccessType, error) {
faultAddr := c.GetFaultAddr()
code, user := c.ErrorCode()
// Reset the pointed SignalInfo.
*info = arch.SignalInfo{Signo: signal}
info.SetAddr(uint64(faultAddr))
read := true
write := false
execute := true
ret := code & _ESR_ELx_FSC
switch ret {
case _ESR_SEGV_MAPERR_L0, _ESR_SEGV_MAPERR_L1, _ESR_SEGV_MAPERR_L2, _ESR_SEGV_MAPERR_L3:
info.Code = 1 //SEGV_MAPERR
read = false
write = true
execute = false
case _ESR_SEGV_ACCERR_L1, _ESR_SEGV_ACCERR_L2, _ESR_SEGV_ACCERR_L3, _ESR_SEGV_PEMERR_L1, _ESR_SEGV_PEMERR_L2, _ESR_SEGV_PEMERR_L3:
info.Code = 2 // SEGV_ACCERR.
read = true
write = false
execute = false
default:
info.Code = 2
}
if !user {
read = true
write = false
execute = true
}
accessType := usermem.AccessType{
Read: read,
Write: write,
Execute: execute,
}
return accessType, platform.ErrContextSignal
}
// retryInGuest runs the given function in guest mode.
//
// If the function does not complete in guest mode (due to execution of a
// system call due to a GC stall, for example), then it will be retried. The
// given function must be idempotent as a result of the retry mechanism.
func (m *machine) retryInGuest(fn func()) {
c := m.Get()
defer m.Put(c)
for {
c.ClearErrorCode() // See below.
bluepill(c) // Force guest mode.
fn() // Execute the given function.
_, user := c.ErrorCode()
if user {
// If user is set, then we haven't bailed back to host
// mode via a kernel exception or system call. We
// consider the full function to have executed in guest
// mode and we can return.
break
}
}
}
@@ -0,0 +1,362 @@
// Copyright 2019 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 kvm
import (
"fmt"
"reflect"
"sync/atomic"
"syscall"
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/platform/ring0"
"gvisor.dev/gvisor/pkg/sentry/usermem"
)
// setMemoryRegion initializes a region.
//
// This may be called from bluepillHandler, and therefore returns an errno
// directly (instead of wrapping in an error) to avoid allocations.
//
//go:nosplit
func (m *machine) setMemoryRegion(slot int, physical, length, virtual uintptr) syscall.Errno {
userRegion := userMemoryRegion{
slot: uint32(slot),
flags: 0,
guestPhysAddr: uint64(physical),
memorySize: uint64(length),
userspaceAddr: uint64(virtual),
}
// Set the region.
_, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(m.fd),
_KVM_SET_USER_MEMORY_REGION,
uintptr(unsafe.Pointer(&userRegion)))
return errno
}
type kvmVcpuInit struct {
target uint32
features [7]uint32
}
var vcpuInit kvmVcpuInit
// initArchState initializes architecture-specific state.
func (m *machine) initArchState() error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(m.fd),
_KVM_ARM_PREFERRED_TARGET,
uintptr(unsafe.Pointer(&vcpuInit))); errno != 0 {
panic(fmt.Sprintf("error setting KVM_ARM_PREFERRED_TARGET failed: %v", errno))
}
return nil
}
func getPageWithReflect(p uintptr) []byte {
return (*(*[0xFFFFFF]byte)(unsafe.Pointer(p & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()]
}
// Work around: move ring0.Vectors() into a specific address with 11-bits alignment.
//
// According to the design documentation of Arm64,
// the start address of exception vector table should be 11-bits aligned.
// Please see the code in linux kernel as reference: arch/arm64/kernel/entry.S
// But, we can't align a function's start address to a specific address by using golang.
// We have raised this question in golang community:
// https://groups.google.com/forum/m/#!topic/golang-dev/RPj90l5x86I
// This function will be removed when golang supports this feature.
//
// There are 2 jobs were implemented in this function:
// 1, move the start address of exception vector table into the specific address.
// 2, modify the offset of each instruction.
func updateVectorTable() {
fromLocation := reflect.ValueOf(ring0.Vectors).Pointer()
offset := fromLocation & (1<<11 - 1)
if offset != 0 {
offset = 1<<11 - offset
}
toLocation := fromLocation + offset
page := getPageWithReflect(toLocation)
if err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC); err != nil {
panic(err)
}
page = getPageWithReflect(toLocation + 4096)
if err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC); err != nil {
panic(err)
}
// Move exception-vector-table into the specific address.
var entry *uint32
var entryFrom *uint32
for i := 1; i <= 0x800; i++ {
entry = (*uint32)(unsafe.Pointer(toLocation + 0x800 - uintptr(i)))
entryFrom = (*uint32)(unsafe.Pointer(fromLocation + 0x800 - uintptr(i)))
*entry = *entryFrom
}
// The offset from the address of each unconditionally branch is changed.
// We should modify the offset of each instruction.
nums := []uint32{0x0, 0x80, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x400, 0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780}
for _, num := range nums {
entry = (*uint32)(unsafe.Pointer(toLocation + uintptr(num)))
*entry = *entry - (uint32)(offset/4)
}
page = getPageWithReflect(toLocation)
if err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC); err != nil {
panic(err)
}
page = getPageWithReflect(toLocation + 4096)
if err := syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC); err != nil {
panic(err)
}
}
// initArchState initializes architecture-specific state.
func (c *vCPU) initArchState() error {
var (
reg kvmOneReg
data uint64
regGet kvmOneReg
dataGet uint64
)
reg.addr = uint64(reflect.ValueOf(&data).Pointer())
regGet.addr = uint64(reflect.ValueOf(&dataGet).Pointer())
vcpuInit.features[0] |= (1 << _KVM_ARM_VCPU_PSCI_0_2)
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_ARM_VCPU_INIT,
uintptr(unsafe.Pointer(&vcpuInit))); errno != 0 {
panic(fmt.Sprintf("error setting KVM_ARM_VCPU_INIT failed: %v", errno))
}
// cpacr_el1
reg.id = _KVM_ARM64_REGS_CPACR_EL1
data = (_FPEN_NOTRAP << _FPEN_SHIFT)
if err := c.setOneRegister(&reg); err != nil {
return err
}
// sctlr_el1
regGet.id = _KVM_ARM64_REGS_SCTLR_EL1
if err := c.getOneRegister(&regGet); err != nil {
return err
}
dataGet |= (_SCTLR_M | _SCTLR_C | _SCTLR_I)
data = dataGet
reg.id = _KVM_ARM64_REGS_SCTLR_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
// tcr_el1
data = _TCR_TXSZ_VA48 | _TCR_CACHE_FLAGS | _TCR_SHARED | _TCR_TG_FLAGS | _TCR_ASID16 | _TCR_IPS_40BITS
reg.id = _KVM_ARM64_REGS_TCR_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
// mair_el1
data = _MT_EL1_INIT
reg.id = _KVM_ARM64_REGS_MAIR_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
// ttbr0_el1
data = c.machine.kernel.PageTables.TTBR0_EL1(false, 0)
reg.id = _KVM_ARM64_REGS_TTBR0_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
c.SetTtbr0Kvm(uintptr(data))
// ttbr1_el1
data = c.machine.kernel.PageTables.TTBR1_EL1(false, 0)
reg.id = _KVM_ARM64_REGS_TTBR1_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
// sp_el1
data = c.CPU.StackTop()
reg.id = _KVM_ARM64_REGS_SP_EL1
if err := c.setOneRegister(&reg); err != nil {
return err
}
// pc
reg.id = _KVM_ARM64_REGS_PC
data = uint64(reflect.ValueOf(ring0.Start).Pointer())
if err := c.setOneRegister(&reg); err != nil {
return err
}
// r8
reg.id = _KVM_ARM64_REGS_R8
data = uint64(reflect.ValueOf(&c.CPU).Pointer())
if err := c.setOneRegister(&reg); err != nil {
return err
}
// vbar_el1
reg.id = _KVM_ARM64_REGS_VBAR_EL1
fromLocation := reflect.ValueOf(ring0.Vectors).Pointer()
offset := fromLocation & (1<<11 - 1)
if offset != 0 {
offset = 1<<11 - offset
}
toLocation := fromLocation + offset
data = uint64(ring0.KernelStartAddress | toLocation)
if err := c.setOneRegister(&reg); err != nil {
return err
}
data = ring0.PsrDefaultSet | ring0.KernelFlagsSet
reg.id = _KVM_ARM64_REGS_PSTATE
if err := c.setOneRegister(&reg); err != nil {
return err
}
return nil
}
//go:nosplit
func (c *vCPU) loadSegments(tid uint64) {
// TODO(gvisor.dev/issue/1238): TLS is not supported.
// Get TLS from tpidr_el0.
atomic.StoreUint64(&c.tid, tid)
}
func (c *vCPU) setOneRegister(reg *kvmOneReg) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_ONE_REG,
uintptr(unsafe.Pointer(reg))); errno != 0 {
return fmt.Errorf("error setting one register: %v", errno)
}
return nil
}
func (c *vCPU) getOneRegister(reg *kvmOneReg) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_GET_ONE_REG,
uintptr(unsafe.Pointer(reg))); errno != 0 {
return fmt.Errorf("error setting one register: %v", errno)
}
return nil
}
// setCPUID sets the CPUID to be used by the guest.
func (c *vCPU) setCPUID() error {
return nil
}
// setSystemTime sets the TSC for the vCPU.
func (c *vCPU) setSystemTime() error {
return nil
}
// setSignalMask sets the vCPU signal mask.
//
// This must be called prior to running the vCPU.
func (c *vCPU) setSignalMask() error {
// The layout of this structure implies that it will not necessarily be
// the same layout chosen by the Go compiler. It gets fudged here.
var data struct {
length uint32
mask1 uint32
mask2 uint32
_ uint32
}
data.length = 8 // Fixed sigset size.
data.mask1 = ^uint32(bounceSignalMask & 0xffffffff)
data.mask2 = ^uint32(bounceSignalMask >> 32)
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_SIGNAL_MASK,
uintptr(unsafe.Pointer(&data))); errno != 0 {
return fmt.Errorf("error setting signal mask: %v", errno)
}
return nil
}
// SwitchToUser unpacks architectural-details.
func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts, info *arch.SignalInfo) (usermem.AccessType, error) {
// Check for canonical addresses.
if regs := switchOpts.Registers; !ring0.IsCanonical(regs.Pc) {
return nonCanonical(regs.Pc, int32(syscall.SIGSEGV), info)
} else if !ring0.IsCanonical(regs.Sp) {
return nonCanonical(regs.Sp, int32(syscall.SIGBUS), info)
}
var vector ring0.Vector
ttbr0App := switchOpts.PageTables.TTBR0_EL1(false, 0)
c.SetTtbr0App(uintptr(ttbr0App))
// TODO(gvisor.dev/issue/1238): full context-switch supporting for Arm64.
// The Arm64 user-mode execution state consists of:
// x0-x30
// PC, SP, PSTATE
// V0-V31: 32 128-bit registers for floating point, and simd
// FPSR
// TPIDR_EL0, used for TLS
appRegs := switchOpts.Registers
c.SetAppAddr(ring0.KernelStartAddress | uintptr(unsafe.Pointer(appRegs)))
entersyscall()
bluepill(c)
vector = c.CPU.SwitchToUser(switchOpts)
exitsyscall()
switch vector {
case ring0.Syscall:
// Fast path: system call executed.
return usermem.NoAccess, nil
case ring0.PageFault:
return c.fault(int32(syscall.SIGSEGV), info)
case 0xaa:
return usermem.NoAccess, nil
default:
return usermem.NoAccess, platform.ErrContextSignal
}
}
-40
View File
@@ -87,46 +87,6 @@ func unmapRunData(r *runData) error {
return nil
}
// setUserRegisters sets user registers in the vCPU.
func (c *vCPU) setUserRegisters(uregs *userRegs) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_REGS,
uintptr(unsafe.Pointer(uregs))); errno != 0 {
return fmt.Errorf("error setting user registers: %v", errno)
}
return nil
}
// getUserRegisters reloads user registers in the vCPU.
//
// This is safe to call from a nosplit context.
//
//go:nosplit
func (c *vCPU) getUserRegisters(uregs *userRegs) syscall.Errno {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_GET_REGS,
uintptr(unsafe.Pointer(uregs))); errno != 0 {
return errno
}
return 0
}
// setSystemRegisters sets system registers.
func (c *vCPU) setSystemRegisters(sregs *systemRegs) error {
if _, _, errno := syscall.RawSyscall(
syscall.SYS_IOCTL,
uintptr(c.fd),
_KVM_SET_SREGS,
uintptr(unsafe.Pointer(sregs))); errno != 0 {
return fmt.Errorf("error setting system registers: %v", errno)
}
return nil
}
// atomicAddressSpace is an atomic address space pointer.
type atomicAddressSpace struct {
pointer unsafe.Pointer