github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 "fmt" 12 "internal/testenv" 13 "io" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "regexp" 18 "runtime" 19 "syscall" 20 "testing" 21 ) 22 23 func canGenerateCore(t *testing.T) bool { 24 // Ensure there is enough RLIMIT_CORE available to generate a full core. 25 var lim syscall.Rlimit 26 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) 27 if err != nil { 28 t.Fatalf("error getting rlimit: %v", err) 29 } 30 // Minimum RLIMIT_CORE max to allow. This is a conservative estimate. 31 // Most systems allow infinity. 32 const minRlimitCore = 100 << 20 // 100 MB 33 if lim.Max < minRlimitCore { 34 t.Skipf("RLIMIT_CORE max too low: %#+v", lim) 35 } 36 37 // Make sure core pattern will send core to the current directory. 38 b, err := os.ReadFile("/proc/sys/kernel/core_pattern") 39 if err != nil { 40 t.Fatalf("error reading core_pattern: %v", err) 41 } 42 if string(b) != "core\n" { 43 t.Skipf("Unexpected core pattern %q", string(b)) 44 } 45 46 coreUsesPID := false 47 b, err = os.ReadFile("/proc/sys/kernel/core_uses_pid") 48 if err == nil { 49 switch string(bytes.TrimSpace(b)) { 50 case "0": 51 case "1": 52 coreUsesPID = true 53 default: 54 t.Skipf("unexpected core_uses_pid value %q", string(b)) 55 } 56 } 57 return coreUsesPID 58 } 59 60 const coreSignalSource = ` 61 package main 62 63 import ( 64 "flag" 65 "fmt" 66 "os" 67 "runtime/debug" 68 "syscall" 69 ) 70 71 var pipeFD = flag.Int("pipe-fd", -1, "FD of write end of control pipe") 72 73 func enableCore() { 74 debug.SetTraceback("crash") 75 76 var lim syscall.Rlimit 77 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) 78 if err != nil { 79 panic(fmt.Sprintf("error getting rlimit: %v", err)) 80 } 81 lim.Cur = lim.Max 82 fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim) 83 err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim) 84 if err != nil { 85 panic(fmt.Sprintf("error setting rlimit: %v", err)) 86 } 87 } 88 89 func main() { 90 flag.Parse() 91 92 enableCore() 93 94 // Ready to go. Notify parent. 95 if err := syscall.Close(*pipeFD); err != nil { 96 panic(fmt.Sprintf("error closing control pipe fd %d: %v", *pipeFD, err)) 97 } 98 99 for {} 100 } 101 ` 102 103 // TestGdbCoreSignalBacktrace tests that gdb can unwind the stack correctly 104 // through a signal handler in a core file 105 func TestGdbCoreSignalBacktrace(t *testing.T) { 106 if runtime.GOOS != "linux" { 107 // N.B. This test isn't fundamentally Linux-only, but it needs 108 // to know how to enable/find core files on each OS. 109 t.Skip("Test only supported on Linux") 110 } 111 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" { 112 // TODO(go.dev/issue/25218): Other architectures use sigreturn 113 // via VDSO, which we somehow don't handle correctly. 114 t.Skip("Backtrace through signal handler only works on 386 and amd64") 115 } 116 117 checkGdbEnvironment(t) 118 t.Parallel() 119 checkGdbVersion(t) 120 121 coreUsesPID := canGenerateCore(t) 122 123 // Build the source code. 124 dir := t.TempDir() 125 src := filepath.Join(dir, "main.go") 126 err := os.WriteFile(src, []byte(coreSignalSource), 0644) 127 if err != nil { 128 t.Fatalf("failed to create file: %v", err) 129 } 130 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 131 cmd.Dir = dir 132 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 133 if err != nil { 134 t.Fatalf("building source %v\n%s", err, out) 135 } 136 137 r, w, err := os.Pipe() 138 if err != nil { 139 t.Fatalf("error creating control pipe: %v", err) 140 } 141 defer r.Close() 142 143 // Start the test binary. 144 cmd = testenv.Command(t, "./a.exe", "-pipe-fd=3") 145 cmd.Dir = dir 146 cmd.ExtraFiles = []*os.File{w} 147 var output bytes.Buffer 148 cmd.Stdout = &output // for test logging 149 cmd.Stderr = &output 150 151 if err := cmd.Start(); err != nil { 152 t.Fatalf("error starting test binary: %v", err) 153 } 154 w.Close() 155 156 pid := cmd.Process.Pid 157 158 // Wait for child to be ready. 159 var buf [1]byte 160 if _, err := r.Read(buf[:]); err != io.EOF { 161 t.Fatalf("control pipe read get err %v want io.EOF", err) 162 } 163 164 // 💥 165 if err := cmd.Process.Signal(os.Signal(syscall.SIGABRT)); err != nil { 166 t.Fatalf("erroring signaling child: %v", err) 167 } 168 169 err = cmd.Wait() 170 t.Logf("child output:\n%s", output.String()) 171 if err == nil { 172 t.Fatalf("Wait succeeded, want SIGABRT") 173 } 174 ee, ok := err.(*exec.ExitError) 175 if !ok { 176 t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee) 177 } 178 ws, ok := ee.Sys().(syscall.WaitStatus) 179 if !ok { 180 t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys()) 181 } 182 if ws.Signal() != syscall.SIGABRT { 183 t.Fatalf("Signal got %d want SIGABRT", ws.Signal()) 184 } 185 if !ws.CoreDump() { 186 t.Fatalf("CoreDump got %v want true", ws.CoreDump()) 187 } 188 189 coreFile := "core" 190 if coreUsesPID { 191 coreFile += fmt.Sprintf(".%d", pid) 192 } 193 194 // Execute gdb commands. 195 args := []string{"-nx", "-batch", 196 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 197 "-ex", "backtrace", 198 filepath.Join(dir, "a.exe"), 199 filepath.Join(dir, coreFile), 200 } 201 cmd = testenv.Command(t, "gdb", args...) 202 203 got, err := cmd.CombinedOutput() 204 t.Logf("gdb output:\n%s", got) 205 if err != nil { 206 t.Fatalf("gdb exited with error: %v", err) 207 } 208 209 // We don't know which thread the fatal signal will land on, but we can still check for basics: 210 // 211 // 1. A frame in the signal handler: runtime.sigtramp 212 // 2. GDB detection of the signal handler: <signal handler called> 213 // 3. A frame before the signal handler: this could be foo, or somewhere in the scheduler 214 215 re := regexp.MustCompile(`#.* runtime\.sigtramp `) 216 if found := re.Find(got) != nil; !found { 217 t.Fatalf("could not find sigtramp in backtrace") 218 } 219 220 re = regexp.MustCompile("#.* <signal handler called>") 221 loc := re.FindIndex(got) 222 if loc == nil { 223 t.Fatalf("could not find signal handler marker in backtrace") 224 } 225 rest := got[loc[1]:] 226 227 // Look for any frames after the signal handler. We want to see 228 // symbolized frames, not garbage unknown frames. 229 // 230 // Since the signal might not be delivered to the main thread we can't 231 // look for main.main. Every thread should have a runtime frame though. 232 re = regexp.MustCompile(`#.* runtime\.`) 233 if found := re.Find(rest) != nil; !found { 234 t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest) 235 } 236 } 237 238 const coreCrashThreadSource = ` 239 package main 240 241 /* 242 #cgo CFLAGS: -g -O0 243 #include <stdio.h> 244 #include <stddef.h> 245 void trigger_crash() 246 { 247 int* ptr = NULL; 248 *ptr = 1024; 249 } 250 */ 251 import "C" 252 import ( 253 "flag" 254 "fmt" 255 "os" 256 "runtime/debug" 257 "syscall" 258 ) 259 260 func enableCore() { 261 debug.SetTraceback("crash") 262 263 var lim syscall.Rlimit 264 err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim) 265 if err != nil { 266 panic(fmt.Sprintf("error getting rlimit: %v", err)) 267 } 268 lim.Cur = lim.Max 269 fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim) 270 err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim) 271 if err != nil { 272 panic(fmt.Sprintf("error setting rlimit: %v", err)) 273 } 274 } 275 276 func main() { 277 flag.Parse() 278 279 enableCore() 280 281 C.trigger_crash() 282 } 283 ` 284 285 // TestGdbCoreCrashThreadBacktrace tests that runtime could let the fault thread to crash process 286 // and make fault thread as number one thread while gdb in a core file 287 func TestGdbCoreCrashThreadBacktrace(t *testing.T) { 288 if runtime.GOOS != "linux" { 289 // N.B. This test isn't fundamentally Linux-only, but it needs 290 // to know how to enable/find core files on each OS. 291 t.Skip("Test only supported on Linux") 292 } 293 if runtime.GOARCH != "386" && runtime.GOARCH != "amd64" { 294 // TODO(go.dev/issue/25218): Other architectures use sigreturn 295 // via VDSO, which we somehow don't handle correctly. 296 t.Skip("Backtrace through signal handler only works on 386 and amd64") 297 } 298 299 testenv.SkipFlaky(t, 65138) 300 301 testenv.MustHaveCGO(t) 302 checkGdbEnvironment(t) 303 t.Parallel() 304 checkGdbVersion(t) 305 306 coreUsesPID := canGenerateCore(t) 307 308 // Build the source code. 309 dir := t.TempDir() 310 src := filepath.Join(dir, "main.go") 311 err := os.WriteFile(src, []byte(coreCrashThreadSource), 0644) 312 if err != nil { 313 t.Fatalf("failed to create file: %v", err) 314 } 315 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go") 316 cmd.Dir = dir 317 out, err := testenv.CleanCmdEnv(cmd).CombinedOutput() 318 if err != nil { 319 t.Fatalf("building source %v\n%s", err, out) 320 } 321 322 // Start the test binary. 323 cmd = testenv.Command(t, "./a.exe") 324 cmd.Dir = dir 325 var output bytes.Buffer 326 cmd.Stdout = &output // for test logging 327 cmd.Stderr = &output 328 329 if err := cmd.Start(); err != nil { 330 t.Fatalf("error starting test binary: %v", err) 331 } 332 333 pid := cmd.Process.Pid 334 335 err = cmd.Wait() 336 t.Logf("child output:\n%s", output.String()) 337 if err == nil { 338 t.Fatalf("Wait succeeded, want SIGABRT") 339 } 340 ee, ok := err.(*exec.ExitError) 341 if !ok { 342 t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee) 343 } 344 ws, ok := ee.Sys().(syscall.WaitStatus) 345 if !ok { 346 t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys()) 347 } 348 if ws.Signal() != syscall.SIGABRT { 349 t.Fatalf("Signal got %d want SIGABRT", ws.Signal()) 350 } 351 if !ws.CoreDump() { 352 t.Fatalf("CoreDump got %v want true", ws.CoreDump()) 353 } 354 355 coreFile := "core" 356 if coreUsesPID { 357 coreFile += fmt.Sprintf(".%d", pid) 358 } 359 360 // Execute gdb commands. 361 args := []string{"-nx", "-batch", 362 "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"), 363 "-ex", "backtrace", 364 filepath.Join(dir, "a.exe"), 365 filepath.Join(dir, coreFile), 366 } 367 cmd = testenv.Command(t, "gdb", args...) 368 369 got, err := cmd.CombinedOutput() 370 t.Logf("gdb output:\n%s", got) 371 if err != nil { 372 t.Fatalf("gdb exited with error: %v", err) 373 } 374 375 re := regexp.MustCompile(`#.* trigger_crash`) 376 if found := re.Find(got) != nil; !found { 377 t.Fatalf("could not find trigger_crash in backtrace") 378 } 379 }