mirror of
https://github.com/golang/go
synced 2024-11-11 19:41:36 -07:00
runtime: reimplement GODEBUG=cgocheck=2 as a GOEXPERIMENT
Move this knob from a binary-startup thing to a build-time thing. This will enable followon optmizations to the write barrier. Change-Id: Ic3323348621c76a7dc390c09ff55016b19c43018 Reviewed-on: https://go-review.googlesource.com/c/go/+/447778 Reviewed-by: Michael Knyszek <mknyszek@google.com> Run-TryBot: Keith Randall <khr@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
44d22e75dd
commit
55044288ad
@ -434,7 +434,22 @@ var ptrTests = []ptrTest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPointerChecks(t *testing.T) {
|
func TestPointerChecks(t *testing.T) {
|
||||||
dir, exe := buildPtrTests(t)
|
var gopath string
|
||||||
|
var dir string
|
||||||
|
if *tmp != "" {
|
||||||
|
gopath = *tmp
|
||||||
|
dir = ""
|
||||||
|
} else {
|
||||||
|
d, err := os.MkdirTemp("", filepath.Base(t.Name()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
dir = d
|
||||||
|
gopath = d
|
||||||
|
}
|
||||||
|
|
||||||
|
exe := buildPtrTests(t, gopath, false)
|
||||||
|
exe2 := buildPtrTests(t, gopath, true)
|
||||||
|
|
||||||
// We (TestPointerChecks) return before the parallel subtest functions do,
|
// We (TestPointerChecks) return before the parallel subtest functions do,
|
||||||
// so we can't just defer os.RemoveAll(dir). Instead we have to wait for
|
// so we can't just defer os.RemoveAll(dir). Instead we have to wait for
|
||||||
@ -451,24 +466,12 @@ func TestPointerChecks(t *testing.T) {
|
|||||||
os.RemoveAll(dir)
|
os.RemoveAll(dir)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
testOne(t, pt, exe)
|
testOne(t, pt, exe, exe2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPtrTests(t *testing.T) (dir, exe string) {
|
func buildPtrTests(t *testing.T, gopath string, cgocheck2 bool) (exe string) {
|
||||||
var gopath string
|
|
||||||
if *tmp != "" {
|
|
||||||
gopath = *tmp
|
|
||||||
dir = ""
|
|
||||||
} else {
|
|
||||||
d, err := os.MkdirTemp("", filepath.Base(t.Name()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
dir = d
|
|
||||||
gopath = d
|
|
||||||
}
|
|
||||||
|
|
||||||
src := filepath.Join(gopath, "src", "ptrtest")
|
src := filepath.Join(gopath, "src", "ptrtest")
|
||||||
if err := os.MkdirAll(src, 0777); err != nil {
|
if err := os.MkdirAll(src, 0777); err != nil {
|
||||||
@ -541,15 +544,31 @@ func buildPtrTests(t *testing.T) (dir, exe string) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", "build", "-o", "ptrtest.exe")
|
exeName := "ptrtest.exe"
|
||||||
|
if cgocheck2 {
|
||||||
|
exeName = "ptrtest2.exe"
|
||||||
|
}
|
||||||
|
cmd := exec.Command("go", "build", "-o", exeName)
|
||||||
cmd.Dir = src
|
cmd.Dir = src
|
||||||
cmd.Env = append(os.Environ(), "GOPATH="+gopath)
|
cmd.Env = append(os.Environ(), "GOPATH="+gopath)
|
||||||
|
if cgocheck2 {
|
||||||
|
found := false
|
||||||
|
for i, e := range cmd.Env {
|
||||||
|
if strings.HasPrefix(e, "GOEXPERIMENT=") {
|
||||||
|
cmd.Env[i] = e + ",cgocheck2"
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
cmd.Env = append(cmd.Env, "GOEXPERIMENT=cgocheck2")
|
||||||
|
}
|
||||||
|
}
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("go build: %v\n%s", err, out)
|
t.Fatalf("go build: %v\n%s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dir, filepath.Join(src, "ptrtest.exe")
|
return filepath.Join(src, exeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ptrTestMain = `
|
const ptrTestMain = `
|
||||||
@ -566,7 +585,7 @@ func main() {
|
|||||||
|
|
||||||
var csem = make(chan bool, 16)
|
var csem = make(chan bool, 16)
|
||||||
|
|
||||||
func testOne(t *testing.T, pt ptrTest, exe string) {
|
func testOne(t *testing.T, pt ptrTest, exe, exe2 string) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Run the tests in parallel, but don't run too many
|
// Run the tests in parallel, but don't run too many
|
||||||
@ -574,7 +593,12 @@ func testOne(t *testing.T, pt ptrTest, exe string) {
|
|||||||
runcmd := func(cgocheck string) ([]byte, error) {
|
runcmd := func(cgocheck string) ([]byte, error) {
|
||||||
csem <- true
|
csem <- true
|
||||||
defer func() { <-csem }()
|
defer func() { <-csem }()
|
||||||
cmd := exec.Command(exe, pt.name)
|
x := exe
|
||||||
|
if cgocheck == "2" {
|
||||||
|
x = exe2
|
||||||
|
cgocheck = "1"
|
||||||
|
}
|
||||||
|
cmd := exec.Command(x, pt.name)
|
||||||
cmd.Env = append(os.Environ(), "GODEBUG=cgocheck="+cgocheck)
|
cmd.Env = append(os.Environ(), "GODEBUG=cgocheck="+cgocheck)
|
||||||
return cmd.CombinedOutput()
|
return cmd.CombinedOutput()
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ var Syms struct {
|
|||||||
AssertI2I2 *obj.LSym
|
AssertI2I2 *obj.LSym
|
||||||
Asanread *obj.LSym
|
Asanread *obj.LSym
|
||||||
Asanwrite *obj.LSym
|
Asanwrite *obj.LSym
|
||||||
|
CgoCheckMemmove *obj.LSym
|
||||||
|
CgoCheckPtrWrite *obj.LSym
|
||||||
CheckPtrAlignment *obj.LSym
|
CheckPtrAlignment *obj.LSym
|
||||||
Deferproc *obj.LSym
|
Deferproc *obj.LSym
|
||||||
DeferprocStack *obj.LSym
|
DeferprocStack *obj.LSym
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"cmd/internal/objabi"
|
"cmd/internal/objabi"
|
||||||
"cmd/internal/src"
|
"cmd/internal/src"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/buildcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ZeroRegion records parts of an object which are known to be zero.
|
// A ZeroRegion records parts of an object which are known to be zero.
|
||||||
@ -131,7 +132,7 @@ func writebarrier(f *Func) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sb, sp, wbaddr, const0 *Value
|
var sb, sp, wbaddr, const0 *Value
|
||||||
var typedmemmove, typedmemclr, gcWriteBarrier *obj.LSym
|
var typedmemmove, typedmemclr, gcWriteBarrier, cgoCheckPtrWrite, cgoCheckMemmove *obj.LSym
|
||||||
var stores, after []*Value
|
var stores, after []*Value
|
||||||
var sset *sparseSet
|
var sset *sparseSet
|
||||||
var storeNumber []int32
|
var storeNumber []int32
|
||||||
@ -186,6 +187,10 @@ func writebarrier(f *Func) {
|
|||||||
gcWriteBarrier = f.fe.Syslook("gcWriteBarrier")
|
gcWriteBarrier = f.fe.Syslook("gcWriteBarrier")
|
||||||
typedmemmove = f.fe.Syslook("typedmemmove")
|
typedmemmove = f.fe.Syslook("typedmemmove")
|
||||||
typedmemclr = f.fe.Syslook("typedmemclr")
|
typedmemclr = f.fe.Syslook("typedmemclr")
|
||||||
|
if buildcfg.Experiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite = f.fe.Syslook("cgoCheckPtrWrite")
|
||||||
|
cgoCheckMemmove = f.fe.Syslook("cgoCheckMemmove")
|
||||||
|
}
|
||||||
const0 = f.ConstInt32(f.Config.Types.UInt32, 0)
|
const0 = f.ConstInt32(f.Config.Types.UInt32, 0)
|
||||||
|
|
||||||
// allocate auxiliary data structures for computing store order
|
// allocate auxiliary data structures for computing store order
|
||||||
@ -337,6 +342,11 @@ func writebarrier(f *Func) {
|
|||||||
switch w.Op {
|
switch w.Op {
|
||||||
case OpStoreWB, OpMoveWB, OpZeroWB:
|
case OpStoreWB, OpMoveWB, OpZeroWB:
|
||||||
if w.Op == OpStoreWB {
|
if w.Op == OpStoreWB {
|
||||||
|
if buildcfg.Experiment.CgoCheck2 {
|
||||||
|
// Issue cgo checking code.
|
||||||
|
memThen = wbcall(pos, bThen, cgoCheckPtrWrite, nil, ptr, val, memThen, sp, sb)
|
||||||
|
}
|
||||||
|
|
||||||
memThen = bThen.NewValue3A(pos, OpWB, types.TypeMem, gcWriteBarrier, ptr, val, memThen)
|
memThen = bThen.NewValue3A(pos, OpWB, types.TypeMem, gcWriteBarrier, ptr, val, memThen)
|
||||||
} else {
|
} else {
|
||||||
srcval := val
|
srcval := val
|
||||||
@ -359,8 +369,16 @@ func writebarrier(f *Func) {
|
|||||||
// else block: normal store
|
// else block: normal store
|
||||||
switch w.Op {
|
switch w.Op {
|
||||||
case OpStoreWB:
|
case OpStoreWB:
|
||||||
|
if buildcfg.Experiment.CgoCheck2 {
|
||||||
|
// Issue cgo checking code.
|
||||||
|
memElse = wbcall(pos, bElse, cgoCheckPtrWrite, nil, ptr, val, memElse, sp, sb)
|
||||||
|
}
|
||||||
memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse)
|
memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse)
|
||||||
case OpMoveWB:
|
case OpMoveWB:
|
||||||
|
if buildcfg.Experiment.CgoCheck2 {
|
||||||
|
// Issue cgo checking code.
|
||||||
|
memElse = wbcall(pos, bElse, cgoCheckMemmove, reflectdata.TypeLinksym(w.Aux.(*types.Type)), ptr, val, memElse, sp, sb)
|
||||||
|
}
|
||||||
memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse)
|
memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse)
|
||||||
memElse.Aux = w.Aux
|
memElse.Aux = w.Aux
|
||||||
case OpZeroWB:
|
case OpZeroWB:
|
||||||
@ -528,7 +546,7 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
|
|||||||
off := config.ctxt.Arch.FixedFrameSize
|
off := config.ctxt.Arch.FixedFrameSize
|
||||||
|
|
||||||
var argTypes []*types.Type
|
var argTypes []*types.Type
|
||||||
if typ != nil { // for typedmemmove
|
if typ != nil { // for typedmemmove/cgoCheckMemmove
|
||||||
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
|
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
|
||||||
argTypes = append(argTypes, b.Func.Config.Types.Uintptr)
|
argTypes = append(argTypes, b.Func.Config.Types.Uintptr)
|
||||||
off = round(off, taddr.Type.Alignment())
|
off = round(off, taddr.Type.Alignment())
|
||||||
|
@ -96,6 +96,8 @@ func InitConfig() {
|
|||||||
ir.Syms.AssertE2I2 = typecheck.LookupRuntimeFunc("assertE2I2")
|
ir.Syms.AssertE2I2 = typecheck.LookupRuntimeFunc("assertE2I2")
|
||||||
ir.Syms.AssertI2I = typecheck.LookupRuntimeFunc("assertI2I")
|
ir.Syms.AssertI2I = typecheck.LookupRuntimeFunc("assertI2I")
|
||||||
ir.Syms.AssertI2I2 = typecheck.LookupRuntimeFunc("assertI2I2")
|
ir.Syms.AssertI2I2 = typecheck.LookupRuntimeFunc("assertI2I2")
|
||||||
|
ir.Syms.CgoCheckMemmove = typecheck.LookupRuntimeFunc("cgoCheckMemmove")
|
||||||
|
ir.Syms.CgoCheckPtrWrite = typecheck.LookupRuntimeFunc("cgoCheckPtrWrite")
|
||||||
ir.Syms.CheckPtrAlignment = typecheck.LookupRuntimeFunc("checkptrAlignment")
|
ir.Syms.CheckPtrAlignment = typecheck.LookupRuntimeFunc("checkptrAlignment")
|
||||||
ir.Syms.Deferproc = typecheck.LookupRuntimeFunc("deferproc")
|
ir.Syms.Deferproc = typecheck.LookupRuntimeFunc("deferproc")
|
||||||
ir.Syms.DeferprocStack = typecheck.LookupRuntimeFunc("deferprocStack")
|
ir.Syms.DeferprocStack = typecheck.LookupRuntimeFunc("deferprocStack")
|
||||||
@ -7917,6 +7919,10 @@ func (e *ssafn) Syslook(name string) *obj.LSym {
|
|||||||
return ir.Syms.Typedmemmove
|
return ir.Syms.Typedmemmove
|
||||||
case "typedmemclr":
|
case "typedmemclr":
|
||||||
return ir.Syms.Typedmemclr
|
return ir.Syms.Typedmemclr
|
||||||
|
case "cgoCheckMemmove":
|
||||||
|
return ir.Syms.CgoCheckMemmove
|
||||||
|
case "cgoCheckPtrWrite":
|
||||||
|
return ir.Syms.CgoCheckPtrWrite
|
||||||
}
|
}
|
||||||
e.Fatalf(src.NoXPos, "unknown Syslook func %v", name)
|
e.Fatalf(src.NoXPos, "unknown Syslook func %v", name)
|
||||||
return nil
|
return nil
|
||||||
|
9
src/internal/goexperiment/exp_cgocheck2_off.go
Normal file
9
src/internal/goexperiment/exp_cgocheck2_off.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build !goexperiment.cgocheck2
|
||||||
|
// +build !goexperiment.cgocheck2
|
||||||
|
|
||||||
|
package goexperiment
|
||||||
|
|
||||||
|
const CgoCheck2 = false
|
||||||
|
const CgoCheck2Int = 0
|
9
src/internal/goexperiment/exp_cgocheck2_on.go
Normal file
9
src/internal/goexperiment/exp_cgocheck2_on.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
//go:build goexperiment.cgocheck2
|
||||||
|
// +build goexperiment.cgocheck2
|
||||||
|
|
||||||
|
package goexperiment
|
||||||
|
|
||||||
|
const CgoCheck2 = true
|
||||||
|
const CgoCheck2Int = 1
|
@ -96,4 +96,9 @@ type Flags struct {
|
|||||||
// this compels the Go runtime to write to some arbitrary file, which
|
// this compels the Go runtime to write to some arbitrary file, which
|
||||||
// may be exploited.
|
// may be exploited.
|
||||||
PageTrace bool
|
PageTrace bool
|
||||||
|
|
||||||
|
// CgoCheck2 enables an expensive cgo rule checker.
|
||||||
|
// When this experiment is enabled, cgo rule checks occur regardless
|
||||||
|
// of the GODEBUG=cgocheck setting provided at runtime.
|
||||||
|
CgoCheck2 bool
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"internal/goexperiment"
|
||||||
"runtime/internal/atomic"
|
"runtime/internal/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -21,7 +22,7 @@ import (
|
|||||||
func atomicwb(ptr *unsafe.Pointer, new unsafe.Pointer) {
|
func atomicwb(ptr *unsafe.Pointer, new unsafe.Pointer) {
|
||||||
slot := (*uintptr)(unsafe.Pointer(ptr))
|
slot := (*uintptr)(unsafe.Pointer(ptr))
|
||||||
if !getg().m.p.ptr().wbBuf.putFast(*slot, uintptr(new)) {
|
if !getg().m.p.ptr().wbBuf.putFast(*slot, uintptr(new)) {
|
||||||
wbBufFlush(slot, uintptr(new))
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +33,9 @@ func atomicstorep(ptr unsafe.Pointer, new unsafe.Pointer) {
|
|||||||
if writeBarrier.enabled {
|
if writeBarrier.enabled {
|
||||||
atomicwb((*unsafe.Pointer)(ptr), new)
|
atomicwb((*unsafe.Pointer)(ptr), new)
|
||||||
}
|
}
|
||||||
|
if goexperiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite((*unsafe.Pointer)(ptr), new)
|
||||||
|
}
|
||||||
atomic.StorepNoWB(noescape(ptr), new)
|
atomic.StorepNoWB(noescape(ptr), new)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +57,9 @@ func atomic_casPointer(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool {
|
|||||||
if writeBarrier.enabled {
|
if writeBarrier.enabled {
|
||||||
atomicwb(ptr, new)
|
atomicwb(ptr, new)
|
||||||
}
|
}
|
||||||
|
if goexperiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite(ptr, new)
|
||||||
|
}
|
||||||
return atomic.Casp1(ptr, old, new)
|
return atomic.Casp1(ptr, old, new)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +76,9 @@ func sync_atomic_StorePointer(ptr *unsafe.Pointer, new unsafe.Pointer) {
|
|||||||
if writeBarrier.enabled {
|
if writeBarrier.enabled {
|
||||||
atomicwb(ptr, new)
|
atomicwb(ptr, new)
|
||||||
}
|
}
|
||||||
|
if goexperiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite(ptr, new)
|
||||||
|
}
|
||||||
sync_atomic_StoreUintptr((*uintptr)(unsafe.Pointer(ptr)), uintptr(new))
|
sync_atomic_StoreUintptr((*uintptr)(unsafe.Pointer(ptr)), uintptr(new))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +91,9 @@ func sync_atomic_SwapPointer(ptr *unsafe.Pointer, new unsafe.Pointer) unsafe.Poi
|
|||||||
if writeBarrier.enabled {
|
if writeBarrier.enabled {
|
||||||
atomicwb(ptr, new)
|
atomicwb(ptr, new)
|
||||||
}
|
}
|
||||||
|
if goexperiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite(ptr, new)
|
||||||
|
}
|
||||||
old := unsafe.Pointer(sync_atomic_SwapUintptr((*uintptr)(noescape(unsafe.Pointer(ptr))), uintptr(new)))
|
old := unsafe.Pointer(sync_atomic_SwapUintptr((*uintptr)(noescape(unsafe.Pointer(ptr))), uintptr(new)))
|
||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
@ -94,5 +107,8 @@ func sync_atomic_CompareAndSwapPointer(ptr *unsafe.Pointer, old, new unsafe.Poin
|
|||||||
if writeBarrier.enabled {
|
if writeBarrier.enabled {
|
||||||
atomicwb(ptr, new)
|
atomicwb(ptr, new)
|
||||||
}
|
}
|
||||||
|
if goexperiment.CgoCheck2 {
|
||||||
|
cgoCheckPtrWrite(ptr, new)
|
||||||
|
}
|
||||||
return sync_atomic_CompareAndSwapUintptr((*uintptr)(noescape(unsafe.Pointer(ptr))), uintptr(old), uintptr(new))
|
return sync_atomic_CompareAndSwapUintptr((*uintptr)(noescape(unsafe.Pointer(ptr))), uintptr(old), uintptr(new))
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,7 @@ package runtime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"internal/goarch"
|
"internal/goarch"
|
||||||
|
"internal/goexperiment"
|
||||||
"runtime/internal/sys"
|
"runtime/internal/sys"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -391,7 +392,7 @@ var racecgosync uint64 // represents possible synchronization in C code
|
|||||||
// cgoCheckPointer checks if the argument contains a Go pointer that
|
// cgoCheckPointer checks if the argument contains a Go pointer that
|
||||||
// points to a Go pointer, and panics if it does.
|
// points to a Go pointer, and panics if it does.
|
||||||
func cgoCheckPointer(ptr any, arg any) {
|
func cgoCheckPointer(ptr any, arg any) {
|
||||||
if debug.cgocheck == 0 {
|
if !goexperiment.CgoCheck2 && debug.cgocheck == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,7 +632,7 @@ func cgoInRange(p unsafe.Pointer, start, end uintptr) bool {
|
|||||||
// exported Go function. It panics if the result is or contains a Go
|
// exported Go function. It panics if the result is or contains a Go
|
||||||
// pointer.
|
// pointer.
|
||||||
func cgoCheckResult(val any) {
|
func cgoCheckResult(val any) {
|
||||||
if debug.cgocheck == 0 {
|
if !goexperiment.CgoCheck2 && debug.cgocheck == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Code to check that pointer writes follow the cgo rules.
|
// Code to check that pointer writes follow the cgo rules.
|
||||||
// These functions are invoked via the write barrier when debug.cgocheck > 1.
|
// These functions are invoked when GOEXPERIMENT=cgocheck2 is enabled.
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
@ -14,16 +14,21 @@ import (
|
|||||||
|
|
||||||
const cgoWriteBarrierFail = "Go pointer stored into non-Go memory"
|
const cgoWriteBarrierFail = "Go pointer stored into non-Go memory"
|
||||||
|
|
||||||
// cgoCheckWriteBarrier is called whenever a pointer is stored into memory.
|
// cgoCheckPtrWrite is called whenever a pointer is stored into memory.
|
||||||
// It throws if the program is storing a Go pointer into non-Go memory.
|
// It throws if the program is storing a Go pointer into non-Go memory.
|
||||||
//
|
//
|
||||||
// This is called from the write barrier, so its entire call tree must
|
// This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled.
|
||||||
// be nosplit.
|
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func cgoCheckWriteBarrier(dst *uintptr, src uintptr) {
|
func cgoCheckPtrWrite(dst *unsafe.Pointer, src unsafe.Pointer) {
|
||||||
if !cgoIsGoPointer(unsafe.Pointer(src)) {
|
if !mainStarted {
|
||||||
|
// Something early in startup hates this function.
|
||||||
|
// Don't start doing any actual checking until the
|
||||||
|
// runtime has set itself up.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !cgoIsGoPointer(src) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cgoIsGoPointer(unsafe.Pointer(dst)) {
|
if cgoIsGoPointer(unsafe.Pointer(dst)) {
|
||||||
@ -51,20 +56,31 @@ func cgoCheckWriteBarrier(dst *uintptr, src uintptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
println("write of Go pointer", hex(src), "to non-Go memory", hex(uintptr(unsafe.Pointer(dst))))
|
println("write of Go pointer", hex(uintptr(src)), "to non-Go memory", hex(uintptr(unsafe.Pointer(dst))))
|
||||||
throw(cgoWriteBarrierFail)
|
throw(cgoWriteBarrierFail)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// cgoCheckMemmove is called when moving a block of memory.
|
// cgoCheckMemmove is called when moving a block of memory.
|
||||||
|
// It throws if the program is copying a block that contains a Go pointer
|
||||||
|
// into non-Go memory.
|
||||||
|
//
|
||||||
|
// This is called from generated code when GOEXPERIMENT=cgocheck2 is enabled.
|
||||||
|
//
|
||||||
|
//go:nosplit
|
||||||
|
//go:nowritebarrier
|
||||||
|
func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer) {
|
||||||
|
cgoCheckMemmove2(typ, dst, src, 0, typ.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cgoCheckMemmove2 is called when moving a block of memory.
|
||||||
// dst and src point off bytes into the value to copy.
|
// dst and src point off bytes into the value to copy.
|
||||||
// size is the number of bytes to copy.
|
// size is the number of bytes to copy.
|
||||||
// It throws if the program is copying a block that contains a Go pointer
|
// It throws if the program is copying a block that contains a Go pointer
|
||||||
// into non-Go memory.
|
// into non-Go memory.
|
||||||
//
|
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
//go:nowritebarrier
|
//go:nowritebarrier
|
||||||
func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer, off, size uintptr) {
|
func cgoCheckMemmove2(typ *_type, dst, src unsafe.Pointer, off, size uintptr) {
|
||||||
if typ.ptrdata == 0 {
|
if typ.ptrdata == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,9 @@ It is a comma-separated list of name=val pairs setting these named variables:
|
|||||||
cgocheck: setting cgocheck=0 disables all checks for packages
|
cgocheck: setting cgocheck=0 disables all checks for packages
|
||||||
using cgo to incorrectly pass Go pointers to non-Go code.
|
using cgo to incorrectly pass Go pointers to non-Go code.
|
||||||
Setting cgocheck=1 (the default) enables relatively cheap
|
Setting cgocheck=1 (the default) enables relatively cheap
|
||||||
checks that may miss some errors. Setting cgocheck=2 enables
|
checks that may miss some errors. A more complete, but slow,
|
||||||
expensive checks that should not miss any errors, but will
|
cgocheck mode can be enabled using GOEXPERIMENT (which
|
||||||
cause your program to run slower.
|
requires a rebuild), see https://pkg.go.dev/internal/goexperiment for details.
|
||||||
|
|
||||||
efence: setting efence=1 causes the allocator to run in a mode
|
efence: setting efence=1 causes the allocator to run in a mode
|
||||||
where each object is allocated on a unique page and addresses are
|
where each object is allocated on a unique page and addresses are
|
||||||
|
@ -16,6 +16,7 @@ package runtime
|
|||||||
import (
|
import (
|
||||||
"internal/abi"
|
"internal/abi"
|
||||||
"internal/goarch"
|
"internal/goarch"
|
||||||
|
"internal/goexperiment"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -169,8 +170,8 @@ func typedmemmove(typ *_type, dst, src unsafe.Pointer) {
|
|||||||
// barrier, so at worst we've unnecessarily greyed the old
|
// barrier, so at worst we've unnecessarily greyed the old
|
||||||
// pointer that was in src.
|
// pointer that was in src.
|
||||||
memmove(dst, src, typ.size)
|
memmove(dst, src, typ.size)
|
||||||
if writeBarrier.cgo {
|
if goexperiment.CgoCheck2 {
|
||||||
cgoCheckMemmove(typ, dst, src, 0, typ.size)
|
cgoCheckMemmove2(typ, dst, src, 0, typ.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,8 +215,8 @@ func reflect_typedmemmovepartial(typ *_type, dst, src unsafe.Pointer, off, size
|
|||||||
}
|
}
|
||||||
|
|
||||||
memmove(dst, src, size)
|
memmove(dst, src, size)
|
||||||
if writeBarrier.cgo {
|
if goexperiment.CgoCheck2 {
|
||||||
cgoCheckMemmove(typ, dst, src, off, size)
|
cgoCheckMemmove2(typ, dst, src, off, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +273,7 @@ func typedslicecopy(typ *_type, dstPtr unsafe.Pointer, dstLen int, srcPtr unsafe
|
|||||||
asanread(srcPtr, uintptr(n)*typ.size)
|
asanread(srcPtr, uintptr(n)*typ.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeBarrier.cgo {
|
if goexperiment.CgoCheck2 {
|
||||||
cgoCheckSliceCopy(typ, dstPtr, srcPtr, n)
|
cgoCheckSliceCopy(typ, dstPtr, srcPtr, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,7 +528,7 @@ func (h heapBits) nextFast() (heapBits, uintptr) {
|
|||||||
// make sure the underlying allocation contains pointers, usually
|
// make sure the underlying allocation contains pointers, usually
|
||||||
// by checking typ.ptrdata.
|
// by checking typ.ptrdata.
|
||||||
//
|
//
|
||||||
// Callers must perform cgo checks if writeBarrier.cgo.
|
// Callers must perform cgo checks if goexperiment.CgoCheck2.
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func bulkBarrierPreWrite(dst, src, size uintptr) {
|
func bulkBarrierPreWrite(dst, src, size uintptr) {
|
||||||
@ -574,7 +574,7 @@ func bulkBarrierPreWrite(dst, src, size uintptr) {
|
|||||||
}
|
}
|
||||||
dstx := (*uintptr)(unsafe.Pointer(addr))
|
dstx := (*uintptr)(unsafe.Pointer(addr))
|
||||||
if !buf.putFast(*dstx, 0) {
|
if !buf.putFast(*dstx, 0) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -586,7 +586,7 @@ func bulkBarrierPreWrite(dst, src, size uintptr) {
|
|||||||
dstx := (*uintptr)(unsafe.Pointer(addr))
|
dstx := (*uintptr)(unsafe.Pointer(addr))
|
||||||
srcx := (*uintptr)(unsafe.Pointer(src + (addr - dst)))
|
srcx := (*uintptr)(unsafe.Pointer(src + (addr - dst)))
|
||||||
if !buf.putFast(*dstx, *srcx) {
|
if !buf.putFast(*dstx, *srcx) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -618,7 +618,7 @@ func bulkBarrierPreWriteSrcOnly(dst, src, size uintptr) {
|
|||||||
}
|
}
|
||||||
srcx := (*uintptr)(unsafe.Pointer(addr - dst + src))
|
srcx := (*uintptr)(unsafe.Pointer(addr - dst + src))
|
||||||
if !buf.putFast(0, *srcx) {
|
if !buf.putFast(0, *srcx) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -651,12 +651,12 @@ func bulkBarrierBitmap(dst, src, size, maskOffset uintptr, bits *uint8) {
|
|||||||
dstx := (*uintptr)(unsafe.Pointer(dst + i))
|
dstx := (*uintptr)(unsafe.Pointer(dst + i))
|
||||||
if src == 0 {
|
if src == 0 {
|
||||||
if !buf.putFast(*dstx, 0) {
|
if !buf.putFast(*dstx, 0) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
srcx := (*uintptr)(unsafe.Pointer(src + i))
|
srcx := (*uintptr)(unsafe.Pointer(src + i))
|
||||||
if !buf.putFast(*dstx, *srcx) {
|
if !buf.putFast(*dstx, *srcx) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -678,7 +678,7 @@ func bulkBarrierBitmap(dst, src, size, maskOffset uintptr, bits *uint8) {
|
|||||||
// Must not be preempted because it typically runs right before memmove,
|
// Must not be preempted because it typically runs right before memmove,
|
||||||
// and the GC must observe them as an atomic action.
|
// and the GC must observe them as an atomic action.
|
||||||
//
|
//
|
||||||
// Callers must perform cgo checks if writeBarrier.cgo.
|
// Callers must perform cgo checks if goexperiment.CgoCheck2.
|
||||||
//
|
//
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func typeBitsBulkBarrier(typ *_type, dst, src, size uintptr) {
|
func typeBitsBulkBarrier(typ *_type, dst, src, size uintptr) {
|
||||||
@ -710,7 +710,7 @@ func typeBitsBulkBarrier(typ *_type, dst, src, size uintptr) {
|
|||||||
dstx := (*uintptr)(unsafe.Pointer(dst + i))
|
dstx := (*uintptr)(unsafe.Pointer(dst + i))
|
||||||
srcx := (*uintptr)(unsafe.Pointer(src + i))
|
srcx := (*uintptr)(unsafe.Pointer(src + i))
|
||||||
if !buf.putFast(*dstx, *srcx) {
|
if !buf.putFast(*dstx, *srcx) {
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,8 +193,7 @@ var gcphase uint32
|
|||||||
var writeBarrier struct {
|
var writeBarrier struct {
|
||||||
enabled bool // compiler emits a check of this before calling write barrier
|
enabled bool // compiler emits a check of this before calling write barrier
|
||||||
pad [3]byte // compiler uses 32-bit load for "enabled" field
|
pad [3]byte // compiler uses 32-bit load for "enabled" field
|
||||||
needed bool // whether we need a write barrier for current GC phase
|
needed bool // identical to enabled, for now (TODO: dedup)
|
||||||
cgo bool // whether we need a write barrier for a cgo check
|
|
||||||
alignme uint64 // guarantee alignment so that compiler can use a 32 or 64-bit load
|
alignme uint64 // guarantee alignment so that compiler can use a 32 or 64-bit load
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +212,7 @@ const (
|
|||||||
func setGCPhase(x uint32) {
|
func setGCPhase(x uint32) {
|
||||||
atomic.Store(&gcphase, x)
|
atomic.Store(&gcphase, x)
|
||||||
writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination
|
writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination
|
||||||
writeBarrier.enabled = writeBarrier.needed || writeBarrier.cgo
|
writeBarrier.enabled = writeBarrier.needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// gcMarkWorkerMode represents the mode that a concurrent mark worker
|
// gcMarkWorkerMode represents the mode that a concurrent mark worker
|
||||||
|
@ -1092,7 +1092,7 @@ func gcDrain(gcw *gcWork, flags gcDrainFlags) {
|
|||||||
// Flush the write barrier
|
// Flush the write barrier
|
||||||
// buffer; this may create
|
// buffer; this may create
|
||||||
// more work.
|
// more work.
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
b = gcw.tryGet()
|
b = gcw.tryGet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1171,7 +1171,7 @@ func gcDrainN(gcw *gcWork, scanWork int64) int64 {
|
|||||||
if b == 0 {
|
if b == 0 {
|
||||||
// Flush the write barrier buffer;
|
// Flush the write barrier buffer;
|
||||||
// this may create more work.
|
// this may create more work.
|
||||||
wbBufFlush(nil, 0)
|
wbBufFlush()
|
||||||
b = gcw.tryGet()
|
b = gcw.tryGet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,11 +80,7 @@ const (
|
|||||||
func (b *wbBuf) reset() {
|
func (b *wbBuf) reset() {
|
||||||
start := uintptr(unsafe.Pointer(&b.buf[0]))
|
start := uintptr(unsafe.Pointer(&b.buf[0]))
|
||||||
b.next = start
|
b.next = start
|
||||||
if writeBarrier.cgo {
|
if testSmallBuf {
|
||||||
// Effectively disable the buffer by forcing a flush
|
|
||||||
// on every barrier.
|
|
||||||
b.end = uintptr(unsafe.Pointer(&b.buf[wbBufEntryPointers]))
|
|
||||||
} else if testSmallBuf {
|
|
||||||
// For testing, allow two barriers in the buffer. If
|
// For testing, allow two barriers in the buffer. If
|
||||||
// we only did one, then barriers of non-heap pointers
|
// we only did one, then barriers of non-heap pointers
|
||||||
// would be no-ops. This lets us combine a buffered
|
// would be no-ops. This lets us combine a buffered
|
||||||
@ -118,15 +114,10 @@ func (b *wbBuf) empty() bool {
|
|||||||
//
|
//
|
||||||
// buf := &getg().m.p.ptr().wbBuf
|
// buf := &getg().m.p.ptr().wbBuf
|
||||||
// if !buf.putFast(old, new) {
|
// if !buf.putFast(old, new) {
|
||||||
// wbBufFlush(...)
|
// wbBufFlush()
|
||||||
// }
|
// }
|
||||||
// ... actual memory write ...
|
// ... actual memory write ...
|
||||||
//
|
//
|
||||||
// The arguments to wbBufFlush depend on whether the caller is doing
|
|
||||||
// its own cgo pointer checks. If it is, then this can be
|
|
||||||
// wbBufFlush(nil, 0). Otherwise, it must pass the slot address and
|
|
||||||
// new.
|
|
||||||
//
|
|
||||||
// The caller must ensure there are no preemption points during the
|
// The caller must ensure there are no preemption points during the
|
||||||
// above sequence. There must be no preemption points while buf is in
|
// above sequence. There must be no preemption points while buf is in
|
||||||
// use because it is a per-P resource. There must be no preemption
|
// use because it is a per-P resource. There must be no preemption
|
||||||
@ -150,8 +141,7 @@ func (b *wbBuf) putFast(old, new uintptr) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wbBufFlush flushes the current P's write barrier buffer to the GC
|
// wbBufFlush flushes the current P's write barrier buffer to the GC
|
||||||
// workbufs. It is passed the slot and value of the write barrier that
|
// workbufs.
|
||||||
// caused the flush so that it can implement cgocheck.
|
|
||||||
//
|
//
|
||||||
// This must not have write barriers because it is part of the write
|
// This must not have write barriers because it is part of the write
|
||||||
// barrier implementation.
|
// barrier implementation.
|
||||||
@ -165,7 +155,7 @@ func (b *wbBuf) putFast(old, new uintptr) bool {
|
|||||||
//
|
//
|
||||||
//go:nowritebarrierrec
|
//go:nowritebarrierrec
|
||||||
//go:nosplit
|
//go:nosplit
|
||||||
func wbBufFlush(dst *uintptr, src uintptr) {
|
func wbBufFlush() {
|
||||||
// Note: Every possible return from this function must reset
|
// Note: Every possible return from this function must reset
|
||||||
// the buffer's next pointer to prevent buffer overflow.
|
// the buffer's next pointer to prevent buffer overflow.
|
||||||
|
|
||||||
@ -184,17 +174,6 @@ func wbBufFlush(dst *uintptr, src uintptr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeBarrier.cgo && dst != nil {
|
|
||||||
// This must be called from the stack that did the
|
|
||||||
// write. It's nosplit all the way down.
|
|
||||||
cgoCheckWriteBarrier(dst, src)
|
|
||||||
if !writeBarrier.needed {
|
|
||||||
// We were only called for cgocheck.
|
|
||||||
getg().m.p.ptr().wbBuf.discard()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the system stack so we don't have to worry about
|
// Switch to the system stack so we don't have to worry about
|
||||||
// the untyped stack slots or safe points.
|
// the untyped stack slots or safe points.
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
|
@ -751,17 +751,6 @@ func schedinit() {
|
|||||||
// World is effectively started now, as P's can run.
|
// World is effectively started now, as P's can run.
|
||||||
worldStarted()
|
worldStarted()
|
||||||
|
|
||||||
// For cgocheck > 1, we turn on the write barrier at all times
|
|
||||||
// and check all pointer writes. We can't do this until after
|
|
||||||
// procresize because the write barrier needs a P.
|
|
||||||
if debug.cgocheck > 1 {
|
|
||||||
writeBarrier.cgo = true
|
|
||||||
writeBarrier.enabled = true
|
|
||||||
for _, pp := range allp {
|
|
||||||
pp.wbBuf.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buildVersion == "" {
|
if buildVersion == "" {
|
||||||
// Condition should never trigger. This code just serves
|
// Condition should never trigger. This code just serves
|
||||||
// to ensure runtime·buildVersion is kept in the resulting binary.
|
// to ensure runtime·buildVersion is kept in the resulting binary.
|
||||||
|
@ -489,6 +489,10 @@ func parsegodebug(godebug string, seen map[string]bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debug.cgocheck > 1 {
|
||||||
|
throw("cgocheck > 1 mode is no longer supported at runtime. Use GOEXPERIMENT=cgocheck2 at build time instead.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname setTraceback runtime/debug.SetTraceback
|
//go:linkname setTraceback runtime/debug.SetTraceback
|
||||||
|
Loading…
Reference in New Issue
Block a user