diff options
Diffstat (limited to 'src/runtime/testdata')
89 files changed, 5867 insertions, 0 deletions
diff --git a/src/runtime/testdata/testexithooks/testexithooks.go b/src/runtime/testdata/testexithooks/testexithooks.go new file mode 100644 index 0000000..ceb3326 --- /dev/null +++ b/src/runtime/testdata/testexithooks/testexithooks.go @@ -0,0 +1,85 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "os" + _ "unsafe" +) + +var modeflag = flag.String("mode", "", "mode to run in") + +func main() { + flag.Parse() + switch *modeflag { + case "simple": + testSimple() + case "goodexit": + testGoodExit() + case "badexit": + testBadExit() + case "panics": + testPanics() + case "callsexit": + testHookCallsExit() + default: + panic("unknown mode") + } +} + +//go:linkname runtime_addExitHook runtime.addExitHook +func runtime_addExitHook(f func(), runOnNonZeroExit bool) + +func testSimple() { + f1 := func() { println("foo") } + f2 := func() { println("bar") } + runtime_addExitHook(f1, false) + runtime_addExitHook(f2, false) + // no explicit call to os.Exit +} + +func testGoodExit() { + f1 := func() { println("apple") } + f2 := func() { println("orange") } + runtime_addExitHook(f1, false) + runtime_addExitHook(f2, false) + // explicit call to os.Exit + os.Exit(0) +} + +func testBadExit() { + f1 := func() { println("blog") } + f2 := func() { println("blix") } + f3 := func() { println("blek") } + f4 := func() { println("blub") } + f5 := func() { println("blat") } + runtime_addExitHook(f1, false) + runtime_addExitHook(f2, true) + runtime_addExitHook(f3, false) + runtime_addExitHook(f4, true) + runtime_addExitHook(f5, false) + os.Exit(1) +} + +func testPanics() { + f1 := func() { println("ok") } + f2 := func() { panic("BADBADBAD") } + f3 := func() { println("good") } + runtime_addExitHook(f1, true) + runtime_addExitHook(f2, true) + runtime_addExitHook(f3, true) + os.Exit(0) +} + +func testHookCallsExit() { + f1 := func() { println("ok") } + f2 := func() { os.Exit(1) } + f3 := func() { println("good") } + runtime_addExitHook(f1, true) + runtime_addExitHook(f2, true) + runtime_addExitHook(f3, true) + os.Exit(1) +} diff --git a/src/runtime/testdata/testfaketime/faketime.go b/src/runtime/testdata/testfaketime/faketime.go new file mode 100644 index 0000000..1fb15eb --- /dev/null +++ b/src/runtime/testdata/testfaketime/faketime.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test faketime support. This is its own test program because we have +// to build it with custom build tags and hence want to minimize +// dependencies. + +package main + +import ( + "os" + "time" +) + +func main() { + println("line 1") + // Stream switch, increments time + os.Stdout.WriteString("line 2\n") + os.Stdout.WriteString("line 3\n") + // Stream switch, increments time + os.Stderr.WriteString("line 4\n") + // Time jump + time.Sleep(1 * time.Second) + os.Stdout.WriteString("line 5\n") + // Print the current time. + os.Stdout.WriteString(time.Now().UTC().Format(time.RFC3339)) +} diff --git a/src/runtime/testdata/testprog/abort.go b/src/runtime/testdata/testprog/abort.go new file mode 100644 index 0000000..9e79d4d --- /dev/null +++ b/src/runtime/testdata/testprog/abort.go @@ -0,0 +1,23 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import _ "unsafe" // for go:linkname + +func init() { + register("Abort", Abort) +} + +//go:linkname runtimeAbort runtime.abort +func runtimeAbort() + +func Abort() { + defer func() { + recover() + panic("BAD: recovered from abort") + }() + runtimeAbort() + println("BAD: after abort") +} diff --git a/src/runtime/testdata/testprog/badtraceback.go b/src/runtime/testdata/testprog/badtraceback.go new file mode 100644 index 0000000..09aa2b8 --- /dev/null +++ b/src/runtime/testdata/testprog/badtraceback.go @@ -0,0 +1,50 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "runtime" + "runtime/debug" + "unsafe" +) + +func init() { + register("BadTraceback", BadTraceback) +} + +func BadTraceback() { + // Disable GC to prevent traceback at unexpected time. + debug.SetGCPercent(-1) + // Out of an abundance of caution, also make sure that there are + // no GCs actively in progress. + runtime.GC() + + // Run badLR1 on its own stack to minimize the stack size and + // exercise the stack bounds logic in the hex dump. + go badLR1() + select {} +} + +//go:noinline +func badLR1() { + // We need two frames on LR machines because we'll smash this + // frame's saved LR. + badLR2(0) +} + +//go:noinline +func badLR2(arg int) { + // Smash the return PC or saved LR. + lrOff := unsafe.Sizeof(uintptr(0)) + if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" { + lrOff = 32 // FIXED_FRAME or sys.MinFrameSize + } + lrPtr := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&arg)) - lrOff)) + *lrPtr = 0xbad + + // Print a backtrace. This should include diagnostics for the + // bad return PC and a hex dump. + panic("backtrace") +} diff --git a/src/runtime/testdata/testprog/checkptr.go b/src/runtime/testdata/testprog/checkptr.go new file mode 100644 index 0000000..60e71e6 --- /dev/null +++ b/src/runtime/testdata/testprog/checkptr.go @@ -0,0 +1,119 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "runtime" + "time" + "unsafe" +) + +func init() { + register("CheckPtrAlignmentNoPtr", CheckPtrAlignmentNoPtr) + register("CheckPtrAlignmentPtr", CheckPtrAlignmentPtr) + register("CheckPtrAlignmentNilPtr", CheckPtrAlignmentNilPtr) + register("CheckPtrArithmetic", CheckPtrArithmetic) + register("CheckPtrArithmetic2", CheckPtrArithmetic2) + register("CheckPtrSize", CheckPtrSize) + register("CheckPtrSmall", CheckPtrSmall) + register("CheckPtrSliceOK", CheckPtrSliceOK) + register("CheckPtrSliceFail", CheckPtrSliceFail) + register("CheckPtrStringOK", CheckPtrStringOK) + register("CheckPtrStringFail", CheckPtrStringFail) + register("CheckPtrAlignmentNested", CheckPtrAlignmentNested) +} + +func CheckPtrAlignmentNoPtr() { + var x [2]int64 + p := unsafe.Pointer(&x[0]) + sink2 = (*int64)(unsafe.Pointer(uintptr(p) + 1)) +} + +func CheckPtrAlignmentPtr() { + var x [2]int64 + p := unsafe.Pointer(&x[0]) + sink2 = (**int64)(unsafe.Pointer(uintptr(p) + 1)) +} + +// CheckPtrAlignmentNilPtr tests that checkptrAlignment doesn't crash +// on nil pointers (#47430). +func CheckPtrAlignmentNilPtr() { + var do func(int) + do = func(n int) { + // Inflate the stack so runtime.shrinkstack gets called during GC + if n > 0 { + do(n - 1) + } + + var p unsafe.Pointer + _ = (*int)(p) + } + + go func() { + for { + runtime.GC() + } + }() + + go func() { + for i := 0; ; i++ { + do(i % 1024) + } + }() + + time.Sleep(time.Second) +} + +func CheckPtrArithmetic() { + var x int + i := uintptr(unsafe.Pointer(&x)) + sink2 = (*int)(unsafe.Pointer(i)) +} + +func CheckPtrArithmetic2() { + var x [2]int64 + p := unsafe.Pointer(&x[1]) + var one uintptr = 1 + sink2 = unsafe.Pointer(uintptr(p) & ^one) +} + +func CheckPtrSize() { + p := new(int64) + sink2 = p + sink2 = (*[100]int64)(unsafe.Pointer(p)) +} + +func CheckPtrSmall() { + sink2 = unsafe.Pointer(uintptr(1)) +} + +func CheckPtrSliceOK() { + p := new([4]int64) + sink2 = unsafe.Slice(&p[1], 3) +} + +func CheckPtrSliceFail() { + p := new(int64) + sink2 = p + sink2 = unsafe.Slice(p, 100) +} + +func CheckPtrStringOK() { + p := new([4]byte) + sink2 = unsafe.String(&p[1], 3) +} + +func CheckPtrStringFail() { + p := new(byte) + sink2 = p + sink2 = unsafe.String(p, 100) +} + +func CheckPtrAlignmentNested() { + s := make([]int8, 100) + p := unsafe.Pointer(&s[0]) + n := 9 + _ = ((*[10]int8)(unsafe.Pointer((*[10]int64)(unsafe.Pointer(&p)))))[:n:n] +} diff --git a/src/runtime/testdata/testprog/crash.go b/src/runtime/testdata/testprog/crash.go new file mode 100644 index 0000000..38c8f6a --- /dev/null +++ b/src/runtime/testdata/testprog/crash.go @@ -0,0 +1,139 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" +) + +func init() { + register("Crash", Crash) + register("DoublePanic", DoublePanic) + register("ErrorPanic", ErrorPanic) + register("StringerPanic", StringerPanic) + register("DoubleErrorPanic", DoubleErrorPanic) + register("DoubleStringerPanic", DoubleStringerPanic) + register("StringPanic", StringPanic) + register("NilPanic", NilPanic) + register("CircularPanic", CircularPanic) +} + +func test(name string) { + defer func() { + if x := recover(); x != nil { + fmt.Printf(" recovered") + } + fmt.Printf(" done\n") + }() + fmt.Printf("%s:", name) + var s *string + _ = *s + fmt.Print("SHOULD NOT BE HERE") +} + +func testInNewThread(name string) { + c := make(chan bool) + go func() { + runtime.LockOSThread() + test(name) + c <- true + }() + <-c +} + +func Crash() { + runtime.LockOSThread() + test("main") + testInNewThread("new-thread") + testInNewThread("second-new-thread") + test("main-again") +} + +type P string + +func (p P) String() string { + // Try to free the "YYY" string header when the "XXX" + // panic is stringified. + runtime.GC() + runtime.GC() + runtime.GC() + return string(p) +} + +// Test that panic message is not clobbered. +// See issue 30150. +func DoublePanic() { + defer func() { + panic(P("YYY")) + }() + panic(P("XXX")) +} + +// Test that panic while panicking discards error message +// See issue 52257 +type exampleError struct{} + +func (e exampleError) Error() string { + panic("important error message") +} + +func ErrorPanic() { + panic(exampleError{}) +} + +type examplePanicError struct{} + +func (e examplePanicError) Error() string { + panic(exampleError{}) +} + +func DoubleErrorPanic() { + panic(examplePanicError{}) +} + +type exampleStringer struct{} + +func (s exampleStringer) String() string { + panic("important stringer message") +} + +func StringerPanic() { + panic(exampleStringer{}) +} + +type examplePanicStringer struct{} + +func (s examplePanicStringer) String() string { + panic(exampleStringer{}) +} + +func DoubleStringerPanic() { + panic(examplePanicStringer{}) +} + +func StringPanic() { + panic("important string message") +} + +func NilPanic() { + panic(nil) +} + +type exampleCircleStartError struct{} + +func (e exampleCircleStartError) Error() string { + panic(exampleCircleEndError{}) +} + +type exampleCircleEndError struct{} + +func (e exampleCircleEndError) Error() string { + panic(exampleCircleStartError{}) +} + +func CircularPanic() { + panic(exampleCircleStartError{}) +} diff --git a/src/runtime/testdata/testprog/crashdump.go b/src/runtime/testdata/testprog/crashdump.go new file mode 100644 index 0000000..bced397 --- /dev/null +++ b/src/runtime/testdata/testprog/crashdump.go @@ -0,0 +1,47 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "runtime" +) + +func init() { + register("CrashDumpsAllThreads", CrashDumpsAllThreads) +} + +func CrashDumpsAllThreads() { + const count = 4 + runtime.GOMAXPROCS(count + 1) + + chans := make([]chan bool, count) + for i := range chans { + chans[i] = make(chan bool) + go crashDumpsAllThreadsLoop(i, chans[i]) + } + + // Wait for all the goroutines to start executing. + for _, c := range chans { + <-c + } + + // Tell our parent that all the goroutines are executing. + if _, err := os.NewFile(3, "pipe").WriteString("x"); err != nil { + fmt.Fprintf(os.Stderr, "write to pipe failed: %v\n", err) + os.Exit(2) + } + + select {} +} + +func crashDumpsAllThreadsLoop(i int, c chan bool) { + close(c) + for { + for j := 0; j < 0x7fffffff; j++ { + } + } +} diff --git a/src/runtime/testdata/testprog/deadlock.go b/src/runtime/testdata/testprog/deadlock.go new file mode 100644 index 0000000..781acbd --- /dev/null +++ b/src/runtime/testdata/testprog/deadlock.go @@ -0,0 +1,363 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" + "runtime/debug" + "time" +) + +func init() { + registerInit("InitDeadlock", InitDeadlock) + registerInit("NoHelperGoroutines", NoHelperGoroutines) + + register("SimpleDeadlock", SimpleDeadlock) + register("LockedDeadlock", LockedDeadlock) + register("LockedDeadlock2", LockedDeadlock2) + register("GoexitDeadlock", GoexitDeadlock) + register("StackOverflow", StackOverflow) + register("ThreadExhaustion", ThreadExhaustion) + register("RecursivePanic", RecursivePanic) + register("RecursivePanic2", RecursivePanic2) + register("RecursivePanic3", RecursivePanic3) + register("RecursivePanic4", RecursivePanic4) + register("RecursivePanic5", RecursivePanic5) + register("GoexitExit", GoexitExit) + register("GoNil", GoNil) + register("MainGoroutineID", MainGoroutineID) + register("Breakpoint", Breakpoint) + register("GoexitInPanic", GoexitInPanic) + register("PanicAfterGoexit", PanicAfterGoexit) + register("RecoveredPanicAfterGoexit", RecoveredPanicAfterGoexit) + register("RecoverBeforePanicAfterGoexit", RecoverBeforePanicAfterGoexit) + register("RecoverBeforePanicAfterGoexit2", RecoverBeforePanicAfterGoexit2) + register("PanicTraceback", PanicTraceback) + register("GoschedInPanic", GoschedInPanic) + register("SyscallInPanic", SyscallInPanic) + register("PanicLoop", PanicLoop) +} + +func SimpleDeadlock() { + select {} + panic("not reached") +} + +func InitDeadlock() { + select {} + panic("not reached") +} + +func LockedDeadlock() { + runtime.LockOSThread() + select {} +} + +func LockedDeadlock2() { + go func() { + runtime.LockOSThread() + select {} + }() + time.Sleep(time.Millisecond) + select {} +} + +func GoexitDeadlock() { + F := func() { + for i := 0; i < 10; i++ { + } + } + + go F() + go F() + runtime.Goexit() +} + +func StackOverflow() { + var f func() byte + f = func() byte { + var buf [64 << 10]byte + return buf[0] + f() + } + debug.SetMaxStack(1474560) + f() +} + +func ThreadExhaustion() { + debug.SetMaxThreads(10) + c := make(chan int) + for i := 0; i < 100; i++ { + go func() { + runtime.LockOSThread() + c <- 0 + select {} + }() + <-c + } +} + +func RecursivePanic() { + func() { + defer func() { + fmt.Println(recover()) + }() + var x [8192]byte + func(x [8192]byte) { + defer func() { + if err := recover(); err != nil { + panic("wrap: " + err.(string)) + } + }() + panic("bad") + }(x) + }() + panic("again") +} + +// Same as RecursivePanic, but do the first recover and the second panic in +// separate defers, and make sure they are executed in the correct order. +func RecursivePanic2() { + func() { + defer func() { + fmt.Println(recover()) + }() + var x [8192]byte + func(x [8192]byte) { + defer func() { + panic("second panic") + }() + defer func() { + fmt.Println(recover()) + }() + panic("first panic") + }(x) + }() + panic("third panic") +} + +// Make sure that the first panic finished as a panic, even though the second +// panic was recovered +func RecursivePanic3() { + defer func() { + defer func() { + recover() + }() + panic("second panic") + }() + panic("first panic") +} + +// Test case where a single defer recovers one panic but starts another panic. If +// the second panic is never recovered, then the recovered first panic will still +// appear on the panic stack (labeled '[recovered]') and the runtime stack. +func RecursivePanic4() { + defer func() { + recover() + panic("second panic") + }() + panic("first panic") +} + +// Test case where we have an open-coded defer higher up the stack (in two), and +// in the current function (three) we recover in a defer while we still have +// another defer to be processed. +func RecursivePanic5() { + one() + panic("third panic") +} + +//go:noinline +func one() { + two() +} + +//go:noinline +func two() { + defer func() { + }() + + three() +} + +//go:noinline +func three() { + defer func() { + }() + + defer func() { + fmt.Println(recover()) + }() + + defer func() { + fmt.Println(recover()) + panic("second panic") + }() + + panic("first panic") +} + +func GoexitExit() { + println("t1") + go func() { + time.Sleep(time.Millisecond) + }() + i := 0 + println("t2") + runtime.SetFinalizer(&i, func(p *int) {}) + println("t3") + runtime.GC() + println("t4") + runtime.Goexit() +} + +func GoNil() { + defer func() { + recover() + }() + var f func() + go f() + select {} +} + +func MainGoroutineID() { + panic("test") +} + +func NoHelperGoroutines() { + i := 0 + runtime.SetFinalizer(&i, func(p *int) {}) + time.AfterFunc(time.Hour, func() {}) + panic("oops") +} + +func Breakpoint() { + runtime.Breakpoint() +} + +func GoexitInPanic() { + go func() { + defer func() { + runtime.Goexit() + }() + panic("hello") + }() + runtime.Goexit() +} + +type errorThatGosched struct{} + +func (errorThatGosched) Error() string { + runtime.Gosched() + return "errorThatGosched" +} + +func GoschedInPanic() { + panic(errorThatGosched{}) +} + +type errorThatPrint struct{} + +func (errorThatPrint) Error() string { + fmt.Println("1") + fmt.Println("2") + return "3" +} + +func SyscallInPanic() { + panic(errorThatPrint{}) +} + +func PanicAfterGoexit() { + defer func() { + panic("hello") + }() + runtime.Goexit() +} + +func RecoveredPanicAfterGoexit() { + defer func() { + defer func() { + r := recover() + if r == nil { + panic("bad recover") + } + }() + panic("hello") + }() + runtime.Goexit() +} + +func RecoverBeforePanicAfterGoexit() { + // 1. defer a function that recovers + // 2. defer a function that panics + // 3. call goexit + // Goexit runs the #2 defer. Its panic + // is caught by the #1 defer. For Goexit, we explicitly + // resume execution in the Goexit loop, instead of resuming + // execution in the caller (which would make the Goexit disappear!) + defer func() { + r := recover() + if r == nil { + panic("bad recover") + } + }() + defer func() { + panic("hello") + }() + runtime.Goexit() +} + +func RecoverBeforePanicAfterGoexit2() { + for i := 0; i < 2; i++ { + defer func() { + }() + } + // 1. defer a function that recovers + // 2. defer a function that panics + // 3. call goexit + // Goexit runs the #2 defer. Its panic + // is caught by the #1 defer. For Goexit, we explicitly + // resume execution in the Goexit loop, instead of resuming + // execution in the caller (which would make the Goexit disappear!) + defer func() { + r := recover() + if r == nil { + panic("bad recover") + } + }() + defer func() { + panic("hello") + }() + runtime.Goexit() +} + +func PanicTraceback() { + pt1() +} + +func pt1() { + defer func() { + panic("panic pt1") + }() + pt2() +} + +func pt2() { + defer func() { + panic("panic pt2") + }() + panic("hello") +} + +type panicError struct{} + +func (*panicError) Error() string { + panic("double error") +} + +func PanicLoop() { + panic(&panicError{}) +} diff --git a/src/runtime/testdata/testprog/gc.go b/src/runtime/testdata/testprog/gc.go new file mode 100644 index 0000000..5dc85fb --- /dev/null +++ b/src/runtime/testdata/testprog/gc.go @@ -0,0 +1,420 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "math" + "os" + "runtime" + "runtime/debug" + "runtime/metrics" + "sync" + "sync/atomic" + "time" + "unsafe" +) + +func init() { + register("GCFairness", GCFairness) + register("GCFairness2", GCFairness2) + register("GCSys", GCSys) + register("GCPhys", GCPhys) + register("DeferLiveness", DeferLiveness) + register("GCZombie", GCZombie) + register("GCMemoryLimit", GCMemoryLimit) + register("GCMemoryLimitNoGCPercent", GCMemoryLimitNoGCPercent) +} + +func GCSys() { + runtime.GOMAXPROCS(1) + memstats := new(runtime.MemStats) + runtime.GC() + runtime.ReadMemStats(memstats) + sys := memstats.Sys + + runtime.MemProfileRate = 0 // disable profiler + + itercount := 100000 + for i := 0; i < itercount; i++ { + workthegc() + } + + // Should only be using a few MB. + // We allocated 100 MB or (if not short) 1 GB. + runtime.ReadMemStats(memstats) + if sys > memstats.Sys { + sys = 0 + } else { + sys = memstats.Sys - sys + } + if sys > 16<<20 { + fmt.Printf("using too much memory: %d bytes\n", sys) + return + } + fmt.Printf("OK\n") +} + +var sink []byte + +func workthegc() []byte { + sink = make([]byte, 1029) + return sink +} + +func GCFairness() { + runtime.GOMAXPROCS(1) + f, err := os.Open("/dev/null") + if os.IsNotExist(err) { + // This test tests what it is intended to test only if writes are fast. + // If there is no /dev/null, we just don't execute the test. + fmt.Println("OK") + return + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + for i := 0; i < 2; i++ { + go func() { + for { + f.Write([]byte(".")) + } + }() + } + time.Sleep(10 * time.Millisecond) + fmt.Println("OK") +} + +func GCFairness2() { + // Make sure user code can't exploit the GC's high priority + // scheduling to make scheduling of user code unfair. See + // issue #15706. + runtime.GOMAXPROCS(1) + debug.SetGCPercent(1) + var count [3]int64 + var sink [3]any + for i := range count { + go func(i int) { + for { + sink[i] = make([]byte, 1024) + atomic.AddInt64(&count[i], 1) + } + }(i) + } + // Note: If the unfairness is really bad, it may not even get + // past the sleep. + // + // If the scheduling rules change, this may not be enough time + // to let all goroutines run, but for now we cycle through + // them rapidly. + // + // OpenBSD's scheduler makes every usleep() take at least + // 20ms, so we need a long time to ensure all goroutines have + // run. If they haven't run after 30ms, give it another 1000ms + // and check again. + time.Sleep(30 * time.Millisecond) + var fail bool + for i := range count { + if atomic.LoadInt64(&count[i]) == 0 { + fail = true + } + } + if fail { + time.Sleep(1 * time.Second) + for i := range count { + if atomic.LoadInt64(&count[i]) == 0 { + fmt.Printf("goroutine %d did not run\n", i) + return + } + } + } + fmt.Println("OK") +} + +func GCPhys() { + // This test ensures that heap-growth scavenging is working as intended. + // + // It attempts to construct a sizeable "swiss cheese" heap, with many + // allocChunk-sized holes. Then, it triggers a heap growth by trying to + // allocate as much memory as would fit in those holes. + // + // The heap growth should cause a large number of those holes to be + // returned to the OS. + + const ( + // The total amount of memory we're willing to allocate. + allocTotal = 32 << 20 + + // The page cache could hide 64 8-KiB pages from the scavenger today. + maxPageCache = (8 << 10) * 64 + ) + + // How big the allocations are needs to depend on the page size. + // If the page size is too big and the allocations are too small, + // they might not be aligned to the physical page size, so the scavenger + // will gloss over them. + pageSize := os.Getpagesize() + var allocChunk int + if pageSize <= 8<<10 { + allocChunk = 64 << 10 + } else { + allocChunk = 512 << 10 + } + allocs := allocTotal / allocChunk + + // Set GC percent just so this test is a little more consistent in the + // face of varying environments. + debug.SetGCPercent(100) + + // Set GOMAXPROCS to 1 to minimize the amount of memory held in the page cache, + // and to reduce the chance that the background scavenger gets scheduled. + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + + // Allocate allocTotal bytes of memory in allocChunk byte chunks. + // Alternate between whether the chunk will be held live or will be + // condemned to GC to create holes in the heap. + saved := make([][]byte, allocs/2+1) + condemned := make([][]byte, allocs/2) + for i := 0; i < allocs; i++ { + b := make([]byte, allocChunk) + if i%2 == 0 { + saved = append(saved, b) + } else { + condemned = append(condemned, b) + } + } + + // Run a GC cycle just so we're at a consistent state. + runtime.GC() + + // Drop the only reference to all the condemned memory. + condemned = nil + + // Clear the condemned memory. + runtime.GC() + + // At this point, the background scavenger is likely running + // and could pick up the work, so the next line of code doesn't + // end up doing anything. That's fine. What's important is that + // this test fails somewhat regularly if the runtime doesn't + // scavenge on heap growth, and doesn't fail at all otherwise. + + // Make a large allocation that in theory could fit, but won't + // because we turned the heap into swiss cheese. + saved = append(saved, make([]byte, allocTotal/2)) + + // heapBacked is an estimate of the amount of physical memory used by + // this test. HeapSys is an estimate of the size of the mapped virtual + // address space (which may or may not be backed by physical pages) + // whereas HeapReleased is an estimate of the amount of bytes returned + // to the OS. Their difference then roughly corresponds to the amount + // of virtual address space that is backed by physical pages. + // + // heapBacked also subtracts out maxPageCache bytes of memory because + // this is memory that may be hidden from the scavenger per-P. Since + // GOMAXPROCS=1 here, subtracting it out once is fine. + var stats runtime.MemStats + runtime.ReadMemStats(&stats) + heapBacked := stats.HeapSys - stats.HeapReleased - maxPageCache + // If heapBacked does not exceed the heap goal by more than retainExtraPercent + // then the scavenger is working as expected; the newly-created holes have been + // scavenged immediately as part of the allocations which cannot fit in the holes. + // + // Since the runtime should scavenge the entirety of the remaining holes, + // theoretically there should be no more free and unscavenged memory. However due + // to other allocations that happen during this test we may still see some physical + // memory over-use. + overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc) + // Check against our overuse threshold, which is what the scavenger always reserves + // to encourage allocation of memory that doesn't need to be faulted in. + // + // Add additional slack in case the page size is large and the scavenger + // can't reach that memory because it doesn't constitute a complete aligned + // physical page. Assume the worst case: a full physical page out of each + // allocation. + threshold := 0.1 + float64(pageSize)/float64(allocChunk) + if overuse <= threshold { + fmt.Println("OK") + return + } + // Physical memory utilization exceeds the threshold, so heap-growth scavenging + // did not operate as expected. + // + // In the context of this test, this indicates a large amount of + // fragmentation with physical pages that are otherwise unused but not + // returned to the OS. + fmt.Printf("exceeded physical memory overuse threshold of %3.2f%%: %3.2f%%\n"+ + "(alloc: %d, goal: %d, sys: %d, rel: %d, objs: %d)\n", threshold*100, overuse*100, + stats.HeapAlloc, stats.NextGC, stats.HeapSys, stats.HeapReleased, len(saved)) + runtime.KeepAlive(saved) + runtime.KeepAlive(condemned) +} + +// Test that defer closure is correctly scanned when the stack is scanned. +func DeferLiveness() { + var x [10]int + escape(&x) + fn := func() { + if x[0] != 42 { + panic("FAIL") + } + } + defer fn() + + x[0] = 42 + runtime.GC() + runtime.GC() + runtime.GC() +} + +//go:noinline +func escape(x any) { sink2 = x; sink2 = nil } + +var sink2 any + +// Test zombie object detection and reporting. +func GCZombie() { + // Allocate several objects of unusual size (so free slots are + // unlikely to all be re-allocated by the runtime). + const size = 190 + const count = 8192 / size + keep := make([]*byte, 0, (count+1)/2) + free := make([]uintptr, 0, (count+1)/2) + zombies := make([]*byte, 0, len(free)) + for i := 0; i < count; i++ { + obj := make([]byte, size) + p := &obj[0] + if i%2 == 0 { + keep = append(keep, p) + } else { + free = append(free, uintptr(unsafe.Pointer(p))) + } + } + + // Free the unreferenced objects. + runtime.GC() + + // Bring the free objects back to life. + for _, p := range free { + zombies = append(zombies, (*byte)(unsafe.Pointer(p))) + } + + // GC should detect the zombie objects. + runtime.GC() + println("failed") + runtime.KeepAlive(keep) + runtime.KeepAlive(zombies) +} + +func GCMemoryLimit() { + gcMemoryLimit(100) +} + +func GCMemoryLimitNoGCPercent() { + gcMemoryLimit(-1) +} + +// Test SetMemoryLimit functionality. +// +// This test lives here instead of runtime/debug because the entire +// implementation is in the runtime, and testprog gives us a more +// consistent testing environment to help avoid flakiness. +func gcMemoryLimit(gcPercent int) { + if oldProcs := runtime.GOMAXPROCS(4); oldProcs < 4 { + // Fail if the default GOMAXPROCS isn't at least 4. + // Whatever invokes this should check and do a proper t.Skip. + println("insufficient CPUs") + return + } + debug.SetGCPercent(gcPercent) + + const myLimit = 256 << 20 + if limit := debug.SetMemoryLimit(-1); limit != math.MaxInt64 { + print("expected MaxInt64 limit, got ", limit, " bytes instead\n") + return + } + if limit := debug.SetMemoryLimit(myLimit); limit != math.MaxInt64 { + print("expected MaxInt64 limit, got ", limit, " bytes instead\n") + return + } + if limit := debug.SetMemoryLimit(-1); limit != myLimit { + print("expected a ", myLimit, "-byte limit, got ", limit, " bytes instead\n") + return + } + + target := make(chan int64) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + sinkSize := int(<-target / memLimitUnit) + for { + if len(memLimitSink) != sinkSize { + memLimitSink = make([]*[memLimitUnit]byte, sinkSize) + } + for i := 0; i < len(memLimitSink); i++ { + memLimitSink[i] = new([memLimitUnit]byte) + // Write to this memory to slow down the allocator, otherwise + // we get flaky behavior. See #52433. + for j := range memLimitSink[i] { + memLimitSink[i][j] = 9 + } + } + // Again, Gosched to slow down the allocator. + runtime.Gosched() + select { + case newTarget := <-target: + if newTarget == math.MaxInt64 { + return + } + sinkSize = int(newTarget / memLimitUnit) + default: + } + } + }() + var m [2]metrics.Sample + m[0].Name = "/memory/classes/total:bytes" + m[1].Name = "/memory/classes/heap/released:bytes" + + // Don't set this too high, because this is a *live heap* target which + // is not directly comparable to a total memory limit. + maxTarget := int64((myLimit / 10) * 8) + increment := int64((myLimit / 10) * 1) + for i := increment; i < maxTarget; i += increment { + target <- i + + // Check to make sure the memory limit is maintained. + // We're just sampling here so if it transiently goes over we might miss it. + // The internal accounting is inconsistent anyway, so going over by a few + // pages is certainly possible. Just make sure we're within some bound. + // Note that to avoid flakiness due to #52433 (especially since we're allocating + // somewhat heavily here) this bound is kept loose. In practice the Go runtime + // should do considerably better than this bound. + bound := int64(myLimit + 16<<20) + start := time.Now() + for time.Since(start) < 200*time.Millisecond { + metrics.Read(m[:]) + retained := int64(m[0].Value.Uint64() - m[1].Value.Uint64()) + if retained > bound { + print("retained=", retained, " limit=", myLimit, " bound=", bound, "\n") + panic("exceeded memory limit by more than bound allows") + } + runtime.Gosched() + } + } + + if limit := debug.SetMemoryLimit(math.MaxInt64); limit != myLimit { + print("expected a ", myLimit, "-byte limit, got ", limit, " bytes instead\n") + return + } + println("OK") +} + +// Pick a value close to the page size. We want to m +const memLimitUnit = 8000 + +var memLimitSink []*[memLimitUnit]byte diff --git a/src/runtime/testdata/testprog/lockosthread.go b/src/runtime/testdata/testprog/lockosthread.go new file mode 100644 index 0000000..e9d7fdb --- /dev/null +++ b/src/runtime/testdata/testprog/lockosthread.go @@ -0,0 +1,246 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "os" + "runtime" + "sync" + "time" +) + +var mainTID int + +func init() { + registerInit("LockOSThreadMain", func() { + // init is guaranteed to run on the main thread. + mainTID = gettid() + }) + register("LockOSThreadMain", LockOSThreadMain) + + registerInit("LockOSThreadAlt", func() { + // Lock the OS thread now so main runs on the main thread. + runtime.LockOSThread() + }) + register("LockOSThreadAlt", LockOSThreadAlt) + + registerInit("LockOSThreadAvoidsStatePropagation", func() { + // Lock the OS thread now so main runs on the main thread. + runtime.LockOSThread() + }) + register("LockOSThreadAvoidsStatePropagation", LockOSThreadAvoidsStatePropagation) + register("LockOSThreadTemplateThreadRace", LockOSThreadTemplateThreadRace) +} + +func LockOSThreadMain() { + // gettid only works on Linux, so on other platforms this just + // checks that the runtime doesn't do anything terrible. + + // This requires GOMAXPROCS=1 from the beginning to reliably + // start a goroutine on the main thread. + if runtime.GOMAXPROCS(-1) != 1 { + println("requires GOMAXPROCS=1") + os.Exit(1) + } + + ready := make(chan bool, 1) + go func() { + // Because GOMAXPROCS=1, this *should* be on the main + // thread. Stay there. + runtime.LockOSThread() + if mainTID != 0 && gettid() != mainTID { + println("failed to start goroutine on main thread") + os.Exit(1) + } + // Exit with the thread locked, which should exit the + // main thread. + ready <- true + }() + <-ready + time.Sleep(1 * time.Millisecond) + // Check that this goroutine is still running on a different + // thread. + if mainTID != 0 && gettid() == mainTID { + println("goroutine migrated to locked thread") + os.Exit(1) + } + println("OK") +} + +func LockOSThreadAlt() { + // This is running locked to the main OS thread. + + var subTID int + ready := make(chan bool, 1) + go func() { + // This goroutine must be running on a new thread. + runtime.LockOSThread() + subTID = gettid() + ready <- true + // Exit with the thread locked. + }() + <-ready + runtime.UnlockOSThread() + for i := 0; i < 100; i++ { + time.Sleep(1 * time.Millisecond) + // Check that this goroutine is running on a different thread. + if subTID != 0 && gettid() == subTID { + println("locked thread reused") + os.Exit(1) + } + exists, supported := tidExists(subTID) + if !supported || !exists { + goto ok + } + } + println("sub thread", subTID, "still running") + return +ok: + println("OK") +} + +func LockOSThreadAvoidsStatePropagation() { + // This test is similar to LockOSThreadAlt in that it will detect if a thread + // which should have died is still running. However, rather than do this with + // thread IDs, it does this by unsharing state on that thread. This way, it + // also detects whether new threads were cloned from the dead thread, and not + // from a clean thread. Cloning from a locked thread is undesirable since + // cloned threads will inherit potentially unwanted OS state. + // + // unshareFs, getcwd, and chdir("/tmp") are only guaranteed to work on + // Linux, so on other platforms this just checks that the runtime doesn't + // do anything terrible. + // + // This is running locked to the main OS thread. + + // GOMAXPROCS=1 makes this fail much more reliably if a tainted thread is + // cloned from. + if runtime.GOMAXPROCS(-1) != 1 { + println("requires GOMAXPROCS=1") + os.Exit(1) + } + + if err := chdir("/"); err != nil { + println("failed to chdir:", err.Error()) + os.Exit(1) + } + // On systems other than Linux, cwd == "". + cwd, err := getcwd() + if err != nil { + println("failed to get cwd:", err.Error()) + os.Exit(1) + } + if cwd != "" && cwd != "/" { + println("unexpected cwd", cwd, " wanted /") + os.Exit(1) + } + + ready := make(chan bool, 1) + go func() { + // This goroutine must be running on a new thread. + runtime.LockOSThread() + + // Unshare details about the FS, like the CWD, with + // the rest of the process on this thread. + // On systems other than Linux, this is a no-op. + if err := unshareFs(); err != nil { + if err == errNotPermitted { + println("unshare not permitted") + os.Exit(0) + } + println("failed to unshare fs:", err.Error()) + os.Exit(1) + } + // Chdir to somewhere else on this thread. + // On systems other than Linux, this is a no-op. + if err := chdir("/tmp"); err != nil { + println("failed to chdir:", err.Error()) + os.Exit(1) + } + + // The state on this thread is now considered "tainted", but it + // should no longer be observable in any other context. + + ready <- true + // Exit with the thread locked. + }() + <-ready + + // Spawn yet another goroutine and lock it. Since GOMAXPROCS=1, if + // for some reason state from the (hopefully dead) locked thread above + // propagated into a newly created thread (via clone), or that thread + // is actually being re-used, then we should get scheduled on such a + // thread with high likelihood. + done := make(chan bool) + go func() { + runtime.LockOSThread() + + // Get the CWD and check if this is the same as the main thread's + // CWD. Every thread should share the same CWD. + // On systems other than Linux, wd == "". + wd, err := getcwd() + if err != nil { + println("failed to get cwd:", err.Error()) + os.Exit(1) + } + if wd != cwd { + println("bad state from old thread propagated after it should have died") + os.Exit(1) + } + <-done + + runtime.UnlockOSThread() + }() + done <- true + runtime.UnlockOSThread() + println("OK") +} + +func LockOSThreadTemplateThreadRace() { + // This test attempts to reproduce the race described in + // golang.org/issue/38931. To do so, we must have a stop-the-world + // (achieved via ReadMemStats) racing with two LockOSThread calls. + // + // While this test attempts to line up the timing, it is only expected + // to fail (and thus hang) around 2% of the time if the race is + // present. + + // Ensure enough Ps to actually run everything in parallel. Though on + // <4 core machines, we are still at the whim of the kernel scheduler. + runtime.GOMAXPROCS(4) + + go func() { + // Stop the world; race with LockOSThread below. + var m runtime.MemStats + for { + runtime.ReadMemStats(&m) + } + }() + + // Try to synchronize both LockOSThreads. + start := time.Now().Add(10 * time.Millisecond) + + var wg sync.WaitGroup + wg.Add(2) + + for i := 0; i < 2; i++ { + go func() { + for time.Now().Before(start) { + } + + // Add work to the local runq to trigger early startm + // in handoffp. + go func() {}() + + runtime.LockOSThread() + runtime.Gosched() // add a preemption point. + wg.Done() + }() + } + + wg.Wait() + // If both LockOSThreads completed then we did not hit the race. + println("OK") +} diff --git a/src/runtime/testdata/testprog/main.go b/src/runtime/testdata/testprog/main.go new file mode 100644 index 0000000..ae491a2 --- /dev/null +++ b/src/runtime/testdata/testprog/main.go @@ -0,0 +1,35 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "os" + +var cmds = map[string]func(){} + +func register(name string, f func()) { + if cmds[name] != nil { + panic("duplicate registration: " + name) + } + cmds[name] = f +} + +func registerInit(name string, f func()) { + if len(os.Args) >= 2 && os.Args[1] == name { + f() + } +} + +func main() { + if len(os.Args) < 2 { + println("usage: " + os.Args[0] + " name-of-test") + return + } + f := cmds[os.Args[1]] + if f == nil { + println("unknown function: " + os.Args[1]) + return + } + f() +} diff --git a/src/runtime/testdata/testprog/map.go b/src/runtime/testdata/testprog/map.go new file mode 100644 index 0000000..5524289 --- /dev/null +++ b/src/runtime/testdata/testprog/map.go @@ -0,0 +1,77 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "runtime" + +func init() { + register("concurrentMapWrites", concurrentMapWrites) + register("concurrentMapReadWrite", concurrentMapReadWrite) + register("concurrentMapIterateWrite", concurrentMapIterateWrite) +} + +func concurrentMapWrites() { + m := map[int]int{} + c := make(chan struct{}) + go func() { + for i := 0; i < 10000; i++ { + m[5] = 0 + runtime.Gosched() + } + c <- struct{}{} + }() + go func() { + for i := 0; i < 10000; i++ { + m[6] = 0 + runtime.Gosched() + } + c <- struct{}{} + }() + <-c + <-c +} + +func concurrentMapReadWrite() { + m := map[int]int{} + c := make(chan struct{}) + go func() { + for i := 0; i < 10000; i++ { + m[5] = 0 + runtime.Gosched() + } + c <- struct{}{} + }() + go func() { + for i := 0; i < 10000; i++ { + _ = m[6] + runtime.Gosched() + } + c <- struct{}{} + }() + <-c + <-c +} + +func concurrentMapIterateWrite() { + m := map[int]int{} + c := make(chan struct{}) + go func() { + for i := 0; i < 10000; i++ { + m[5] = 0 + runtime.Gosched() + } + c <- struct{}{} + }() + go func() { + for i := 0; i < 10000; i++ { + for range m { + } + runtime.Gosched() + } + c <- struct{}{} + }() + <-c + <-c +} diff --git a/src/runtime/testdata/testprog/memprof.go b/src/runtime/testdata/testprog/memprof.go new file mode 100644 index 0000000..0392e60 --- /dev/null +++ b/src/runtime/testdata/testprog/memprof.go @@ -0,0 +1,51 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + "runtime" + "runtime/pprof" +) + +func init() { + register("MemProf", MemProf) +} + +var memProfBuf bytes.Buffer +var memProfStr string + +func MemProf() { + // Force heap sampling for determinism. + runtime.MemProfileRate = 1 + + for i := 0; i < 10; i++ { + fmt.Fprintf(&memProfBuf, "%*d\n", i, i) + } + memProfStr = memProfBuf.String() + + runtime.GC() + + f, err := os.CreateTemp("", "memprof") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := pprof.WriteHeapProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + name := f.Name() + if err := f.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println(name) +} diff --git a/src/runtime/testdata/testprog/misc.go b/src/runtime/testdata/testprog/misc.go new file mode 100644 index 0000000..7ccd389 --- /dev/null +++ b/src/runtime/testdata/testprog/misc.go @@ -0,0 +1,15 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "runtime" + +func init() { + register("NumGoroutine", NumGoroutine) +} + +func NumGoroutine() { + println(runtime.NumGoroutine()) +} diff --git a/src/runtime/testdata/testprog/numcpu_freebsd.go b/src/runtime/testdata/testprog/numcpu_freebsd.go new file mode 100644 index 0000000..310c212 --- /dev/null +++ b/src/runtime/testdata/testprog/numcpu_freebsd.go @@ -0,0 +1,140 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "strings" + "syscall" +) + +var ( + cpuSetRE = regexp.MustCompile(`(\d,?)+`) +) + +func init() { + register("FreeBSDNumCPU", FreeBSDNumCPU) + register("FreeBSDNumCPUHelper", FreeBSDNumCPUHelper) +} + +func FreeBSDNumCPUHelper() { + fmt.Printf("%d\n", runtime.NumCPU()) +} + +func FreeBSDNumCPU() { + _, err := exec.LookPath("cpuset") + if err != nil { + // Can not test without cpuset command. + fmt.Println("OK") + return + } + _, err = exec.LookPath("sysctl") + if err != nil { + // Can not test without sysctl command. + fmt.Println("OK") + return + } + cmd := exec.Command("sysctl", "-n", "kern.smp.active") + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("fail to launch '%s', error: %s, output: %s\n", strings.Join(cmd.Args, " "), err, output) + return + } + if !bytes.Equal(output, []byte("1\n")) { + // SMP mode deactivated in kernel. + fmt.Println("OK") + return + } + + list, err := getList() + if err != nil { + fmt.Printf("%s\n", err) + return + } + err = checkNCPU(list) + if err != nil { + fmt.Printf("%s\n", err) + return + } + if len(list) >= 2 { + err = checkNCPU(list[:len(list)-1]) + if err != nil { + fmt.Printf("%s\n", err) + return + } + } + fmt.Println("OK") + return +} + +func getList() ([]string, error) { + pid := syscall.Getpid() + + // Launch cpuset to print a list of available CPUs: pid <PID> mask: 0, 1, 2, 3. + cmd := exec.Command("cpuset", "-g", "-p", strconv.Itoa(pid)) + cmdline := strings.Join(cmd.Args, " ") + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("fail to execute '%s': %s", cmdline, err) + } + output, _, ok := bytes.Cut(output, []byte("\n")) + if !ok { + return nil, fmt.Errorf("invalid output from '%s', '\\n' not found: %s", cmdline, output) + } + + _, cpus, ok := bytes.Cut(output, []byte(":")) + if !ok { + return nil, fmt.Errorf("invalid output from '%s', ':' not found: %s", cmdline, output) + } + + var list []string + for _, val := range bytes.Split(cpus, []byte(",")) { + index := string(bytes.TrimSpace(val)) + if len(index) == 0 { + continue + } + list = append(list, index) + } + if len(list) == 0 { + return nil, fmt.Errorf("empty CPU list from '%s': %s", cmdline, output) + } + return list, nil +} + +func checkNCPU(list []string) error { + listString := strings.Join(list, ",") + if len(listString) == 0 { + return fmt.Errorf("could not check against an empty CPU list") + } + + cListString := cpuSetRE.FindString(listString) + if len(cListString) == 0 { + return fmt.Errorf("invalid cpuset output '%s'", listString) + } + // Launch FreeBSDNumCPUHelper() with specified CPUs list. + cmd := exec.Command("cpuset", "-l", cListString, os.Args[0], "FreeBSDNumCPUHelper") + cmdline := strings.Join(cmd.Args, " ") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("fail to launch child '%s', error: %s, output: %s", cmdline, err, output) + } + + // NumCPU from FreeBSDNumCPUHelper come with '\n'. + output = bytes.TrimSpace(output) + n, err := strconv.Atoi(string(output)) + if err != nil { + return fmt.Errorf("fail to parse output from child '%s', error: %s, output: %s", cmdline, err, output) + } + if n != len(list) { + return fmt.Errorf("runtime.NumCPU() expected to %d, got %d when run with CPU list %s", len(list), n, cListString) + } + return nil +} diff --git a/src/runtime/testdata/testprog/panicprint.go b/src/runtime/testdata/testprog/panicprint.go new file mode 100644 index 0000000..c8deabe --- /dev/null +++ b/src/runtime/testdata/testprog/panicprint.go @@ -0,0 +1,111 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type MyBool bool +type MyComplex128 complex128 +type MyComplex64 complex64 +type MyFloat32 float32 +type MyFloat64 float64 +type MyInt int +type MyInt8 int8 +type MyInt16 int16 +type MyInt32 int32 +type MyInt64 int64 +type MyString string +type MyUint uint +type MyUint8 uint8 +type MyUint16 uint16 +type MyUint32 uint32 +type MyUint64 uint64 +type MyUintptr uintptr + +func panicCustomComplex64() { + panic(MyComplex64(0.11 + 3i)) +} + +func panicCustomComplex128() { + panic(MyComplex128(32.1 + 10i)) +} + +func panicCustomString() { + panic(MyString("Panic")) +} + +func panicCustomBool() { + panic(MyBool(true)) +} + +func panicCustomInt() { + panic(MyInt(93)) +} + +func panicCustomInt8() { + panic(MyInt8(93)) +} + +func panicCustomInt16() { + panic(MyInt16(93)) +} + +func panicCustomInt32() { + panic(MyInt32(93)) +} + +func panicCustomInt64() { + panic(MyInt64(93)) +} + +func panicCustomUint() { + panic(MyUint(93)) +} + +func panicCustomUint8() { + panic(MyUint8(93)) +} + +func panicCustomUint16() { + panic(MyUint16(93)) +} + +func panicCustomUint32() { + panic(MyUint32(93)) +} + +func panicCustomUint64() { + panic(MyUint64(93)) +} + +func panicCustomUintptr() { + panic(MyUintptr(93)) +} + +func panicCustomFloat64() { + panic(MyFloat64(-93.70)) +} + +func panicCustomFloat32() { + panic(MyFloat32(-93.70)) +} + +func init() { + register("panicCustomComplex64", panicCustomComplex64) + register("panicCustomComplex128", panicCustomComplex128) + register("panicCustomBool", panicCustomBool) + register("panicCustomFloat32", panicCustomFloat32) + register("panicCustomFloat64", panicCustomFloat64) + register("panicCustomInt", panicCustomInt) + register("panicCustomInt8", panicCustomInt8) + register("panicCustomInt16", panicCustomInt16) + register("panicCustomInt32", panicCustomInt32) + register("panicCustomInt64", panicCustomInt64) + register("panicCustomString", panicCustomString) + register("panicCustomUint", panicCustomUint) + register("panicCustomUint8", panicCustomUint8) + register("panicCustomUint16", panicCustomUint16) + register("panicCustomUint32", panicCustomUint32) + register("panicCustomUint64", panicCustomUint64) + register("panicCustomUintptr", panicCustomUintptr) +} diff --git a/src/runtime/testdata/testprog/panicrace.go b/src/runtime/testdata/testprog/panicrace.go new file mode 100644 index 0000000..f058994 --- /dev/null +++ b/src/runtime/testdata/testprog/panicrace.go @@ -0,0 +1,27 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "runtime" + "sync" +) + +func init() { + register("PanicRace", PanicRace) +} + +func PanicRace() { + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer func() { + wg.Done() + runtime.Gosched() + }() + panic("crash") + }() + wg.Wait() +} diff --git a/src/runtime/testdata/testprog/preempt.go b/src/runtime/testdata/testprog/preempt.go new file mode 100644 index 0000000..fb6755a --- /dev/null +++ b/src/runtime/testdata/testprog/preempt.go @@ -0,0 +1,75 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "runtime" + "runtime/debug" + "sync/atomic" +) + +func init() { + register("AsyncPreempt", AsyncPreempt) +} + +func AsyncPreempt() { + // Run with just 1 GOMAXPROCS so the runtime is required to + // use scheduler preemption. + runtime.GOMAXPROCS(1) + // Disable GC so we have complete control of what we're testing. + debug.SetGCPercent(-1) + // Out of an abundance of caution, also make sure that there are + // no GCs actively in progress. The sweep phase of a GC cycle + // for instance tries to preempt Ps at the very beginning. + runtime.GC() + + // Start a goroutine with no sync safe-points. + var ready, ready2 uint32 + go func() { + for { + atomic.StoreUint32(&ready, 1) + dummy() + dummy() + } + }() + // Also start one with a frameless function. + // This is an especially interesting case for + // LR machines. + go func() { + atomic.AddUint32(&ready2, 1) + frameless() + }() + // Also test empty infinite loop. + go func() { + atomic.AddUint32(&ready2, 1) + for { + } + }() + + // Wait for the goroutine to stop passing through sync + // safe-points. + for atomic.LoadUint32(&ready) == 0 || atomic.LoadUint32(&ready2) < 2 { + runtime.Gosched() + } + + // Run a GC, which will have to stop the goroutine for STW and + // for stack scanning. If this doesn't work, the test will + // deadlock and timeout. + runtime.GC() + + println("OK") +} + +//go:noinline +func frameless() { + for i := int64(0); i < 1<<62; i++ { + out += i * i * i * i * i * 12345 + } +} + +var out int64 + +//go:noinline +func dummy() {} diff --git a/src/runtime/testdata/testprog/signal.go b/src/runtime/testdata/testprog/signal.go new file mode 100644 index 0000000..cc5ac8a --- /dev/null +++ b/src/runtime/testdata/testprog/signal.go @@ -0,0 +1,30 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 +// +build !windows,!plan9 + +package main + +import ( + "syscall" + "time" +) + +func init() { + register("SignalExitStatus", SignalExitStatus) +} + +func SignalExitStatus() { + syscall.Kill(syscall.Getpid(), syscall.SIGTERM) + + // Should die immediately, but we've seen flakiness on various + // systems (see issue 14063). It's possible that the signal is + // being delivered to a different thread and we are returning + // and exiting before that thread runs again. Give the program + // a little while to die to make sure we pick up the signal + // before we return and exit the program. The time here + // shouldn't matter--we'll never really sleep this long. + time.Sleep(time.Second) +} diff --git a/src/runtime/testdata/testprog/sleep.go b/src/runtime/testdata/testprog/sleep.go new file mode 100644 index 0000000..b230e60 --- /dev/null +++ b/src/runtime/testdata/testprog/sleep.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "os" + "time" +) + +// for golang.org/issue/27250 + +func init() { + register("After1", After1) +} + +func After1() { + os.Stdout.WriteString("ready\n") + os.Stdout.Close() + <-time.After(1 * time.Second) +} diff --git a/src/runtime/testdata/testprog/stringconcat.go b/src/runtime/testdata/testprog/stringconcat.go new file mode 100644 index 0000000..f233e66 --- /dev/null +++ b/src/runtime/testdata/testprog/stringconcat.go @@ -0,0 +1,20 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "strings" + +func init() { + register("stringconcat", stringconcat) +} + +func stringconcat() { + s0 := strings.Repeat("0", 1<<10) + s1 := strings.Repeat("1", 1<<10) + s2 := strings.Repeat("2", 1<<10) + s3 := strings.Repeat("3", 1<<10) + s := s0 + s1 + s2 + s3 + panic(s) +} diff --git a/src/runtime/testdata/testprog/syscall_windows.go b/src/runtime/testdata/testprog/syscall_windows.go new file mode 100644 index 0000000..71bf384 --- /dev/null +++ b/src/runtime/testdata/testprog/syscall_windows.go @@ -0,0 +1,73 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "internal/syscall/windows" + "runtime" + "sync" + "syscall" + "unsafe" +) + +func init() { + register("RaiseException", RaiseException) + register("ZeroDivisionException", ZeroDivisionException) + register("StackMemory", StackMemory) +} + +func RaiseException() { + const EXCEPTION_NONCONTINUABLE = 1 + mod := syscall.MustLoadDLL("kernel32.dll") + proc := mod.MustFindProc("RaiseException") + proc.Call(0xbad, EXCEPTION_NONCONTINUABLE, 0, 0) + println("RaiseException should not return") +} + +func ZeroDivisionException() { + x := 1 + y := 0 + z := x / y + println(z) +} + +func getPagefileUsage() (uintptr, error) { + p, err := syscall.GetCurrentProcess() + if err != nil { + return 0, err + } + var m windows.PROCESS_MEMORY_COUNTERS + err = windows.GetProcessMemoryInfo(p, &m, uint32(unsafe.Sizeof(m))) + if err != nil { + return 0, err + } + return m.PagefileUsage, nil +} + +func StackMemory() { + mem1, err := getPagefileUsage() + if err != nil { + panic(err) + } + const threadCount = 100 + var wg sync.WaitGroup + for i := 0; i < threadCount; i++ { + wg.Add(1) + go func() { + runtime.LockOSThread() + wg.Done() + select {} + }() + } + wg.Wait() + mem2, err := getPagefileUsage() + if err != nil { + panic(err) + } + // assumes that this process creates 1 thread for each + // thread locked goroutine plus extra 5 threads + // like sysmon and others + print((mem2 - mem1) / (threadCount + 5)) +} diff --git a/src/runtime/testdata/testprog/syscalls.go b/src/runtime/testdata/testprog/syscalls.go new file mode 100644 index 0000000..098d5ca --- /dev/null +++ b/src/runtime/testdata/testprog/syscalls.go @@ -0,0 +1,11 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" +) + +var errNotPermitted = errors.New("operation not permitted") diff --git a/src/runtime/testdata/testprog/syscalls_linux.go b/src/runtime/testdata/testprog/syscalls_linux.go new file mode 100644 index 0000000..48f8014 --- /dev/null +++ b/src/runtime/testdata/testprog/syscalls_linux.go @@ -0,0 +1,58 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + "syscall" +) + +func gettid() int { + return syscall.Gettid() +} + +func tidExists(tid int) (exists, supported bool) { + stat, err := os.ReadFile(fmt.Sprintf("/proc/self/task/%d/stat", tid)) + if os.IsNotExist(err) { + return false, true + } + // Check if it's a zombie thread. + state := bytes.Fields(stat)[2] + return !(len(state) == 1 && state[0] == 'Z'), true +} + +func getcwd() (string, error) { + if !syscall.ImplementsGetwd { + return "", nil + } + // Use the syscall to get the current working directory. + // This is imperative for checking for OS thread state + // after an unshare since os.Getwd might just check the + // environment, or use some other mechanism. + var buf [4096]byte + n, err := syscall.Getcwd(buf[:]) + if err != nil { + return "", err + } + // Subtract one for null terminator. + return string(buf[:n-1]), nil +} + +func unshareFs() error { + err := syscall.Unshare(syscall.CLONE_FS) + if err != nil { + errno, ok := err.(syscall.Errno) + if ok && errno == syscall.EPERM { + return errNotPermitted + } + } + return err +} + +func chdir(path string) error { + return syscall.Chdir(path) +} diff --git a/src/runtime/testdata/testprog/syscalls_none.go b/src/runtime/testdata/testprog/syscalls_none.go new file mode 100644 index 0000000..068bb59 --- /dev/null +++ b/src/runtime/testdata/testprog/syscalls_none.go @@ -0,0 +1,28 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux +// +build !linux + +package main + +func gettid() int { + return 0 +} + +func tidExists(tid int) (exists, supported bool) { + return false, false +} + +func getcwd() (string, error) { + return "", nil +} + +func unshareFs() error { + return nil +} + +func chdir(path string) error { + return nil +} diff --git a/src/runtime/testdata/testprog/timeprof.go b/src/runtime/testdata/testprog/timeprof.go new file mode 100644 index 0000000..1e90af4 --- /dev/null +++ b/src/runtime/testdata/testprog/timeprof.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "runtime/pprof" + "time" +) + +func init() { + register("TimeProf", TimeProf) +} + +func TimeProf() { + f, err := os.CreateTemp("", "timeprof") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + t0 := time.Now() + // We should get a profiling signal 100 times a second, + // so running for 1/10 second should be sufficient. + for time.Since(t0) < time.Second/10 { + } + + pprof.StopCPUProfile() + + name := f.Name() + if err := f.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println(name) +} diff --git a/src/runtime/testdata/testprog/traceback_ancestors.go b/src/runtime/testdata/testprog/traceback_ancestors.go new file mode 100644 index 0000000..8fc1aa7 --- /dev/null +++ b/src/runtime/testdata/testprog/traceback_ancestors.go @@ -0,0 +1,96 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "runtime" + "strings" +) + +func init() { + register("TracebackAncestors", TracebackAncestors) +} + +const numGoroutines = 3 +const numFrames = 2 + +func TracebackAncestors() { + w := make(chan struct{}) + recurseThenCallGo(w, numGoroutines, numFrames, true) + <-w + printStack() + close(w) +} + +var ignoreGoroutines = make(map[string]bool) + +func printStack() { + buf := make([]byte, 1024) + for { + n := runtime.Stack(buf, true) + if n < len(buf) { + all := string(buf[:n]) + var saved string + + // Delete any ignored goroutines, if present. + for all != "" { + var g string + g, all, _ = strings.Cut(all, "\n\n") + + if strings.HasPrefix(g, "goroutine ") { + id, _, _ := strings.Cut(strings.TrimPrefix(g, "goroutine "), " ") + if ignoreGoroutines[id] { + continue + } + } + if saved != "" { + saved += "\n\n" + } + saved += g + } + + fmt.Print(saved) + return + } + buf = make([]byte, 2*len(buf)) + } +} + +func recurseThenCallGo(w chan struct{}, frames int, goroutines int, main bool) { + if frames == 0 { + // Signal to TracebackAncestors that we are done recursing and starting goroutines. + w <- struct{}{} + <-w + return + } + if goroutines == 0 { + // Record which goroutine this is so we can ignore it + // in the traceback if it hasn't finished exiting by + // the time we printStack. + if !main { + ignoreGoroutines[goroutineID()] = true + } + + // Start the next goroutine now that there are no more recursions left + // for this current goroutine. + go recurseThenCallGo(w, frames-1, numFrames, false) + return + } + recurseThenCallGo(w, frames, goroutines-1, main) +} + +func goroutineID() string { + buf := make([]byte, 128) + runtime.Stack(buf, false) + prefix := []byte("goroutine ") + var found bool + if buf, found = bytes.CutPrefix(buf, prefix); !found { + panic(fmt.Sprintf("expected %q at beginning of traceback:\n%s", prefix, buf)) + } + id, _, _ := bytes.Cut(buf, []byte(" ")) + return string(id) +} diff --git a/src/runtime/testdata/testprog/unsafe.go b/src/runtime/testdata/testprog/unsafe.go new file mode 100644 index 0000000..021b08f --- /dev/null +++ b/src/runtime/testdata/testprog/unsafe.go @@ -0,0 +1,12 @@ +package main + +import "unsafe" + +func init() { + register("panicOnNilAndEleSizeIsZero", panicOnNilAndEleSizeIsZero) +} + +func panicOnNilAndEleSizeIsZero() { + var p *struct{} + _ = unsafe.Slice(p, 5) +} diff --git a/src/runtime/testdata/testprog/vdso.go b/src/runtime/testdata/testprog/vdso.go new file mode 100644 index 0000000..b18bc74 --- /dev/null +++ b/src/runtime/testdata/testprog/vdso.go @@ -0,0 +1,54 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Invoke signal handler in the VDSO context (see issue 32912). + +package main + +import ( + "fmt" + "os" + "runtime/pprof" + "time" +) + +func init() { + register("SignalInVDSO", signalInVDSO) +} + +func signalInVDSO() { + f, err := os.CreateTemp("", "timeprofnow") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + t0 := time.Now() + t1 := t0 + // We should get a profiling signal 100 times a second, + // so running for 1 second should be sufficient. + for t1.Sub(t0) < time.Second { + t1 = time.Now() + } + + pprof.StopCPUProfile() + + name := f.Name() + if err := f.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := os.Remove(name); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println("success") +} diff --git a/src/runtime/testdata/testprogcgo/aprof.go b/src/runtime/testdata/testprogcgo/aprof.go new file mode 100644 index 0000000..1687014 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/aprof.go @@ -0,0 +1,56 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Test that SIGPROF received in C code does not crash the process +// looking for the C code's func pointer. + +// This is a regression test for issue 14599, where profiling fails when the +// function is the first C function. Exported functions are the first C +// functions, so we use an exported function. Exported functions are created in +// lexicographical order of source files, so this file is named aprof.go to +// ensure its function is first. + +// extern void CallGoNop(); +import "C" + +import ( + "bytes" + "fmt" + "runtime/pprof" + "time" +) + +func init() { + register("CgoCCodeSIGPROF", CgoCCodeSIGPROF) +} + +//export GoNop +func GoNop() {} + +func CgoCCodeSIGPROF() { + c := make(chan bool) + go func() { + <-c + start := time.Now() + for i := 0; i < 1e7; i++ { + if i%1000 == 0 { + if time.Since(start) > time.Second { + break + } + } + C.CallGoNop() + } + c <- true + }() + + var buf bytes.Buffer + pprof.StartCPUProfile(&buf) + c <- true + <-c + pprof.StopCPUProfile() + + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/aprof_c.c b/src/runtime/testdata/testprogcgo/aprof_c.c new file mode 100644 index 0000000..d588e13 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/aprof_c.c @@ -0,0 +1,9 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "_cgo_export.h" + +void CallGoNop() { + GoNop(); +} diff --git a/src/runtime/testdata/testprogcgo/bigstack1_windows.c b/src/runtime/testdata/testprogcgo/bigstack1_windows.c new file mode 100644 index 0000000..551fb68 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/bigstack1_windows.c @@ -0,0 +1,12 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This is not in bigstack_windows.c because it needs to be part of +// testprogcgo but is not part of the DLL built from bigstack_windows.c. + +#include "_cgo_export.h" + +void CallGoBigStack1(char* p) { + goBigStack1(p); +} diff --git a/src/runtime/testdata/testprogcgo/bigstack_windows.c b/src/runtime/testdata/testprogcgo/bigstack_windows.c new file mode 100644 index 0000000..cd85ac8 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/bigstack_windows.c @@ -0,0 +1,46 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This test source is used by both TestBigStackCallbackCgo (linked +// directly into the Go binary) and TestBigStackCallbackSyscall +// (compiled into a DLL). + +#include <windows.h> +#include <stdio.h> + +#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION +#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 +#endif + +typedef void callback(char*); + +// Allocate a stack that's much larger than the default. +static const int STACK_SIZE = 16<<20; + +static callback *bigStackCallback; + +static void useStack(int bytes) { + // Windows doesn't like huge frames, so we grow the stack 64k at a time. + char x[64<<10]; + if (bytes < sizeof x) { + bigStackCallback(x); + } else { + useStack(bytes - sizeof x); + } +} + +static DWORD WINAPI threadEntry(LPVOID lpParam) { + useStack(STACK_SIZE - (128<<10)); + return 0; +} + +void bigStack(callback *cb) { + bigStackCallback = cb; + HANDLE hThread = CreateThread(NULL, STACK_SIZE, threadEntry, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); + if (hThread == NULL) { + fprintf(stderr, "CreateThread failed\n"); + exit(1); + } + WaitForSingleObject(hThread, INFINITE); +} diff --git a/src/runtime/testdata/testprogcgo/bigstack_windows.go b/src/runtime/testdata/testprogcgo/bigstack_windows.go new file mode 100644 index 0000000..135b5fc --- /dev/null +++ b/src/runtime/testdata/testprogcgo/bigstack_windows.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +typedef void callback(char*); +extern void CallGoBigStack1(char*); +extern void bigStack(callback*); +*/ +import "C" + +func init() { + register("BigStack", BigStack) +} + +func BigStack() { + // Create a large thread stack and call back into Go to test + // if Go correctly determines the stack bounds. + C.bigStack((*C.callback)(C.CallGoBigStack1)) +} + +//export goBigStack1 +func goBigStack1(x *C.char) { + println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/callback.go b/src/runtime/testdata/testprogcgo/callback.go new file mode 100644 index 0000000..25f0715 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/callback.go @@ -0,0 +1,94 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <pthread.h> + +void go_callback(); + +static void *thr(void *arg) { + go_callback(); + return 0; +} + +static void foo() { + pthread_t th; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 256 << 10); + pthread_create(&th, &attr, thr, 0); + pthread_join(th, 0); +} +*/ +import "C" + +import ( + "fmt" + "os" + "runtime" +) + +func init() { + register("CgoCallbackGC", CgoCallbackGC) +} + +//export go_callback +func go_callback() { + runtime.GC() + grow() + runtime.GC() +} + +var cnt int + +func grow() { + x := 10000 + sum := 0 + if grow1(&x, &sum) == 0 { + panic("bad") + } +} + +func grow1(x, sum *int) int { + if *x == 0 { + return *sum + 1 + } + *x-- + sum1 := *sum + *x + return grow1(x, &sum1) +} + +func CgoCallbackGC() { + P := 100 + if os.Getenv("RUNTIME_TEST_SHORT") != "" { + P = 10 + } + done := make(chan bool) + // allocate a bunch of stack frames and spray them with pointers + for i := 0; i < P; i++ { + go func() { + grow() + done <- true + }() + } + for i := 0; i < P; i++ { + <-done + } + // now give these stack frames to cgo callbacks + for i := 0; i < P; i++ { + go func() { + C.foo() + done <- true + }() + } + for i := 0; i < P; i++ { + <-done + } + fmt.Printf("OK\n") +} diff --git a/src/runtime/testdata/testprogcgo/catchpanic.go b/src/runtime/testdata/testprogcgo/catchpanic.go new file mode 100644 index 0000000..c722d40 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/catchpanic.go @@ -0,0 +1,47 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <signal.h> +#include <stdlib.h> +#include <string.h> + +static void abrthandler(int signum) { + if (signum == SIGABRT) { + exit(0); // success + } +} + +void registerAbortHandler() { + struct sigaction act; + memset(&act, 0, sizeof act); + act.sa_handler = abrthandler; + sigaction(SIGABRT, &act, NULL); +} + +static void __attribute__ ((constructor)) sigsetup(void) { + if (getenv("CGOCATCHPANIC_EARLY_HANDLER") == NULL) + return; + registerAbortHandler(); +} +*/ +import "C" +import "os" + +func init() { + register("CgoCatchPanic", CgoCatchPanic) +} + +// Test that the SIGABRT raised by panic can be caught by an early signal handler. +func CgoCatchPanic() { + if _, ok := os.LookupEnv("CGOCATCHPANIC_EARLY_HANDLER"); !ok { + C.registerAbortHandler() + } + panic("catch me") +} diff --git a/src/runtime/testdata/testprogcgo/cgo.go b/src/runtime/testdata/testprogcgo/cgo.go new file mode 100644 index 0000000..a587db3 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/cgo.go @@ -0,0 +1,108 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +void foo1(void) {} +void foo2(void* p) {} +*/ +import "C" +import ( + "fmt" + "os" + "runtime" + "strconv" + "time" + "unsafe" +) + +func init() { + register("CgoSignalDeadlock", CgoSignalDeadlock) + register("CgoTraceback", CgoTraceback) + register("CgoCheckBytes", CgoCheckBytes) +} + +func CgoSignalDeadlock() { + runtime.GOMAXPROCS(100) + ping := make(chan bool) + go func() { + for i := 0; ; i++ { + runtime.Gosched() + select { + case done := <-ping: + if done { + ping <- true + return + } + ping <- true + default: + } + func() { + defer func() { + recover() + }() + var s *string + *s = "" + fmt.Printf("continued after expected panic\n") + }() + } + }() + time.Sleep(time.Millisecond) + start := time.Now() + var times []time.Duration + n := 64 + if os.Getenv("RUNTIME_TEST_SHORT") != "" { + n = 16 + } + for i := 0; i < n; i++ { + go func() { + runtime.LockOSThread() + select {} + }() + go func() { + runtime.LockOSThread() + select {} + }() + time.Sleep(time.Millisecond) + ping <- false + select { + case <-ping: + times = append(times, time.Since(start)) + case <-time.After(time.Second): + fmt.Printf("HANG 1 %v\n", times) + return + } + } + ping <- true + select { + case <-ping: + case <-time.After(time.Second): + fmt.Printf("HANG 2 %v\n", times) + return + } + fmt.Printf("OK\n") +} + +func CgoTraceback() { + C.foo1() + buf := make([]byte, 1) + runtime.Stack(buf, true) + fmt.Printf("OK\n") +} + +func CgoCheckBytes() { + try, _ := strconv.Atoi(os.Getenv("GO_CGOCHECKBYTES_TRY")) + if try <= 0 { + try = 1 + } + b := make([]byte, 1e6*try) + start := time.Now() + for i := 0; i < 1e3*try; i++ { + C.foo2(unsafe.Pointer(&b[0])) + if time.Since(start) > time.Second { + break + } + } +} diff --git a/src/runtime/testdata/testprogcgo/crash.go b/src/runtime/testdata/testprogcgo/crash.go new file mode 100644 index 0000000..4d83132 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/crash.go @@ -0,0 +1,45 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" +) + +func init() { + register("Crash", Crash) +} + +func test(name string) { + defer func() { + if x := recover(); x != nil { + fmt.Printf(" recovered") + } + fmt.Printf(" done\n") + }() + fmt.Printf("%s:", name) + var s *string + _ = *s + fmt.Print("SHOULD NOT BE HERE") +} + +func testInNewThread(name string) { + c := make(chan bool) + go func() { + runtime.LockOSThread() + test(name) + c <- true + }() + <-c +} + +func Crash() { + runtime.LockOSThread() + test("main") + testInNewThread("new-thread") + testInNewThread("second-new-thread") + test("main-again") +} diff --git a/src/runtime/testdata/testprogcgo/deadlock.go b/src/runtime/testdata/testprogcgo/deadlock.go new file mode 100644 index 0000000..2cc68a8 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/deadlock.go @@ -0,0 +1,30 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +char *geterror() { + return "cgo error"; +} +*/ +import "C" +import ( + "fmt" +) + +func init() { + register("CgoPanicDeadlock", CgoPanicDeadlock) +} + +type cgoError struct{} + +func (cgoError) Error() string { + fmt.Print("") // necessary to trigger the deadlock + return C.GoString(C.geterror()) +} + +func CgoPanicDeadlock() { + panic(cgoError{}) +} diff --git a/src/runtime/testdata/testprogcgo/dll_windows.go b/src/runtime/testdata/testprogcgo/dll_windows.go new file mode 100644 index 0000000..25380fb --- /dev/null +++ b/src/runtime/testdata/testprogcgo/dll_windows.go @@ -0,0 +1,25 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +/* +#include <windows.h> + +DWORD getthread() { + return GetCurrentThreadId(); +} +*/ +import "C" +import "runtime/testdata/testprogcgo/windows" + +func init() { + register("CgoDLLImportsMain", CgoDLLImportsMain) +} + +func CgoDLLImportsMain() { + C.getthread() + windows.GetThread() + println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/dropm.go b/src/runtime/testdata/testprogcgo/dropm.go new file mode 100644 index 0000000..700b7fa --- /dev/null +++ b/src/runtime/testdata/testprogcgo/dropm.go @@ -0,0 +1,60 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +// Test that a sequence of callbacks from C to Go get the same m. +// This failed to be true on arm and arm64, which was the root cause +// of issue 13881. + +package main + +/* +#include <stddef.h> +#include <pthread.h> + +extern void GoCheckM(); + +static void* thread(void* arg __attribute__ ((unused))) { + GoCheckM(); + return NULL; +} + +static void CheckM() { + pthread_t tid; + pthread_create(&tid, NULL, thread, NULL); + pthread_join(tid, NULL); + pthread_create(&tid, NULL, thread, NULL); + pthread_join(tid, NULL); +} +*/ +import "C" + +import ( + "fmt" + "os" +) + +func init() { + register("EnsureDropM", EnsureDropM) +} + +var savedM uintptr + +//export GoCheckM +func GoCheckM() { + m := runtime_getm_for_test() + if savedM == 0 { + savedM = m + } else if savedM != m { + fmt.Printf("m == %x want %x\n", m, savedM) + os.Exit(1) + } +} + +func EnsureDropM() { + C.CheckM() + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/dropm_stub.go b/src/runtime/testdata/testprogcgo/dropm_stub.go new file mode 100644 index 0000000..6997cfd --- /dev/null +++ b/src/runtime/testdata/testprogcgo/dropm_stub.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import _ "unsafe" // for go:linkname + +// Defined in the runtime package. +// +//go:linkname runtime_getm_for_test runtime.getm +func runtime_getm_for_test() uintptr diff --git a/src/runtime/testdata/testprogcgo/eintr.go b/src/runtime/testdata/testprogcgo/eintr.go new file mode 100644 index 0000000..6e9677f --- /dev/null +++ b/src/runtime/testdata/testprogcgo/eintr.go @@ -0,0 +1,247 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <errno.h> +#include <signal.h> +#include <string.h> + +static int clearRestart(int sig) { + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + if (sigaction(sig, NULL, &sa) < 0) { + return errno; + } + sa.sa_flags &=~ SA_RESTART; + if (sigaction(sig, &sa, NULL) < 0) { + return errno; + } + return 0; +} +*/ +import "C" + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "net" + "os" + "os/exec" + "sync" + "syscall" + "time" +) + +func init() { + register("EINTR", EINTR) + register("Block", Block) +} + +// Test various operations when a signal handler is installed without +// the SA_RESTART flag. This tests that the os and net APIs handle EINTR. +func EINTR() { + if errno := C.clearRestart(C.int(syscall.SIGURG)); errno != 0 { + log.Fatal(syscall.Errno(errno)) + } + if errno := C.clearRestart(C.int(syscall.SIGWINCH)); errno != 0 { + log.Fatal(syscall.Errno(errno)) + } + if errno := C.clearRestart(C.int(syscall.SIGCHLD)); errno != 0 { + log.Fatal(syscall.Errno(errno)) + } + + var wg sync.WaitGroup + testPipe(&wg) + testNet(&wg) + testExec(&wg) + wg.Wait() + fmt.Println("OK") +} + +// spin does CPU bound spinning and allocating for a millisecond, +// to get a SIGURG. +// +//go:noinline +func spin() (float64, []byte) { + stop := time.Now().Add(time.Millisecond) + r1 := 0.0 + r2 := make([]byte, 200) + for time.Now().Before(stop) { + for i := 1; i < 1e6; i++ { + r1 += r1 / float64(i) + r2 = append(r2, bytes.Repeat([]byte{byte(i)}, 100)...) + r2 = r2[100:] + } + } + return r1, r2 +} + +// winch sends a few SIGWINCH signals to the process. +func winch() { + ticker := time.NewTicker(100 * time.Microsecond) + defer ticker.Stop() + pid := syscall.Getpid() + for n := 10; n > 0; n-- { + syscall.Kill(pid, syscall.SIGWINCH) + <-ticker.C + } +} + +// sendSomeSignals triggers a few SIGURG and SIGWINCH signals. +func sendSomeSignals() { + done := make(chan struct{}) + go func() { + spin() + close(done) + }() + winch() + <-done +} + +// testPipe tests pipe operations. +func testPipe(wg *sync.WaitGroup) { + r, w, err := os.Pipe() + if err != nil { + log.Fatal(err) + } + if err := syscall.SetNonblock(int(r.Fd()), false); err != nil { + log.Fatal(err) + } + if err := syscall.SetNonblock(int(w.Fd()), false); err != nil { + log.Fatal(err) + } + wg.Add(2) + go func() { + defer wg.Done() + defer w.Close() + // Spin before calling Write so that the first ReadFull + // in the other goroutine will likely be interrupted + // by a signal. + sendSomeSignals() + // This Write will likely be interrupted by a signal + // as the other goroutine spins in the middle of reading. + // We write enough data that we should always fill the + // pipe buffer and need multiple write system calls. + if _, err := w.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil { + log.Fatal(err) + } + }() + go func() { + defer wg.Done() + defer r.Close() + b := make([]byte, 1<<20) + // This ReadFull will likely be interrupted by a signal, + // as the other goroutine spins before writing anything. + if _, err := io.ReadFull(r, b); err != nil { + log.Fatal(err) + } + // Spin after reading half the data so that the Write + // in the other goroutine will likely be interrupted + // before it completes. + sendSomeSignals() + if _, err := io.ReadFull(r, b); err != nil { + log.Fatal(err) + } + }() +} + +// testNet tests network operations. +func testNet(wg *sync.WaitGroup) { + ln, err := net.Listen("tcp4", "127.0.0.1:0") + if err != nil { + if errors.Is(err, syscall.EAFNOSUPPORT) || errors.Is(err, syscall.EPROTONOSUPPORT) { + return + } + log.Fatal(err) + } + wg.Add(2) + go func() { + defer wg.Done() + defer ln.Close() + c, err := ln.Accept() + if err != nil { + log.Fatal(err) + } + defer c.Close() + cf, err := c.(*net.TCPConn).File() + if err != nil { + log.Fatal(err) + } + defer cf.Close() + if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil { + log.Fatal(err) + } + // See comments in testPipe. + sendSomeSignals() + if _, err := cf.Write(bytes.Repeat([]byte{0}, 2<<20)); err != nil { + log.Fatal(err) + } + }() + go func() { + defer wg.Done() + sendSomeSignals() + c, err := net.Dial("tcp", ln.Addr().String()) + if err != nil { + log.Fatal(err) + } + defer c.Close() + cf, err := c.(*net.TCPConn).File() + if err != nil { + log.Fatal(err) + } + defer cf.Close() + if err := syscall.SetNonblock(int(cf.Fd()), false); err != nil { + log.Fatal(err) + } + // See comments in testPipe. + b := make([]byte, 1<<20) + if _, err := io.ReadFull(cf, b); err != nil { + log.Fatal(err) + } + sendSomeSignals() + if _, err := io.ReadFull(cf, b); err != nil { + log.Fatal(err) + } + }() +} + +func testExec(wg *sync.WaitGroup) { + wg.Add(1) + go func() { + defer wg.Done() + cmd := exec.Command(os.Args[0], "Block") + stdin, err := cmd.StdinPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = new(bytes.Buffer) + cmd.Stdout = cmd.Stderr + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + go func() { + sendSomeSignals() + stdin.Close() + }() + + if err := cmd.Wait(); err != nil { + log.Fatalf("%v:\n%s", err, cmd.Stdout) + } + }() +} + +// Block blocks until stdin is closed. +func Block() { + io.Copy(io.Discard, os.Stdin) +} diff --git a/src/runtime/testdata/testprogcgo/exec.go b/src/runtime/testdata/testprogcgo/exec.go new file mode 100644 index 0000000..c268bcd --- /dev/null +++ b/src/runtime/testdata/testprogcgo/exec.go @@ -0,0 +1,107 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <stddef.h> +#include <signal.h> +#include <pthread.h> + +// Save the signal mask at startup so that we see what it is before +// the Go runtime starts setting up signals. + +static sigset_t mask; + +static void init(void) __attribute__ ((constructor)); + +static void init() { + sigemptyset(&mask); + pthread_sigmask(SIG_SETMASK, NULL, &mask); +} + +int SIGINTBlocked() { + return sigismember(&mask, SIGINT); +} +*/ +import "C" + +import ( + "fmt" + "io/fs" + "os" + "os/exec" + "os/signal" + "sync" + "syscall" +) + +func init() { + register("CgoExecSignalMask", CgoExecSignalMask) +} + +func CgoExecSignalMask() { + if len(os.Args) > 2 && os.Args[2] == "testsigint" { + if C.SIGINTBlocked() != 0 { + os.Exit(1) + } + os.Exit(0) + } + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGTERM) + go func() { + for range c { + } + }() + + const goCount = 10 + const execCount = 10 + var wg sync.WaitGroup + wg.Add(goCount*execCount + goCount) + for i := 0; i < goCount; i++ { + go func() { + defer wg.Done() + for j := 0; j < execCount; j++ { + c2 := make(chan os.Signal, 1) + signal.Notify(c2, syscall.SIGUSR1) + syscall.Kill(os.Getpid(), syscall.SIGTERM) + go func(j int) { + defer wg.Done() + cmd := exec.Command(os.Args[0], "CgoExecSignalMask", "testsigint") + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + // An overloaded system + // may fail with EAGAIN. + // This doesn't tell us + // anything useful; ignore it. + // Issue #27731. + if isEAGAIN(err) { + return + } + fmt.Printf("iteration %d: %v\n", j, err) + os.Exit(1) + } + }(j) + signal.Stop(c2) + } + }() + } + wg.Wait() + + fmt.Println("OK") +} + +// isEAGAIN reports whether err is an EAGAIN error from a process execution. +func isEAGAIN(err error) bool { + if p, ok := err.(*fs.PathError); ok { + err = p.Err + } + return err == syscall.EAGAIN +} diff --git a/src/runtime/testdata/testprogcgo/gprof.go b/src/runtime/testdata/testprogcgo/gprof.go new file mode 100644 index 0000000..d453b4d --- /dev/null +++ b/src/runtime/testdata/testprogcgo/gprof.go @@ -0,0 +1,46 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Test taking a goroutine profile with C traceback. + +/* +// Defined in gprof_c.c. +void CallGoSleep(void); +void gprofCgoTraceback(void* parg); +void gprofCgoContext(void* parg); +*/ +import "C" + +import ( + "fmt" + "io" + "runtime" + "runtime/pprof" + "time" + "unsafe" +) + +func init() { + register("GoroutineProfile", GoroutineProfile) +} + +func GoroutineProfile() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.gprofCgoTraceback), unsafe.Pointer(C.gprofCgoContext), nil) + + go C.CallGoSleep() + go C.CallGoSleep() + go C.CallGoSleep() + time.Sleep(1 * time.Second) + + prof := pprof.Lookup("goroutine") + prof.WriteTo(io.Discard, 1) + fmt.Println("OK") +} + +//export GoSleep +func GoSleep() { + time.Sleep(time.Hour) +} diff --git a/src/runtime/testdata/testprogcgo/gprof_c.c b/src/runtime/testdata/testprogcgo/gprof_c.c new file mode 100644 index 0000000..5c7cd77 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/gprof_c.c @@ -0,0 +1,30 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The C definitions for gprof.go. That file uses //export so +// it can't put function definitions in the "C" import comment. + +#include <stdint.h> +#include <stdlib.h> + +// Functions exported from Go. +extern void GoSleep(); + +struct cgoContextArg { + uintptr_t context; +}; + +void gprofCgoContext(void *arg) { + ((struct cgoContextArg*)arg)->context = 1; +} + +void gprofCgoTraceback(void *arg) { + // spend some time here so the P is more likely to be retaken. + volatile int i; + for (i = 0; i < 123456789; i++); +} + +void CallGoSleep() { + GoSleep(); +} diff --git a/src/runtime/testdata/testprogcgo/issue29707.go b/src/runtime/testdata/testprogcgo/issue29707.go new file mode 100644 index 0000000..7d9299f --- /dev/null +++ b/src/runtime/testdata/testprogcgo/issue29707.go @@ -0,0 +1,60 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +// This is for issue #29707 + +package main + +/* +#include <pthread.h> + +extern void* callbackTraceParser(void*); +typedef void* (*cbTraceParser)(void*); + +static void testCallbackTraceParser(cbTraceParser cb) { + pthread_t thread_id; + pthread_create(&thread_id, NULL, cb, NULL); + pthread_join(thread_id, NULL); +} +*/ +import "C" + +import ( + "bytes" + "fmt" + traceparser "internal/trace" + "runtime/trace" + "time" + "unsafe" +) + +func init() { + register("CgoTraceParser", CgoTraceParser) +} + +//export callbackTraceParser +func callbackTraceParser(unsafe.Pointer) unsafe.Pointer { + time.Sleep(time.Millisecond) + return nil +} + +func CgoTraceParser() { + buf := new(bytes.Buffer) + + trace.Start(buf) + C.testCallbackTraceParser(C.cbTraceParser(C.callbackTraceParser)) + trace.Stop() + + _, err := traceparser.Parse(buf, "") + if err == traceparser.ErrTimeOrder { + fmt.Println("ErrTimeOrder") + } else if err != nil { + fmt.Println("Parse error: ", err) + } else { + fmt.Println("OK") + } +} diff --git a/src/runtime/testdata/testprogcgo/lockosthread.c b/src/runtime/testdata/testprogcgo/lockosthread.c new file mode 100644 index 0000000..b10cc4f --- /dev/null +++ b/src/runtime/testdata/testprogcgo/lockosthread.c @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9,!windows + +#include <stdint.h> + +uint32_t threadExited; + +void setExited(void *x) { + __sync_fetch_and_add(&threadExited, 1); +} diff --git a/src/runtime/testdata/testprogcgo/lockosthread.go b/src/runtime/testdata/testprogcgo/lockosthread.go new file mode 100644 index 0000000..8fcea35 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/lockosthread.go @@ -0,0 +1,112 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +import ( + "os" + "runtime" + "sync/atomic" + "time" + "unsafe" +) + +/* +#include <pthread.h> +#include <stdint.h> + +extern uint32_t threadExited; + +void setExited(void *x); +*/ +import "C" + +var mainThread C.pthread_t + +func init() { + registerInit("LockOSThreadMain", func() { + // init is guaranteed to run on the main thread. + mainThread = C.pthread_self() + }) + register("LockOSThreadMain", LockOSThreadMain) + + registerInit("LockOSThreadAlt", func() { + // Lock the OS thread now so main runs on the main thread. + runtime.LockOSThread() + }) + register("LockOSThreadAlt", LockOSThreadAlt) +} + +func LockOSThreadMain() { + // This requires GOMAXPROCS=1 from the beginning to reliably + // start a goroutine on the main thread. + if runtime.GOMAXPROCS(-1) != 1 { + println("requires GOMAXPROCS=1") + os.Exit(1) + } + + ready := make(chan bool, 1) + go func() { + // Because GOMAXPROCS=1, this *should* be on the main + // thread. Stay there. + runtime.LockOSThread() + self := C.pthread_self() + if C.pthread_equal(mainThread, self) == 0 { + println("failed to start goroutine on main thread") + os.Exit(1) + } + // Exit with the thread locked, which should exit the + // main thread. + ready <- true + }() + <-ready + time.Sleep(1 * time.Millisecond) + // Check that this goroutine is still running on a different + // thread. + self := C.pthread_self() + if C.pthread_equal(mainThread, self) != 0 { + println("goroutine migrated to locked thread") + os.Exit(1) + } + println("OK") +} + +func LockOSThreadAlt() { + // This is running locked to the main OS thread. + + var subThread C.pthread_t + ready := make(chan bool, 1) + C.threadExited = 0 + go func() { + // This goroutine must be running on a new thread. + runtime.LockOSThread() + subThread = C.pthread_self() + // Register a pthread destructor so we can tell this + // thread has exited. + var key C.pthread_key_t + C.pthread_key_create(&key, (*[0]byte)(unsafe.Pointer(C.setExited))) + C.pthread_setspecific(key, unsafe.Pointer(new(int))) + ready <- true + // Exit with the thread locked. + }() + <-ready + for i := 0; i < 100; i++ { + time.Sleep(1 * time.Millisecond) + // Check that this goroutine is running on a different thread. + self := C.pthread_self() + if C.pthread_equal(subThread, self) != 0 { + println("locked thread reused") + os.Exit(1) + } + if atomic.LoadUint32((*uint32)(&C.threadExited)) != 0 { + println("OK") + return + } + } + println("sub thread still running") + os.Exit(1) +} diff --git a/src/runtime/testdata/testprogcgo/main.go b/src/runtime/testdata/testprogcgo/main.go new file mode 100644 index 0000000..ae491a2 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/main.go @@ -0,0 +1,35 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "os" + +var cmds = map[string]func(){} + +func register(name string, f func()) { + if cmds[name] != nil { + panic("duplicate registration: " + name) + } + cmds[name] = f +} + +func registerInit(name string, f func()) { + if len(os.Args) >= 2 && os.Args[1] == name { + f() + } +} + +func main() { + if len(os.Args) < 2 { + println("usage: " + os.Args[0] + " name-of-test") + return + } + f := cmds[os.Args[1]] + if f == nil { + println("unknown function: " + os.Args[1]) + return + } + f() +} diff --git a/src/runtime/testdata/testprogcgo/needmdeadlock.go b/src/runtime/testdata/testprogcgo/needmdeadlock.go new file mode 100644 index 0000000..b95ec77 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/needmdeadlock.go @@ -0,0 +1,96 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +// This is for issue #42207. +// During a call to needm we could get a SIGCHLD signal +// which would itself call needm, causing a deadlock. + +/* +#include <signal.h> +#include <pthread.h> +#include <sched.h> +#include <unistd.h> + +extern void GoNeedM(); + +#define SIGNALERS 10 + +static void* needmSignalThread(void* p) { + pthread_t* pt = (pthread_t*)(p); + int i; + + for (i = 0; i < 100; i++) { + if (pthread_kill(*pt, SIGCHLD) < 0) { + return NULL; + } + usleep(1); + } + return NULL; +} + +// We don't need many calls, as the deadlock is only likely +// to occur the first couple of times that needm is called. +// After that there will likely be an extra M available. +#define CALLS 10 + +static void* needmCallbackThread(void* p) { + int i; + + for (i = 0; i < SIGNALERS; i++) { + sched_yield(); // Help the signal threads get started. + } + for (i = 0; i < CALLS; i++) { + GoNeedM(); + } + return NULL; +} + +static void runNeedmSignalThread() { + int i; + pthread_t caller; + pthread_t s[SIGNALERS]; + + pthread_create(&caller, NULL, needmCallbackThread, NULL); + for (i = 0; i < SIGNALERS; i++) { + pthread_create(&s[i], NULL, needmSignalThread, &caller); + } + for (i = 0; i < SIGNALERS; i++) { + pthread_join(s[i], NULL); + } + pthread_join(caller, NULL); +} +*/ +import "C" + +import ( + "fmt" + "os" + "time" +) + +func init() { + register("NeedmDeadlock", NeedmDeadlock) +} + +//export GoNeedM +func GoNeedM() { +} + +func NeedmDeadlock() { + // The failure symptom is that the program hangs because of a + // deadlock in needm, so set an alarm. + go func() { + time.Sleep(5 * time.Second) + fmt.Println("Hung for 5 seconds") + os.Exit(1) + }() + + C.runNeedmSignalThread() + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/numgoroutine.go b/src/runtime/testdata/testprogcgo/numgoroutine.go new file mode 100644 index 0000000..1b9f202 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/numgoroutine.go @@ -0,0 +1,93 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <stddef.h> +#include <pthread.h> + +extern void CallbackNumGoroutine(); + +static void* thread2(void* arg __attribute__ ((unused))) { + CallbackNumGoroutine(); + return NULL; +} + +static void CheckNumGoroutine() { + pthread_t tid; + pthread_create(&tid, NULL, thread2, NULL); + pthread_join(tid, NULL); +} +*/ +import "C" + +import ( + "fmt" + "runtime" + "strings" +) + +var baseGoroutines int + +func init() { + register("NumGoroutine", NumGoroutine) +} + +func NumGoroutine() { + // Test that there are just the expected number of goroutines + // running. Specifically, test that the spare M's goroutine + // doesn't show up. + if _, ok := checkNumGoroutine("first", 1+baseGoroutines); !ok { + return + } + + // Test that the goroutine for a callback from C appears. + if C.CheckNumGoroutine(); !callbackok { + return + } + + // Make sure we're back to the initial goroutines. + if _, ok := checkNumGoroutine("third", 1+baseGoroutines); !ok { + return + } + + fmt.Println("OK") +} + +func checkNumGoroutine(label string, want int) (string, bool) { + n := runtime.NumGoroutine() + if n != want { + fmt.Printf("%s NumGoroutine: want %d; got %d\n", label, want, n) + return "", false + } + + sbuf := make([]byte, 32<<10) + sbuf = sbuf[:runtime.Stack(sbuf, true)] + n = strings.Count(string(sbuf), "goroutine ") + if n != want { + fmt.Printf("%s Stack: want %d; got %d:\n%s\n", label, want, n, string(sbuf)) + return "", false + } + return string(sbuf), true +} + +var callbackok bool + +//export CallbackNumGoroutine +func CallbackNumGoroutine() { + stk, ok := checkNumGoroutine("second", 2+baseGoroutines) + if !ok { + return + } + if !strings.Contains(stk, "CallbackNumGoroutine") { + fmt.Printf("missing CallbackNumGoroutine from stack:\n%s\n", stk) + return + } + + callbackok = true +} diff --git a/src/runtime/testdata/testprogcgo/panic.c b/src/runtime/testdata/testprogcgo/panic.c new file mode 100644 index 0000000..deb5ed5 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/panic.c @@ -0,0 +1,9 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +extern void panic_callback(); + +void call_callback(void) { + panic_callback(); +} diff --git a/src/runtime/testdata/testprogcgo/panic.go b/src/runtime/testdata/testprogcgo/panic.go new file mode 100644 index 0000000..57ac895 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/panic.go @@ -0,0 +1,23 @@ +package main + +// This program will crash. +// We want to test unwinding from a cgo callback. + +/* +void call_callback(void); +*/ +import "C" + +func init() { + register("PanicCallback", PanicCallback) +} + +//export panic_callback +func panic_callback() { + var i *int + *i = 42 +} + +func PanicCallback() { + C.call_callback() +} diff --git a/src/runtime/testdata/testprogcgo/pprof.go b/src/runtime/testdata/testprogcgo/pprof.go new file mode 100644 index 0000000..8870d0c --- /dev/null +++ b/src/runtime/testdata/testprogcgo/pprof.go @@ -0,0 +1,93 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Run a slow C function saving a CPU profile. + +/* +#include <stdint.h> + +int salt1; +int salt2; + +void cpuHog() { + int foo = salt1; + int i; + + for (i = 0; i < 100000; i++) { + if (foo > 0) { + foo *= foo; + } else { + foo *= foo + 1; + } + } + salt2 = foo; +} + +void cpuHog2() { +} + +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +// pprofCgoTraceback is passed to runtime.SetCgoTraceback. +// For testing purposes it pretends that all CPU hits in C code are in cpuHog. +// Issue #29034: At least 2 frames are required to verify all frames are captured +// since runtime/pprof ignores the runtime.goexit base frame if it exists. +void pprofCgoTraceback(void* parg) { + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + arg->buf[0] = (uintptr_t)(cpuHog) + 0x10; + arg->buf[1] = (uintptr_t)(cpuHog2) + 0x4; + arg->buf[2] = 0; +} +*/ +import "C" + +import ( + "fmt" + "os" + "runtime" + "runtime/pprof" + "time" + "unsafe" +) + +func init() { + register("CgoPprof", CgoPprof) +} + +func CgoPprof() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.pprofCgoTraceback), nil, nil) + + f, err := os.CreateTemp("", "prof") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + t0 := time.Now() + for time.Since(t0) < time.Second { + C.cpuHog() + } + + pprof.StopCPUProfile() + + name := f.Name() + if err := f.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println(name) +} diff --git a/src/runtime/testdata/testprogcgo/pprof_callback.go b/src/runtime/testdata/testprogcgo/pprof_callback.go new file mode 100644 index 0000000..fd87eb8 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/pprof_callback.go @@ -0,0 +1,89 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows + +package main + +// Make many C-to-Go callback while collecting a CPU profile. +// +// This is a regression test for issue 50936. + +/* +#include <unistd.h> + +void goCallbackPprof(); + +static void callGo() { + // Spent >20us in C so this thread is eligible for sysmon to retake its + // P. + usleep(50); + goCallbackPprof(); +} +*/ +import "C" + +import ( + "fmt" + "os" + "runtime" + "runtime/pprof" + "time" +) + +func init() { + register("CgoPprofCallback", CgoPprofCallback) +} + +//export goCallbackPprof +func goCallbackPprof() { + // No-op. We want to stress the cgocall and cgocallback internals, + // landing as many pprof signals there as possible. +} + +func CgoPprofCallback() { + // Issue 50936 was a crash in the SIGPROF handler when the signal + // arrived during the exitsyscall following a cgocall(back) in dropg or + // execute, when updating mp.curg. + // + // These are reachable only when exitsyscall finds no P available. Thus + // we make C calls from significantly more Gs than there are available + // Ps. Lots of runnable work combined with >20us spent in callGo makes + // it possible for sysmon to retake Ps, forcing C calls to go down the + // desired exitsyscall path. + // + // High GOMAXPROCS is used to increase opportunities for failure on + // high CPU machines. + const ( + P = 16 + G = 64 + ) + runtime.GOMAXPROCS(P) + + f, err := os.CreateTemp("", "prof") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + defer f.Close() + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + for i := 0; i < G; i++ { + go func() { + for { + C.callGo() + } + }() + } + + time.Sleep(time.Second) + + pprof.StopCPUProfile() + + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/raceprof.go b/src/runtime/testdata/testprogcgo/raceprof.go new file mode 100644 index 0000000..c098e16 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/raceprof.go @@ -0,0 +1,79 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (linux && amd64) || (freebsd && amd64) +// +build linux,amd64 freebsd,amd64 + +package main + +// Test that we can collect a lot of colliding profiling signals from +// an external C thread. This used to fail when built with the race +// detector, because a call of the predeclared function copy was +// turned into a call to runtime.slicecopy, which is not marked nosplit. + +/* +#include <signal.h> +#include <stdint.h> +#include <pthread.h> +#include <sched.h> + +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +static int raceprofCount; + +// We want a bunch of different profile stacks that collide in the +// hash table maintained in runtime/cpuprof.go. This code knows the +// size of the hash table (1 << 10) and knows that the hash function +// is simply multiplicative. +void raceprofTraceback(void* parg) { + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + raceprofCount++; + arg->buf[0] = raceprofCount * (1 << 10); + arg->buf[1] = 0; +} + +static void* raceprofThread(void* p) { + int i; + + for (i = 0; i < 100; i++) { + pthread_kill(pthread_self(), SIGPROF); + sched_yield(); + } + return 0; +} + +void runRaceprofThread() { + pthread_t tid; + pthread_create(&tid, 0, raceprofThread, 0); + pthread_join(tid, 0); +} +*/ +import "C" + +import ( + "bytes" + "fmt" + "runtime" + "runtime/pprof" + "unsafe" +) + +func init() { + register("CgoRaceprof", CgoRaceprof) +} + +func CgoRaceprof() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.raceprofTraceback), nil, nil) + + var buf bytes.Buffer + pprof.StartCPUProfile(&buf) + + C.runRaceprofThread() + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/racesig.go b/src/runtime/testdata/testprogcgo/racesig.go new file mode 100644 index 0000000..9352679 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/racesig.go @@ -0,0 +1,103 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (linux && amd64) || (freebsd && amd64) +// +build linux,amd64 freebsd,amd64 + +package main + +// Test that an external C thread that is calling malloc can be hit +// with SIGCHLD signals. This used to fail when built with the race +// detector, because in that case the signal handler would indirectly +// call the C malloc function. + +/* +#include <errno.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <sched.h> +#include <unistd.h> + +#define ALLOCERS 100 +#define SIGNALERS 10 + +static void* signalThread(void* p) { + pthread_t* pt = (pthread_t*)(p); + int i, j; + + for (i = 0; i < 100; i++) { + for (j = 0; j < ALLOCERS; j++) { + if (pthread_kill(pt[j], SIGCHLD) < 0) { + return NULL; + } + } + usleep(1); + } + return NULL; +} + +#define CALLS 100 + +static void* mallocThread(void* p) { + int i; + void *a[CALLS]; + + for (i = 0; i < ALLOCERS; i++) { + sched_yield(); + } + for (i = 0; i < CALLS; i++) { + a[i] = malloc(i); + } + for (i = 0; i < CALLS; i++) { + free(a[i]); + } + return NULL; +} + +void runRaceSignalThread() { + int i; + pthread_t m[ALLOCERS]; + pthread_t s[SIGNALERS]; + + for (i = 0; i < ALLOCERS; i++) { + pthread_create(&m[i], NULL, mallocThread, NULL); + } + for (i = 0; i < SIGNALERS; i++) { + pthread_create(&s[i], NULL, signalThread, &m[0]); + } + for (i = 0; i < SIGNALERS; i++) { + pthread_join(s[i], NULL); + } + for (i = 0; i < ALLOCERS; i++) { + pthread_join(m[i], NULL); + } +} +*/ +import "C" + +import ( + "fmt" + "os" + "time" +) + +func init() { + register("CgoRaceSignal", CgoRaceSignal) +} + +func CgoRaceSignal() { + // The failure symptom is that the program hangs because of a + // deadlock in malloc, so set an alarm. + go func() { + time.Sleep(5 * time.Second) + fmt.Println("Hung for 5 seconds") + os.Exit(1) + }() + + C.runRaceSignalThread() + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/segv.go b/src/runtime/testdata/testprogcgo/segv.go new file mode 100644 index 0000000..bf5aa31 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/segv.go @@ -0,0 +1,55 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix +// +build unix + +package main + +// #include <unistd.h> +// static void nop() {} +import "C" + +import "syscall" + +func init() { + register("Segv", Segv) + register("SegvInCgo", SegvInCgo) +} + +var Sum int + +func Segv() { + c := make(chan bool) + go func() { + close(c) + for i := 0; ; i++ { + Sum += i + } + }() + + <-c + + syscall.Kill(syscall.Getpid(), syscall.SIGSEGV) + + // Wait for the OS to deliver the signal. + C.pause() +} + +func SegvInCgo() { + c := make(chan bool) + go func() { + close(c) + for { + C.nop() + } + }() + + <-c + + syscall.Kill(syscall.Getpid(), syscall.SIGSEGV) + + // Wait for the OS to deliver the signal. + C.pause() +} diff --git a/src/runtime/testdata/testprogcgo/segv_linux.go b/src/runtime/testdata/testprogcgo/segv_linux.go new file mode 100644 index 0000000..fe93778 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/segv_linux.go @@ -0,0 +1,51 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// #include <unistd.h> +// static void nop() {} +import "C" + +import "syscall" + +func init() { + register("TgkillSegv", TgkillSegv) + register("TgkillSegvInCgo", TgkillSegvInCgo) +} + +func TgkillSegv() { + c := make(chan bool) + go func() { + close(c) + for i := 0; ; i++ { + // Sum defined in segv.go. + Sum += i + } + }() + + <-c + + syscall.Tgkill(syscall.Getpid(), syscall.Gettid(), syscall.SIGSEGV) + + // Wait for the OS to deliver the signal. + C.pause() +} + +func TgkillSegvInCgo() { + c := make(chan bool) + go func() { + close(c) + for { + C.nop() + } + }() + + <-c + + syscall.Tgkill(syscall.Getpid(), syscall.Gettid(), syscall.SIGSEGV) + + // Wait for the OS to deliver the signal. + C.pause() +} diff --git a/src/runtime/testdata/testprogcgo/sigfwd.go b/src/runtime/testdata/testprogcgo/sigfwd.go new file mode 100644 index 0000000..f6a0c03 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/sigfwd.go @@ -0,0 +1,87 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build unix + +package main + +import ( + "fmt" + "os" +) + +/* +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +sig_atomic_t expectCSigsegv; +int *sigfwdP; + +static void sigsegv() { + expectCSigsegv = 1; + *sigfwdP = 1; + fprintf(stderr, "ERROR: C SIGSEGV not thrown on caught?.\n"); + exit(2); +} + +static void segvhandler(int signum) { + if (signum == SIGSEGV) { + if (expectCSigsegv == 0) { + fprintf(stderr, "SIGSEGV caught in C unexpectedly\n"); + exit(1); + } + fprintf(stdout, "OK\n"); + exit(0); // success + } +} + +static void __attribute__ ((constructor)) sigsetup(void) { + if (getenv("GO_TEST_CGOSIGFWD") == NULL) { + return; + } + + struct sigaction act; + + memset(&act, 0, sizeof act); + act.sa_handler = segvhandler; + sigaction(SIGSEGV, &act, NULL); +} +*/ +import "C" + +func init() { + register("CgoSigfwd", CgoSigfwd) +} + +var nilPtr *byte + +func f() (ret bool) { + defer func() { + if recover() == nil { + fmt.Fprintf(os.Stderr, "ERROR: couldn't raise SIGSEGV in Go\n") + C.exit(2) + } + ret = true + }() + *nilPtr = 1 + return false +} + +func CgoSigfwd() { + if os.Getenv("GO_TEST_CGOSIGFWD") == "" { + fmt.Fprintf(os.Stderr, "test must be run with GO_TEST_CGOSIGFWD set\n") + os.Exit(1) + } + + // Test that the signal originating in Go is handled (and recovered) by Go. + if !f() { + fmt.Fprintf(os.Stderr, "couldn't recover from SIGSEGV in Go.\n") + C.exit(2) + } + + // Test that the signal originating in C is handled by C. + C.sigsegv() +} diff --git a/src/runtime/testdata/testprogcgo/sigpanic.go b/src/runtime/testdata/testprogcgo/sigpanic.go new file mode 100644 index 0000000..cb46030 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/sigpanic.go @@ -0,0 +1,28 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program will crash. +// We want to test unwinding from sigpanic into C code (without a C symbolizer). + +/* +#cgo CFLAGS: -O0 + +char *pnil; + +static int f1(void) { + *pnil = 0; + return 0; +} +*/ +import "C" + +func init() { + register("TracebackSigpanic", TracebackSigpanic) +} + +func TracebackSigpanic() { + C.f1() +} diff --git a/src/runtime/testdata/testprogcgo/sigstack.go b/src/runtime/testdata/testprogcgo/sigstack.go new file mode 100644 index 0000000..12ca661 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/sigstack.go @@ -0,0 +1,99 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +// Test handling of Go-allocated signal stacks when calling from +// C-created threads with and without signal stacks. (See issue +// #22930.) + +package main + +/* +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> + +#ifdef _AIX +// On AIX, SIGSTKSZ is too small to handle Go sighandler. +#define CSIGSTKSZ 0x4000 +#else +#define CSIGSTKSZ SIGSTKSZ +#endif + +extern void SigStackCallback(); + +static void* WithSigStack(void* arg __attribute__((unused))) { + // Set up an alternate system stack. + void* base = mmap(0, CSIGSTKSZ, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (base == MAP_FAILED) { + perror("mmap failed"); + abort(); + } + stack_t st = {}, ost = {}; + st.ss_sp = (char*)base; + st.ss_flags = 0; + st.ss_size = CSIGSTKSZ; + if (sigaltstack(&st, &ost) < 0) { + perror("sigaltstack failed"); + abort(); + } + + // Call Go. + SigStackCallback(); + + // Disable signal stack and protect it so we can detect reuse. + if (ost.ss_flags & SS_DISABLE) { + // Darwin libsystem has a bug where it checks ss_size + // even if SS_DISABLE is set. (The kernel gets it right.) + ost.ss_size = CSIGSTKSZ; + } + if (sigaltstack(&ost, NULL) < 0) { + perror("sigaltstack restore failed"); + abort(); + } + mprotect(base, CSIGSTKSZ, PROT_NONE); + return NULL; +} + +static void* WithoutSigStack(void* arg __attribute__((unused))) { + SigStackCallback(); + return NULL; +} + +static void DoThread(int sigstack) { + pthread_t tid; + if (sigstack) { + pthread_create(&tid, NULL, WithSigStack, NULL); + } else { + pthread_create(&tid, NULL, WithoutSigStack, NULL); + } + pthread_join(tid, NULL); +} +*/ +import "C" + +func init() { + register("SigStack", SigStack) +} + +func SigStack() { + C.DoThread(0) + C.DoThread(1) + C.DoThread(0) + C.DoThread(1) + println("OK") +} + +var BadPtr *int + +//export SigStackCallback +func SigStackCallback() { + // Cause the Go signal handler to run. + defer func() { recover() }() + *BadPtr = 42 +} diff --git a/src/runtime/testdata/testprogcgo/sigthrow.go b/src/runtime/testdata/testprogcgo/sigthrow.go new file mode 100644 index 0000000..665e3b0 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/sigthrow.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program will abort. + +/* +#include <stdlib.h> +*/ +import "C" + +func init() { + register("Abort", Abort) +} + +func Abort() { + C.abort() +} diff --git a/src/runtime/testdata/testprogcgo/stack_windows.go b/src/runtime/testdata/testprogcgo/stack_windows.go new file mode 100644 index 0000000..0be1126 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/stack_windows.go @@ -0,0 +1,57 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "C" +import ( + "internal/syscall/windows" + "runtime" + "sync" + "syscall" + "unsafe" +) + +func init() { + register("StackMemory", StackMemory) +} + +func getPagefileUsage() (uintptr, error) { + p, err := syscall.GetCurrentProcess() + if err != nil { + return 0, err + } + var m windows.PROCESS_MEMORY_COUNTERS + err = windows.GetProcessMemoryInfo(p, &m, uint32(unsafe.Sizeof(m))) + if err != nil { + return 0, err + } + return m.PagefileUsage, nil +} + +func StackMemory() { + mem1, err := getPagefileUsage() + if err != nil { + panic(err) + } + const threadCount = 100 + var wg sync.WaitGroup + for i := 0; i < threadCount; i++ { + wg.Add(1) + go func() { + runtime.LockOSThread() + wg.Done() + select {} + }() + } + wg.Wait() + mem2, err := getPagefileUsage() + if err != nil { + panic(err) + } + // assumes that this process creates 1 thread for each + // thread locked goroutine plus extra 5 threads + // like sysmon and others + print((mem2 - mem1) / (threadCount + 5)) +} diff --git a/src/runtime/testdata/testprogcgo/threadpanic.go b/src/runtime/testdata/testprogcgo/threadpanic.go new file mode 100644 index 0000000..2d24fe6 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/threadpanic.go @@ -0,0 +1,25 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 +// +build !plan9 + +package main + +// void start(void); +import "C" + +func init() { + register("CgoExternalThreadPanic", CgoExternalThreadPanic) +} + +func CgoExternalThreadPanic() { + C.start() + select {} +} + +//export gopanic +func gopanic() { + panic("BOOM") +} diff --git a/src/runtime/testdata/testprogcgo/threadpanic_unix.c b/src/runtime/testdata/testprogcgo/threadpanic_unix.c new file mode 100644 index 0000000..c426452 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/threadpanic_unix.c @@ -0,0 +1,26 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9,!windows + +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> + +void gopanic(void); + +static void* +die(void* x) +{ + gopanic(); + return 0; +} + +void +start(void) +{ + pthread_t t; + if(pthread_create(&t, 0, die, 0) != 0) + printf("pthread_create failed\n"); +} diff --git a/src/runtime/testdata/testprogcgo/threadpanic_windows.c b/src/runtime/testdata/testprogcgo/threadpanic_windows.c new file mode 100644 index 0000000..ba66d0f --- /dev/null +++ b/src/runtime/testdata/testprogcgo/threadpanic_windows.c @@ -0,0 +1,23 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include <process.h> +#include <stdlib.h> +#include <stdio.h> + +void gopanic(void); + +static unsigned int __attribute__((__stdcall__)) +die(void* x) +{ + gopanic(); + return 0; +} + +void +start(void) +{ + if(_beginthreadex(0, 0, die, 0, 0, 0) != 0) + printf("_beginthreadex failed\n"); +} diff --git a/src/runtime/testdata/testprogcgo/threadpprof.go b/src/runtime/testdata/testprogcgo/threadpprof.go new file mode 100644 index 0000000..70717e0 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/threadpprof.go @@ -0,0 +1,128 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +// Run a slow C function saving a CPU profile. + +/* +#include <stdint.h> +#include <time.h> +#include <pthread.h> + +int threadSalt1; +int threadSalt2; + +static pthread_t tid; + +void cpuHogThread() { + int foo = threadSalt1; + int i; + + for (i = 0; i < 100000; i++) { + if (foo > 0) { + foo *= foo; + } else { + foo *= foo + 1; + } + } + threadSalt2 = foo; +} + +void cpuHogThread2() { +} + +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +// pprofCgoThreadTraceback is passed to runtime.SetCgoTraceback. +// For testing purposes it pretends that all CPU hits on the cpuHog +// C thread are in cpuHog. +void pprofCgoThreadTraceback(void* parg) { + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + if (pthread_self() == tid) { + arg->buf[0] = (uintptr_t)(cpuHogThread) + 0x10; + arg->buf[1] = (uintptr_t)(cpuHogThread2) + 0x4; + arg->buf[2] = 0; + } else + arg->buf[0] = 0; +} + +static void* cpuHogDriver(void* arg __attribute__ ((unused))) { + while (1) { + cpuHogThread(); + } + return 0; +} + +void runCPUHogThread(void) { + pthread_create(&tid, 0, cpuHogDriver, 0); +} +*/ +import "C" + +import ( + "context" + "fmt" + "os" + "runtime" + "runtime/pprof" + "time" + "unsafe" +) + +func init() { + register("CgoPprofThread", CgoPprofThread) + register("CgoPprofThreadNoTraceback", CgoPprofThreadNoTraceback) +} + +func CgoPprofThread() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.pprofCgoThreadTraceback), nil, nil) + pprofThread() +} + +func CgoPprofThreadNoTraceback() { + pprofThread() +} + +func pprofThread() { + f, err := os.CreateTemp("", "prof") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + if err := pprof.StartCPUProfile(f); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + // This goroutine may receive a profiling signal while creating the C-owned + // thread. If it does, the SetCgoTraceback handler will make the leaf end of + // the stack look almost (but not exactly) like the stacks the test case is + // trying to find. Attach a profiler label so the test can filter out those + // confusing samples. + pprof.Do(context.Background(), pprof.Labels("ignore", "ignore"), func(ctx context.Context) { + C.runCPUHogThread() + }) + + time.Sleep(1 * time.Second) + + pprof.StopCPUProfile() + + name := f.Name() + if err := f.Close(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println(name) +} diff --git a/src/runtime/testdata/testprogcgo/threadprof.go b/src/runtime/testdata/testprogcgo/threadprof.go new file mode 100644 index 0000000..d62d4b4 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/threadprof.go @@ -0,0 +1,103 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !plan9 && !windows +// +build !plan9,!windows + +package main + +/* +#include <stdint.h> +#include <stdlib.h> +#include <signal.h> +#include <pthread.h> + +volatile int32_t spinlock; + +// Note that this thread is only started if GO_START_SIGPROF_THREAD +// is set in the environment, which is only done when running the +// CgoExternalThreadSIGPROF test. +static void *thread1(void *p) { + (void)p; + while (spinlock == 0) + ; + pthread_kill(pthread_self(), SIGPROF); + spinlock = 0; + return NULL; +} + +// This constructor function is run when the program starts. +// It is used for the CgoExternalThreadSIGPROF test. +__attribute__((constructor)) void issue9456() { + if (getenv("GO_START_SIGPROF_THREAD") != NULL) { + pthread_t tid; + pthread_create(&tid, 0, thread1, NULL); + } +} + +void **nullptr; + +void *crash(void *p) { + *nullptr = p; + return 0; +} + +int start_crashing_thread(void) { + pthread_t tid; + return pthread_create(&tid, 0, crash, 0); +} +*/ +import "C" + +import ( + "fmt" + "os" + "os/exec" + "runtime" + "sync/atomic" + "time" + "unsafe" +) + +func init() { + register("CgoExternalThreadSIGPROF", CgoExternalThreadSIGPROF) + register("CgoExternalThreadSignal", CgoExternalThreadSignal) +} + +func CgoExternalThreadSIGPROF() { + // This test intends to test that sending SIGPROF to foreign threads + // before we make any cgo call will not abort the whole process, so + // we cannot make any cgo call here. See https://golang.org/issue/9456. + atomic.StoreInt32((*int32)(unsafe.Pointer(&C.spinlock)), 1) + for atomic.LoadInt32((*int32)(unsafe.Pointer(&C.spinlock))) == 1 { + runtime.Gosched() + } + println("OK") +} + +func CgoExternalThreadSignal() { + if len(os.Args) > 2 && os.Args[2] == "crash" { + i := C.start_crashing_thread() + if i != 0 { + fmt.Println("pthread_create failed:", i) + // Exit with 0 because parent expects us to crash. + return + } + + // We should crash immediately, but give it plenty of + // time before failing (by exiting 0) in case we are + // running on a slow system. + time.Sleep(5 * time.Second) + return + } + + out, err := exec.Command(os.Args[0], "CgoExternalThreadSignal", "crash").CombinedOutput() + if err == nil { + fmt.Println("C signal did not crash as expected") + fmt.Printf("\n%s\n", out) + os.Exit(1) + } + + fmt.Println("OK") +} diff --git a/src/runtime/testdata/testprogcgo/traceback.go b/src/runtime/testdata/testprogcgo/traceback.go new file mode 100644 index 0000000..e2d7599 --- /dev/null +++ b/src/runtime/testdata/testprogcgo/traceback.go @@ -0,0 +1,54 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This program will crash. +// We want the stack trace to include the C functions. +// We use a fake traceback, and a symbolizer that dumps a string we recognize. + +/* +#cgo CFLAGS: -g -O0 + +// Defined in traceback_c.c. +extern int crashInGo; +int tracebackF1(void); +void cgoTraceback(void* parg); +void cgoSymbolizer(void* parg); +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +func init() { + register("CrashTraceback", CrashTraceback) + register("CrashTracebackGo", CrashTracebackGo) +} + +func CrashTraceback() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.cgoTraceback), nil, unsafe.Pointer(C.cgoSymbolizer)) + C.tracebackF1() +} + +func CrashTracebackGo() { + C.crashInGo = 1 + CrashTraceback() +} + +//export h1 +func h1() { + h2() +} + +func h2() { + h3() +} + +func h3() { + var x *int + *x = 0 +} diff --git a/src/runtime/testdata/testprogcgo/traceback_c.c b/src/runtime/testdata/testprogcgo/traceback_c.c new file mode 100644 index 0000000..56eda8f --- /dev/null +++ b/src/runtime/testdata/testprogcgo/traceback_c.c @@ -0,0 +1,65 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The C definitions for traceback.go. That file uses //export so +// it can't put function definitions in the "C" import comment. + +#include <stdint.h> + +char *p; + +int crashInGo; +extern void h1(void); + +int tracebackF3(void) { + if (crashInGo) + h1(); + else + *p = 0; + return 0; +} + +int tracebackF2(void) { + return tracebackF3(); +} + +int tracebackF1(void) { + return tracebackF2(); +} + +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +struct cgoSymbolizerArg { + uintptr_t pc; + const char* file; + uintptr_t lineno; + const char* func; + uintptr_t entry; + uintptr_t more; + uintptr_t data; +}; + +void cgoTraceback(void* parg) { + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + arg->buf[0] = 1; + arg->buf[1] = 2; + arg->buf[2] = 3; + arg->buf[3] = 0; +} + +void cgoSymbolizer(void* parg) { + struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg); + if (arg->pc != arg->data + 1) { + arg->file = "unexpected data"; + } else { + arg->file = "cgo symbolizer"; + } + arg->lineno = arg->data + 1; + arg->data++; +} diff --git a/src/runtime/testdata/testprogcgo/tracebackctxt.go b/src/runtime/testdata/testprogcgo/tracebackctxt.go new file mode 100644 index 0000000..62ff8ec --- /dev/null +++ b/src/runtime/testdata/testprogcgo/tracebackctxt.go @@ -0,0 +1,136 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Test the context argument to SetCgoTraceback. +// Use fake context, traceback, and symbolizer functions. + +/* +// Defined in tracebackctxt_c.c. +extern void C1(void); +extern void C2(void); +extern void tcContext(void*); +extern void tcContextSimple(void*); +extern void tcTraceback(void*); +extern void tcSymbolizer(void*); +extern int getContextCount(void); +extern void TracebackContextPreemptionCallGo(int); +*/ +import "C" + +import ( + "fmt" + "runtime" + "sync" + "unsafe" +) + +func init() { + register("TracebackContext", TracebackContext) + register("TracebackContextPreemption", TracebackContextPreemption) +} + +var tracebackOK bool + +func TracebackContext() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.tcTraceback), unsafe.Pointer(C.tcContext), unsafe.Pointer(C.tcSymbolizer)) + C.C1() + if got := C.getContextCount(); got != 0 { + fmt.Printf("at end contextCount == %d, expected 0\n", got) + tracebackOK = false + } + if tracebackOK { + fmt.Println("OK") + } +} + +//export G1 +func G1() { + C.C2() +} + +//export G2 +func G2() { + pc := make([]uintptr, 32) + n := runtime.Callers(0, pc) + cf := runtime.CallersFrames(pc[:n]) + var frames []runtime.Frame + for { + frame, more := cf.Next() + frames = append(frames, frame) + if !more { + break + } + } + + want := []struct { + function string + line int + }{ + {"main.G2", 0}, + {"cFunction", 0x10200}, + {"cFunction", 0x200}, + {"cFunction", 0x10201}, + {"cFunction", 0x201}, + {"main.G1", 0}, + {"cFunction", 0x10100}, + {"cFunction", 0x100}, + {"main.TracebackContext", 0}, + } + + ok := true + i := 0 +wantLoop: + for _, w := range want { + for ; i < len(frames); i++ { + if w.function == frames[i].Function { + if w.line != 0 && w.line != frames[i].Line { + fmt.Printf("found function %s at wrong line %#x (expected %#x)\n", w.function, frames[i].Line, w.line) + ok = false + } + i++ + continue wantLoop + } + } + fmt.Printf("did not find function %s in\n", w.function) + for _, f := range frames { + fmt.Println(f) + } + ok = false + break + } + tracebackOK = ok + if got := C.getContextCount(); got != 2 { + fmt.Printf("at bottom contextCount == %d, expected 2\n", got) + tracebackOK = false + } +} + +// Issue 47441. +func TracebackContextPreemption() { + runtime.SetCgoTraceback(0, unsafe.Pointer(C.tcTraceback), unsafe.Pointer(C.tcContextSimple), unsafe.Pointer(C.tcSymbolizer)) + + const funcs = 10 + const calls = 1e5 + var wg sync.WaitGroup + for i := 0; i < funcs; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + for j := 0; j < calls; j++ { + C.TracebackContextPreemptionCallGo(C.int(i*calls + j)) + } + }(i) + } + wg.Wait() + + fmt.Println("OK") +} + +//export TracebackContextPreemptionGoFunction +func TracebackContextPreemptionGoFunction(i C.int) { + // Do some busy work. + fmt.Sprintf("%d\n", i) +} diff --git a/src/runtime/testdata/testprogcgo/tracebackctxt_c.c b/src/runtime/testdata/testprogcgo/tracebackctxt_c.c new file mode 100644 index 0000000..910cb7b --- /dev/null +++ b/src/runtime/testdata/testprogcgo/tracebackctxt_c.c @@ -0,0 +1,103 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The C definitions for tracebackctxt.go. That file uses //export so +// it can't put function definitions in the "C" import comment. + +#include <stdlib.h> +#include <stdint.h> + +// Functions exported from Go. +extern void G1(void); +extern void G2(void); +extern void TracebackContextPreemptionGoFunction(int); + +void C1() { + G1(); +} + +void C2() { + G2(); +} + +struct cgoContextArg { + uintptr_t context; +}; + +struct cgoTracebackArg { + uintptr_t context; + uintptr_t sigContext; + uintptr_t* buf; + uintptr_t max; +}; + +struct cgoSymbolizerArg { + uintptr_t pc; + const char* file; + uintptr_t lineno; + const char* func; + uintptr_t entry; + uintptr_t more; + uintptr_t data; +}; + +// Uses atomic adds and subtracts to catch the possibility of +// erroneous calls from multiple threads; that should be impossible in +// this test case, but we check just in case. +static int contextCount; + +int getContextCount() { + return __sync_add_and_fetch(&contextCount, 0); +} + +void tcContext(void* parg) { + struct cgoContextArg* arg = (struct cgoContextArg*)(parg); + if (arg->context == 0) { + arg->context = __sync_add_and_fetch(&contextCount, 1); + } else { + if (arg->context != __sync_add_and_fetch(&contextCount, 0)) { + abort(); + } + __sync_sub_and_fetch(&contextCount, 1); + } +} + +void tcContextSimple(void* parg) { + struct cgoContextArg* arg = (struct cgoContextArg*)(parg); + if (arg->context == 0) { + arg->context = 1; + } +} + +void tcTraceback(void* parg) { + int base, i; + struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg); + if (arg->context == 0 && arg->sigContext == 0) { + // This shouldn't happen in this program. + abort(); + } + // Return a variable number of PC values. + base = arg->context << 8; + for (i = 0; i < arg->context; i++) { + if (i < arg->max) { + arg->buf[i] = base + i; + } + } +} + +void tcSymbolizer(void *parg) { + struct cgoSymbolizerArg* arg = (struct cgoSymbolizerArg*)(parg); + if (arg->pc == 0) { + return; + } + // Report two lines per PC returned by traceback, to test more handling. + arg->more = arg->file == NULL; + arg->file = "tracebackctxt.go"; + arg->func = "cFunction"; + arg->lineno = arg->pc + (arg->more << 16); +} + +void TracebackContextPreemptionCallGo(int i) { + TracebackContextPreemptionGoFunction(i); +} diff --git a/src/runtime/testdata/testprogcgo/windows/win.go b/src/runtime/testdata/testprogcgo/windows/win.go new file mode 100644 index 0000000..9d9f86c --- /dev/null +++ b/src/runtime/testdata/testprogcgo/windows/win.go @@ -0,0 +1,14 @@ +package windows + +/* +#include <windows.h> + +DWORD agetthread() { + return GetCurrentThreadId(); +} +*/ +import "C" + +func GetThread() uint32 { + return uint32(C.agetthread()) +} diff --git a/src/runtime/testdata/testprognet/main.go b/src/runtime/testdata/testprognet/main.go new file mode 100644 index 0000000..ae491a2 --- /dev/null +++ b/src/runtime/testdata/testprognet/main.go @@ -0,0 +1,35 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "os" + +var cmds = map[string]func(){} + +func register(name string, f func()) { + if cmds[name] != nil { + panic("duplicate registration: " + name) + } + cmds[name] = f +} + +func registerInit(name string, f func()) { + if len(os.Args) >= 2 && os.Args[1] == name { + f() + } +} + +func main() { + if len(os.Args) < 2 { + println("usage: " + os.Args[0] + " name-of-test") + return + } + f := cmds[os.Args[1]] + if f == nil { + println("unknown function: " + os.Args[1]) + return + } + f() +} diff --git a/src/runtime/testdata/testprognet/net.go b/src/runtime/testdata/testprognet/net.go new file mode 100644 index 0000000..714b101 --- /dev/null +++ b/src/runtime/testdata/testprognet/net.go @@ -0,0 +1,29 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "net" +) + +func init() { + registerInit("NetpollDeadlock", NetpollDeadlockInit) + register("NetpollDeadlock", NetpollDeadlock) +} + +func NetpollDeadlockInit() { + fmt.Println("dialing") + c, err := net.Dial("tcp", "localhost:14356") + if err == nil { + c.Close() + } else { + fmt.Println("error: ", err) + } +} + +func NetpollDeadlock() { + fmt.Println("done") +} diff --git a/src/runtime/testdata/testprognet/signal.go b/src/runtime/testdata/testprognet/signal.go new file mode 100644 index 0000000..dfa2e10 --- /dev/null +++ b/src/runtime/testdata/testprognet/signal.go @@ -0,0 +1,27 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 +// +build !windows,!plan9 + +// This is in testprognet instead of testprog because testprog +// must not import anything (like net, but also like os/signal) +// that kicks off background goroutines during init. + +package main + +import ( + "os/signal" + "syscall" +) + +func init() { + register("SignalIgnoreSIGTRAP", SignalIgnoreSIGTRAP) +} + +func SignalIgnoreSIGTRAP() { + signal.Ignore(syscall.SIGTRAP) + syscall.Kill(syscall.Getpid(), syscall.SIGTRAP) + println("OK") +} diff --git a/src/runtime/testdata/testprognet/signalexec.go b/src/runtime/testdata/testprognet/signalexec.go new file mode 100644 index 0000000..62ebce7 --- /dev/null +++ b/src/runtime/testdata/testprognet/signalexec.go @@ -0,0 +1,71 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd + +// This is in testprognet instead of testprog because testprog +// must not import anything (like net, but also like os/signal) +// that kicks off background goroutines during init. + +package main + +import ( + "fmt" + "os" + "os/exec" + "os/signal" + "sync" + "syscall" + "time" +) + +func init() { + register("SignalDuringExec", SignalDuringExec) + register("Nop", Nop) +} + +func SignalDuringExec() { + pgrp := syscall.Getpgrp() + + const tries = 10 + + var wg sync.WaitGroup + c := make(chan os.Signal, tries) + signal.Notify(c, syscall.SIGWINCH) + wg.Add(1) + go func() { + defer wg.Done() + for range c { + } + }() + + for i := 0; i < tries; i++ { + time.Sleep(time.Microsecond) + wg.Add(2) + go func() { + defer wg.Done() + cmd := exec.Command(os.Args[0], "Nop") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + fmt.Printf("Start failed: %v", err) + } + }() + go func() { + defer wg.Done() + syscall.Kill(-pgrp, syscall.SIGWINCH) + }() + } + + signal.Stop(c) + close(c) + wg.Wait() + + fmt.Println("OK") +} + +func Nop() { + // This is just for SignalDuringExec. +} diff --git a/src/runtime/testdata/testsuid/main.go b/src/runtime/testdata/testsuid/main.go new file mode 100644 index 0000000..1949d2d --- /dev/null +++ b/src/runtime/testdata/testsuid/main.go @@ -0,0 +1,25 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "log" + "os" +) + +func main() { + if os.Geteuid() == os.Getuid() { + os.Exit(99) + } + + fmt.Fprintf(os.Stdout, "GOTRACEBACK=%s\n", os.Getenv("GOTRACEBACK")) + f, err := os.OpenFile(os.Getenv("TEST_OUTPUT"), os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + log.Fatalf("os.Open failed: %s", err) + } + defer f.Close() + fmt.Fprintf(os.Stderr, "hello\n") +} diff --git a/src/runtime/testdata/testwinlib/main.c b/src/runtime/testdata/testwinlib/main.c new file mode 100644 index 0000000..c3fe3cb --- /dev/null +++ b/src/runtime/testdata/testwinlib/main.c @@ -0,0 +1,60 @@ +#include <stdio.h> +#include <windows.h> +#include "testwinlib.h" + +int exceptionCount; +int continueCount; +LONG WINAPI customExceptionHandlder(struct _EXCEPTION_POINTERS *ExceptionInfo) +{ + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { + exceptionCount++; + // prepare context to resume execution + CONTEXT *c = ExceptionInfo->ContextRecord; + c->Rip = *(ULONG_PTR *)c->Rsp; + c->Rsp += 8; + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} +LONG WINAPI customContinueHandlder(struct _EXCEPTION_POINTERS *ExceptionInfo) +{ + if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) + { + continueCount++; + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +void throwFromC() +{ + DebugBreak(); +} +int main() +{ + // simulate a "lazily" attached debugger, by calling some go code before attaching the exception/continue handler + Dummy(); + exceptionCount = 0; + continueCount = 0; + void *exceptionHandlerHandle = AddVectoredExceptionHandler(0, customExceptionHandlder); + if (NULL == exceptionHandlerHandle) + { + printf("cannot add vectored exception handler\n"); + fflush(stdout); + return 2; + } + void *continueHandlerHandle = AddVectoredContinueHandler(0, customContinueHandlder); + if (NULL == continueHandlerHandle) + { + printf("cannot add vectored continue handler\n"); + fflush(stdout); + return 2; + } + CallMeBack(throwFromC); + RemoveVectoredContinueHandler(continueHandlerHandle); + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + printf("exceptionCount: %d\ncontinueCount: %d\n", exceptionCount, continueCount); + fflush(stdout); + return 0; +} diff --git a/src/runtime/testdata/testwinlib/main.go b/src/runtime/testdata/testwinlib/main.go new file mode 100644 index 0000000..407331b --- /dev/null +++ b/src/runtime/testdata/testwinlib/main.go @@ -0,0 +1,31 @@ +//go:build windows && cgo +// +build windows,cgo + +package main + +// #include <windows.h> +// typedef void(*callmeBackFunc)(); +// static void bridgeCallback(callmeBackFunc callback) { +// callback(); +//} +import "C" + +// CallMeBack call backs C code. +// +//export CallMeBack +func CallMeBack(callback C.callmeBackFunc) { + C.bridgeCallback(callback) +} + +// Dummy is called by the C code before registering the exception/continue handlers simulating a debugger. +// This makes sure that the Go runtime's lastcontinuehandler is reached before the C continue handler and thus, +// validate that it does not crash the program before another handler could take an action. +// The idea here is to reproduce what happens when you attach a debugger to a running program. +// It also simulate the behavior of the .Net debugger, which register its exception/continue handlers lazily. +// +//export Dummy +func Dummy() int { + return 42 +} + +func main() {} diff --git a/src/runtime/testdata/testwinlibsignal/dummy.go b/src/runtime/testdata/testwinlibsignal/dummy.go new file mode 100644 index 0000000..e610f15 --- /dev/null +++ b/src/runtime/testdata/testwinlibsignal/dummy.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package main + +import "C" + +//export Dummy +func Dummy() int { + return 42 +} + +func main() {} diff --git a/src/runtime/testdata/testwinlibsignal/main.c b/src/runtime/testdata/testwinlibsignal/main.c new file mode 100644 index 0000000..37f2482 --- /dev/null +++ b/src/runtime/testdata/testwinlibsignal/main.c @@ -0,0 +1,57 @@ +#include <windows.h> +#include <stdio.h> + +HANDLE waitForCtrlBreakEvent; + +BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) +{ + switch (fdwCtrlType) + { + case CTRL_BREAK_EVENT: + SetEvent(waitForCtrlBreakEvent); + return TRUE; + default: + return FALSE; + } +} + +int main(void) +{ + waitForCtrlBreakEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!waitForCtrlBreakEvent) { + fprintf(stderr, "ERROR: Could not create event\n"); + return 1; + } + + if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) + { + fprintf(stderr, "ERROR: Could not set control handler\n"); + return 1; + } + + // The library must be loaded after the SetConsoleCtrlHandler call + // so that the library handler registers after the main program. + // This way the library handler gets called first. + HMODULE dummyDll = LoadLibrary("dummy.dll"); + if (!dummyDll) { + fprintf(stderr, "ERROR: Could not load dummy.dll\n"); + return 1; + } + + // Call the Dummy function so that Go initialization completes, since + // all cgo entry points call out to _cgo_wait_runtime_init_done. + if (((int(*)(void))GetProcAddress(dummyDll, "Dummy"))() != 42) { + fprintf(stderr, "ERROR: Dummy function did not return 42\n"); + return 1; + } + + printf("ready\n"); + fflush(stdout); + + if (WaitForSingleObject(waitForCtrlBreakEvent, 5000) != WAIT_OBJECT_0) { + fprintf(stderr, "FAILURE: No signal received\n"); + return 1; + } + + return 0; +} diff --git a/src/runtime/testdata/testwinlibthrow/main.go b/src/runtime/testdata/testwinlibthrow/main.go new file mode 100644 index 0000000..ce0c92f --- /dev/null +++ b/src/runtime/testdata/testwinlibthrow/main.go @@ -0,0 +1,19 @@ +package main
+
+import (
+ "os"
+ "syscall"
+)
+
+func main() {
+ dll := syscall.MustLoadDLL("veh.dll")
+ RaiseNoExcept := dll.MustFindProc("RaiseNoExcept")
+ ThreadRaiseNoExcept := dll.MustFindProc("ThreadRaiseNoExcept")
+
+ thread := len(os.Args) > 1 && os.Args[1] == "thread"
+ if !thread {
+ RaiseNoExcept.Call()
+ } else {
+ ThreadRaiseNoExcept.Call()
+ }
+}
diff --git a/src/runtime/testdata/testwinlibthrow/veh.c b/src/runtime/testdata/testwinlibthrow/veh.c new file mode 100644 index 0000000..08c1f9e --- /dev/null +++ b/src/runtime/testdata/testwinlibthrow/veh.c @@ -0,0 +1,26 @@ +//go:build ignore
+
+#include <windows.h>
+
+__declspec(dllexport)
+void RaiseNoExcept(void)
+{
+ RaiseException(42, 0, 0, 0);
+}
+
+static DWORD WINAPI ThreadRaiser(void* Context)
+{
+ RaiseNoExcept();
+ return 0;
+}
+
+__declspec(dllexport)
+void ThreadRaiseNoExcept(void)
+{
+ HANDLE thread = CreateThread(0, 0, ThreadRaiser, 0, 0, 0);
+ if (0 != thread)
+ {
+ WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+ }
+}
diff --git a/src/runtime/testdata/testwinsignal/main.go b/src/runtime/testdata/testwinsignal/main.go new file mode 100644 index 0000000..e1136f3 --- /dev/null +++ b/src/runtime/testdata/testwinsignal/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + // Ensure that this process terminates when the test times out, + // even if the expected signal never arrives. + go func() { + io.Copy(io.Discard, os.Stdin) + log.Fatal("stdin is closed; terminating") + }() + + // Register to receive all signals. + c := make(chan os.Signal, 1) + signal.Notify(c) + + // Get console window handle. + kernel32 := syscall.NewLazyDLL("kernel32.dll") + getConsoleWindow := kernel32.NewProc("GetConsoleWindow") + hwnd, _, err := getConsoleWindow.Call() + if hwnd == 0 { + log.Fatal("no associated console: ", err) + } + + // Send message to close the console window. + const _WM_CLOSE = 0x0010 + user32 := syscall.NewLazyDLL("user32.dll") + postMessage := user32.NewProc("PostMessageW") + ok, _, err := postMessage.Call(hwnd, _WM_CLOSE, 0, 0) + if ok == 0 { + log.Fatal("post message failed: ", err) + } + + sig := <-c + + // Allow some time for the handler to complete if it's going to. + // + // (In https://go.dev/issue/41884 the handler returned immediately, + // which caused Windows to terminate the program before the goroutine + // that received the SIGTERM had a chance to actually clean up.) + time.Sleep(time.Second) + + // Print the signal's name: "terminated" makes the test succeed. + fmt.Println(sig) +} diff --git a/src/runtime/testdata/testwintls/main.c b/src/runtime/testdata/testwintls/main.c new file mode 100644 index 0000000..6061828 --- /dev/null +++ b/src/runtime/testdata/testwintls/main.c @@ -0,0 +1,29 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include <windows.h> + +int main(int argc, char **argv) { + if (argc < 3) { + return 1; + } + // Allocate more than 64 TLS indices + // so the Go runtime doesn't find + // enough space in the TEB TLS slots. + for (int i = 0; i < 65; i++) { + TlsAlloc(); + } + HMODULE hlib = LoadLibrary(argv[1]); + if (hlib == NULL) { + return 2; + } + FARPROC proc = GetProcAddress(hlib, argv[2]); + if (proc == NULL) { + return 3; + } + if (proc() != 42) { + return 4; + } + return 0; +}
\ No newline at end of file diff --git a/src/runtime/testdata/testwintls/main.go b/src/runtime/testdata/testwintls/main.go new file mode 100644 index 0000000..1cf296c --- /dev/null +++ b/src/runtime/testdata/testwintls/main.go @@ -0,0 +1,12 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "C" + +//export GoFunc +func GoFunc() int { return 42 } + +func main() {} |