github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 aix darwin dragonfly freebsd linux netbsd openbsd solaris 6 7 package runtime_test 8 9 import ( 10 "bytes" 11 "internal/testenv" 12 "io" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "sync" 19 "syscall" 20 "testing" 21 "time" 22 "unsafe" 23 ) 24 25 // sigquit is the signal to send to kill a hanging testdata program. 26 // Send SIGQUIT to get a stack trace. 27 var sigquit = syscall.SIGQUIT 28 29 func init() { 30 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 31 // We can't use SIGQUIT to kill subprocesses because 32 // it's blocked. Use SIGKILL instead. See issue 33 // #19196 for an example of when this happens. 34 sigquit = syscall.SIGKILL 35 } 36 } 37 38 func TestBadOpen(t *testing.T) { 39 // make sure we get the correct error code if open fails. Same for 40 // read/write/close on the resulting -1 fd. See issue 10052. 41 nonfile := []byte("/notreallyafile") 42 fd := runtime.Open(&nonfile[0], 0, 0) 43 if fd != -1 { 44 t.Errorf("open(%q)=%d, want -1", nonfile, fd) 45 } 46 var buf [32]byte 47 r := runtime.Read(-1, unsafe.Pointer(&buf[0]), int32(len(buf))) 48 if got, want := r, -int32(syscall.EBADF); got != want { 49 t.Errorf("read()=%d, want %d", got, want) 50 } 51 w := runtime.Write(^uintptr(0), unsafe.Pointer(&buf[0]), int32(len(buf))) 52 if got, want := w, -int32(syscall.EBADF); got != want { 53 t.Errorf("write()=%d, want %d", got, want) 54 } 55 c := runtime.Close(-1) 56 if c != -1 { 57 t.Errorf("close()=%d, want -1", c) 58 } 59 } 60 61 func TestCrashDumpsAllThreads(t *testing.T) { 62 if *flagQuick { 63 t.Skip("-quick") 64 } 65 66 switch runtime.GOOS { 67 case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris": 68 default: 69 t.Skipf("skipping; not supported on %v", runtime.GOOS) 70 } 71 72 if runtime.GOOS == "openbsd" && runtime.GOARCH == "mips64" { 73 t.Skipf("skipping; test fails on %s/%s - see issue #42464", runtime.GOOS, runtime.GOARCH) 74 } 75 76 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 77 t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196") 78 } 79 80 // We don't use executeTest because we need to kill the 81 // program while it is running. 82 83 testenv.MustHaveGoBuild(t) 84 85 t.Parallel() 86 87 dir, err := os.MkdirTemp("", "go-build") 88 if err != nil { 89 t.Fatalf("failed to create temp directory: %v", err) 90 } 91 defer os.RemoveAll(dir) 92 93 if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(crashDumpsAllThreadsSource), 0666); err != nil { 94 t.Fatalf("failed to create Go file: %v", err) 95 } 96 97 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 98 cmd.Dir = dir 99 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 100 if err != nil { 101 t.Fatalf("building source: %v\n%s", err, out) 102 } 103 104 cmd = exec.Command(filepath.Join(dir, "a.exe")) 105 cmd = testenv.CleanCmdEnv(cmd) 106 cmd.Env = append(cmd.Env, 107 "GOTRACEBACK=crash", 108 // Set GOGC=off. Because of golang.org/issue/10958, the tight 109 // loops in the test program are not preemptible. If GC kicks 110 // in, it may lock up and prevent main from saying it's ready. 111 "GOGC=off", 112 // Set GODEBUG=asyncpreemptoff=1. If a thread is preempted 113 // when it receives SIGQUIT, it won't show the expected 114 // stack trace. See issue 35356. 115 "GODEBUG=asyncpreemptoff=1", 116 ) 117 118 var outbuf bytes.Buffer 119 cmd.Stdout = &outbuf 120 cmd.Stderr = &outbuf 121 122 rp, wp, err := os.Pipe() 123 if err != nil { 124 t.Fatal(err) 125 } 126 cmd.ExtraFiles = []*os.File{wp} 127 128 if err := cmd.Start(); err != nil { 129 t.Fatalf("starting program: %v", err) 130 } 131 132 if err := wp.Close(); err != nil { 133 t.Logf("closing write pipe: %v", err) 134 } 135 if _, err := rp.Read(make([]byte, 1)); err != nil { 136 t.Fatalf("reading from pipe: %v", err) 137 } 138 139 if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil { 140 t.Fatalf("signal: %v", err) 141 } 142 143 // No point in checking the error return from Wait--we expect 144 // it to fail. 145 cmd.Wait() 146 147 // We want to see a stack trace for each thread. 148 // Before https://golang.org/cl/2811 running threads would say 149 // "goroutine running on other thread; stack unavailable". 150 out = outbuf.Bytes() 151 n := bytes.Count(out, []byte("main.loop(")) 152 if n != 4 { 153 t.Errorf("found %d instances of main.loop; expected 4", n) 154 t.Logf("%s", out) 155 } 156 } 157 158 const crashDumpsAllThreadsSource = ` 159 package main 160 161 import ( 162 "fmt" 163 "os" 164 "runtime" 165 ) 166 167 func main() { 168 const count = 4 169 runtime.GOMAXPROCS(count + 1) 170 171 chans := make([]chan bool, count) 172 for i := range chans { 173 chans[i] = make(chan bool) 174 go loop(i, chans[i]) 175 } 176 177 // Wait for all the goroutines to start executing. 178 for _, c := range chans { 179 <-c 180 } 181 182 // Tell our parent that all the goroutines are executing. 183 if _, err := os.NewFile(3, "pipe").WriteString("x"); err != nil { 184 fmt.Fprintf(os.Stderr, "write to pipe failed: %v\n", err) 185 os.Exit(2) 186 } 187 188 select {} 189 } 190 191 func loop(i int, c chan bool) { 192 close(c) 193 for { 194 for j := 0; j < 0x7fffffff; j++ { 195 } 196 } 197 } 198 ` 199 200 func TestPanicSystemstack(t *testing.T) { 201 // Test that GOTRACEBACK=crash prints both the system and user 202 // stack of other threads. 203 204 // The GOTRACEBACK=crash handler takes 0.1 seconds even if 205 // it's not writing a core file and potentially much longer if 206 // it is. Skip in short mode. 207 if testing.Short() { 208 t.Skip("Skipping in short mode (GOTRACEBACK=crash is slow)") 209 } 210 211 if runtime.Sigisblocked(int(syscall.SIGQUIT)) { 212 t.Skip("skipping; SIGQUIT is blocked, see golang.org/issue/19196") 213 } 214 215 t.Parallel() 216 cmd := exec.Command(os.Args[0], "testPanicSystemstackInternal") 217 cmd = testenv.CleanCmdEnv(cmd) 218 cmd.Env = append(cmd.Env, "GOTRACEBACK=crash") 219 pr, pw, err := os.Pipe() 220 if err != nil { 221 t.Fatal("creating pipe: ", err) 222 } 223 cmd.Stderr = pw 224 if err := cmd.Start(); err != nil { 225 t.Fatal("starting command: ", err) 226 } 227 defer cmd.Process.Wait() 228 defer cmd.Process.Kill() 229 if err := pw.Close(); err != nil { 230 t.Log("closing write pipe: ", err) 231 } 232 defer pr.Close() 233 234 // Wait for "x\nx\n" to indicate almost-readiness. 235 buf := make([]byte, 4) 236 _, err = io.ReadFull(pr, buf) 237 if err != nil || string(buf) != "x\nx\n" { 238 t.Fatal("subprocess failed; output:\n", string(buf)) 239 } 240 241 // The child blockers print "x\n" and then block on a lock. Receiving 242 // those bytes only indicates that the child is _about to block_. Since 243 // we don't have a way to know when it is fully blocked, sleep a bit to 244 // make us less likely to lose the race and signal before the child 245 // blocks. 246 time.Sleep(100 * time.Millisecond) 247 248 // Send SIGQUIT. 249 if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil { 250 t.Fatal("signaling subprocess: ", err) 251 } 252 253 // Get traceback. 254 tb, err := io.ReadAll(pr) 255 if err != nil { 256 t.Fatal("reading traceback from pipe: ", err) 257 } 258 259 // Traceback should have two testPanicSystemstackInternal's 260 // and two blockOnSystemStackInternal's. 261 if bytes.Count(tb, []byte("testPanicSystemstackInternal")) != 2 { 262 t.Fatal("traceback missing user stack:\n", string(tb)) 263 } else if bytes.Count(tb, []byte("blockOnSystemStackInternal")) != 2 { 264 t.Fatal("traceback missing system stack:\n", string(tb)) 265 } 266 } 267 268 func init() { 269 if len(os.Args) >= 2 && os.Args[1] == "testPanicSystemstackInternal" { 270 // Get two threads running on the system stack with 271 // something recognizable in the stack trace. 272 runtime.GOMAXPROCS(2) 273 go testPanicSystemstackInternal() 274 testPanicSystemstackInternal() 275 } 276 } 277 278 func testPanicSystemstackInternal() { 279 runtime.BlockOnSystemStack() 280 os.Exit(1) // Should be unreachable. 281 } 282 283 func TestSignalExitStatus(t *testing.T) { 284 testenv.MustHaveGoBuild(t) 285 exe, err := buildTestProg(t, "testprog") 286 if err != nil { 287 t.Fatal(err) 288 } 289 err = testenv.CleanCmdEnv(exec.Command(exe, "SignalExitStatus")).Run() 290 if err == nil { 291 t.Error("test program succeeded unexpectedly") 292 } else if ee, ok := err.(*exec.ExitError); !ok { 293 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 294 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 295 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 296 } else if !ws.Signaled() || ws.Signal() != syscall.SIGTERM { 297 t.Errorf("got %v; expected SIGTERM", ee) 298 } 299 } 300 301 func TestSignalIgnoreSIGTRAP(t *testing.T) { 302 if runtime.GOOS == "openbsd" { 303 if bn := testenv.Builder(); strings.HasSuffix(bn, "-62") || strings.HasSuffix(bn, "-64") { 304 testenv.SkipFlaky(t, 17496) 305 } 306 } 307 308 output := runTestProg(t, "testprognet", "SignalIgnoreSIGTRAP") 309 want := "OK\n" 310 if output != want { 311 t.Fatalf("want %s, got %s\n", want, output) 312 } 313 } 314 315 func TestSignalDuringExec(t *testing.T) { 316 switch runtime.GOOS { 317 case "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd": 318 default: 319 t.Skipf("skipping test on %s", runtime.GOOS) 320 } 321 output := runTestProg(t, "testprognet", "SignalDuringExec") 322 want := "OK\n" 323 if output != want { 324 t.Fatalf("want %s, got %s\n", want, output) 325 } 326 } 327 328 func TestSignalM(t *testing.T) { 329 r, w, errno := runtime.Pipe() 330 if errno != 0 { 331 t.Fatal(syscall.Errno(errno)) 332 } 333 defer func() { 334 runtime.Close(r) 335 runtime.Close(w) 336 }() 337 runtime.Closeonexec(r) 338 runtime.Closeonexec(w) 339 340 var want, got int64 341 var wg sync.WaitGroup 342 ready := make(chan *runtime.M) 343 wg.Add(1) 344 go func() { 345 runtime.LockOSThread() 346 want, got = runtime.WaitForSigusr1(r, w, func(mp *runtime.M) { 347 ready <- mp 348 }) 349 runtime.UnlockOSThread() 350 wg.Done() 351 }() 352 waitingM := <-ready 353 runtime.SendSigusr1(waitingM) 354 355 timer := time.AfterFunc(time.Second, func() { 356 // Write 1 to tell WaitForSigusr1 that we timed out. 357 bw := byte(1) 358 if n := runtime.Write(uintptr(w), unsafe.Pointer(&bw), 1); n != 1 { 359 t.Errorf("pipe write failed: %d", n) 360 } 361 }) 362 defer timer.Stop() 363 364 wg.Wait() 365 if got == -1 { 366 t.Fatal("signalM signal not received") 367 } else if want != got { 368 t.Fatalf("signal sent to M %d, but received on M %d", want, got) 369 } 370 }