github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/runtime-gdb_unix_test.go (about) 1 // Copyright 2023 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 //go:build unix 6 7 package runtime_test 8 9 import ( 10 "bytes" 11 "internal/testenv" 12 "io" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "regexp" 17 "runtime" 18 "syscall" 19 "testing" 20 ) 21 22 const coreSignalSource = ` 23 package main 24 25 import ( 26 "flag" 27 "fmt" 28 "os" 29 "runtime/debug" 30 "syscall" 31 ) 32 33 var pipeFD = flag.Int("pipe-fd", -1, "FD of write end of control pipe") 34 35 func enableCore() { 36 debug.SetTraceback("crash") 37 38 var lim syscall.Rlimit 39 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) 40 if err != nil { 41 panic(fmt.Sprintf("error getting rlimit: %v", err)) 42 } 43 lim.Cur = lim.Max 44 fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim) 45 err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim) 46 if err != nil { 47 panic(fmt.Sprintf("error setting rlimit: %v", err)) 48 } 49 } 50 51 func main() { 52 flag.Parse() 53 54 enableCore() 55 56 // Ready to go. Notify parent. 57 if err := syscall.Close(*pipeFD); err != nil { 58 panic(fmt.Sprintf("error closing control pipe fd %d: %v", *pipeFD, err)) 59 } 60 61 for {} 62 } 63 ` 64 65 // TestGdbCoreSignalBacktrace tests that gdb can unwind the stack correctly 66 // through a signal handler in a core file 67 func TestGdbCoreSignalBacktrace(t *testing.T) { 68 if runtime.GOOS != "linux" { 69 // N.B. This test isn't fundamentally Linux-only, but it needs 70 // to know how to enable/find core files on each OS. 71 t.Skip("Test only supported on Linux") 72 } 73 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" { 74 // TODO(go.dev/issue/25218): Other architectures use sigreturn 75 // via VDSO, which we somehow don't handle correctly. 76 t.Skip("Backtrace through signal handler only works on 386 and amd64") 77 } 78 79 checkGdbEnvironment(t) 80 t.Parallel() 81 checkGdbVersion(t) 82 83 // Ensure there is enough RLIMIT_CORE available to generate a full core. 84 var lim syscall.Rlimit 85 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) 86 if err != nil { 87 t.Fatalf("error getting rlimit: %v", err) 88 } 89 // Minimum RLIMIT_CORE max to allow. This is a conservative estimate. 90 // Most systems allow infinity. 91 const minRlimitCore = 100 << 20 // 100 MB 92 if lim.Max < minRlimitCore { 93 t.Skipf("RLIMIT_CORE max too low: %#+v", lim) 94 } 95 96 // Make sure core pattern will send core to the current directory. 97 b, err := os.ReadFile("/proc/sys/kernel/core_pattern") 98 if err != nil { 99 t.Fatalf("error reading core_pattern: %v", err) 100 } 101 if string(b) != "core\n" { 102 t.Skipf("Unexpected core pattern %q", string(b)) 103 } 104 105 dir := t.TempDir() 106 107 // Build the source code. 108 src := filepath.Join(dir, "main.go") 109 err = os.WriteFile(src, []byte(coreSignalSource), 0644) 110 if err != nil { 111 t.Fatalf("failed to create file: %v", err) 112 } 113 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 114 cmd.Dir = dir 115 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 116 if err != nil { 117 t.Fatalf("building source %v\n%s", err, out) 118 } 119 120 r, w, err := os.Pipe() 121 if err != nil { 122 t.Fatalf("error creating control pipe: %v", err) 123 } 124 defer r.Close() 125 126 // Start the test binary. 127 cmd = testenv.Command(t, "./a.exe", "-pipe-fd=3") 128 cmd.Dir = dir 129 cmd.ExtraFiles = []*os.File{w} 130 var output bytes.Buffer 131 cmd.Stdout = &output // for test logging 132 cmd.Stderr = &output 133 134 if err := cmd.Start(); err != nil { 135 t.Fatalf("error starting test binary: %v", err) 136 } 137 w.Close() 138 139 // Wait for child to be ready. 140 var buf [1]byte 141 if _, err := r.Read(buf[:]); err != io.EOF { 142 t.Fatalf("control pipe read get err %v want io.EOF", err) 143 } 144 145 // 💥 146 if err := cmd.Process.Signal(os.Signal(syscall.SIGABRT)); err != nil { 147 t.Fatalf("erroring signaling child: %v", err) 148 } 149 150 err = cmd.Wait() 151 t.Logf("child output:\n%s", output.String()) 152 if err == nil { 153 t.Fatalf("Wait succeeded, want SIGABRT") 154 } 155 ee, ok := err.(*exec.ExitError) 156 if !ok { 157 t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee) 158 } 159 ws, ok := ee.Sys().(syscall.WaitStatus) 160 if !ok { 161 t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys()) 162 } 163 if ws.Signal() != syscall.SIGABRT { 164 t.Fatalf("Signal got %d want SIGABRT", ws.Signal()) 165 } 166 if !ws.CoreDump() { 167 t.Fatalf("CoreDump got %v want true", ws.CoreDump()) 168 } 169 170 // Execute gdb commands. 171 args := []string{"-nx", "-batch", 172 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 173 "-ex", "backtrace", 174 filepath.Join(dir, "a.exe"), 175 filepath.Join(dir, "core"), 176 } 177 cmd = testenv.Command(t, "gdb", args...) 178 179 got, err := cmd.CombinedOutput() 180 t.Logf("gdb output:\n%s", got) 181 if err != nil { 182 t.Fatalf("gdb exited with error: %v", err) 183 } 184 185 // We don't know which thread the fatal signal will land on, but we can still check for basics: 186 // 187 // 1. A frame in the signal handler: runtime.sigtramp 188 // 2. GDB detection of the signal handler: <signal handler called> 189 // 3. A frame before the signal handler: this could be foo, or somewhere in the scheduler 190 191 re := regexp.MustCompile(`#.* runtime\.sigtramp `) 192 if found := re.Find(got) != nil; !found { 193 t.Fatalf("could not find sigtramp in backtrace") 194 } 195 196 re = regexp.MustCompile("#.* <signal handler called>") 197 loc := re.FindIndex(got) 198 if loc == nil { 199 t.Fatalf("could not find signal handler marker in backtrace") 200 } 201 rest := got[loc[1]:] 202 203 // Look for any frames after the signal handler. We want to see 204 // symbolized frames, not garbage unknown frames. 205 // 206 // Since the signal might not be delivered to the main thread we can't 207 // look for main.main. Every thread should have a runtime frame though. 208 re = regexp.MustCompile(`#.* runtime\.`) 209 if found := re.Find(rest) != nil; !found { 210 t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest) 211 } 212 }