Skip to content

Commit 983a55a

Browse files
amscannegvisor-bot
authored andcommitted
Support stdlib analyzers with nogo.
This immediately revealed an escape analysis violation (!), where the sync.Map was being used in a context that escapes were not allowed. This is a relatively minor fix and is included. PiperOrigin-RevId: 328611237
1 parent 366f1a8 commit 983a55a

File tree

17 files changed

+560
-177
lines changed

17 files changed

+560
-177
lines changed

pkg/sentry/platform/kvm/bluepill_fault.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func handleBluepillFault(m *machine, physical uintptr, phyRegions []physicalRegi
9898
}
9999
errno := m.setMemoryRegion(int(slot), physicalStart, length, virtualStart, flags)
100100
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)
101105
// Successfully added region; we can increment nextSlot and
102106
// allow another set to proceed here.
103107
atomic.StoreUint32(&m.nextSlot, slot+1)

pkg/sentry/platform/kvm/kvm_const.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const (
5656

5757
// KVM capability options.
5858
const (
59+
_KVM_CAP_MAX_MEMSLOTS = 0x0a
5960
_KVM_CAP_MAX_VCPUS = 0x42
6061
_KVM_CAP_ARM_VM_IPA_SIZE = 0xa5
6162
_KVM_CAP_VCPU_EVENTS = 0x29
@@ -64,6 +65,7 @@ const (
6465

6566
// KVM limits.
6667
const (
68+
_KVM_NR_MEMSLOTS = 0x100
6769
_KVM_NR_VCPUS = 0xff
6870
_KVM_NR_INTERRUPTS = 0x100
6971
_KVM_NR_CPUID_ENTRIES = 0x100

pkg/sentry/platform/kvm/machine.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ type machine struct {
4343
// kernel is the set of global structures.
4444
kernel ring0.Kernel
4545

46-
// mappingCache is used for mapPhysical.
47-
mappingCache sync.Map
48-
4946
// mu protects vCPUs.
5047
mu sync.RWMutex
5148

@@ -63,6 +60,12 @@ type machine struct {
6360
// maxVCPUs is the maximum number of vCPUs supported by the machine.
6461
maxVCPUs int
6562

63+
// maxSlots is the maximum number of memory slots supported by the machine.
64+
maxSlots int
65+
66+
// usedSlots is the set of used physical addresses (sorted).
67+
usedSlots []uintptr
68+
6669
// nextID is the next vCPU ID.
6770
nextID uint32
6871
}
@@ -184,18 +187,27 @@ func newMachine(vm int) (*machine, error) {
184187
PageTables: pagetables.New(newAllocator()),
185188
})
186189

190+
// Pull the maximum vCPUs.
187191
maxVCPUs, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(m.fd), _KVM_CHECK_EXTENSION, _KVM_CAP_MAX_VCPUS)
188192
if errno != 0 {
189193
m.maxVCPUs = _KVM_NR_VCPUS
190194
} else {
191195
m.maxVCPUs = int(maxVCPUs)
192196
}
193197
log.Debugf("The maximum number of vCPUs is %d.", m.maxVCPUs)
194-
195-
// Create the vCPUs map/slices.
196198
m.vCPUsByTID = make(map[uint64]*vCPU)
197199
m.vCPUsByID = make([]*vCPU, m.maxVCPUs)
198200

201+
// Pull the maximum slots.
202+
maxSlots, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(m.fd), _KVM_CHECK_EXTENSION, _KVM_CAP_MAX_MEMSLOTS)
203+
if errno != 0 {
204+
m.maxSlots = _KVM_NR_MEMSLOTS
205+
} else {
206+
m.maxSlots = int(maxSlots)
207+
}
208+
log.Debugf("The maximum number of slots is %d.", m.maxSlots)
209+
m.usedSlots = make([]uintptr, m.maxSlots)
210+
199211
// Apply the physical mappings. Note that these mappings may point to
200212
// guest physical addresses that are not actually available. These
201213
// physical pages are mapped on demand, see kernel_unsafe.go.
@@ -272,6 +284,20 @@ func newMachine(vm int) (*machine, error) {
272284
return m, nil
273285
}
274286

287+
// hasSlot returns true iff the given address is mapped.
288+
//
289+
// This must be done via a linear scan.
290+
//
291+
//go:nosplit
292+
func (m *machine) hasSlot(physical uintptr) bool {
293+
for i := 0; i < len(m.usedSlots); i++ {
294+
if p := atomic.LoadUintptr(&m.usedSlots[i]); p == physical {
295+
return true
296+
}
297+
}
298+
return false
299+
}
300+
275301
// mapPhysical checks for the mapping of a physical range, and installs one if
276302
// not available. This attempts to be efficient for calls in the hot path.
277303
//
@@ -286,8 +312,8 @@ func (m *machine) mapPhysical(physical, length uintptr, phyRegions []physicalReg
286312
panic("mapPhysical on unknown physical address")
287313
}
288314

289-
if _, ok := m.mappingCache.LoadOrStore(physicalStart, true); !ok {
290-
// Not present in the cache; requires setting the slot.
315+
// Is this already mapped? Check the usedSlots.
316+
if !m.hasSlot(physicalStart) {
291317
if _, ok := handleBluepillFault(m, physical, phyRegions, flags); !ok {
292318
panic("handleBluepillFault failed")
293319
}

tools/bazeldefs/defs.bzl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def go_rule(rule, implementation, **kwargs):
147147
Returns:
148148
The result of invoking the rule.
149149
"""
150-
attrs = kwargs.pop("attrs", [])
150+
attrs = kwargs.pop("attrs", dict())
151151
attrs["_go_context_data"] = attr.label(default = "@io_bazel_rules_go//:go_context_data")
152152
attrs["_stdlib"] = attr.label(default = "@io_bazel_rules_go//:stdlib")
153153
toolchains = kwargs.get("toolchains", []) + ["@io_bazel_rules_go//go:toolchain"]
@@ -158,12 +158,17 @@ def go_test_library(target):
158158
return target.attr.embed[0]
159159
return None
160160

161-
def go_context(ctx):
161+
def go_context(ctx, std = False):
162+
# We don't change anything for the standard library analysis. All Go files
163+
# are available in all instances. Note that this includes the standard
164+
# library sources, which are analyzed by nogo.
162165
go_ctx = _go_context(ctx)
163166
return struct(
164167
go = go_ctx.go,
165168
env = go_ctx.env,
166-
runfiles = depset([go_ctx.go] + go_ctx.sdk.tools + go_ctx.stdlib.libs),
169+
nogo_args = [],
170+
stdlib_srcs = go_ctx.sdk.srcs,
171+
runfiles = depset([go_ctx.go] + go_ctx.sdk.srcs + go_ctx.sdk.tools + go_ctx.stdlib.libs),
167172
goos = go_ctx.sdk.goos,
168173
goarch = go_ctx.sdk.goarch,
169174
tags = go_ctx.tags,

tools/checkescape/BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ go_library(
88
nogo = False,
99
visibility = ["//tools/nogo:__subpackages__"],
1010
deps = [
11-
"//tools/nogo/data",
11+
"//tools/nogo/dump",
1212
"@org_golang_x_tools//go/analysis:go_tool_library",
1313
"@org_golang_x_tools//go/analysis/passes/buildssa:go_tool_library",
1414
"@org_golang_x_tools//go/ssa:go_tool_library",

tools/checkescape/checkescape.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,14 @@ import (
6666
"go/token"
6767
"go/types"
6868
"io"
69-
"os"
7069
"path/filepath"
7170
"strconv"
7271
"strings"
7372

7473
"golang.org/x/tools/go/analysis"
7574
"golang.org/x/tools/go/analysis/passes/buildssa"
7675
"golang.org/x/tools/go/ssa"
77-
"gvisor.dev/gvisor/tools/nogo/data"
76+
"gvisor.dev/gvisor/tools/nogo/dump"
7877
)
7978

8079
const (
@@ -256,15 +255,14 @@ func (ec *EscapeCount) Record(reason EscapeReason) bool {
256255
// used only to remove false positives for escape analysis. The call will be
257256
// elided if escape analysis is able to put the object on the heap exclusively.
258257
func loadObjdump() (map[LinePosition]string, error) {
259-
f, err := os.Open(data.Objdump)
258+
cmd, out, err := dump.Command()
260259
if err != nil {
261260
return nil, err
262261
}
263-
defer f.Close()
264262

265263
// Build the map.
266264
m := make(map[LinePosition]string)
267-
r := bufio.NewReader(f)
265+
r := bufio.NewReader(out)
268266
var (
269267
lastField string
270268
lastPos LinePosition
@@ -329,6 +327,11 @@ func loadObjdump() (map[LinePosition]string, error) {
329327
}
330328
}
331329

330+
// Wait for the dump to finish.
331+
if err := cmd.Wait(); err != nil {
332+
return nil, err
333+
}
334+
332335
return m, nil
333336
}
334337

@@ -413,12 +416,6 @@ func run(pass *analysis.Pass) (interface{}, error) {
413416
return escapes(unknownPackage, "no package", inst, ec)
414417
}
415418

416-
// Atomic functions are instrinics. We can
417-
// assume that they don't escape.
418-
if x.Pkg.Pkg.Name() == "atomic" {
419-
return nil
420-
}
421-
422419
// Is this a local function? If yes, call the
423420
// function to load the local function. The
424421
// local escapes are the escapes found in the

tools/checkescape/test1/test1.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package test1
1717

1818
import (
1919
"fmt"
20-
"reflect"
2120
)
2221

2322
// Interface is a generic interface.
@@ -163,20 +162,6 @@ func dynamicRec(f func()) {
163162
Dynamic(f)
164163
}
165164

166-
// +mustescape:local,unknown
167-
//go:noinline
168-
//go:nosplit
169-
func Unknown() {
170-
_ = reflect.TypeOf((*Type)(nil)) // Does not actually escape.
171-
}
172-
173-
// +mustescape:unknown
174-
//go:noinline
175-
//go:nosplit
176-
func unknownRec() {
177-
Unknown()
178-
}
179-
180165
//go:noinline
181166
//go:nosplit
182167
func internalFunc() {

tools/checkescape/test2/test2.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,6 @@ func dynamicCrossPkg(f func()) {
8181
test1.Dynamic(f)
8282
}
8383

84-
// +mustescape:unknown
85-
//go:noinline
86-
func unknownCrossPkg() {
87-
test1.Unknown()
88-
}
89-
9084
// +mustescape:stack
9185
//go:noinline
9286
func splitCrosssPkt() {

tools/go_marshal/gomarshal/generator_interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func (g *interfaceGenerator) emitNoEscapeSliceDataPointer(srcPtr, dstVar string)
224224
func (g *interfaceGenerator) emitKeepAlive(ptrVar string) {
225225
g.emit("// Since we bypassed the compiler's escape analysis, indicate that %s\n", ptrVar)
226226
g.emit("// must live until the use above.\n")
227-
g.emit("runtime.KeepAlive(%s)\n", ptrVar)
227+
g.emit("runtime.KeepAlive(%s) // escapes: replaced by intrinsic.\n", ptrVar)
228228
}
229229

230230
func (g *interfaceGenerator) expandBinaryExpr(b *strings.Builder, e *ast.BinaryExpr) {

tools/nogo/BUILD

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
load("//tools:defs.bzl", "bzl_library", "go_library")
2+
load("//tools/nogo:defs.bzl", "nogo_dump_tool", "nogo_stdlib")
23

34
package(licenses = ["notice"])
45

6+
nogo_dump_tool(
7+
name = "dump_tool",
8+
visibility = ["//visibility:public"],
9+
)
10+
11+
nogo_stdlib(
12+
name = "stdlib",
13+
visibility = ["//visibility:public"],
14+
)
15+
516
go_library(
617
name = "nogo",
718
srcs = [
@@ -16,7 +27,7 @@ go_library(
1627
deps = [
1728
"//tools/checkescape",
1829
"//tools/checkunsafe",
19-
"//tools/nogo/data",
30+
"//tools/nogo/dump",
2031
"@org_golang_x_tools//go/analysis:go_tool_library",
2132
"@org_golang_x_tools//go/analysis/internal/facts:go_tool_library",
2233
"@org_golang_x_tools//go/analysis/passes/asmdecl:go_tool_library",

tools/nogo/build.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ var (
3131
)
3232

3333
// findStdPkg needs to find the bundled standard library packages.
34-
func (i *importer) findStdPkg(path string) (io.ReadCloser, error) {
34+
func findStdPkg(GOOS, GOARCH, path string) (io.ReadCloser, error) {
3535
if path == "C" {
3636
// Cgo builds cannot be analyzed. Skip.
3737
return nil, ErrSkip
3838
}
39-
return os.Open(fmt.Sprintf("external/go_sdk/pkg/%s_%s/%s.a", i.GOOS, i.GOARCH, path))
39+
return os.Open(fmt.Sprintf("external/go_sdk/pkg/%s_%s/%s.a", GOOS, GOARCH, path))
4040
}

tools/nogo/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ var analyzerConfig = map[*analysis.Analyzer]matcher{
8484
externalExcluded(
8585
".*protobuf/.*.go", // Bad conversions.
8686
".*flate/huffman_bit_writer.go", // Bad conversion.
87+
88+
// Runtime internal violations.
89+
".*reflect/value.go",
90+
".*encoding/xml/xml.go",
91+
".*runtime/pprof/internal/profile/proto.go",
92+
".*fmt/scan.go",
93+
".*go/types/conversions.go",
94+
".*golang.org/x/net/dns/dnsmessage/message.go",
8795
),
8896
),
8997
shadow.Analyzer: disableMatches(), // Disabled for now.

tools/nogo/data/data.go

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)