...
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)
31void 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
42static ucontext_t uctx_save, uctx_switch;
43
44extern void stackSwitchCallback(void);
45
46char *stack2;
47
48static 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
103static 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
138void 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
View as plain text