gvisor/pkg/sentry/platform/ring0/pagetables/pagetables.go

222 lines
5.5 KiB
Go

// Copyright 2018 Google Inc.
//
// 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 pagetables provides a generic implementation of pagetables.
//
// The core functions must be safe to call from a nosplit context. Furthermore,
// this pagetables implementation goes to lengths to ensure that all functions
// are free from runtime allocation. Calls to NewPTEs/FreePTEs may be made
// during walks, but these can be cached elsewhere if required.
package pagetables
import (
"gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
)
// PageTables is a set of page tables.
type PageTables struct {
// Allocator is used to allocate nodes.
Allocator Allocator
// root is the pagetable root.
root *PTEs
// rootPhysical is the cached physical address of the root.
//
// This is saved only to prevent constant translation.
rootPhysical uintptr
// archPageTables includes architecture-specific features.
archPageTables
}
// New returns new PageTables.
func New(a Allocator) *PageTables {
p := new(PageTables)
p.Init(a)
return p
}
// Init initializes a set of PageTables.
//
//go:nosplit
func (p *PageTables) Init(allocator Allocator) {
p.Allocator = allocator
p.root = p.Allocator.NewPTEs()
p.rootPhysical = p.Allocator.PhysicalFor(p.root)
}
// mapVisitor is used for map.
type mapVisitor struct {
target uintptr // Input.
physical uintptr // Input.
opts MapOpts // Input.
prev bool // Output.
}
// visit is used for map.
//
//go:nosplit
func (v *mapVisitor) visit(start uintptr, pte *PTE, align uintptr) {
p := v.physical + (start - uintptr(v.target))
if pte.Valid() && (pte.Address() != p || pte.Opts() != v.opts) {
v.prev = true
}
if p&align != 0 {
// We will install entries at a smaller granulaity if we don't
// install a valid entry here, however we must zap any existing
// entry to ensure this happens.
pte.Clear()
return
}
pte.Set(p, v.opts)
}
//go:nosplit
func (*mapVisitor) requiresAlloc() bool { return true }
//go:nosplit
func (*mapVisitor) requiresSplit() bool { return true }
// Map installs a mapping with the given physical address.
//
// True is returned iff there was a previous mapping in the range.
//
// Precondition: addr & length must be page-aligned, their sum must not overflow.
//
//go:nosplit
func (p *PageTables) Map(addr usermem.Addr, length uintptr, opts MapOpts, physical uintptr) bool {
if !opts.AccessType.Any() {
return p.Unmap(addr, length)
}
w := mapWalker{
pageTables: p,
visitor: mapVisitor{
target: uintptr(addr),
physical: physical,
opts: opts,
},
}
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.prev
}
// unmapVisitor is used for unmap.
type unmapVisitor struct {
count int
}
//go:nosplit
func (*unmapVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*unmapVisitor) requiresSplit() bool { return true }
// visit unmaps the given entry.
//
//go:nosplit
func (v *unmapVisitor) visit(start uintptr, pte *PTE, align uintptr) {
pte.Clear()
v.count++
}
// Unmap unmaps the given range.
//
// True is returned iff there was a previous mapping in the range.
//
// Precondition: addr & length must be page-aligned.
//
//go:nosplit
func (p *PageTables) Unmap(addr usermem.Addr, length uintptr) bool {
w := unmapWalker{
pageTables: p,
visitor: unmapVisitor{
count: 0,
},
}
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.count > 0
}
// emptyVisitor is used for emptiness checks.
type emptyVisitor struct {
count int
}
//go:nosplit
func (*emptyVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*emptyVisitor) requiresSplit() bool { return false }
// visit unmaps the given entry.
//
//go:nosplit
func (v *emptyVisitor) visit(start uintptr, pte *PTE, align uintptr) {
v.count++
}
// IsEmpty checks if the given range is empty.
//
// Precondition: addr & length must be page-aligned.
//
//go:nosplit
func (p *PageTables) IsEmpty(addr usermem.Addr, length uintptr) bool {
w := emptyWalker{
pageTables: p,
}
w.iterateRange(uintptr(addr), uintptr(addr)+length)
return w.visitor.count == 0
}
// lookupVisitor is used for lookup.
type lookupVisitor struct {
target uintptr // Input.
physical uintptr // Output.
opts MapOpts // Output.
}
// visit matches the given address.
//
//go:nosplit
func (v *lookupVisitor) visit(start uintptr, pte *PTE, align uintptr) {
if !pte.Valid() {
return
}
v.physical = pte.Address() + (start - uintptr(v.target))
v.opts = pte.Opts()
}
//go:nosplit
func (*lookupVisitor) requiresAlloc() bool { return false }
//go:nosplit
func (*lookupVisitor) requiresSplit() bool { return false }
// Lookup returns the physical address for the given virtual address.
//
//go:nosplit
func (p *PageTables) Lookup(addr usermem.Addr) (physical uintptr, opts MapOpts) {
mask := uintptr(usermem.PageSize - 1)
offset := uintptr(addr) & mask
w := lookupWalker{
pageTables: p,
visitor: lookupVisitor{
target: uintptr(addr &^ usermem.Addr(mask)),
},
}
w.iterateRange(uintptr(addr), uintptr(addr)+1)
return w.visitor.physical + offset, w.visitor.opts
}