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)
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
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
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
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
View as plain text