]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: add "sigaction" to sigreturn symbol name
authorMichael Pratt <mpratt@google.com>
Thu, 23 Mar 2023 19:28:42 +0000 (15:28 -0400)
committerGopher Robot <gobot@golang.org>
Fri, 24 Mar 2023 18:53:44 +0000 (18:53 +0000)
In order to identify the sigreturn function, gdb looks for
"__restore_rt". However because that symbol is sometimes missing from
the symbol table, it also performs the same instruction matching as
libgcc, but only in symbols containing "sigaction" (it expects sigaction
to preceed __restore_rt).

To match this heuristic, we add __sigaction to the sigreturn symbol
name.

Fixes #25218.

Change-Id: I09cb231ad23f668d451f31dd5633f782355fc91d
Reviewed-on: https://go-review.googlesource.com/c/go/+/479096
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>

src/runtime/os_linux.go
src/runtime/runtime-gdb_unix_test.go [new file with mode: 0644]
src/runtime/sys_linux_386.s
src/runtime/sys_linux_amd64.s
src/runtime/sys_linux_arm.s
src/runtime/sys_linux_arm64.s
src/runtime/sys_linux_ppc64x.s
src/runtime/sys_linux_s390x.s

index 194d698798c9a0de68115326dabc243fbc1b323d..398ff189914c96bdf6b96a70fddd87afaeedfd0d 100644 (file)
@@ -424,7 +424,7 @@ func mdestroy(mp *m) {
 //#define sa_handler k_sa_handler
 //#endif
 
-func sigreturn()
+func sigreturn__sigaction()
 func sigtramp() // Called via C ABI
 func cgoSigtramp()
 
@@ -481,7 +481,7 @@ func setsig(i uint32, fn uintptr) {
        // should not be used". x86_64 kernel requires it. Only use it on
        // x86.
        if GOARCH == "386" || GOARCH == "amd64" {
-               sa.sa_restorer = abi.FuncPCABI0(sigreturn)
+               sa.sa_restorer = abi.FuncPCABI0(sigreturn__sigaction)
        }
        if fn == abi.FuncPCABIInternal(sighandler) { // abi.FuncPCABIInternal(sighandler) matches the callers in signal_unix.go
                if iscgo {
diff --git a/src/runtime/runtime-gdb_unix_test.go b/src/runtime/runtime-gdb_unix_test.go
new file mode 100644 (file)
index 0000000..a276fdb
--- /dev/null
@@ -0,0 +1,207 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package runtime_test
+
+import (
+       "bytes"
+       "internal/testenv"
+       "io"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "regexp"
+       "runtime"
+       "syscall"
+       "testing"
+)
+
+const coreSignalSource = `
+package main
+
+import (
+       "flag"
+       "fmt"
+       "os"
+       "runtime/debug"
+       "syscall"
+)
+
+var pipeFD = flag.Int("pipe-fd", -1, "FD of write end of control pipe")
+
+func enableCore() {
+       debug.SetTraceback("crash")
+
+       var lim syscall.Rlimit
+       err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
+       if err != nil {
+               panic(fmt.Sprintf("error getting rlimit: %v", err))
+       }
+       lim.Cur = lim.Max
+       fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
+       err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
+       if err != nil {
+               panic(fmt.Sprintf("error setting rlimit: %v", err))
+       }
+}
+
+func main() {
+       flag.Parse()
+
+       enableCore()
+
+       // Ready to go. Notify parent.
+       if err := syscall.Close(*pipeFD); err != nil {
+               panic(fmt.Sprintf("error closing control pipe fd %d: %v", *pipeFD, err))
+       }
+
+       for {}
+}
+`
+
+// TestGdbCoreSignalBacktrace tests that gdb can unwind the stack correctly
+// through a signal handler in a core file
+func TestGdbCoreSignalBacktrace(t *testing.T) {
+       if runtime.GOOS != "linux" {
+               // N.B. This test isn't fundamentally Linux-only, but it needs
+               // to know how to enable/find core files on each OS.
+               t.Skip("Test only supported on Linux")
+       }
+
+       checkGdbEnvironment(t)
+       t.Parallel()
+       checkGdbVersion(t)
+
+       // Ensure there is enough RLIMIT_CORE available to generate a full core.
+       var lim syscall.Rlimit
+       err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
+       if err != nil {
+               t.Fatalf("error getting rlimit: %v", err)
+       }
+       // Minimum RLIMIT_CORE max to allow. This is a conservative estimate.
+       // Most systems allow infinity.
+       const minRlimitCore = 100 << 20 // 100 MB
+       if lim.Max < minRlimitCore {
+               t.Skipf("RLIMIT_CORE max too low: %#+v", lim)
+       }
+
+       // Make sure core pattern will send core to the current directory.
+       b, err := os.ReadFile("/proc/sys/kernel/core_pattern")
+       if err != nil {
+               t.Fatalf("error reading core_pattern: %v", err)
+       }
+       if string(b) != "core\n" {
+               t.Skipf("Unexpected core pattern %q", string(b))
+       }
+
+       dir := t.TempDir()
+
+       // Build the source code.
+       src := filepath.Join(dir, "main.go")
+       err = os.WriteFile(src, []byte(coreSignalSource), 0644)
+       if err != nil {
+               t.Fatalf("failed to create file: %v", err)
+       }
+       cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
+       cmd.Dir = dir
+       out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
+       if err != nil {
+               t.Fatalf("building source %v\n%s", err, out)
+       }
+
+       r, w, err := os.Pipe()
+       if err != nil {
+               t.Fatalf("error creating control pipe: %v", err)
+       }
+       defer r.Close()
+
+       // Start the test binary.
+       cmd = testenv.Command(t, "./a.exe", "-pipe-fd=3")
+       cmd.Dir = dir
+       cmd.ExtraFiles = []*os.File{w}
+       var output bytes.Buffer
+       cmd.Stdout = &output // for test logging
+       cmd.Stderr = &output
+
+       if err := cmd.Start(); err != nil {
+               t.Fatalf("error starting test binary: %v", err)
+       }
+       w.Close()
+
+       // Wait for child to be ready.
+       var buf [1]byte
+       if _, err := r.Read(buf[:]); err != io.EOF {
+               t.Fatalf("control pipe read get err %v want io.EOF", err)
+       }
+
+       // 馃挜
+       if err := cmd.Process.Signal(os.Signal(syscall.SIGABRT)); err != nil {
+               t.Fatalf("erroring signaling child: %v", err)
+       }
+
+       err = cmd.Wait()
+       t.Logf("child output:\n%s", output.String())
+       if err == nil {
+               t.Fatalf("Wait succeeded, want SIGABRT")
+       }
+       ee, ok := err.(*exec.ExitError)
+       if !ok {
+               t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee)
+       }
+       ws, ok := ee.Sys().(syscall.WaitStatus)
+       if !ok {
+               t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys())
+       }
+       if ws.Signal() != syscall.SIGABRT {
+               t.Fatalf("Signal got %d want SIGABRT", ws.Signal())
+       }
+       if !ws.CoreDump() {
+               t.Fatalf("CoreDump got %v want true", ws.CoreDump())
+       }
+
+       // Execute gdb commands.
+       args := []string{"-nx", "-batch",
+               "-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
+               "-ex", "backtrace",
+               filepath.Join(dir, "a.exe"),
+               filepath.Join(dir, "core"),
+       }
+       cmd = testenv.Command(t, "gdb", args...)
+
+       got, err := cmd.CombinedOutput()
+       t.Logf("gdb output:\n%s", got)
+       if err != nil {
+               t.Fatalf("gdb exited with error: %v", err)
+       }
+
+       // We don't know which thread the fatal signal will land on, but we can still check for basics:
+       //
+       // 1. A frame in the signal handler: runtime.sigtramp
+       // 2. GDB detection of the signal handler: <signal handler called>
+       // 3. A frame before the signal handler: this could be foo, or somewhere in the scheduler
+
+       re := regexp.MustCompile(`#.* runtime\.sigtramp `)
+       if found := re.Find(got) != nil; !found {
+               t.Fatalf("could not find sigtramp in backtrace")
+       }
+
+       re = regexp.MustCompile("#.* <signal handler called>")
+       loc := re.FindIndex(got)
+       if loc == nil {
+               t.Fatalf("could not find signal handler marker in backtrace")
+       }
+       rest := got[loc[1]:]
+
+       // Look for any frames after the signal handler. We want to see
+       // symbolized frames, not garbage unknown frames.
+       //
+       // Since the signal might not be delivered to the main thread we can't
+       // look for main.main. Every thread should have a runtime frame though.
+       re = regexp.MustCompile(`#.* runtime\.`)
+       if found := re.Find(rest) != nil; !found {
+               t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest)
+       }
+}
index 12a294153d2dc0745692ea3ffcb320d651893c8a..d53be243fe7001fe21a3e5c5534951a344768212 100644 (file)
@@ -454,7 +454,17 @@ TEXT runtime路sigtramp(SB),NOSPLIT|TOPFRAME,$28
 TEXT runtime路cgoSigtramp(SB),NOSPLIT,$0
        JMP     runtime路sigtramp(SB)
 
-TEXT runtime路sigreturn(SB),NOSPLIT,$0
+// For cgo unwinding to work, this function must look precisely like
+// the one in glibc. The glibc source code is:
+// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/i386/libc_sigaction.c;h=0665b41bbcd0986f0b33bf19a7ecbcedf9961d0a#l59
+// The code that cares about the precise instructions used is:
+// https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/config/i386/linux-unwind.h;h=5486223d60272c73d5103b29ae592d2ee998e1cf#l136
+//
+// For gdb unwinding to work, this function must look precisely like the one in
+// glibc and must be named "__restore_rt" or contain the string "sigaction" in
+// the name. The gdb source code is:
+// https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/i386-linux-tdep.c;h=a6adeca1b97416f7194341151a8ce30723a786a3#l168
+TEXT runtime路sigreturn__sigaction(SB),NOSPLIT,$0
        MOVL    $SYS_rt_sigreturn, AX
        // Sigreturn expects same SP as signal handler,
        // so cannot CALL 0x10(GS) here.
index db6d1cbbb7d386876d364bb380381da6af42329d..b6c64dc0959c6575ecfbcaa16203ac829005c532 100644 (file)
@@ -458,11 +458,16 @@ sigtrampnog:
        JMP     AX
 
 // For cgo unwinding to work, this function must look precisely like
-// the one in glibc.  The glibc source code is:
-// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/sigaction.c
+// the one in glibc. The glibc source code is:
+// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c;h=afdce87381228f0cf32fa9fa6c8c4efa5179065c#l80
 // The code that cares about the precise instructions used is:
-// https://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/i386/linux-unwind.h?revision=219188&view=markup
-TEXT runtime路sigreturn(SB),NOSPLIT,$0
+// https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/config/i386/linux-unwind.h;h=5486223d60272c73d5103b29ae592d2ee998e1cf#l49
+//
+// For gdb unwinding to work, this function must look precisely like the one in
+// glibc and must be named "__restore_rt" or contain the string "sigaction" in
+// the name. The gdb source code is:
+// https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/amd64-linux-tdep.c;h=cbbac1a0c64e1deb8181b9d0ff6404e328e2979d#l178
+TEXT runtime路sigreturn__sigaction(SB),NOSPLIT,$0
        MOVQ    $SYS_rt_sigreturn, AX
        SYSCALL
        INT $3  // not reached
index 7b8c4f0e047a4abf53e273c9d8db514822457b42..992d32ab6c9b586a1e43f0b9953a8e8f58238621 100644 (file)
@@ -650,6 +650,3 @@ TEXT runtime路sbrk0(SB),NOSPLIT,$0-4
        SWI     $0
        MOVW    R0, ret+0(FP)
        RET
-
-TEXT runtime路sigreturn(SB),NOSPLIT,$0-0
-       RET
index aa8d6ca619958a217289854ac0540c5e0988ae54..51c87bea05a24efd019a4d1ab3d11cfcd76e3fc8 100644 (file)
@@ -785,6 +785,3 @@ TEXT runtime路sbrk0(SB),NOSPLIT,$0-8
        SVC
        MOVD    R0, ret+0(FP)
        RET
-
-TEXT runtime路sigreturn(SB),NOSPLIT,$0-0
-       RET
index f293442de99ce6c433f2d164416fe61da6316167..ec9b966c8c3c80594f38148bc2cc2224f5ae2f32 100644 (file)
@@ -447,9 +447,6 @@ TEXT runtime路sigfwd(SB),NOSPLIT,$0-32
        MOVD    24(R1), R2
        RET
 
-TEXT runtime路sigreturn(SB),NOSPLIT,$0-0
-       RET
-
 #ifdef GOARCH_ppc64le
 // ppc64le doesn't need function descriptors
 // Save callee-save registers in the case of signal forwarding.
index 1448670b913b9176040eb4fbcb4754d41d7a88b2..adf5612c3cfbee29ad531f8027f5ff1ddffa10cb 100644 (file)
@@ -412,9 +412,6 @@ TEXT runtime路sigfwd(SB),NOSPLIT,$0-32
        BL      R5
        RET
 
-TEXT runtime路sigreturn(SB),NOSPLIT,$0-0
-       RET
-
 TEXT runtime路sigtramp(SB),NOSPLIT|TOPFRAME,$64
        // initialize essential registers (just in case)
        XOR     R0, R0