github.com/c9s/go@v0.0.0-20180120015821-984e81f64e0c/src/runtime/crash_unix_test.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package runtime_test 8 9 import ( 10 "bytes" 11 "internal/testenv" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "runtime" 18 "strings" 19 "syscall" 20 "testing" 21 ) 22 23 // sigquit is the signal to send to kill a hanging testdata program. 24 // Send SIGQUIT to get a stack trace. 25 var sigquit = syscall.SIGQUIT 26 27 func init() { 28 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 29 // We can't use SIGQUIT to kill subprocesses because 30 // it's blocked. Use SIGKILL instead. See issue 31 // #19196 for an example of when this happens. 32 sigquit = syscall.SIGKILL 33 } 34 } 35 36 func TestCrashDumpsAllThreads(t *testing.T) { 37 switch runtime.GOOS { 38 case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 39 default: 40 t.Skipf("skipping; not supported on %v", runtime.GOOS) 41 } 42 43 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 44 t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196") 45 } 46 47 // We don't use executeTest because we need to kill the 48 // program while it is running. 49 50 testenv.MustHaveGoBuild(t) 51 52 checkStaleRuntime(t) 53 54 t.Parallel() 55 56 dir, err := ioutil.TempDir("", "go-build") 57 if err != nil { 58 t.Fatalf("failed to create temp directory: %v", err) 59 } 60 defer os.RemoveAll(dir) 61 62 if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(crashDumpsAllThreadsSource), 0666); err != nil { 63 t.Fatalf("failed to create Go file: %v", err) 64 } 65 66 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe") 67 cmd.Dir = dir 68 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 69 if err != nil { 70 t.Fatalf("building source: %v\n%s", err, out) 71 } 72 73 cmd = exec.Command(filepath.Join(dir, "a.exe")) 74 cmd = testenv.CleanCmdEnv(cmd) 75 cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") 76 77 // Set GOGC=off. Because of golang.org/issue/10958, the tight 78 // loops in the test program are not preemptible. If GC kicks 79 // in, it may lock up and prevent main from saying it's ready. 80 newEnv := []string{} 81 for _, s := range cmd.Env { 82 if !strings.HasPrefix(s, "GOGC=") { 83 newEnv = append(newEnv, s) 84 } 85 } 86 cmd.Env = append(newEnv, "GOGC=off") 87 88 var outbuf bytes.Buffer 89 cmd.Stdout = &outbuf 90 cmd.Stderr = &outbuf 91 92 rp, wp, err := os.Pipe() 93 if err != nil { 94 t.Fatal(err) 95 } 96 cmd.ExtraFiles = []*os.File{wp} 97 98 if err := cmd.Start(); err != nil { 99 t.Fatalf("starting program: %v", err) 100 } 101 102 if err := wp.Close(); err != nil { 103 t.Logf("closing write pipe: %v", err) 104 } 105 if _, err := rp.Read(make([]byte, 1)); err != nil { 106 t.Fatalf("reading from pipe: %v", err) 107 } 108 109 if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil { 110 t.Fatalf("signal: %v", err) 111 } 112 113 // No point in checking the error return from Wait--we expect 114 // it to fail. 115 cmd.Wait() 116 117 // We want to see a stack trace for each thread. 118 // Before https://golang.org/cl/2811 running threads would say 119 // "goroutine running on other thread; stack unavailable". 120 out = outbuf.Bytes() 121 n := bytes.Count(out, []byte("main.loop(")) 122 if n != 4 { 123 t.Errorf("found %d instances of main.loop; expected 4", n) 124 t.Logf("%s", out) 125 } 126 } 127 128 const crashDumpsAllThreadsSource = ` 129 package main 130 131 import ( 132 "fmt" 133 "os" 134 "runtime" 135 ) 136 137 func main() { 138 const count = 4 139 runtime.GOMAXPROCS(count + 1) 140 141 chans := make([]chan bool, count) 142 for i := range chans { 143 chans[i] = make(chan bool) 144 go loop(i, chans[i]) 145 } 146 147 // Wait for all the goroutines to start executing. 148 for _, c := range chans { 149 <-c 150 } 151 152 // Tell our parent that all the goroutines are executing. 153 if _, err := os.NewFile(3, "pipe").WriteString("x"); err != nil { 154 fmt.Fprintf(os.Stderr, "write to pipe failed: %v\n", err) 155 os.Exit(2) 156 } 157 158 select {} 159 } 160 161 func loop(i int, c chan bool) { 162 close(c) 163 for { 164 for j := 0; j < 0x7fffffff; j++ { 165 } 166 } 167 } 168 ` 169 170 func TestPanicSystemstack(t *testing.T) { 171 // Test that GOTRACEBACK=crash prints both the system and user 172 // stack of other threads. 173 174 // The GOTRACEBACK=crash handler takes 0.1 seconds even if 175 // it's not writing a core file and potentially much longer if 176 // it is. Skip in short mode. 177 if testing.Short() { 178 t.Skip("Skipping in short mode (GOTRACEBACK=crash is slow)") 179 } 180 181 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 182 t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196") 183 } 184 185 t.Parallel() 186 cmd := exec.Command(os.Args[0], "testPanicSystemstackInternal") 187 cmd = testenv.CleanCmdEnv(cmd) 188 cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") 189 pr, pw, err := os.Pipe() 190 if err != nil { 191 t.Fatal("creating pipe: ", err) 192 } 193 cmd.Stderr = pw 194 if err := cmd.Start(); err != nil { 195 t.Fatal("starting command: ", err) 196 } 197 defer cmd.Process.Wait() 198 defer cmd.Process.Kill() 199 if err := pw.Close(); err != nil { 200 t.Log("closing write pipe: ", err) 201 } 202 defer pr.Close() 203 204 // Wait for "x\nx\n" to indicate readiness. 205 buf := make([]byte, 4) 206 _, err = io.ReadFull(pr, buf) 207 if err != nil || string(buf) != "x\nx\n" { 208 t.Fatal("subprocess failed; output:\n", string(buf)) 209 } 210 211 // Send SIGQUIT. 212 if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil { 213 t.Fatal("signaling subprocess: ", err) 214 } 215 216 // Get traceback. 217 tb, err := ioutil.ReadAll(pr) 218 if err != nil { 219 t.Fatal("reading traceback from pipe: ", err) 220 } 221 222 // Traceback should have two testPanicSystemstackInternal's 223 // and two blockOnSystemStackInternal's. 224 if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 { 225 t.Fatal("traceback missing user stack:\n", string(tb)) 226 } else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 { 227 t.Fatal("traceback missing system stack:\n", string(tb)) 228 } 229 } 230 231 func init() { 232 if len(os.Args) >= 2 && os.Args[1] == "testPanicSystemstackInternal" { 233 // Get two threads running on the system stack with 234 // something recognizable in the stack trace. 235 runtime.GOMAXPROCS(2) 236 go testPanicSystemstackInternal() 237 testPanicSystemstackInternal() 238 } 239 } 240 241 func testPanicSystemstackInternal() { 242 runtime.BlockOnSystemStack() 243 os.Exit(1) // Should be unreachable. 244 } 245 246 func TestSignalExitStatus(t *testing.T) { 247 testenv.MustHaveGoBuild(t) 248 exe, err := buildTestProg(t, "testprog") 249 if err != nil { 250 t.Fatal(err) 251 } 252 err = testenv.CleanCmdEnv(exec.Command(exe, "SignalExitStatus")).Run() 253 if err == nil { 254 t.Error("test program succeeded unexpectedly") 255 } else if ee, ok := err.(*exec.ExitError); !ok { 256 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 257 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 258 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 259 } else if !ws.Signaled() || ws.Signal() != syscall.SIGTERM { 260 t.Errorf("got %v; expected SIGTERM", ee) 261 } 262 } 263 264 func TestSignalIgnoreSIGTRAP(t *testing.T) { 265 output := runTestProg(t, "testprognet", "SignalIgnoreSIGTRAP") 266 want := "OK\n" 267 if output != want { 268 t.Fatalf("want %s, got %s\n", want, output) 269 } 270 } 271 272 func TestSignalDuringExec(t *testing.T) { 273 switch runtime.GOOS { 274 case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd": 275 default: 276 t.Skipf("skipping test on %s", runtime.GOOS) 277 } 278 output := runTestProg(t, "testprognet", "SignalDuringExec") 279 want := "OK\n" 280 if output != want { 281 t.Fatalf("want %s, got %s\n", want, output) 282 } 283 }