Text file src/runtime/testdata/testprogcgo/stackswitch.c

     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