enable ring0/pagetables to support arm64

Signed-off-by: Bin Lu <bin.lu@arm.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/891 from lubinszARM:pr_pagetable 2385de75a8662af3ab1ae289dd74dd0e5dcfaf66
PiperOrigin-RevId: 282013224
This commit is contained in:
lubinszARM
2019-11-22 12:05:35 -08:00
committed by gVisor bot
parent 4e27ba372e
commit 07635d20d4
7 changed files with 684 additions and 13 deletions
+12 -4
View File
@@ -1,14 +1,17 @@
load("//tools/go_stateify:defs.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_test")
load("//tools/go_stateify:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template", "go_template_instance")
package(licenses = ["notice"])
config_setting(
name = "aarch64",
constraint_values = ["@bazel_tools//platforms:aarch64"],
)
go_template(
name = "generic_walker",
srcs = [
"walker_amd64.go",
],
srcs = ["walker_amd64.go"],
opt_types = [
"Visitor",
],
@@ -76,9 +79,13 @@ go_library(
"allocator.go",
"allocator_unsafe.go",
"pagetables.go",
"pagetables_aarch64.go",
"pagetables_amd64.go",
"pagetables_arm64.go",
"pagetables_x86.go",
"pcids_x86.go",
"walker_amd64.go",
"walker_arm64.go",
"walker_empty.go",
"walker_lookup.go",
"walker_map.go",
@@ -97,6 +104,7 @@ go_test(
size = "small",
srcs = [
"pagetables_amd64_test.go",
"pagetables_arm64_test.go",
"pagetables_test.go",
"walker_check.go",
],
@@ -48,15 +48,6 @@ func New(a Allocator) *PageTables {
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.
@@ -0,0 +1,212 @@
// Copyright 2019 The gVisor Authors.
//
// 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.
// +build arm64
package pagetables
import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/sentry/usermem"
)
// archPageTables is architecture-specific data.
type archPageTables struct {
// root is the pagetable root for kernel space.
root *PTEs
// rootPhysical is the cached physical address of the root.
//
// This is saved only to prevent constant translation.
rootPhysical uintptr
asid uint16
}
// TTBR0_EL1 returns the translation table base register 0.
//
//go:nosplit
func (p *PageTables) TTBR0_EL1(noFlush bool, asid uint16) uint64 {
return uint64(p.rootPhysical) | (uint64(asid)&ttbrASIDMask)<<ttbrASIDOffset
}
// TTBR1_EL1 returns the translation table base register 1.
//
//go:nosplit
func (p *PageTables) TTBR1_EL1(noFlush bool, asid uint16) uint64 {
return uint64(p.archPageTables.rootPhysical) | (uint64(asid)&ttbrASIDMask)<<ttbrASIDOffset
}
// Bits in page table entries.
const (
typeTable = 0x3 << 0
typeSect = 0x1 << 0
typePage = 0x3 << 0
pteValid = 0x1 << 0
pteTableBit = 0x1 << 1
pteTypeMask = 0x3 << 0
present = pteValid | pteTableBit
user = 0x1 << 6 /* AP[1] */
readOnly = 0x1 << 7 /* AP[2] */
accessed = 0x1 << 10
dbm = 0x1 << 51
writable = dbm
cont = 0x1 << 52
pxn = 0x1 << 53
xn = 0x1 << 54
dirty = 0x1 << 55
nG = 0x1 << 11
shared = 0x3 << 8
)
const (
mtNormal = 0x4 << 2
)
const (
executeDisable = xn
optionMask = 0xfff | 0xfff<<48
protDefault = accessed | shared | mtNormal
)
// MapOpts are x86 options.
type MapOpts struct {
// AccessType defines permissions.
AccessType usermem.AccessType
// Global indicates the page is globally accessible.
Global bool
// User indicates the page is a user page.
User bool
}
// PTE is a page table entry.
type PTE uintptr
// Clear clears this PTE, including sect page information.
//
//go:nosplit
func (p *PTE) Clear() {
atomic.StoreUintptr((*uintptr)(p), 0)
}
// Valid returns true iff this entry is valid.
//
//go:nosplit
func (p *PTE) Valid() bool {
return atomic.LoadUintptr((*uintptr)(p))&present != 0
}
// Opts returns the PTE options.
//
// These are all options except Valid and Sect.
//
//go:nosplit
func (p *PTE) Opts() MapOpts {
v := atomic.LoadUintptr((*uintptr)(p))
return MapOpts{
AccessType: usermem.AccessType{
Read: true,
Write: v&readOnly == 0,
Execute: v&xn == 0,
},
Global: v&nG == 0,
User: v&user != 0,
}
}
// SetSect sets this page as a sect page.
//
// The page must not be valid or a panic will result.
//
//go:nosplit
func (p *PTE) SetSect() {
if p.Valid() {
// This is not allowed.
panic("SetSect called on valid page!")
}
atomic.StoreUintptr((*uintptr)(p), typeSect)
}
// IsSect returns true iff this page is a sect page.
//
//go:nosplit
func (p *PTE) IsSect() bool {
return atomic.LoadUintptr((*uintptr)(p))&pteTypeMask == typeSect
}
// Set sets this PTE value.
//
// This does not change the sect page property.
//
//go:nosplit
func (p *PTE) Set(addr uintptr, opts MapOpts) {
if !opts.AccessType.Any() {
p.Clear()
return
}
v := (addr &^ optionMask) | protDefault | nG | readOnly
if p.IsSect() {
// Note that this is inherited from the previous instance. Set
// does not change the value of Sect. See above.
v |= typeSect
} else {
v |= typePage
}
if opts.Global {
v = v &^ nG
}
if opts.AccessType.Execute {
v = v &^ executeDisable
} else {
v |= executeDisable
}
if opts.AccessType.Write {
v = v &^ readOnly
}
if opts.User {
v |= user
} else {
v = v &^ user
}
atomic.StoreUintptr((*uintptr)(p), v)
}
// setPageTable sets this PTE value and forces the write bit and sect bit to
// be cleared. This is used explicitly for breaking sect pages.
//
//go:nosplit
func (p *PTE) setPageTable(pt *PageTables, ptes *PTEs) {
addr := pt.Allocator.PhysicalFor(ptes)
if addr&^optionMask != addr {
// This should never happen.
panic("unaligned physical address!")
}
v := addr | typeTable | protDefault
atomic.StoreUintptr((*uintptr)(p), v)
}
// Address extracts the address. This should only be used if Valid returns true.
//
//go:nosplit
func (p *PTE) Address() uintptr {
return atomic.LoadUintptr((*uintptr)(p)) &^ optionMask
}
@@ -41,5 +41,14 @@ const (
entriesPerPage = 512
)
// 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)
}
// PTEs is a collection of entries.
type PTEs [entriesPerPage]PTE
@@ -0,0 +1,57 @@
// Copyright 2019 The gVisor Authors.
//
// 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
// Address constraints.
//
// The lowerTop and upperBottom currently apply to four-level pagetables;
// additional refactoring would be necessary to support five-level pagetables.
const (
lowerTop = 0x0000ffffffffffff
upperBottom = 0xffff000000000000
pteShift = 12
pmdShift = 21
pudShift = 30
pgdShift = 39
pteMask = 0x1ff << pteShift
pmdMask = 0x1ff << pmdShift
pudMask = 0x1ff << pudShift
pgdMask = 0x1ff << pgdShift
pteSize = 1 << pteShift
pmdSize = 1 << pmdShift
pudSize = 1 << pudShift
pgdSize = 1 << pgdShift
ttbrASIDOffset = 55
ttbrASIDMask = 0xff
entriesPerPage = 512
)
// 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)
p.archPageTables.root = p.Allocator.NewPTEs()
p.archPageTables.rootPhysical = p.Allocator.PhysicalFor(p.archPageTables.root)
}
// PTEs is a collection of entries.
type PTEs [entriesPerPage]PTE
@@ -0,0 +1,80 @@
// Copyright 2019 The gVisor Authors.
//
// 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.
// +build arm64
package pagetables
import (
"testing"
"gvisor.dev/gvisor/pkg/sentry/usermem"
)
func Test2MAnd4K(t *testing.T) {
pt := New(NewRuntimeAllocator())
// Map a small page and a huge page.
pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite, User: true}, pteSize*42)
pt.Map(0x0000ff0000000000, pmdSize, MapOpts{AccessType: usermem.Read, User: true}, pmdSize*47)
pt.Map(0xffff000000400000, pteSize, MapOpts{AccessType: usermem.ReadWrite, User: false}, pteSize*42)
pt.Map(0xffffff0000000000, pmdSize, MapOpts{AccessType: usermem.Read, User: false}, pmdSize*47)
checkMappings(t, pt, []mapping{
{0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite, User: true}},
{0x0000ff0000000000, pmdSize, pmdSize * 47, MapOpts{AccessType: usermem.Read, User: true}},
{0xffff000000400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite, User: false}},
{0xffffff0000000000, pmdSize, pmdSize * 47, MapOpts{AccessType: usermem.Read, User: false}},
})
}
func Test1GAnd4K(t *testing.T) {
pt := New(NewRuntimeAllocator())
// Map a small page and a super page.
pt.Map(0x400000, pteSize, MapOpts{AccessType: usermem.ReadWrite, User: true}, pteSize*42)
pt.Map(0x0000ff0000000000, pudSize, MapOpts{AccessType: usermem.Read, User: true}, pudSize*47)
checkMappings(t, pt, []mapping{
{0x400000, pteSize, pteSize * 42, MapOpts{AccessType: usermem.ReadWrite, User: true}},
{0x0000ff0000000000, pudSize, pudSize * 47, MapOpts{AccessType: usermem.Read, User: true}},
})
}
func TestSplit1GPage(t *testing.T) {
pt := New(NewRuntimeAllocator())
// Map a super page and knock out the middle.
pt.Map(0x0000ff0000000000, pudSize, MapOpts{AccessType: usermem.Read, User: true}, pudSize*42)
pt.Unmap(usermem.Addr(0x0000ff0000000000+pteSize), pudSize-(2*pteSize))
checkMappings(t, pt, []mapping{
{0x0000ff0000000000, pteSize, pudSize * 42, MapOpts{AccessType: usermem.Read, User: true}},
{0x0000ff0000000000 + pudSize - pteSize, pteSize, pudSize*42 + pudSize - pteSize, MapOpts{AccessType: usermem.Read, User: true}},
})
}
func TestSplit2MPage(t *testing.T) {
pt := New(NewRuntimeAllocator())
// Map a huge page and knock out the middle.
pt.Map(0x0000ff0000000000, pmdSize, MapOpts{AccessType: usermem.Read, User: true}, pmdSize*42)
pt.Unmap(usermem.Addr(0x0000ff0000000000+pteSize), pmdSize-(2*pteSize))
checkMappings(t, pt, []mapping{
{0x0000ff0000000000, pteSize, pmdSize * 42, MapOpts{AccessType: usermem.Read, User: true}},
{0x0000ff0000000000 + pmdSize - pteSize, pteSize, pmdSize*42 + pmdSize - pteSize, MapOpts{AccessType: usermem.Read, User: true}},
})
}
@@ -0,0 +1,314 @@
// Copyright 2019 The gVisor Authors.
//
// 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.
// +build arm64
package pagetables
// Visitor is a generic type.
type Visitor interface {
// visit is called on each PTE.
visit(start uintptr, pte *PTE, align uintptr)
// requiresAlloc indicates that new entries should be allocated within
// the walked range.
requiresAlloc() bool
// requiresSplit indicates that entries in the given range should be
// split if they are huge or jumbo pages.
requiresSplit() bool
}
// Walker walks page tables.
type Walker struct {
// pageTables are the tables to walk.
pageTables *PageTables
// Visitor is the set of arguments.
visitor Visitor
}
// iterateRange iterates over all appropriate levels of page tables for the given range.
//
// If requiresAlloc is true, then Set _must_ be called on all given PTEs. The
// exception is sect pages. If a valid sect page (huge or jumbo) cannot be
// installed, then the walk will continue to individual entries.
//
// This algorithm will attempt to maximize the use of sect pages whenever
// possible. Whether a sect page is provided will be clear through the range
// provided in the callback.
//
// Note that if requiresAlloc is true, then no gaps will be present. However,
// if alloc is not set, then the iteration will likely be full of gaps.
//
// Note that this function should generally be avoided in favor of Map, Unmap,
// etc. when not necessary.
//
// Precondition: start must be page-aligned.
//
// Precondition: start must be less than end.
//
// Precondition: If requiresAlloc is true, then start and end should not span
// non-canonical ranges. If they do, a panic will result.
//
//go:nosplit
func (w *Walker) iterateRange(start, end uintptr) {
if start%pteSize != 0 {
panic("unaligned start")
}
if end < start {
panic("start > end")
}
if start < lowerTop {
if end <= lowerTop {
w.iterateRangeCanonical(start, end)
} else if end > lowerTop && end <= upperBottom {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(start, lowerTop)
} else {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(start, lowerTop)
w.iterateRangeCanonical(upperBottom, end)
}
} else if start < upperBottom {
if end <= upperBottom {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
} else {
if w.visitor.requiresAlloc() {
panic("alloc spans non-canonical range")
}
w.iterateRangeCanonical(upperBottom, end)
}
} else {
w.iterateRangeCanonical(start, end)
}
}
// next returns the next address quantized by the given size.
//
//go:nosplit
func next(start uintptr, size uintptr) uintptr {
start &= ^(size - 1)
start += size
return start
}
// iterateRangeCanonical walks a canonical range.
//
//go:nosplit
func (w *Walker) iterateRangeCanonical(start, end uintptr) {
pgdEntryIndex := w.pageTables.root
if start >= upperBottom {
pgdEntryIndex = w.pageTables.archPageTables.root
}
for pgdIndex := (uint16((start & pgdMask) >> pgdShift)); start < end && pgdIndex < entriesPerPage; pgdIndex++ {
var (
pgdEntry = &pgdEntryIndex[pgdIndex]
pudEntries *PTEs
)
if !pgdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
start = next(start, pgdSize)
continue
}
// Allocate a new pgd.
pudEntries = w.pageTables.Allocator.NewPTEs()
pgdEntry.setPageTable(w.pageTables, pudEntries)
} else {
pudEntries = w.pageTables.Allocator.LookupPTEs(pgdEntry.Address())
}
// Map the next level.
clearPUDEntries := uint16(0)
for pudIndex := uint16((start & pudMask) >> pudShift); start < end && pudIndex < entriesPerPage; pudIndex++ {
var (
pudEntry = &pudEntries[pudIndex]
pmdEntries *PTEs
)
if !pudEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPUDEntries++
start = next(start, pudSize)
continue
}
// This level has 1-GB sect pages. Is this
// entire region at least as large as a single
// PUD entry? If so, we can skip allocating a
// new page for the pmd.
if start&(pudSize-1) == 0 && end-start >= pudSize {
pudEntry.SetSect()
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
if pudEntry.Valid() {
start = next(start, pudSize)
continue
}
}
// Allocate a new pud.
pmdEntries = w.pageTables.Allocator.NewPTEs()
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else if pudEntry.IsSect() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pudSize-1) != 0 || end < next(start, pudSize)) {
// Install the relevant entries.
pmdEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pmdEntries[index].SetSect()
pmdEntries[index].Set(
pudEntry.Address()+(pmdSize*uintptr(index)),
pudEntry.Opts())
}
pudEntry.setPageTable(w.pageTables, pmdEntries)
} else {
// A sect page to be checked directly.
w.visitor.visit(uintptr(start), pudEntry, pudSize-1)
// Might have been cleared.
if !pudEntry.Valid() {
clearPUDEntries++
}
// Note that the sect page was changed.
start = next(start, pudSize)
continue
}
} else {
pmdEntries = w.pageTables.Allocator.LookupPTEs(pudEntry.Address())
}
// Map the next level, since this is valid.
clearPMDEntries := uint16(0)
for pmdIndex := uint16((start & pmdMask) >> pmdShift); start < end && pmdIndex < entriesPerPage; pmdIndex++ {
var (
pmdEntry = &pmdEntries[pmdIndex]
pteEntries *PTEs
)
if !pmdEntry.Valid() {
if !w.visitor.requiresAlloc() {
// Skip over this entry.
clearPMDEntries++
start = next(start, pmdSize)
continue
}
// This level has 2-MB huge pages. If this
// region is contined in a single PMD entry?
// As above, we can skip allocating a new page.
if start&(pmdSize-1) == 0 && end-start >= pmdSize {
pmdEntry.SetSect()
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
if pmdEntry.Valid() {
start = next(start, pmdSize)
continue
}
}
// Allocate a new pmd.
pteEntries = w.pageTables.Allocator.NewPTEs()
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else if pmdEntry.IsSect() {
// Does this page need to be split?
if w.visitor.requiresSplit() && (start&(pmdSize-1) != 0 || end < next(start, pmdSize)) {
// Install the relevant entries.
pteEntries = w.pageTables.Allocator.NewPTEs()
for index := uint16(0); index < entriesPerPage; index++ {
pteEntries[index].Set(
pmdEntry.Address()+(pteSize*uintptr(index)),
pmdEntry.Opts())
}
pmdEntry.setPageTable(w.pageTables, pteEntries)
} else {
// A huge page to be checked directly.
w.visitor.visit(uintptr(start), pmdEntry, pmdSize-1)
// Might have been cleared.
if !pmdEntry.Valid() {
clearPMDEntries++
}
// Note that the huge page was changed.
start = next(start, pmdSize)
continue
}
} else {
pteEntries = w.pageTables.Allocator.LookupPTEs(pmdEntry.Address())
}
// Map the next level, since this is valid.
clearPTEEntries := uint16(0)
for pteIndex := uint16((start & pteMask) >> pteShift); start < end && pteIndex < entriesPerPage; pteIndex++ {
var (
pteEntry = &pteEntries[pteIndex]
)
if !pteEntry.Valid() && !w.visitor.requiresAlloc() {
clearPTEEntries++
start += pteSize
continue
}
// At this point, we are guaranteed that start%pteSize == 0.
w.visitor.visit(uintptr(start), pteEntry, pteSize-1)
if !pteEntry.Valid() {
if w.visitor.requiresAlloc() {
panic("PTE not set after iteration with requiresAlloc!")
}
clearPTEEntries++
}
// Note that the pte was changed.
start += pteSize
continue
}
// Check if we no longer need this page.
if clearPTEEntries == entriesPerPage {
pmdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pteEntries)
clearPMDEntries++
}
}
// Check if we no longer need this page.
if clearPMDEntries == entriesPerPage {
pudEntry.Clear()
w.pageTables.Allocator.FreePTEs(pmdEntries)
clearPUDEntries++
}
}
// Check if we no longer need this page.
if clearPUDEntries == entriesPerPage {
pgdEntry.Clear()
w.pageTables.Allocator.FreePTEs(pudEntries)
}
}
}