github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/platform/kvm/bluepill_fault.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kvm
    16  
    17  import (
    18  	"sync/atomic"
    19  
    20  	"golang.org/x/sys/unix"
    21  	"github.com/SagerNet/gvisor/pkg/hostarch"
    22  )
    23  
    24  const (
    25  	// faultBlockSize is the size used for servicing memory faults.
    26  	//
    27  	// This should be large enough to avoid frequent faults and avoid using
    28  	// all available KVM slots (~512), but small enough that KVM does not
    29  	// complain about slot sizes (~4GB). See handleBluepillFault for how
    30  	// this block is used.
    31  	faultBlockSize = 2 << 30
    32  
    33  	// faultBlockMask is the mask for the fault blocks.
    34  	//
    35  	// This must be typed to avoid overflow complaints (ugh).
    36  	faultBlockMask = ^uintptr(faultBlockSize - 1)
    37  )
    38  
    39  // yield yields the CPU.
    40  //
    41  //go:nosplit
    42  func yield() {
    43  	unix.RawSyscall(unix.SYS_SCHED_YIELD, 0, 0, 0)
    44  }
    45  
    46  // calculateBluepillFault calculates the fault address range.
    47  //
    48  //go:nosplit
    49  func calculateBluepillFault(physical uintptr, phyRegions []physicalRegion) (virtualStart, physicalStart, length uintptr, ok bool) {
    50  	alignedPhysical := physical &^ uintptr(hostarch.PageSize-1)
    51  	for _, pr := range phyRegions {
    52  		end := pr.physical + pr.length
    53  		if physical < pr.physical || physical >= end {
    54  			continue
    55  		}
    56  
    57  		// Adjust the block to match our size.
    58  		physicalStart = alignedPhysical & faultBlockMask
    59  		if physicalStart < pr.physical {
    60  			// Bound the starting point to the start of the region.
    61  			physicalStart = pr.physical
    62  		}
    63  		virtualStart = pr.virtual + (physicalStart - pr.physical)
    64  		physicalEnd := physicalStart + faultBlockSize
    65  		if physicalEnd > end {
    66  			physicalEnd = end
    67  		}
    68  		length = physicalEnd - physicalStart
    69  		return virtualStart, physicalStart, length, true
    70  	}
    71  
    72  	return 0, 0, 0, false
    73  }
    74  
    75  // handleBluepillFault handles a physical fault.
    76  //
    77  // The corresponding virtual address is returned. This may throw on error.
    78  //
    79  //go:nosplit
    80  func handleBluepillFault(m *machine, physical uintptr, phyRegions []physicalRegion, flags uint32) (uintptr, bool) {
    81  	// Paging fault: we need to map the underlying physical pages for this
    82  	// fault. This all has to be done in this function because we're in a
    83  	// signal handler context. (We can't call any functions that might
    84  	// split the stack.)
    85  	virtualStart, physicalStart, length, ok := calculateBluepillFault(physical, phyRegions)
    86  	if !ok {
    87  		return 0, false
    88  	}
    89  
    90  	// Set the KVM slot.
    91  	//
    92  	// First, we need to acquire the exclusive right to set a slot.  See
    93  	// machine.nextSlot for information about the protocol.
    94  	slot := atomic.SwapUint32(&m.nextSlot, ^uint32(0))
    95  	for slot == ^uint32(0) {
    96  		yield() // Race with another call.
    97  		slot = atomic.SwapUint32(&m.nextSlot, ^uint32(0))
    98  	}
    99  	errno := m.setMemoryRegion(int(slot), physicalStart, length, virtualStart, flags)
   100  	if errno == 0 {
   101  		// Store the physical address in the slot. This is used to
   102  		// avoid calls to handleBluepillFault in the future (see
   103  		// machine.mapPhysical).
   104  		atomic.StoreUintptr(&m.usedSlots[slot], physical)
   105  		// Successfully added region; we can increment nextSlot and
   106  		// allow another set to proceed here.
   107  		atomic.StoreUint32(&m.nextSlot, slot+1)
   108  		return virtualStart + (physical - physicalStart), true
   109  	}
   110  
   111  	// Release our slot (still available).
   112  	atomic.StoreUint32(&m.nextSlot, slot)
   113  
   114  	switch errno {
   115  	case unix.EEXIST:
   116  		// The region already exists. It's possible that we raced with
   117  		// another vCPU here. We just revert nextSlot and return true,
   118  		// because this must have been satisfied by some other vCPU.
   119  		return virtualStart + (physical - physicalStart), true
   120  	case unix.EINVAL:
   121  		throw("set memory region failed; out of slots")
   122  	case unix.ENOMEM:
   123  		throw("set memory region failed: out of memory")
   124  	case unix.EFAULT:
   125  		throw("set memory region failed: invalid physical range")
   126  	default:
   127  		throw("set memory region failed: unknown reason")
   128  	}
   129  
   130  	panic("unreachable")
   131  }