diff --git a/test/.gitignore b/test/.gitignore index eb57d7a4..ac709f7d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -72,6 +72,7 @@ basic/ext_thread_future basic/ext_thread_join basic/ext_thread_mutex basic/ext_thread_rwlock +basic/stack_guard basic/timer basic/info_print basic/info_print_stack diff --git a/test/basic/Makefile.am b/test/basic/Makefile.am index 02b27941..84f9bff2 100644 --- a/test/basic/Makefile.am +++ b/test/basic/Makefile.am @@ -77,6 +77,7 @@ TESTS = \ ext_thread_join \ ext_thread_mutex \ ext_thread_rwlock \ + stack_guard \ timer \ info_print \ info_print_stack \ @@ -173,6 +174,7 @@ ext_thread_future_SOURCES = ext_thread_future.c ext_thread_join_SOURCES = ext_thread_join.c ext_thread_mutex_SOURCES = ext_thread_mutex.c ext_thread_rwlock_SOURCES = ext_thread_rwlock.c +stack_guard_SOURCES = stack_guard.c timer_SOURCES = timer.c info_print_SOURCES = info_print.c info_print_stack_SOURCES = info_print_stack.c @@ -255,6 +257,7 @@ testing: ./ext_thread_join ./ext_thread_mutex ./ext_thread_rwlock + ./stack_guard ./timer ./info_print ./info_print_stack diff --git a/test/basic/stack_guard.c b/test/basic/stack_guard.c new file mode 100644 index 00000000..db7ce0ab --- /dev/null +++ b/test/basic/stack_guard.c @@ -0,0 +1,205 @@ +/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil ; -*- */ +/* + * See COPYRIGHT in top-level directory. + */ + +#include +#include + +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L + +#include +#include +#include +#include +#include "abt.h" +#include "abttest.h" + +#define DUMMY_SIZE ((int)(1024 / sizeof(double))) +#define SYS_PAGE_SIZE 4096 + +int g_mprotect_signal = 0; +volatile int g_sig_err = 0; +volatile int g_is_segv = 0; +volatile char *gp_stack = NULL; + +void segv_handler(int sig, siginfo_t *si, void *unused) +{ + if (sig != SIGSEGV) { + g_sig_err = 1; /* We cannot call assert(). */ + } else if (si->si_addr != gp_stack) { + g_sig_err = 2; + } else { + /* Since POSIX does not mark mprotect() as async-signal safe, we need to + * ask another thread to call mprotect() instead of this thread even if + * we control where the signal happens; calling an async signal-unsafe + * function can cause any unexpected issues. */ + ATS_atomic_store(&g_mprotect_signal, 1); + while (ATS_atomic_load(&g_mprotect_signal) == 1) { + ; /* Waiting for the helper thread. */ + } + /* mprotect() finished. */ + g_is_segv = 1; + } +} + +void *helper_func(void *arg) +{ + /* Waiting for g_mprotect_signal from a signal handler. */ + while (ATS_atomic_load(&g_mprotect_signal) == 0) + ; + /* Call mprotect() to temporarily allow an access. */ + int ret = mprotect((void *)gp_stack, SYS_PAGE_SIZE, PROT_READ | PROT_WRITE); + assert(ret == 0); + /* Tell the signal handler that mprotect has finished. */ + ATS_atomic_store(&g_mprotect_signal, 0); + return NULL; +} + +void thread_func(void *arg) +{ + int ret; + void *p_stack; + size_t stack_size; + /* Get the stack information. */ + { + ABT_thread self_thread; + ABT_thread_attr self_thread_attr; + ret = ABT_self_get_thread(&self_thread); + ATS_ERROR(ret, "ABT_self_get_thread"); + ret = ABT_thread_get_attr(self_thread, &self_thread_attr); + ATS_ERROR(ret, "ABT_thread_get_attr"); + ret = + ABT_thread_attr_get_stack(self_thread_attr, &p_stack, &stack_size); + ATS_ERROR(ret, "ABT_thread_attr_get_stack"); + ret = ABT_thread_attr_free(&self_thread_attr); + ATS_ERROR(ret, "ABT_thread_attr_free"); + } + + /* We can reasonably assume that we do not corrupt the function stack of + * thread_func(). Let's assume that the protected page is within a few + * pages from the bottom of the stack. + * gp_stack should be aligned with the page size. */ + gp_stack = (char *)(((((uintptr_t)p_stack) + SYS_PAGE_SIZE - 1) / SYS_PAGE_SIZE) * + SYS_PAGE_SIZE + + SYS_PAGE_SIZE * 2); + while (1) { + /* Using this stack variable to see if we can observe SEGV. */ + gp_stack -= SYS_PAGE_SIZE; + assert(((char *)p_stack) <= gp_stack); + volatile char val = gp_stack[0]; + /* Though we use "volatile", we'd like to put a compiler barrier just in + * case. */ + __asm__ __volatile__("" ::: "memory"); + /* The following should cause SEGV. If SEGV happens, the signal handler + * will allow this ULT to temporarily access this. */ + gp_stack[0] = val; + __asm__ __volatile__("" ::: "memory"); + /* Signal might have happened. */ + if (g_is_segv) { + assert(g_sig_err == 0); + /* Succeeded! Undo the mprotect setting. Originally it should be + * read-protected. */ + g_is_segv = 0; + ret = mprotect((void *)gp_stack, SYS_PAGE_SIZE, PROT_READ); + assert(ret == 0); + return; + } + /* We must catch SEGV until we touch stack_size */ + } +} + +int main(int argc, char *argv[]) +{ + int ret, i; + /* Catch SEGV. */ + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = segv_handler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, NULL) == -1) { + /* Unsupported. */ + return 77; + } + /* Set memory protection */ + putenv("ABT_STACK_OVERFLOW_CHECK=mprotect_strict"); + + /* Initialize */ + ATS_read_args(argc, argv); + ATS_init(argc, argv, 2); + + /* Check if the mprotect-based stack guard is enabled. */ + int stack_overflow_check_mode = 0; + ret = + ABT_info_query_config(ABT_INFO_QUERY_KIND_ENABLED_STACK_OVERFLOW_CHECK, + &stack_overflow_check_mode); + ATS_ERROR(ret, "ABT_info_query_config"); + if (stack_overflow_check_mode != 3) { + /* Unsupported. */ + return 77; + } + + ABT_xstream xstream; + ABT_pool main_pool; + ret = ABT_self_get_xstream(&xstream); + ATS_ERROR(ret, "ABT_self_get_xstream"); + ret = ABT_xstream_get_main_pools(xstream, 1, &main_pool); + ATS_ERROR(ret, "ABT_xstream_get_main_pools"); + + for (i = 0; i < 3; i++) { + pthread_t helper_thread; + ret = pthread_create(&helper_thread, NULL, helper_func, NULL); + assert(ret == 0); + ABT_thread thread; + void *stack = NULL; + if (i == 0) { + /* 1. ULT + default parameters. */ + ret = ABT_thread_create(main_pool, thread_func, NULL, + ABT_THREAD_ATTR_NULL, &thread); + ATS_ERROR(ret, "ABT_thread_create"); + } else if (i == 1) { + /* 2. ULT + user-given stack size. */ + ABT_thread_attr thread_attr; + ret = ABT_thread_attr_create(&thread_attr); + ATS_ERROR(ret, "ABT_thread_attr_create"); + ret = ABT_thread_attr_set_stacksize(thread_attr, 1024 * 1024); + ATS_ERROR(ret, "ABT_thread_attr_set_stacksize"); + ret = ABT_thread_create(main_pool, thread_func, NULL, thread_attr, + &thread); + ATS_ERROR(ret, "ABT_thread_create"); + ret = ABT_thread_attr_free(&thread_attr); + ATS_ERROR(ret, "ABT_thread_attr_free"); + } else if (i == 2) { + /* 3. ULT + user-given stack. */ + ABT_thread_attr thread_attr; + ret = ABT_thread_attr_create(&thread_attr); + ATS_ERROR(ret, "ABT_thread_attr_create"); + stack = calloc(1, 1024 * 1024); + ret = ABT_thread_attr_set_stack(thread_attr, stack, 1024 * 1024); + ATS_ERROR(ret, "ABT_thread_attr_set_stack"); + ret = ABT_thread_create(main_pool, thread_func, NULL, + ABT_THREAD_ATTR_NULL, &thread); + ATS_ERROR(ret, "ABT_thread_create"); + ret = ABT_thread_attr_free(&thread_attr); + ATS_ERROR(ret, "ABT_thread_attr_free"); + } + ret = ABT_thread_free(&thread); + ATS_ERROR(ret, "ABT_thread_free"); + if (stack) + free(stack); + ret = pthread_join(helper_thread, NULL); + assert(ret == 0); + } + /* Finalize */ + return ATS_finalize(0); +} + +#else /* _POSIX_C_SOURCE */ + +int main() +{ + /* Unsupported. */ + return 77; +} + +#endif