Skip to content

Commit 5c8fbc6

Browse files
spetrovic77ianlancetaylor
authored andcommitted
runtime: signal forwarding
Forward signals to signal handlers installed before Go installs its own, under certain circumstances. In particular, as iant@ suggests, signals are forwarded iff: (1) a non-SIG_DFL signal handler existed before Go, and (2) signal is synchronous (i.e., one of SIGSEGV, SIGBUS, SIGFPE), and (3a) signal occured on a non-Go thread, or (3b) signal occurred on a Go thread but in CGo code. Supported only on Linux, for now. Change-Id: I403219ee47b26cf65da819fb86cf1ec04d3e25f5 Reviewed-on: https://go-review.googlesource.com/8712 Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent b075d1f commit 5c8fbc6

File tree

10 files changed

+180
-130
lines changed

10 files changed

+180
-130
lines changed

misc/cgo/testsigfwd/main.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2015 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+
package main
6+
7+
import "fmt"
8+
9+
/*
10+
#include <signal.h>
11+
#include <stdlib.h>
12+
#include <stdio.h>
13+
14+
int *p;
15+
static void sigsegv() {
16+
*p = 1;
17+
fprintf(stderr, "ERROR: C SIGSEGV not thrown on caught?.\n");
18+
exit(2);
19+
}
20+
21+
static void sighandler(int signum) {
22+
if (signum == SIGSEGV) {
23+
exit(0); // success
24+
}
25+
}
26+
27+
static void __attribute__ ((constructor)) sigsetup(void) {
28+
struct sigaction act;
29+
act.sa_handler = &sighandler;
30+
sigaction(SIGSEGV, &act, 0);
31+
}
32+
*/
33+
import "C"
34+
35+
var p *byte
36+
37+
func f() (ret bool) {
38+
defer func() {
39+
if recover() == nil {
40+
fmt.Errorf("ERROR: couldn't raise SIGSEGV in Go.")
41+
C.exit(2)
42+
}
43+
ret = true
44+
}()
45+
*p = 1
46+
return false
47+
}
48+
49+
func main() {
50+
// Test that the signal originating in Go is handled (and recovered) by Go.
51+
if !f() {
52+
fmt.Errorf("couldn't recover from SIGSEGV in Go.")
53+
C.exit(2)
54+
}
55+
56+
// Test that the signal originating in C is handled by C.
57+
C.sigsegv()
58+
}

