Skip to content

Commit 0ed364a

Browse files
prattmicgopherbot
authored andcommitted
runtime: add "sigaction" to sigreturn symbol name
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 <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Cherry Mui <[email protected]> Run-TryBot: Michael Pratt <[email protected]>
1 parent 5f882d8 commit 0ed364a

8 files changed

+229
-19
lines changed

src/runtime/os_linux.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ func mdestroy(mp *m) {
424424
//#define sa_handler k_sa_handler
425425
//#endif
426426

427-
func sigreturn()
427+
func sigreturn__sigaction()
428428
func sigtramp() // Called via C ABI
429429
func cgoSigtramp()
430430

@@ -481,7 +481,7 @@ func setsig(i uint32, fn uintptr) {
481481
// should not be used". x86_64 kernel requires it. Only use it on
482482
// x86.
483483
if GOARCH == "386" || GOARCH == "amd64" {
484-
sa.sa_restorer = abi.FuncPCABI0(sigreturn)
484+
sa.sa_restorer = abi.FuncPCABI0(sigreturn__sigaction)
485485
}
486486
if fn == abi.FuncPCABIInternal(sighandler) { // abi.FuncPCABIInternal(sighandler) matches the callers in signal_unix.go
487487
if iscgo {

src/runtime/runtime-gdb_unix_test.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
74+
checkGdbEnvironment(t)
75+
t.Parallel()
76+
checkGdbVersion(t)
77+
78+
// Ensure there is enough RLIMIT_CORE available to generate a full core.
79+
var lim syscall.Rlimit
80+
err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
81+
if err != nil {
82+
t.Fatalf("error getting rlimit: %v", err)
83+
}
84+
// Minimum RLIMIT_CORE max to allow. This is a conservative estimate.
85+
// Most systems allow infinity.
86+
const minRlimitCore = 100 << 20 // 100 MB
87+
if lim.Max < minRlimitCore {
88+
t.Skipf("RLIMIT_CORE max too low: %#+v", lim)
89+
}
90+
91+
// Make sure core pattern will send core to the current directory.
92+
b, err := os.ReadFile("/proc/sys/kernel/core_pattern")
93+
if err != nil {
94+
t.Fatalf("error reading core_pattern: %v", err)
95+
}
96+
if string(b) != "core\n" {
97+
t.Skipf("Unexpected core pattern %q", string(b))
98+
}
99+
100+
dir := t.TempDir()
101+
102+
// Build the source code.
103+
src := filepath.Join(dir, "main.go")
104+
err = os.WriteFile(src, []byte(coreSignalSource), 0644)
105+
if err != nil {
106+
t.Fatalf("failed to create file: %v", err)
107+
}
108+
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "a.exe", "main.go")
109+
cmd.Dir = dir
110+
out, err := testenv.CleanCmdEnv(cmd).CombinedOutput()
111+
if err != nil {
112+
t.Fatalf("building source %v\n%s", err, out)
113+
}
114+
115+
r, w, err := os.Pipe()
116+
if err != nil {
117+
t.Fatalf("error creating control pipe: %v", err)
118+
}
119+
defer r.Close()
120+
121+
// Start the test binary.
122+
cmd = testenv.Command(t, "./a.exe", "-pipe-fd=3")
123+
cmd.Dir = dir
124+
cmd.ExtraFiles = []*os.File{w}
125+
var output bytes.Buffer
126+
cmd.Stdout = &output // for test logging
127+
cmd.Stderr = &output
128+
129+
if err := cmd.Start(); err != nil {
130+
t.Fatalf("error starting test binary: %v", err)
131+
}
132+
w.Close()
133+
134+
// Wait for child to be ready.
135+
var buf [1]byte
136+
if _, err := r.Read(buf[:]); err != io.EOF {
137+
t.Fatalf("control pipe read get err %v want io.EOF", err)
138+
}
139+
140+
// 💥
141+
if err := cmd.Process.Signal(os.Signal(syscall.SIGABRT)); err != nil {
142+
t.Fatalf("erroring signaling child: %v", err)
143+
}
144+
145+
err = cmd.Wait()
146+
t.Logf("child output:\n%s", output.String())
147+
if err == nil {
148+
t.Fatalf("Wait succeeded, want SIGABRT")
149+
}
150+
ee, ok := err.(*exec.ExitError)
151+
if !ok {
152+
t.Fatalf("Wait err got %T %v, want exec.ExitError", ee, ee)
153+
}
154+
ws, ok := ee.Sys().(syscall.WaitStatus)
155+
if !ok {
156+
t.Fatalf("Sys got %T %v, want syscall.WaitStatus", ee.Sys(), ee.Sys())
157+
}
158+
if ws.Signal() != syscall.SIGABRT {
159+
t.Fatalf("Signal got %d want SIGABRT", ws.Signal())
160+
}
161+
if !ws.CoreDump() {
162+
t.Fatalf("CoreDump got %v want true", ws.CoreDump())
163+
}
164+
165+
// Execute gdb commands.
166+
args := []string{"-nx", "-batch",
167+
"-iex", "add-auto-load-safe-path " + filepath.Join(testenv.GOROOT(t), "src", "runtime"),
168+
"-ex", "backtrace",
169+
filepath.Join(dir, "a.exe"),
170+
filepath.Join(dir, "core"),
171+
}
172+
cmd = testenv.Command(t, "gdb", args...)
173+
174+
got, err := cmd.CombinedOutput()
175+
t.Logf("gdb output:\n%s", got)
176+
if err != nil {
177+
t.Fatalf("gdb exited with error: %v", err)
178+
}
179+
180+
// We don't know which thread the fatal signal will land on, but we can still check for basics:
181+
//
182+
// 1. A frame in the signal handler: runtime.sigtramp
183+
// 2. GDB detection of the signal handler: <signal handler called>
184+
// 3. A frame before the signal handler: this could be foo, or somewhere in the scheduler
185+
186+
re := regexp.MustCompile(`#.* runtime\.sigtramp `)
187+
if found := re.Find(got) != nil; !found {
188+
t.Fatalf("could not find sigtramp in backtrace")
189+
}
190+
191+
re = regexp.MustCompile("#.* <signal handler called>")
192+
loc := re.FindIndex(got)
193+
if loc == nil {
194+
t.Fatalf("could not find signal handler marker in backtrace")
195+
}
196+
rest := got[loc[1]:]
197+
198+
// Look for any frames after the signal handler. We want to see
199+
// symbolized frames, not garbage unknown frames.
200+
//
201+
// Since the signal might not be delivered to the main thread we can't
202+
// look for main.main. Every thread should have a runtime frame though.
203+
re = regexp.MustCompile(`#.* runtime\.`)
204+
if found := re.Find(rest) != nil; !found {
205+
t.Fatalf("could not find runtime symbol in backtrace after signal handler:\n%s", rest)
206+
}
207+
}

src/runtime/sys_linux_386.s

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,17 @@ TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME,$28
454454
TEXT runtime·cgoSigtramp(SB),NOSPLIT,$0
455455
JMP runtime·sigtramp(SB)
456456

457-
TEXT runtime·sigreturn(SB),NOSPLIT,$0
457+
// For cgo unwinding to work, this function must look precisely like
458+
// the one in glibc. The glibc source code is:
459+
// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/i386/libc_sigaction.c;h=0665b41bbcd0986f0b33bf19a7ecbcedf9961d0a#l59
460+
// The code that cares about the precise instructions used is:
461+
// https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/config/i386/linux-unwind.h;h=5486223d60272c73d5103b29ae592d2ee998e1cf#l136
462+
//
463+
// For gdb unwinding to work, this function must look precisely like the one in
464+
// glibc and must be named "__restore_rt" or contain the string "sigaction" in
465+
// the name. The gdb source code is:
466+
// https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/i386-linux-tdep.c;h=a6adeca1b97416f7194341151a8ce30723a786a3#l168
467+
TEXT runtime·sigreturn__sigaction(SB),NOSPLIT,$0
458468
MOVL $SYS_rt_sigreturn, AX
459469
// Sigreturn expects same SP as signal handler,
460470
// so cannot CALL 0x10(GS) here.

src/runtime/sys_linux_amd64.s

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,16 @@ sigtrampnog:
458458
JMP AX
459459

460460
// For cgo unwinding to work, this function must look precisely like
461-
// the one in glibc. The glibc source code is:
462-
// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/sigaction.c
461+
// the one in glibc. The glibc source code is:
462+
// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c;h=afdce87381228f0cf32fa9fa6c8c4efa5179065c#l80
463463
// The code that cares about the precise instructions used is:
464-
// https://gcc.gnu.org/viewcvs/gcc/trunk/libgcc/config/i386/linux-unwind.h?revision=219188&view=markup
465-
TEXT runtime·sigreturn(SB),NOSPLIT,$0
464+
// https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/config/i386/linux-unwind.h;h=5486223d60272c73d5103b29ae592d2ee998e1cf#l49
465+
//
466+
// For gdb unwinding to work, this function must look precisely like the one in
467+
// glibc and must be named "__restore_rt" or contain the string "sigaction" in
468+
// the name. The gdb source code is:
469+
// https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/amd64-linux-tdep.c;h=cbbac1a0c64e1deb8181b9d0ff6404e328e2979d#l178
470+
TEXT runtime·sigreturn__sigaction(SB),NOSPLIT,$0
466471
MOVQ $SYS_rt_sigreturn, AX
467472
SYSCALL
468473
INT $3 // not reached

src/runtime/sys_linux_arm.s

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,3 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-4
650650
SWI $0
651651
MOVW R0, ret+0(FP)
652652
RET
653-
654-
TEXT runtime·sigreturn(SB),NOSPLIT,$0-0
655-
RET

src/runtime/sys_linux_arm64.s

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,3 @@ TEXT runtime·sbrk0(SB),NOSPLIT,$0-8
785785
SVC
786786
MOVD R0, ret+0(FP)
787787
RET
788-
789-
TEXT runtime·sigreturn(SB),NOSPLIT,$0-0
790-
RET

src/runtime/sys_linux_ppc64x.s

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,9 +447,6 @@ TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
447447
MOVD 24(R1), R2
448448
RET
449449

450-
TEXT runtime·sigreturn(SB),NOSPLIT,$0-0
451-
RET
452-
453450
#ifdef GOARCH_ppc64le
454451
// ppc64le doesn't need function descriptors
455452
// Save callee-save registers in the case of signal forwarding.

src/runtime/sys_linux_s390x.s

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,9 +412,6 @@ TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
412412
BL R5
413413
RET
414414

415-
TEXT runtime·sigreturn(SB),NOSPLIT,$0-0
416-
RET
417-
418415
TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME,$64
419416
// initialize essential registers (just in case)
420417
XOR R0, R0

0 commit comments

Comments
 (0)