diff options
Diffstat (limited to 'src/runtime/crash_cgo_test.go')
-rw-r--r-- | src/runtime/crash_cgo_test.go | 770 |
1 files changed, 770 insertions, 0 deletions
diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go new file mode 100644 index 0000000..51d7bb5 --- /dev/null +++ b/src/runtime/crash_cgo_test.go @@ -0,0 +1,770 @@ +// Copyright 2012 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 cgo + +package runtime_test + +import ( + "fmt" + "internal/goos" + "internal/testenv" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "testing" + "time" +) + +func TestCgoCrashHandler(t *testing.T) { + t.Parallel() + testCrashHandler(t, true) +} + +func TestCgoSignalDeadlock(t *testing.T) { + // Don't call t.Parallel, since too much work going on at the + // same time can cause the testprogcgo code to overrun its + // timeouts (issue #18598). + + if testing.Short() && runtime.GOOS == "windows" { + t.Skip("Skipping in short mode") // takes up to 64 seconds + } + got := runTestProg(t, "testprogcgo", "CgoSignalDeadlock") + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} + +func TestCgoTraceback(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", "CgoTraceback") + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} + +func TestCgoCallbackGC(t *testing.T) { + t.Parallel() + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + if testing.Short() { + switch { + case runtime.GOOS == "dragonfly": + t.Skip("see golang.org/issue/11990") + case runtime.GOOS == "linux" && runtime.GOARCH == "arm": + t.Skip("too slow for arm builders") + case runtime.GOOS == "linux" && (runtime.GOARCH == "mips64" || runtime.GOARCH == "mips64le"): + t.Skip("too slow for mips64x builders") + } + } + if testenv.Builder() == "darwin-amd64-10_14" { + // TODO(#23011): When the 10.14 builders are gone, remove this skip. + t.Skip("skipping due to platform bug on macOS 10.14; see https://golang.org/issue/43926") + } + got := runTestProg(t, "testprogcgo", "CgoCallbackGC") + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} + +func TestCgoExternalThreadPanic(t *testing.T) { + t.Parallel() + if runtime.GOOS == "plan9" { + t.Skipf("no pthreads on %s", runtime.GOOS) + } + got := runTestProg(t, "testprogcgo", "CgoExternalThreadPanic") + want := "panic: BOOM" + if !strings.Contains(got, want) { + t.Fatalf("want failure containing %q. output:\n%s\n", want, got) + } +} + +func TestCgoExternalThreadSIGPROF(t *testing.T) { + t.Parallel() + // issue 9456. + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + + got := runTestProg(t, "testprogcgo", "CgoExternalThreadSIGPROF", "GO_START_SIGPROF_THREAD=1") + if want := "OK\n"; got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} + +func TestCgoExternalThreadSignal(t *testing.T) { + t.Parallel() + // issue 10139 + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + + got := runTestProg(t, "testprogcgo", "CgoExternalThreadSignal") + if want := "OK\n"; got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} + +func TestCgoDLLImports(t *testing.T) { + // test issue 9356 + if runtime.GOOS != "windows" { + t.Skip("skipping windows specific test") + } + got := runTestProg(t, "testprogcgo", "CgoDLLImportsMain") + want := "OK\n" + if got != want { + t.Fatalf("expected %q, but got %v", want, got) + } +} + +func TestCgoExecSignalMask(t *testing.T) { + t.Parallel() + // Test issue 13164. + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping signal mask test on %s", runtime.GOOS) + } + got := runTestProg(t, "testprogcgo", "CgoExecSignalMask", "GOTRACEBACK=system") + want := "OK\n" + if got != want { + t.Errorf("expected %q, got %v", want, got) + } +} + +func TestEnsureDropM(t *testing.T) { + t.Parallel() + // Test for issue 13881. + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping dropm test on %s", runtime.GOOS) + } + got := runTestProg(t, "testprogcgo", "EnsureDropM") + want := "OK\n" + if got != want { + t.Errorf("expected %q, got %v", want, got) + } +} + +// Test for issue 14387. +// Test that the program that doesn't need any cgo pointer checking +// takes about the same amount of time with it as without it. +func TestCgoCheckBytes(t *testing.T) { + t.Parallel() + // Make sure we don't count the build time as part of the run time. + testenv.MustHaveGoBuild(t) + exe, err := buildTestProg(t, "testprogcgo") + if err != nil { + t.Fatal(err) + } + + // Try it 10 times to avoid flakiness. + const tries = 10 + var tot1, tot2 time.Duration + for i := 0; i < tries; i++ { + cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes")) + cmd.Env = append(cmd.Env, "GODEBUG=cgocheck=0", fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i)) + + start := time.Now() + cmd.Run() + d1 := time.Since(start) + + cmd = testenv.CleanCmdEnv(exec.Command(exe, "CgoCheckBytes")) + cmd.Env = append(cmd.Env, fmt.Sprintf("GO_CGOCHECKBYTES_TRY=%d", i)) + + start = time.Now() + cmd.Run() + d2 := time.Since(start) + + if d1*20 > d2 { + // The slow version (d2) was less than 20 times + // slower than the fast version (d1), so OK. + return + } + + tot1 += d1 + tot2 += d2 + } + + t.Errorf("cgo check too slow: got %v, expected at most %v", tot2/tries, (tot1/tries)*20) +} + +func TestCgoPanicDeadlock(t *testing.T) { + t.Parallel() + // test issue 14432 + got := runTestProg(t, "testprogcgo", "CgoPanicDeadlock") + want := "panic: cgo error\n\n" + if !strings.HasPrefix(got, want) { + t.Fatalf("output does not start with %q:\n%s", want, got) + } +} + +func TestCgoCCodeSIGPROF(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", "CgoCCodeSIGPROF") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func TestCgoPprofCallback(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") // takes a full second + } + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping cgo pprof callback test on %s", runtime.GOOS) + } + got := runTestProg(t, "testprogcgo", "CgoPprofCallback") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func TestCgoCrashTraceback(t *testing.T) { + t.Parallel() + switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform { + case "darwin/amd64": + case "linux/amd64": + case "linux/arm64": + case "linux/ppc64le": + default: + t.Skipf("not yet supported on %s", platform) + } + got := runTestProg(t, "testprogcgo", "CrashTraceback") + for i := 1; i <= 3; i++ { + if !strings.Contains(got, fmt.Sprintf("cgo symbolizer:%d", i)) { + t.Errorf("missing cgo symbolizer:%d", i) + } + } +} + +func TestCgoCrashTracebackGo(t *testing.T) { + t.Parallel() + switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform { + case "darwin/amd64": + case "linux/amd64": + case "linux/arm64": + case "linux/ppc64le": + default: + t.Skipf("not yet supported on %s", platform) + } + got := runTestProg(t, "testprogcgo", "CrashTracebackGo") + for i := 1; i <= 3; i++ { + want := fmt.Sprintf("main.h%d", i) + if !strings.Contains(got, want) { + t.Errorf("missing %s", want) + } + } +} + +func TestCgoTracebackContext(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", "TracebackContext") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func TestCgoTracebackContextPreemption(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", "TracebackContextPreemption") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func testCgoPprof(t *testing.T, buildArg, runArg, top, bottom string) { + t.Parallel() + if runtime.GOOS != "linux" || (runtime.GOARCH != "amd64" && runtime.GOARCH != "ppc64le" && runtime.GOARCH != "arm64") { + t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) + } + testenv.MustHaveGoRun(t) + + exe, err := buildTestProg(t, "testprogcgo", buildArg) + if err != nil { + t.Fatal(err) + } + + cmd := testenv.CleanCmdEnv(exec.Command(exe, runArg)) + got, err := cmd.CombinedOutput() + if err != nil { + if testenv.Builder() == "linux-amd64-alpine" { + // See Issue 18243 and Issue 19938. + t.Skipf("Skipping failing test on Alpine (golang.org/issue/18243). Ignoring error: %v", err) + } + t.Fatalf("%s\n\n%v", got, err) + } + fn := strings.TrimSpace(string(got)) + defer os.Remove(fn) + + for try := 0; try < 2; try++ { + cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "tool", "pprof", "-tagignore=ignore", "-traces")) + // Check that pprof works both with and without explicit executable on command line. + if try == 0 { + cmd.Args = append(cmd.Args, exe, fn) + } else { + cmd.Args = append(cmd.Args, fn) + } + + found := false + for i, e := range cmd.Env { + if strings.HasPrefix(e, "PPROF_TMPDIR=") { + cmd.Env[i] = "PPROF_TMPDIR=" + os.TempDir() + found = true + break + } + } + if !found { + cmd.Env = append(cmd.Env, "PPROF_TMPDIR="+os.TempDir()) + } + + out, err := cmd.CombinedOutput() + t.Logf("%s:\n%s", cmd.Args, out) + if err != nil { + t.Error(err) + continue + } + + trace := findTrace(string(out), top) + if len(trace) == 0 { + t.Errorf("%s traceback missing.", top) + continue + } + if trace[len(trace)-1] != bottom { + t.Errorf("invalid traceback origin: got=%v; want=[%s ... %s]", trace, top, bottom) + } + } +} + +func TestCgoPprof(t *testing.T) { + testCgoPprof(t, "", "CgoPprof", "cpuHog", "runtime.main") +} + +func TestCgoPprofPIE(t *testing.T) { + testCgoPprof(t, "-buildmode=pie", "CgoPprof", "cpuHog", "runtime.main") +} + +func TestCgoPprofThread(t *testing.T) { + testCgoPprof(t, "", "CgoPprofThread", "cpuHogThread", "cpuHogThread2") +} + +func TestCgoPprofThreadNoTraceback(t *testing.T) { + testCgoPprof(t, "", "CgoPprofThreadNoTraceback", "cpuHogThread", "runtime._ExternalCode") +} + +func TestRaceProf(t *testing.T) { + if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" { + t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) + } + + testenv.MustHaveGoRun(t) + + // This test requires building various packages with -race, so + // it's somewhat slow. + if testing.Short() { + t.Skip("skipping test in -short mode") + } + + exe, err := buildTestProg(t, "testprogcgo", "-race") + if err != nil { + t.Fatal(err) + } + + got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceprof")).CombinedOutput() + if err != nil { + t.Fatal(err) + } + want := "OK\n" + if string(got) != want { + t.Errorf("expected %q got %s", want, got) + } +} + +func TestRaceSignal(t *testing.T) { + t.Parallel() + if (runtime.GOOS != "linux" && runtime.GOOS != "freebsd") || runtime.GOARCH != "amd64" { + t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH) + } + + testenv.MustHaveGoRun(t) + + // This test requires building various packages with -race, so + // it's somewhat slow. + if testing.Short() { + t.Skip("skipping test in -short mode") + } + + exe, err := buildTestProg(t, "testprogcgo", "-race") + if err != nil { + t.Fatal(err) + } + + got, err := testenv.CleanCmdEnv(exec.Command(exe, "CgoRaceSignal")).CombinedOutput() + if err != nil { + t.Logf("%s\n", got) + t.Fatal(err) + } + want := "OK\n" + if string(got) != want { + t.Errorf("expected %q got %s", want, got) + } +} + +func TestCgoNumGoroutine(t *testing.T) { + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping numgoroutine test on %s", runtime.GOOS) + } + t.Parallel() + got := runTestProg(t, "testprogcgo", "NumGoroutine") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func TestCatchPanic(t *testing.T) { + t.Parallel() + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no signals on %s", runtime.GOOS) + case "darwin": + if runtime.GOARCH == "amd64" { + t.Skipf("crash() on darwin/amd64 doesn't raise SIGABRT") + } + } + + testenv.MustHaveGoRun(t) + + exe, err := buildTestProg(t, "testprogcgo") + if err != nil { + t.Fatal(err) + } + + for _, early := range []bool{true, false} { + cmd := testenv.CleanCmdEnv(exec.Command(exe, "CgoCatchPanic")) + // Make sure a panic results in a crash. + cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") + if early { + // Tell testprogcgo to install an early signal handler for SIGABRT + cmd.Env = append(cmd.Env, "CGOCATCHPANIC_EARLY_HANDLER=1") + } + if out, err := cmd.CombinedOutput(); err != nil { + t.Errorf("testprogcgo CgoCatchPanic failed: %v\n%s", err, out) + } + } +} + +func TestCgoLockOSThreadExit(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + t.Parallel() + testLockOSThreadExit(t, "testprogcgo") +} + +func TestWindowsStackMemoryCgo(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("skipping windows specific test") + } + testenv.SkipFlaky(t, 22575) + o := runTestProg(t, "testprogcgo", "StackMemory") + stackUsage, err := strconv.Atoi(o) + if err != nil { + t.Fatalf("Failed to read stack usage: %v", err) + } + if expected, got := 100<<10, stackUsage; got > expected { + t.Fatalf("expected < %d bytes of memory per thread, got %d", expected, got) + } +} + +func TestSigStackSwapping(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no sigaltstack on %s", runtime.GOOS) + } + t.Parallel() + got := runTestProg(t, "testprogcgo", "SigStack") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func TestCgoTracebackSigpanic(t *testing.T) { + // Test unwinding over a sigpanic in C code without a C + // symbolizer. See issue #23576. + if runtime.GOOS == "windows" { + // On Windows if we get an exception in C code, we let + // the Windows exception handler unwind it, rather + // than injecting a sigpanic. + t.Skip("no sigpanic in C on windows") + } + t.Parallel() + got := runTestProg(t, "testprogcgo", "TracebackSigpanic") + t.Log(got) + want := "runtime.sigpanic" + if !strings.Contains(got, want) { + t.Errorf("did not see %q in output", want) + } + // No runtime errors like "runtime: unexpected return pc". + nowant := "runtime: " + if strings.Contains(got, nowant) { + t.Errorf("unexpectedly saw %q in output", nowant) + } +} + +func TestCgoPanicCallback(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", "PanicCallback") + t.Log(got) + want := "panic: runtime error: invalid memory address or nil pointer dereference" + if !strings.Contains(got, want) { + t.Errorf("did not see %q in output", want) + } + want = "panic_callback" + if !strings.Contains(got, want) { + t.Errorf("did not see %q in output", want) + } + want = "PanicCallback" + if !strings.Contains(got, want) { + t.Errorf("did not see %q in output", want) + } + // No runtime errors like "runtime: unexpected return pc". + nowant := "runtime: " + if strings.Contains(got, nowant) { + t.Errorf("did not see %q in output", want) + } +} + +// Test that C code called via cgo can use large Windows thread stacks +// and call back in to Go without crashing. See issue #20975. +// +// See also TestBigStackCallbackSyscall. +func TestBigStackCallbackCgo(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("skipping windows specific test") + } + t.Parallel() + got := runTestProg(t, "testprogcgo", "BigStack") + want := "OK\n" + if got != want { + t.Errorf("expected %q got %v", want, got) + } +} + +func nextTrace(lines []string) ([]string, []string) { + var trace []string + for n, line := range lines { + if strings.HasPrefix(line, "---") { + return trace, lines[n+1:] + } + fields := strings.Fields(strings.TrimSpace(line)) + if len(fields) == 0 { + continue + } + // Last field contains the function name. + trace = append(trace, fields[len(fields)-1]) + } + return nil, nil +} + +func findTrace(text, top string) []string { + lines := strings.Split(text, "\n") + _, lines = nextTrace(lines) // Skip the header. + for len(lines) > 0 { + var t []string + t, lines = nextTrace(lines) + if len(t) == 0 { + continue + } + if t[0] == top { + return t + } + } + return nil +} + +func TestSegv(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no signals on %s", runtime.GOOS) + } + + for _, test := range []string{"Segv", "SegvInCgo", "TgkillSegv", "TgkillSegvInCgo"} { + test := test + + // The tgkill variants only run on Linux. + if runtime.GOOS != "linux" && strings.HasPrefix(test, "Tgkill") { + continue + } + + t.Run(test, func(t *testing.T) { + t.Parallel() + got := runTestProg(t, "testprogcgo", test) + t.Log(got) + want := "SIGSEGV" + if !strings.Contains(got, want) { + if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" && strings.Contains(got, "fatal: morestack on g0") { + testenv.SkipFlaky(t, 39457) + } + t.Errorf("did not see %q in output", want) + } + + // No runtime errors like "runtime: unknown pc". + switch runtime.GOOS { + case "darwin", "illumos", "solaris": + // Runtime sometimes throws when generating the traceback. + testenv.SkipFlaky(t, 49182) + case "linux": + if runtime.GOARCH == "386" { + // Runtime throws when generating a traceback from + // a VDSO call via asmcgocall. + testenv.SkipFlaky(t, 50504) + } + } + if test == "SegvInCgo" && strings.Contains(got, "unknown pc") { + testenv.SkipFlaky(t, 50979) + } + + for _, nowant := range []string{"fatal error: ", "runtime: "} { + if strings.Contains(got, nowant) { + if runtime.GOOS == "darwin" && strings.Contains(got, "0xb01dfacedebac1e") { + // See the comment in signal_darwin_amd64.go. + t.Skip("skipping due to Darwin handling of malformed addresses") + } + t.Errorf("unexpectedly saw %q in output", nowant) + } + } + }) + } +} + +func TestAbortInCgo(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + // N.B. On Windows, C abort() causes the program to exit + // without going through the runtime at all. + t.Skipf("no signals on %s", runtime.GOOS) + } + + t.Parallel() + got := runTestProg(t, "testprogcgo", "Abort") + t.Log(got) + want := "SIGABRT" + if !strings.Contains(got, want) { + t.Errorf("did not see %q in output", want) + } + // No runtime errors like "runtime: unknown pc". + nowant := "runtime: " + if strings.Contains(got, nowant) { + t.Errorf("did not see %q in output", want) + } +} + +// TestEINTR tests that we handle EINTR correctly. +// See issue #20400 and friends. +func TestEINTR(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no EINTR on %s", runtime.GOOS) + case "linux": + if runtime.GOARCH == "386" { + // On linux-386 the Go signal handler sets + // a restorer function that is not preserved + // by the C sigaction call in the test, + // causing the signal handler to crash when + // returning the normal code. The test is not + // architecture-specific, so just skip on 386 + // rather than doing a complicated workaround. + t.Skip("skipping on linux-386; C sigaction does not preserve Go restorer") + } + } + + t.Parallel() + output := runTestProg(t, "testprogcgo", "EINTR") + want := "OK\n" + if output != want { + t.Fatalf("want %s, got %s\n", want, output) + } +} + +// Issue #42207. +func TestNeedmDeadlock(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no signals on %s", runtime.GOOS) + } + output := runTestProg(t, "testprogcgo", "NeedmDeadlock") + want := "OK\n" + if output != want { + t.Fatalf("want %s, got %s\n", want, output) + } +} + +func TestCgoTracebackGoroutineProfile(t *testing.T) { + output := runTestProg(t, "testprogcgo", "GoroutineProfile") + want := "OK\n" + if output != want { + t.Fatalf("want %s, got %s\n", want, output) + } +} + +func TestCgoTraceParser(t *testing.T) { + // Test issue 29707. + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + output := runTestProg(t, "testprogcgo", "CgoTraceParser") + want := "OK\n" + ErrTimeOrder := "ErrTimeOrder\n" + if output == ErrTimeOrder { + t.Skipf("skipping due to golang.org/issue/16755: %v", output) + } else if output != want { + t.Fatalf("want %s, got %s\n", want, output) + } +} + +func TestCgoTraceParserWithOneProc(t *testing.T) { + // Test issue 29707. + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("no pthreads on %s", runtime.GOOS) + } + output := runTestProg(t, "testprogcgo", "CgoTraceParser", "GOMAXPROCS=1") + want := "OK\n" + ErrTimeOrder := "ErrTimeOrder\n" + if output == ErrTimeOrder { + t.Skipf("skipping due to golang.org/issue/16755: %v", output) + } else if output != want { + t.Fatalf("GOMAXPROCS=1, want %s, got %s\n", want, output) + } +} + +func TestCgoSigfwd(t *testing.T) { + t.Parallel() + if !goos.IsUnix { + t.Skipf("no signals on %s", runtime.GOOS) + } + + got := runTestProg(t, "testprogcgo", "CgoSigfwd", "GO_TEST_CGOSIGFWD=1") + if want := "OK\n"; got != want { + t.Fatalf("expected %q, but got:\n%s", want, got) + } +} |