src/cmd/dist/test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ func (t *tester) registerTests() {
284284
if t.hasBash() && t.goos != "android" && !iOS && t.gohostos != "windows" {
285285
t.registerTest("cgo_errors", "../misc/cgo/errors", "./test.bash")
286286
}
287+
if t.gohostos == "linux" && t.extLink() {
288+
t.registerTest("testsigfwd", "../misc/cgo/testsigfwd", "go", "run", "main.go")
289+
}
287290
}
288291
if t.hasBash() && t.goos != "nacl" && t.goos != "android" && !iOS {
289292
t.registerTest("doc_progs", "../doc/progs", "time", "go", "run", "run.go")

src/runtime/os_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ func rt_sigaction(sig uintptr, new, old *sigactiont, size uintptr) int32
1818
//go:noescape
1919
func sigaltstack(new, old *sigaltstackt)
2020

21+
//go:noescape
22+
func sigfwd(fn uintptr, sig uint32, info *siginfo, ctx unsafe.Pointer)
23+
2124
//go:noescape
2225
func setitimer(mode int32, new, old *itimerval)
2326

src/runtime/signal1_unix.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ const (
1111
_SIG_IGN uintptr = 1
1212
)
1313

14+
// Stores the signal handlers registered before Go installed its own.
15+
// These signal handlers will be invoked in cases where Go doesn't want to
16+
// handle a particular signal (e.g., signal occurred on a non-Go thread).
17+
// See sigfwdgo() for more information on when the signals are forwarded.
18+
//
19+
// Signal forwarding is currently available only on Linux.
20+
var fwdSig [_NSIG]uintptr
21+
1422
func initsig() {
1523
// _NSIG is the number of signals on this operating system.
1624
// sigtable should describe what to do for all the possible signals.
@@ -25,7 +33,7 @@ func initsig() {
2533
if t.flags == 0 || t.flags&_SigDefault != 0 {
2634
continue
2735
}
28-
36+
fwdSig[i] = getsig(i)
2937
// For some signals, we respect an inherited SIG_IGN handler
3038
// rather than insist on installing our own default handler.
3139
// Even these signals can be fetched using the os/signal package.

src/runtime/signal_linux.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package runtime
66

7+
import "unsafe"
8+
79
type sigTabT struct {
810
flags int32
911
name string
@@ -76,3 +78,53 @@ var sigtable = [...]sigTabT{
7678
/* 63 */ {_SigNotify, "signal 63"},
7779
/* 64 */ {_SigNotify, "signal 64"},
7880
}
81+
82+
// Determines if the signal should be handled by Go and if not, forwards the
83+
// signal to the handler that was installed before Go's. Returns whether the
84+
// signal was forwarded.
85+
//go:nosplit
86+
func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool {
87+
g := getg()
88+
c := &sigctxt{info, ctx}
89+
if sig >= uint32(len(sigtable)) {
90+
return false
91+
}
92+
fwdFn := fwdSig[sig]
93+
flags := sigtable[sig].flags
94+
95+
// If there is no handler to forward to, no need to forward.
96+
if fwdFn == _SIG_DFL {
97+
return false
98+
}
99+
// Only forward synchronous signals.
100+
if c.sigcode() == _SI_USER || flags&_SigPanic == 0 {
101+
return false
102+
}
103+
// Determine if the signal occurred inside Go code. We test that:
104+
// (1) we were in a goroutine (i.e., m.curg != nil), and
105+
// (2) we weren't in CGO (i.e., m.curg.syscallsp == 0).
106+
if g != nil && g.m != nil && g.m.curg != nil && g.m.curg.syscallsp == 0 {
107+
return false
108+
}
109+
// Signal not handled by Go, forward it.
110+
if fwdFn != _SIG_IGN {
111+
sigfwd(fwdFn, sig, info, ctx)
112+
}
113+
return true
114+
}
115+
116+
// Continuation of the (assembly) sigtramp() logic.
117+
//go:nosplit
118+
func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) {
119+
if sigfwdgo(sig, info, ctx) {
120+
return
121+
}
122+
g := getg()
123+
if g == nil {
124+
badsignal(uintptr(sig))
125+
return
126+
}
127+
setg(g.m.gsignal)
128+
sighandler(sig, info, ctx, g)
129+
setg(g)
130+
}

src/runtime/sys_linux_386.s

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -191,43 +191,25 @@ TEXT runtime·rt_sigaction(SB),NOSPLIT,$0
191191
MOVL AX, ret+16(FP)
192192
RET
193193

194-
TEXT runtime·sigtramp(SB),NOSPLIT,$44
195-
get_tls(CX)
196-
197-
// check that g exists
198-
MOVL g(CX), DI
199-
CMPL DI, $0
200-
JNE 6(PC)
201-
MOVL sig+0(FP), BX
202-
MOVL BX, 0(SP)
203-
MOVL $runtime·badsignal(SB), AX
194+
TEXT runtime·sigfwd(SB),NOSPLIT,$12-16
195+
MOVL sig+4(FP), AX
196+
MOVL AX, 0(SP)
197+
MOVL info+8(FP), AX
198+
MOVL AX, 4(SP)
199+
MOVL ctx+12(FP), AX
200+
MOVL AX, 8(SP)
201+
MOVL fn+0(FP), AX
204202
CALL AX
205203
RET
206204

207-
// save g
208-
MOVL DI, 20(SP)
209-
210-
// g = m->gsignal
211-
MOVL g_m(DI), BX
212-
MOVL m_gsignal(BX), BX
213-
MOVL BX, g(CX)
214-
215-
// copy arguments for call to sighandler
205+
TEXT runtime·sigtramp(SB),NOSPLIT,$12
216206
MOVL sig+0(FP), BX
217207
MOVL BX, 0(SP)
218208
MOVL info+4(FP), BX
219209
MOVL BX, 4(SP)
220210
MOVL context+8(FP), BX
221211
MOVL BX, 8(SP)
222-
MOVL DI, 12(SP)
223-
224-
CALL runtime·sighandler(SB)
225-
226-
// restore g
227-
get_tls(CX)
228-
MOVL 20(SP), BX
229-
MOVL BX, g(CX)
230-
212+
CALL runtime·sigtrampgo(SB)
231213
RET
232214

233215
TEXT runtime·sigreturn(SB),NOSPLIT,$0

src/runtime/sys_linux_amd64.s

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -212,37 +212,20 @@ TEXT runtime·rt_sigaction(SB),NOSPLIT,$0-36
212212
MOVL AX, ret+32(FP)
213213
RET
214214

215-
TEXT runtime·sigtramp(SB),NOSPLIT,$64
216-
get_tls(BX)
217-
218-
// check that g exists
219-
MOVQ g(BX), R10
220-
CMPQ R10, $0
221-
JNE 5(PC)
222-
MOVQ DI, 0(SP)
223-
MOVQ $runtime·badsignal(SB), AX
215+
TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
216+
MOVQ sig+8(FP), DI
217+
MOVQ info+16(FP), SI
218+
MOVQ ctx+24(FP), DX
219+
MOVQ fn+0(FP), AX
224220
CALL AX
225221
RET
226222

227-
// save g
228-
MOVQ R10, 40(SP)
229-
230-
// g = m->gsignal
231-
MOVQ g_m(R10), AX
232-
MOVQ m_gsignal(AX), AX
233-
MOVQ AX, g(BX)
234-
235-
MOVQ DI, 0(SP)
236-
MOVQ SI, 8(SP)
237-
MOVQ DX, 16(SP)
238-
MOVQ R10, 24(SP)
239-
240-
CALL runtime·sighandler(SB)
241-
242-
// restore g
243-
get_tls(BX)
244-
MOVQ 40(SP), R10
245-
MOVQ R10, g(BX)
223+
TEXT runtime·sigtramp(SB),NOSPLIT,$24
224+
MOVQ DI, 0(SP) // signum
225+
MOVQ SI, 8(SP) // info
226+
MOVQ DX, 16(SP) // ctx
227+
MOVQ $runtime·sigtrampgo(SB), AX
228+
CALL AX
246229
RET
247230

248231
TEXT runtime·sigreturn(SB),NOSPLIT,$0

src/runtime/sys_linux_arm.s

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,15 @@ TEXT runtime·sigaltstack(SB),NOSPLIT,$0
327327
MOVW.HI R8, (R8)
328328
RET
329329

330-
TEXT runtime·sigtramp(SB),NOSPLIT,$24
330+
TEXT runtime·sigfwd(SB),NOSPLIT,$0-16
331+
MOVW sig+4(FP), R0
332+
MOVW info+8(FP), R1
333+
MOVW ctx+12(FP), R2
334+
MOVW fn+0(FP), R11
335+
BL (R11)
336+
RET
337+
338+
TEXT runtime·sigtramp(SB),NOSPLIT,$12
331339
// this might be called in external code context,
332340
// where g is not set.
333341
// first save R0, because runtime·load_g will clobber it
@@ -336,32 +344,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$24
336344
CMP $0, R0
337345
BL.NE runtime·load_g(SB)
338346

339-
CMP $0, g
340-
BNE 4(PC)
341-
// signal number is already prepared in 4(R13)
342-
MOVW $runtime·badsignal(SB), R11
343-
BL (R11)
344-
RET
345-
346-
// save g
347-
MOVW g, R3
348-
MOVW g, 20(R13)
349-
350-
// g = m->gsignal
351-
MOVW g_m(g), R8
352-
MOVW m_gsignal(R8), g
353-
354-
// copy arguments for call to sighandler
355-
// R0 is already saved above
356347
MOVW R1, 8(R13)
357348
MOVW R2, 12(R13)
358-
MOVW R3, 16(R13)
359-
360-
BL runtime·sighandler(SB)
361-
362-
// restore g
363-
MOVW 20(R13), g
364-
349+
MOVW $runtime·sigtrampgo(SB), R11
350+
BL (R11)
365351
RET
366352

367353
TEXT runtime·rtsigprocmask(SB),NOSPLIT,$0

src/runtime/sys_linux_arm64.s

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,15 @@ TEXT runtime·rt_sigaction(SB),NOSPLIT,$-8-36
215215
MOVW R0, ret+32(FP)
216216
RET
217217

218-
TEXT runtime·sigtramp(SB),NOSPLIT,$64
218+
TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
219+
MOVW sig+8(FP), R0
220+
MOVD info+16(FP), R1
221+
MOVD ctx+24(FP), R2
222+
MOVD fn+0(FP), R11
223+
BL (R11)
224+
RET
225+
226+
TEXT runtime·sigtramp(SB),NOSPLIT,$24
219227
// this might be called in external code context,
220228
// where g is not set.
221229
// first save R0, because runtime·load_g will clobber it
@@ -225,31 +233,10 @@ TEXT runtime·sigtramp(SB),NOSPLIT,$64
225233
BEQ 2(PC)
226234
BL runtime·load_g(SB)
227235

228-
// check that g exists
229-
CMP g, ZR
230-
BNE ok
231-
MOVD $runtime·badsignal(SB), R0
232-
BL (R0)
233-
RET
234-
235-
ok:
236-
// save g
237-
MOVD g, 40(RSP)
238-
MOVD g, R6
239-
240-
// g = m->gsignal
241-
MOVD g_m(g), R7
242-
MOVD m_gsignal(R7), g
243-
244-
// R0 is already saved above
245236
MOVD R1, 16(RSP)
246237
MOVD R2, 24(RSP)
247-
MOVD R6, 32(RSP)
248-
249-
BL runtime·sighandler(SB)
250-
251-
// restore g
252-
MOVD 40(RSP), g
238+
MOVD $runtime·sigtrampgo(SB), R0
239+
BL (R0)
253240
RET
254241

255242
TEXT runtime·mmap(SB),NOSPLIT,$-8

0 commit comments

Comments
 (0)