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 that a signal handler that uses up stack space does not crash
6// if the signal is delivered to a thread running a goroutine.
7// This is a lot like misc/cgo/testcarchive/main2.c.
8
9#include <setjmp.h>
10#include <signal.h>
11#include <stddef.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/types.h>
16#include <unistd.h>
17#include <sched.h>
18#include <time.h>
19#include <dlfcn.h>
20
21static void die(const char* msg) {
22	perror(msg);
23	exit(EXIT_FAILURE);
24}
25
26static volatile sig_atomic_t sigioSeen;
27
28// Use up some stack space.
29static void recur(int i, char *p) {
30	char a[1024];
31
32	*p = '\0';
33	if (i > 0) {
34		recur(i - 1, a);
35	}
36}
37
38// Signal handler that uses up more stack space than a goroutine will have.
39static void ioHandler(int signo, siginfo_t* info, void* ctxt) {
40	char a[1024];
41
42	recur(4, a);
43	sigioSeen = 1;
44}
45
46static jmp_buf jmp;
47static char* nullPointer;
48
49// Signal handler for SIGSEGV on a C thread.
50static void segvHandler(int signo, siginfo_t* info, void* ctxt) {
51	sigset_t mask;
52	int i;
53
54	if (sigemptyset(&mask) < 0) {
55		die("sigemptyset");
56	}
57	if (sigaddset(&mask, SIGSEGV) < 0) {
58		die("sigaddset");
59	}
60	i = sigprocmask(SIG_UNBLOCK, &mask, NULL);
61	if (i != 0) {
62		fprintf(stderr, "sigprocmask: %s\n", strerror(i));
63		exit(EXIT_FAILURE);
64	}
65
66	// Don't try this at home.
67	longjmp(jmp, signo);
68
69	// We should never get here.
70	abort();
71}
72
73int main(int argc, char** argv) {
74	int verbose;
75	struct sigaction sa;
76	void* handle;
77	void (*fn)(void);
78	sigset_t mask;
79	int i;
80	struct timespec ts;
81
82	verbose = argc > 2;
83	setvbuf(stdout, NULL, _IONBF, 0);
84
85	// Call setsid so that we can use kill(0, SIGIO) below.
86	// Don't check the return value so that this works both from
87	// a job control shell and from a shell script.
88	setsid();
89
90	if (verbose) {
91		printf("calling sigaction\n");
92	}
93
94	memset(&sa, 0, sizeof sa);
95	sa.sa_sigaction = ioHandler;
96	if (sigemptyset(&sa.sa_mask) < 0) {
97		die("sigemptyset");
98	}
99	sa.sa_flags = SA_SIGINFO;
100	if (sigaction(SIGIO, &sa, NULL) < 0) {
101		die("sigaction");
102	}
103
104	sa.sa_sigaction = segvHandler;
105	if (sigaction(SIGSEGV, &sa, NULL) < 0 || sigaction(SIGBUS, &sa, NULL) < 0) {
106		die("sigaction");
107	}
108
109	if (verbose) {
110		printf("calling dlopen\n");
111	}
112
113	handle = dlopen(argv[1], RTLD_NOW | RTLD_GLOBAL);
114	if (handle == NULL) {
115		fprintf(stderr, "%s\n", dlerror());
116		exit(EXIT_FAILURE);
117	}
118
119	if (verbose) {
120		printf("calling dlsym\n");
121	}
122
123	// Start some goroutines.
124	fn = (void(*)(void))dlsym(handle, "RunGoroutines");
125	if (fn == NULL) {
126		fprintf(stderr, "%s\n", dlerror());
127		exit(EXIT_FAILURE);
128	}
129
130	if (verbose) {
131		printf("calling RunGoroutines\n");
132	}
133
134	fn();
135
136	// Block SIGIO in this thread to make it more likely that it
137	// will be delivered to a goroutine.
138
139	if (verbose) {
140		printf("calling pthread_sigmask\n");
141	}
142
143	if (sigemptyset(&mask) < 0) {
144		die("sigemptyset");
145	}
146	if (sigaddset(&mask, SIGIO) < 0) {
147		die("sigaddset");
148	}
149	i = pthread_sigmask(SIG_BLOCK, &mask, NULL);
150	if (i != 0) {
151		fprintf(stderr, "pthread_sigmask: %s\n", strerror(i));
152		exit(EXIT_FAILURE);
153	}
154
155	if (verbose) {
156		printf("calling kill\n");
157	}
158
159	if (kill(0, SIGIO) < 0) {
160		die("kill");
161	}
162
163	if (verbose) {
164		printf("waiting for sigioSeen\n");
165	}
166
167	// Wait until the signal has been delivered.
168	i = 0;
169	while (!sigioSeen) {
170		ts.tv_sec = 0;
171		ts.tv_nsec = 1000000;
172		nanosleep(&ts, NULL);
173		i++;
174		if (i > 5000) {
175			fprintf(stderr, "looping too long waiting for signal\n");
176			exit(EXIT_FAILURE);
177		}
178	}
179
180	if (verbose) {
181		printf("calling setjmp\n");
182	}
183
184	// Test that a SIGSEGV on this thread is delivered to us.
185	if (setjmp(jmp) == 0) {
186		if (verbose) {
187			printf("triggering SIGSEGV\n");
188		}
189
190		*nullPointer = '\0';
191
192		fprintf(stderr, "continued after address error\n");
193		exit(EXIT_FAILURE);
194	}
195
196	if (verbose) {
197		printf("calling dlsym\n");
198	}
199
200	// Make sure that a SIGSEGV in Go causes a run-time panic.
201	fn = (void (*)(void))dlsym(handle, "TestSEGV");
202	if (fn == NULL) {
203		fprintf(stderr, "%s\n", dlerror());
204		exit(EXIT_FAILURE);
205	}
206
207	if (verbose) {
208		printf("calling TestSEGV\n");
209	}
210
211	fn();
212
213	printf("PASS\n");
214	return 0;
215}
216