Skip to content

Commit a7cad52

Browse files
runtime: preserve signal stack when calling Go on C thread
When calling a Go function on a C thread, if the C thread already has an alternate signal stack, use that signal stack instead of installing a new one. Update #9896. Change-Id: I62aa3a6a4a1dc4040fca050757299c8e6736987c Reviewed-on: https://go-review.googlesource.com/18108 Reviewed-by: Russ Cox <[email protected]>
1 parent 62c280a commit a7cad52

File tree

11 files changed

+383
-14
lines changed

11 files changed

+383
-14
lines changed

misc/cgo/testcarchive/main4.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
// Test a C thread that calls sigaltstack and then calls Go code.
6+
7+
#include <signal.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <string.h>
11+
#include <sched.h>
12+
#include <pthread.h>
13+
14+
#include "libgo4.h"
15+
16+
static void die(const char* msg) {
17+
perror(msg);
18+
exit(EXIT_FAILURE);
19+
}
20+
21+
static int ok = 1;
22+
23+
static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
24+
}
25+
26+
// Set up the SIGIO signal handler in a high priority constructor, so
27+
// that it is installed before the Go code starts.
28+
29+
static void init(void) __attribute__ ((constructor (200)));
30+
31+
static void init() {
32+
struct sigaction sa;
33+
34+
memset(&sa, 0, sizeof sa);
35+
sa.sa_sigaction = ioHandler;
36+
if (sigemptyset(&sa.sa_mask) < 0) {
37+
die("sigemptyset");
38+
}
39+
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
40+
if (sigaction(SIGIO, &sa, NULL) < 0) {
41+
die("sigaction");
42+
}
43+
}
44+
45+
// Test raising SIGIO on a C thread with an alternate signal stack
46+
// when there is a Go signal handler for SIGIO.
47+
static void* thread1(void* arg) {
48+
pthread_t* ptid = (pthread_t*)(arg);
49+
stack_t ss;
50+
int i;
51+
stack_t nss;
52+
53+
// Set up an alternate signal stack for this thread.
54+
memset(&ss, 0, sizeof ss);
55+
ss.ss_sp = malloc(SIGSTKSZ);
56+
if (ss.ss_sp == NULL) {
57+
die("malloc");
58+
}
59+
ss.ss_flags = 0;
60+
ss.ss_size = SIGSTKSZ;
61+
if (sigaltstack(&ss, NULL) < 0) {
62+
die("sigaltstack");
63+
}
64+
65+
// Send ourselves a SIGIO. This will be caught by the Go
66+
// signal handler which should forward to the C signal
67+
// handler.
68+
i = pthread_kill(*ptid, SIGIO);
69+
if (i != 0) {
70+
fprintf(stderr, "pthread_kill: %s\n", strerror(i));
71+
exit(EXIT_FAILURE);
72+
}
73+
74+
// Wait until the signal has been delivered.
75+
i = 0;
76+
while (SIGIOCount() == 0) {
77+
if (sched_yield() < 0) {
78+
perror("sched_yield");
79+
}
80+
i++;
81+
if (i > 10000) {
82+
fprintf(stderr, "looping too long waiting for signal\n");
83+
exit(EXIT_FAILURE);
84+
}
85+
}
86+
87+
// We should still be on the same signal stack.
88+
if (sigaltstack(NULL, &nss) < 0) {
89+
die("sigaltstack check");
90+
}
91+
if ((nss.ss_flags & SS_DISABLE) != 0) {
92+
fprintf(stderr, "sigaltstack disabled on return from Go\n");
93+
ok = 0;
94+
} else if (nss.ss_sp != ss.ss_sp) {
95+
fprintf(stderr, "sigalstack changed on return from Go\n");
96+
ok = 0;
97+
}
98+
99+
return NULL;
100+
}
101+
102+
// Test calling a Go function to raise SIGIO on a C thread with an
103+
// alternate signal stack when there is a Go signal handler for SIGIO.
104+
static void* thread2(void* arg) {
105+
pthread_t* ptid = (pthread_t*)(arg);
106+
stack_t ss;
107+
int i;
108+
int oldcount;
109+
stack_t nss;
110+
111+
// Set up an alternate signal stack for this thread.
112+
memset(&ss, 0, sizeof ss);
113+
ss.ss_sp = malloc(SIGSTKSZ);
114+
if (ss.ss_sp == NULL) {
115+
die("malloc");
116+
}
117+
ss.ss_flags = 0;
118+
ss.ss_size = SIGSTKSZ;
119+
if (sigaltstack(&ss, NULL) < 0) {
120+
die("sigaltstack");
121+
}
122+
123+
oldcount = SIGIOCount();
124+
125+
// Call a Go function that will call a C function to send us a
126+
// SIGIO.
127+
GoRaiseSIGIO(ptid);
128+
129+
// Wait until the signal has been delivered.
130+
i = 0;
131+
while (SIGIOCount() == oldcount) {
132+
if (sched_yield() < 0) {
133+
perror("sched_yield");
134+
}
135+
i++;
136+
if (i > 10000) {
137+
fprintf(stderr, "looping too long waiting for signal\n");
138+
exit(EXIT_FAILURE);
139+
}
140+
}
141+
142+
// We should still be on the same signal stack.
143+
if (sigaltstack(NULL, &nss) < 0) {
144+
die("sigaltstack check");
145+
}
146+
if ((nss.ss_flags & SS_DISABLE) != 0) {
147+
fprintf(stderr, "sigaltstack disabled on return from Go\n");
148+
ok = 0;
149+
} else if (nss.ss_sp != ss.ss_sp) {
150+
fprintf(stderr, "sigalstack changed on return from Go\n");
151+
ok = 0;
152+
}
153+
154+
return NULL;
155+
}
156+
157+
int main(int argc, char **argv) {
158+
pthread_t tid;
159+
int i;
160+
161+
// Tell the Go library to start looking for SIGIO.
162+
GoCatchSIGIO();
163+
164+
i = pthread_create(&tid, NULL, thread1, (void*)(&tid));
165+
if (i != 0) {
166+
fprintf(stderr, "pthread_create: %s\n", strerror(i));
167+
exit(EXIT_FAILURE);
168+
}
169+
170+
i = pthread_join(tid, NULL);
171+
if (i != 0) {
172+
fprintf(stderr, "pthread_join: %s\n", strerror(i));
173+
exit(EXIT_FAILURE);
174+
}
175+
176+
i = pthread_create(&tid, NULL, thread2, (void*)(&tid));
177+
if (i != 0) {
178+
fprintf(stderr, "pthread_create: %s\n", strerror(i));
179+
exit(EXIT_FAILURE);
180+
}
181+
182+
i = pthread_join(tid, NULL);
183+
if (i != 0) {
184+
fprintf(stderr, "pthread_join: %s\n", strerror(i));
185+
exit(EXIT_FAILURE);
186+
}
187+
188+
if (!ok) {
189+
exit(EXIT_FAILURE);
190+
}
191+
192+
printf("PASS\n");
193+
return 0;
194+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
/*
8+
#include <signal.h>
9+
#include <pthread.h>
10+
11+
// Raise SIGIO.
12+
static void CRaiseSIGIO(pthread_t* p) {
13+
pthread_kill(*p, SIGIO);
14+
}
15+
*/
16+
import "C"
17+
18+
import (
19+
"os"
20+
"os/signal"
21+
"sync/atomic"
22+
"syscall"
23+
)
24+
25+
var sigioCount int32
26+
27+
// Catch SIGIO.
28+
//export GoCatchSIGIO
29+
func GoCatchSIGIO() {
30+
c := make(chan os.Signal, 1)
31+
signal.Notify(c, syscall.SIGIO)
32+
go func() {
33+
for range c {
34+
atomic.AddInt32(&sigioCount, 1)
35+
}
36+
}()
37+
}
38+
39+
// Raise SIGIO.
40+
//export GoRaiseSIGIO
41+
func GoRaiseSIGIO(p *C.pthread_t) {
42+
C.CRaiseSIGIO(p)
43+
}
44+
45+
// Return the number of SIGIO signals seen.
46+
//export SIGIOCount
47+
func SIGIOCount() C.int {
48+
return C.int(atomic.LoadInt32(&sigioCount))
49+
}
50+
51+
func main() {
52+
}

misc/cgo/testcarchive/test.bash

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,12 @@ if ! $bin; then
7777
fi
7878
rm -rf libgo3.a libgo3.h testp pkg
7979

80+
GOPATH=$(pwd) go build -buildmode=c-archive -o libgo4.a libgo4
81+
$(go env CC) $(go env GOGCCFLAGS) $ccargs -o testp main4.c libgo4.a
82+
if ! $bin; then
83+
echo "FAIL test4"
84+
status=1
85+
fi
86+
rm -rf libgo4.a libgo4.h testp pkg
87+
8088
exit $status

src/runtime/os1_darwin.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,22 @@ func sigblock() {
152152
func minit() {
153153
// Initialize signal handling.
154154
_g_ := getg()
155-
signalstack(&_g_.m.gsignal.stack)
155+
156+
var st stackt
157+
sigaltstack(nil, &st)
158+
if st.ss_flags&_SS_DISABLE != 0 {
159+
signalstack(&_g_.m.gsignal.stack)
160+
_g_.m.newSigstack = true
161+
} else {
162+
// Use existing signal stack.
163+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
164+
_g_.m.gsignal.stack.lo = stsp
165+
_g_.m.gsignal.stack.hi = stsp + st.ss_size
166+
_g_.m.gsignal.stackguard0 = stsp + _StackGuard
167+
_g_.m.gsignal.stackguard1 = stsp + _StackGuard
168+
_g_.m.gsignal.stackAlloc = st.ss_size
169+
_g_.m.newSigstack = false
170+
}
156171

157172
// restore signal mask from m.sigmask and unblock essential signals
158173
nmask := _g_.m.sigmask
@@ -167,7 +182,9 @@ func minit() {
167182
// Called from dropm to undo the effect of an minit.
168183
//go:nosplit
169184
func unminit() {
170-
signalstack(nil)
185+
if getg().m.newSigstack {
186+
signalstack(nil)
187+
}
171188
}
172189

173190
// Mach IPC, to get at semaphores

src/runtime/os1_dragonfly.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,21 @@ func minit() {
141141
_g_.m.procid = uint64(*(*int32)(unsafe.Pointer(&_g_.m.procid)))
142142

143143
// Initialize signal handling
144-
signalstack(&_g_.m.gsignal.stack)
144+
var st sigaltstackt
145+
sigaltstack(nil, &st)
146+
if st.ss_flags&_SS_DISABLE != 0 {
147+
signalstack(&_g_.m.gsignal.stack)
148+
_g_.m.newSigstack = true
149+
} else {
150+
// Use existing signal stack.
151+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
152+
_g_.m.gsignal.stack.lo = stsp
153+
_g_.m.gsignal.stack.hi = stsp + st.ss_size
154+
_g_.m.gsignal.stackguard0 = stsp + _StackGuard
155+
_g_.m.gsignal.stackguard1 = stsp + _StackGuard
156+
_g_.m.gsignal.stackAlloc = st.ss_size
157+
_g_.m.newSigstack = false
158+
}
145159

146160
// restore signal mask from m.sigmask and unblock essential signals
147161
nmask := _g_.m.sigmask
@@ -155,7 +169,9 @@ func minit() {
155169

156170
// Called from dropm to undo the effect of an minit.
157171
func unminit() {
158-
signalstack(nil)
172+
if getg().m.newSigstack {
173+
signalstack(nil)
174+
}
159175
}
160176

161177
func memlimit() uintptr {

src/runtime/os1_freebsd.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,21 @@ func minit() {
147147
}
148148

149149
// Initialize signal handling.
150-
signalstack(&_g_.m.gsignal.stack)
150+
var st stackt
151+
sigaltstack(nil, &st)
152+
if st.ss_flags&_SS_DISABLE != 0 {
153+
signalstack(&_g_.m.gsignal.stack)
154+
_g_.m.newSigstack = true
155+
} else {
156+
// Use existing signal stack.
157+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
158+
_g_.m.gsignal.stack.lo = stsp
159+
_g_.m.gsignal.stack.hi = stsp + st.ss_size
160+
_g_.m.gsignal.stackguard0 = stsp + _StackGuard
161+
_g_.m.gsignal.stackguard1 = stsp + _StackGuard
162+
_g_.m.gsignal.stackAlloc = st.ss_size
163+
_g_.m.newSigstack = false
164+
}
151165

152166
// restore signal mask from m.sigmask and unblock essential signals
153167
nmask := _g_.m.sigmask
@@ -162,7 +176,9 @@ func minit() {
162176
// Called from dropm to undo the effect of an minit.
163177
//go:nosplit
164178
func unminit() {
165-
signalstack(nil)
179+
if getg().m.newSigstack {
180+
signalstack(nil)
181+
}
166182
}
167183

168184
func memlimit() uintptr {

src/runtime/os1_linux.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,22 @@ func gettid() uint32
221221
func minit() {
222222
// Initialize signal handling.
223223
_g_ := getg()
224-
signalstack(&_g_.m.gsignal.stack)
224+
225+
var st sigaltstackt
226+
sigaltstack(nil, &st)
227+
if st.ss_flags&_SS_DISABLE != 0 {
228+
signalstack(&_g_.m.gsignal.stack)
229+
_g_.m.newSigstack = true
230+
} else {
231+
// Use existing signal stack.
232+
stsp := uintptr(unsafe.Pointer(st.ss_sp))
233+
_g_.m.gsignal.stack.lo = stsp
234+
_g_.m.gsignal.stack.hi = stsp + st.ss_size
235+
_g_.m.gsignal.stackguard0 = stsp + _StackGuard
236+
_g_.m.gsignal.stackguard1 = stsp + _StackGuard
237+
_g_.m.gsignal.stackAlloc = st.ss_size
238+
_g_.m.newSigstack = false
239+
}
225240

226241
// for debuggers, in case cgo created the thread
227242
_g_.m.procid = uint64(gettid())
@@ -239,7 +254,9 @@ func minit() {
239254
// Called from dropm to undo the effect of an minit.
240255
//go:nosplit
241256
func unminit() {
242-
signalstack(nil)
257+
if getg().m.newSigstack {
258+
signalstack(nil)
259+
}
243260
}
244261

245262
func memlimit() uintptr {

0 commit comments

Comments
 (0)