mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
kvm: add detailed traces on vCPU errors.
This improves debuggability greatly. PiperOrigin-RevId: 219551560 Change-Id: I2ecaffdd1c17b0d9f25911538ea6f693e2bc699f
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/platform/safecopy"
|
||||
)
|
||||
|
||||
@@ -28,14 +29,55 @@ func bluepill(*vCPU)
|
||||
// sighandler is the signal entry point.
|
||||
func sighandler()
|
||||
|
||||
// savedHandler is a pointer to the previous handler.
|
||||
// dieTrampoline is the assembly trampoline. This calls dieHandler.
|
||||
//
|
||||
// This is called by bluepillHandler.
|
||||
var savedHandler uintptr
|
||||
// This uses an architecture-specific calling convention, documented in
|
||||
// dieArchSetup and the assembly implementation for dieTrampoline.
|
||||
func dieTrampoline()
|
||||
|
||||
var (
|
||||
// savedHandler is a pointer to the previous handler.
|
||||
//
|
||||
// This is called by bluepillHandler.
|
||||
savedHandler uintptr
|
||||
|
||||
// dieTrampolineAddr is the address of dieTrampoline.
|
||||
dieTrampolineAddr uintptr
|
||||
)
|
||||
|
||||
// dieHandler is called by dieTrampoline.
|
||||
//
|
||||
//go:nosplit
|
||||
func dieHandler(c *vCPU) {
|
||||
throw(c.dieMessage)
|
||||
}
|
||||
|
||||
// die is called to set the vCPU up to panic.
|
||||
//
|
||||
// This loads vCPU state, and sets up a call for the trampoline.
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) die(context *arch.SignalContext64, msg string) {
|
||||
// Save the death message, which will be thrown.
|
||||
c.dieMessage = msg
|
||||
|
||||
// Reload all registers to have an accurate stack trace when we return
|
||||
// to host mode. This means that the stack should be unwound correctly.
|
||||
var guestRegs userRegs
|
||||
if errno := c.getUserRegisters(&guestRegs); errno != 0 {
|
||||
throw(msg)
|
||||
}
|
||||
|
||||
// Setup the trampoline.
|
||||
dieArchSetup(c, context, &guestRegs)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// Extract the address for the trampoline.
|
||||
dieTrampolineAddr = reflect.ValueOf(dieTrampoline).Pointer()
|
||||
}
|
||||
|
||||
@@ -85,3 +85,9 @@ fallback:
|
||||
XORQ CX, CX
|
||||
MOVQ ·savedHandler(SB), AX
|
||||
JMP AX
|
||||
|
||||
// dieTrampoline: see bluepill.go, bluepill_amd64_unsafe.go for documentation.
|
||||
TEXT ·dieTrampoline(SB),NOSPLIT,$0
|
||||
PUSHQ BX // First argument (vCPU).
|
||||
PUSHQ AX // Fake the old RIP as caller.
|
||||
JMP ·dieHandler(SB)
|
||||
|
||||
@@ -20,9 +20,37 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
|
||||
"gvisor.googlesource.com/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 initialies the state for dieTrampoline.
|
||||
//
|
||||
// The amd64 dieTrampoline requires the vCPU to be set in BX, and the last RIP
|
||||
// to be in AX. The trampoline then simulates a call to dieHandler from the
|
||||
// provided RIP.
|
||||
//
|
||||
//go:nosplit
|
||||
func dieArchSetup(c *vCPU, context *arch.SignalContext64, guestRegs *userRegs) {
|
||||
// If the vCPU is in user mode, we set the stack to the stored stack
|
||||
// value in the vCPU itself. We don't want to unwind the user stack.
|
||||
if guestRegs.RFLAGS&ring0.UserFlagsSet == ring0.UserFlagsSet {
|
||||
regs := c.CPU.Registers()
|
||||
context.Rax = regs.Rax
|
||||
context.Rsp = regs.Rsp
|
||||
context.Rbp = regs.Rbp
|
||||
} else {
|
||||
context.Rax = guestRegs.RIP
|
||||
context.Rsp = guestRegs.RSP
|
||||
context.Rbp = guestRegs.RBP
|
||||
context.Eflags = guestRegs.RFLAGS
|
||||
}
|
||||
context.Rbx = uint64(uintptr(unsafe.Pointer(c)))
|
||||
context.Rip = uint64(dieTrampolineAddr)
|
||||
}
|
||||
|
||||
@@ -113,9 +113,11 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
|
||||
switch c.runData.exitReason {
|
||||
case _KVM_EXIT_EXCEPTION:
|
||||
throw("exception")
|
||||
c.die(bluepillArchContext(context), "exception")
|
||||
return
|
||||
case _KVM_EXIT_IO:
|
||||
throw("I/O")
|
||||
c.die(bluepillArchContext(context), "I/O")
|
||||
return
|
||||
case _KVM_EXIT_INTERNAL_ERROR:
|
||||
// An internal error is typically thrown when emulation
|
||||
// fails. This can occur via the MMIO path below (and
|
||||
@@ -123,9 +125,11 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
// are not mapped). We would actually prefer that no
|
||||
// emulation occur, and don't mind at all if it fails.
|
||||
case _KVM_EXIT_HYPERCALL:
|
||||
throw("hypercall")
|
||||
c.die(bluepillArchContext(context), "hypercall")
|
||||
return
|
||||
case _KVM_EXIT_DEBUG:
|
||||
throw("debug")
|
||||
c.die(bluepillArchContext(context), "debug")
|
||||
return
|
||||
case _KVM_EXIT_HLT:
|
||||
// Copy out registers.
|
||||
bluepillArchExit(c, bluepillArchContext(context))
|
||||
@@ -145,9 +149,11 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
atomic.AddUint32(&c.faults, 1)
|
||||
|
||||
// For MMIO, the physical address is the first data item.
|
||||
virtual, ok := handleBluepillFault(c.machine, uintptr(c.runData.data[0]))
|
||||
physical := uintptr(c.runData.data[0])
|
||||
virtual, ok := handleBluepillFault(c.machine, physical)
|
||||
if !ok {
|
||||
throw("physical address not valid")
|
||||
c.die(bluepillArchContext(context), "invalid physical address")
|
||||
return
|
||||
}
|
||||
|
||||
// We now need to fill in the data appropriately. KVM
|
||||
@@ -158,7 +164,7 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
// not create invalid page table mappings.
|
||||
data := (*[8]byte)(unsafe.Pointer(&c.runData.data[1]))
|
||||
length := (uintptr)((uint32)(c.runData.data[2]))
|
||||
write := (uint8)((c.runData.data[2] >> 32 & 0xff)) != 0
|
||||
write := (uint8)(((c.runData.data[2] >> 32) & 0xff)) != 0
|
||||
for i := uintptr(0); i < length; i++ {
|
||||
b := bytePtr(uintptr(virtual) + i)
|
||||
if write {
|
||||
@@ -182,11 +188,14 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
// Clear previous injection request.
|
||||
c.runData.requestInterruptWindow = 0
|
||||
case _KVM_EXIT_SHUTDOWN:
|
||||
throw("shutdown")
|
||||
c.die(bluepillArchContext(context), "shutdown")
|
||||
return
|
||||
case _KVM_EXIT_FAIL_ENTRY:
|
||||
throw("entry failed")
|
||||
c.die(bluepillArchContext(context), "entry failed")
|
||||
return
|
||||
default:
|
||||
throw("unknown failure")
|
||||
c.die(bluepillArchContext(context), "unknown")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ const (
|
||||
_KVM_SET_USER_MEMORY_REGION = 0x4020ae46
|
||||
_KVM_SET_REGS = 0x4090ae82
|
||||
_KVM_SET_SREGS = 0x4138ae84
|
||||
_KVM_GET_REGS = 0x8090ae81
|
||||
_KVM_GET_SUPPORTED_CPUID = 0xc008ae05
|
||||
_KVM_SET_CPUID2 = 0x4008ae90
|
||||
_KVM_SET_SIGNAL_MASK = 0x4004ae8b
|
||||
|
||||
@@ -120,6 +120,9 @@ type vCPU struct {
|
||||
|
||||
// vCPUArchState is the architecture-specific state.
|
||||
vCPUArchState
|
||||
|
||||
// dieMessage is thrown from die.
|
||||
dieMessage string
|
||||
}
|
||||
|
||||
// newVCPU creates a returns a new vCPU.
|
||||
|
||||
@@ -73,30 +73,6 @@ func (c *vCPU) loadSegments(tid uint64) {
|
||||
atomic.StoreUint64(&c.tid, tid)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// setCPUID sets the CPUID to be used by the guest.
|
||||
func (c *vCPU) setCPUID() error {
|
||||
if _, _, errno := syscall.RawSyscall(
|
||||
|
||||
@@ -57,6 +57,46 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user