| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build unix && !android && !openbsd |
| |
| // Required for darwin ucontext. |
| #define _XOPEN_SOURCE |
| // Required for netbsd stack_t if _XOPEN_SOURCE is set. |
| #define _XOPEN_SOURCE_EXTENDED 1 |
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" |
| |
| #include <assert.h> |
| #include <pthread.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <ucontext.h> |
| |
| // musl libc does not provide getcontext, etc. Skip the test there. |
| // |
| // musl libc doesn't provide any direct detection mechanism. So assume any |
| // non-glibc linux is using musl. |
| // |
| // Note that bionic does not provide getcontext either, but that is skipped via |
| // the android build tag. |
| #if defined(__linux__) && !defined(__GLIBC__) |
| #define MUSL 1 |
| #endif |
| #if defined(MUSL) |
| void callStackSwitchCallbackFromThread(void) { |
| printf("SKIP\n"); |
| exit(0); |
| } |
| #else |
| |
| // Use a stack size larger than the 32kb estimate in |
| // runtime.callbackUpdateSystemStack. This ensures that a second stack |
| // allocation won't accidentally count as in bounds of the first stack |
| #define STACK_SIZE (64ull << 10) |
| |
| static ucontext_t uctx_save, uctx_switch; |
| |
| extern void stackSwitchCallback(void); |
| |
| char *stack2; |
| |
| static void *stackSwitchThread(void *arg) { |
| // Simple test: callback works from the normal system stack. |
| stackSwitchCallback(); |
| |
| // Next, verify that switching stacks doesn't break callbacks. |
| |
| char *stack1 = malloc(STACK_SIZE); |
| if (stack1 == NULL) { |
| perror("malloc"); |
| exit(1); |
| } |
| |
| // Allocate the second stack before freeing the first to ensure we don't get |
| // the same address from malloc. |
| // |
| // Will be freed in stackSwitchThread2. |
| stack2 = malloc(STACK_SIZE); |
| if (stack1 == NULL) { |
| perror("malloc"); |
| exit(1); |
| } |
| |
| if (getcontext(&uctx_switch) == -1) { |
| perror("getcontext"); |
| exit(1); |
| } |
| uctx_switch.uc_stack.ss_sp = stack1; |
| uctx_switch.uc_stack.ss_size = STACK_SIZE; |
| uctx_switch.uc_link = &uctx_save; |
| makecontext(&uctx_switch, stackSwitchCallback, 0); |
| |
| if (swapcontext(&uctx_save, &uctx_switch) == -1) { |
| perror("swapcontext"); |
| exit(1); |
| } |
| |
| if (getcontext(&uctx_switch) == -1) { |
| perror("getcontext"); |
| exit(1); |
| } |
| uctx_switch.uc_stack.ss_sp = stack2; |
| uctx_switch.uc_stack.ss_size = STACK_SIZE; |
| uctx_switch.uc_link = &uctx_save; |
| makecontext(&uctx_switch, stackSwitchCallback, 0); |
| |
| if (swapcontext(&uctx_save, &uctx_switch) == -1) { |
| perror("swapcontext"); |
| exit(1); |
| } |
| |
| free(stack1); |
| |
| return NULL; |
| } |
| |
| static void *stackSwitchThread2(void *arg) { |
| // New thread. Use stack bounds that partially overlap the previous |
| // bounds. needm should refresh the stack bounds anyway since this is a |
| // new thread. |
| |
| // N.B. since we used a custom stack with makecontext, |
| // callbackUpdateSystemStack had to guess the bounds. Its guess assumes |
| // a 32KiB stack. |
| char *prev_stack_lo = stack2 + STACK_SIZE - (32*1024); |
| |
| // New SP is just barely in bounds, but if we don't update the bounds |
| // we'll almost certainly overflow. The SP that |
| // callbackUpdateSystemStack sees already has some data pushed, so it |
| // will be a bit below what we set here. Thus we include some slack. |
| char *new_stack_hi = prev_stack_lo + 128; |
| |
| if (getcontext(&uctx_switch) == -1) { |
| perror("getcontext"); |
| exit(1); |
| } |
| uctx_switch.uc_stack.ss_sp = new_stack_hi - (STACK_SIZE / 2); |
| uctx_switch.uc_stack.ss_size = STACK_SIZE / 2; |
| uctx_switch.uc_link = &uctx_save; |
| makecontext(&uctx_switch, stackSwitchCallback, 0); |
| |
| if (swapcontext(&uctx_save, &uctx_switch) == -1) { |
| perror("swapcontext"); |
| exit(1); |
| } |
| |
| free(stack2); |
| |
| return NULL; |
| } |
| |
| void callStackSwitchCallbackFromThread(void) { |
| pthread_t thread; |
| assert(pthread_create(&thread, NULL, stackSwitchThread, NULL) == 0); |
| assert(pthread_join(thread, NULL) == 0); |
| |
| assert(pthread_create(&thread, NULL, stackSwitchThread2, NULL) == 0); |
| assert(pthread_join(thread, NULL) == 0); |
| } |
| |
| #endif |