kvm: add detailed traces on vCPU errors.

This improves debuggability greatly.

PiperOrigin-RevId: 219551560
Change-Id: I2ecaffdd1c17b0d9f25911538ea6f693e2bc699f
This commit is contained in:
Adin Scannell
2018-10-31 15:50:10 -07:00
committed by Shentubot
parent ccc3d7ca11
commit c4bbb54168
8 changed files with 142 additions and 37 deletions
+45 -3
View File
@@ -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()
}
+6
View File
@@ -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)
}
+19 -10
View File
@@ -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
}
}
}
+1
View File
@@ -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
+3
View File
@@ -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(
+40
View File
@@ -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