diff options
author | Adin Scannell <ascannell@google.com> | 2018-10-31 15:49:10 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-10-31 15:50:10 -0700 |
commit | c4bbb54168a9014048d2144110e70daf5a5b8211 (patch) | |
tree | fa82c830fa173e68d10772430be165f278123c91 /pkg/sentry | |
parent | ccc3d7ca11a2a623587c651a6690aaa46d2c2665 (diff) |
kvm: add detailed traces on vCPU errors.
This improves debuggability greatly.
PiperOrigin-RevId: 219551560
Change-Id: I2ecaffdd1c17b0d9f25911538ea6f693e2bc699f
Diffstat (limited to 'pkg/sentry')
-rw-r--r-- | pkg/sentry/platform/kvm/bluepill.go | 48 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/bluepill_amd64.s | 6 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go | 28 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/bluepill_unsafe.go | 29 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/kvm_const.go | 1 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/machine.go | 3 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/machine_amd64_unsafe.go | 24 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/machine_unsafe.go | 40 |
8 files changed, 142 insertions, 37 deletions
diff --git a/pkg/sentry/platform/kvm/bluepill.go b/pkg/sentry/platform/kvm/bluepill.go index 9f1c9510b..d98ec8377 100644 --- a/pkg/sentry/platform/kvm/bluepill.go +++ b/pkg/sentry/platform/kvm/bluepill.go @@ -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() } diff --git a/pkg/sentry/platform/kvm/bluepill_amd64.s b/pkg/sentry/platform/kvm/bluepill_amd64.s index ec017f6c2..65b01f358 100644 --- a/pkg/sentry/platform/kvm/bluepill_amd64.s +++ b/pkg/sentry/platform/kvm/bluepill_amd64.s @@ -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) diff --git a/pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go b/pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go index cd00a47f2..21de2488e 100644 --- a/pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go +++ b/pkg/sentry/platform/kvm/bluepill_amd64_unsafe.go @@ -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) +} diff --git a/pkg/sentry/platform/kvm/bluepill_unsafe.go b/pkg/sentry/platform/kvm/bluepill_unsafe.go index 747a95997..77cf7e800 100644 --- a/pkg/sentry/platform/kvm/bluepill_unsafe.go +++ b/pkg/sentry/platform/kvm/bluepill_unsafe.go @@ -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 } } } diff --git a/pkg/sentry/platform/kvm/kvm_const.go b/pkg/sentry/platform/kvm/kvm_const.go index 8c53c6f06..cac8d9937 100644 --- a/pkg/sentry/platform/kvm/kvm_const.go +++ b/pkg/sentry/platform/kvm/kvm_const.go @@ -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 diff --git a/pkg/sentry/platform/kvm/machine.go b/pkg/sentry/platform/kvm/machine.go index fc7ad258f..4ba3a185a 100644 --- a/pkg/sentry/platform/kvm/machine.go +++ b/pkg/sentry/platform/kvm/machine.go @@ -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. diff --git a/pkg/sentry/platform/kvm/machine_amd64_unsafe.go b/pkg/sentry/platform/kvm/machine_amd64_unsafe.go index 50e513f3b..8ebd4ab71 100644 --- a/pkg/sentry/platform/kvm/machine_amd64_unsafe.go +++ b/pkg/sentry/platform/kvm/machine_amd64_unsafe.go @@ -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( diff --git a/pkg/sentry/platform/kvm/machine_unsafe.go b/pkg/sentry/platform/kvm/machine_unsafe.go index 38c1f102f..22ae60b63 100644 --- a/pkg/sentry/platform/kvm/machine_unsafe.go +++ b/pkg/sentry/platform/kvm/machine_unsafe.go @@ -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 |