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  }