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 && !android && !openbsd
6 
7 // Required for darwin ucontext.
8 #define _XOPEN_SOURCE
9 // Required for netbsd stack_t if _XOPEN_SOURCE is set.
10 #define _XOPEN_SOURCE_EXTENDED	1
11 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
12 
13 #include <assert.h>
14 #include <pthread.h>
15 #include <stddef.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <ucontext.h>
19 
20 // musl libc does not provide getcontext, etc. Skip the test there.
21 //
22 // musl libc doesn't provide any direct detection mechanism. So assume any
23 // non-glibc linux is using musl.
24 //
25 // Note that bionic does not provide getcontext either, but that is skipped via
26 // the android build tag.
27 #if defined(__linux__) && !defined(__GLIBC__)
28 #define MUSL 1
29 #endif
30 #if defined(MUSL)
callStackSwitchCallbackFromThread(void)31 void callStackSwitchCallbackFromThread(void) {
32 	printf("SKIP\n");
33 	exit(0);
34 }
35 #else
36 
37 // Use a stack size larger than the 32kb estimate in
38 // runtime.callbackUpdateSystemStack. This ensures that a second stack
39 // allocation won't accidentally count as in bounds of the first stack
40 #define STACK_SIZE	(64ull << 10)
41 
42 static ucontext_t uctx_save, uctx_switch;
43 
44 extern void stackSwitchCallback(void);
45 
46 char *stack2;
47 
stackSwitchThread(void * arg)48 static void *stackSwitchThread(void *arg) {
49 	// Simple test: callback works from the normal system stack.
50 	stackSwitchCallback();
51 
52 	// Next, verify that switching stacks doesn't break callbacks.
53 
54 	char *stack1 = malloc(STACK_SIZE);
55 	if (stack1 == NULL) {
56 		perror("malloc");
57 		exit(1);
58 	}
59 
60 	// Allocate the second stack before freeing the first to ensure we don't get
61 	// the same address from malloc.
62 	//
63 	// Will be freed in stackSwitchThread2.
64 	stack2 = malloc(STACK_SIZE);
65 	if (stack1 == NULL) {
66 		perror("malloc");
67 		exit(1);
68 	}
69 
70 	if (getcontext(&uctx_switch) == -1) {
71 		perror("getcontext");
72 		exit(1);
73 	}
74 	uctx_switch.uc_stack.ss_sp = stack1;
75 	uctx_switch.uc_stack.ss_size = STACK_SIZE;
76 	uctx_switch.uc_link = &uctx_save;
77 	makecontext(&uctx_switch, stackSwitchCallback, 0);
78 
79 	if (swapcontext(&uctx_save, &uctx_switch) == -1) {
80 		perror("swapcontext");
81 		exit(1);
82 	}
83 
84 	if (getcontext(&uctx_switch) == -1) {
85 		perror("getcontext");
86 		exit(1);
87 	}
88 	uctx_switch.uc_stack.ss_sp = stack2;
89 	uctx_switch.uc_stack.ss_size = STACK_SIZE;
90 	uctx_switch.uc_link = &uctx_save;
91 	makecontext(&uctx_switch, stackSwitchCallback, 0);
92 
93 	if (swapcontext(&uctx_save, &uctx_switch) == -1) {
94 		perror("swapcontext");
95 		exit(1);
96 	}
97 
98 	free(stack1);
99 
100 	return NULL;
101 }
102 
stackSwitchThread2(void * arg)103 static void *stackSwitchThread2(void *arg) {
104 	// New thread. Use stack bounds that partially overlap the previous
105 	// bounds. needm should refresh the stack bounds anyway since this is a
106 	// new thread.
107 
108 	// N.B. since we used a custom stack with makecontext,
109 	// callbackUpdateSystemStack had to guess the bounds. Its guess assumes
110 	// a 32KiB stack.
111 	char *prev_stack_lo = stack2 + STACK_SIZE - (32*1024);
112 
113 	// New SP is just barely in bounds, but if we don't update the bounds
114 	// we'll almost certainly overflow. The SP that
115 	// callbackUpdateSystemStack sees already has some data pushed, so it
116 	// will be a bit below what we set here. Thus we include some slack.
117 	char *new_stack_hi = prev_stack_lo + 128;
118 
119 	if (getcontext(&uctx_switch) == -1) {
120 		perror("getcontext");
121 		exit(1);
122 	}
123 	uctx_switch.uc_stack.ss_sp = new_stack_hi - (STACK_SIZE / 2);
124 	uctx_switch.uc_stack.ss_size = STACK_SIZE / 2;
125 	uctx_switch.uc_link = &uctx_save;
126 	makecontext(&uctx_switch, stackSwitchCallback, 0);
127 
128 	if (swapcontext(&uctx_save, &uctx_switch) == -1) {
129 		perror("swapcontext");
130 		exit(1);
131 	}
132 
133 	free(stack2);
134 
135 	return NULL;
136 }
137 
callStackSwitchCallbackFromThread(void)138 void callStackSwitchCallbackFromThread(void) {
139 	pthread_t thread;
140 	assert(pthread_create(&thread, NULL, stackSwitchThread, NULL) == 0);
141 	assert(pthread_join(thread, NULL) == 0);
142 
143 	assert(pthread_create(&thread, NULL, stackSwitchThread2, NULL) == 0);
144 	assert(pthread_join(thread, NULL) == 0);
145 }
146 
147 #endif
148