|
| 1 | +/* A detached thread ends, and the program that started it goes on. |
| 2 | + * |
| 3 | + * musl releases a detached thread's mapping from inside the thread, and on Linux |
| 4 | + * the thread is standing on that mapping --- so `__unmapself' first moves to a |
| 5 | + * 256-byte stack every exiting thread shares, and makes the two system calls |
| 6 | + * that end the thread from there. In this port neither call is a system call: |
| 7 | + * each passes through the port's dispatcher, the context table and openkal, |
| 8 | + * and an unoptimized build of that path needs several times 256 bytes. The |
| 9 | + * overflow wrote over whatever the linker placed beneath the shared stack. On |
| 10 | + * macOS that is musl's table of thread-specific keys and this port's context |
| 11 | + * table, and the thread then looked up its own record in the table it had just |
| 12 | + * overwritten and jumped into it: every program whose detached thread ended |
| 13 | + * stopped with an access violation. |
| 14 | + * |
| 15 | + * ⭐ WHAT IS OBSERVED. Detached threads that end one after another, then the |
| 16 | + * two tables beneath the shared stack in use: a key with a destructor, and a |
| 17 | + * joinable thread's own error value, which is reached through the context table. |
| 18 | + */ |
| 19 | +#include <errno.h> |
| 20 | +#include <pthread.h> |
| 21 | +#include <stdio.h> |
| 22 | +#include <time.h> |
| 23 | + |
| 24 | +enum { DETACHED = 8 }; |
| 25 | + |
| 26 | +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
| 27 | +static pthread_cond_t reported = PTHREAD_COND_INITIALIZER; |
| 28 | +static int started; |
| 29 | +static int ended; |
| 30 | + |
| 31 | +static void* detached(void* arg) |
| 32 | +{ |
| 33 | + (void)arg; |
| 34 | + pthread_mutex_lock(&lock); |
| 35 | + ++ended; |
| 36 | + pthread_cond_broadcast(&reported); |
| 37 | + pthread_mutex_unlock(&lock); |
| 38 | + return 0; |
| 39 | +} |
| 40 | + |
| 41 | +static int destroyed; |
| 42 | +static void destroy(void* value) { if (value == &destroyed) ++destroyed; } |
| 43 | + |
| 44 | +static pthread_key_t key; |
| 45 | +static void* keyed(void* arg) |
| 46 | +{ |
| 47 | + (void)arg; |
| 48 | + errno = 0; |
| 49 | + pthread_setspecific(key, &destroyed); |
| 50 | + errno = 7; |
| 51 | + return (void*)(long)errno; |
| 52 | +} |
| 53 | + |
| 54 | +static void pause_briefly(void) |
| 55 | +{ |
| 56 | + struct timespec ts = { 0, 50 * 1000 * 1000 }; |
| 57 | + nanosleep(&ts, 0); |
| 58 | +} |
| 59 | + |
| 60 | +int main(void) |
| 61 | +{ |
| 62 | + int failures = 0; |
| 63 | + |
| 64 | + pthread_attr_t attr; |
| 65 | + pthread_attr_init(&attr); |
| 66 | + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| 67 | + for (int i = 0; i < DETACHED; ++i) { |
| 68 | + pthread_t thread; |
| 69 | + if (pthread_create(&thread, &attr, detached, 0) != 0) { |
| 70 | + printf("FAIL: detached thread %d was not created\n", i); |
| 71 | + ++failures; |
| 72 | + break; |
| 73 | + } |
| 74 | + ++started; |
| 75 | + pthread_mutex_lock(&lock); |
| 76 | + while (ended < started) pthread_cond_wait(&reported, &lock); |
| 77 | + pthread_mutex_unlock(&lock); |
| 78 | + /* Reporting precedes ending; the pause lets the thread finish ending |
| 79 | + * before the next one starts, which is where the fault was. */ |
| 80 | + pause_briefly(); |
| 81 | + } |
| 82 | + pthread_attr_destroy(&attr); |
| 83 | + printf("detached: %d started, %d ended\n", started, ended); |
| 84 | + if (ended != DETACHED) { printf("FAIL: %d of %d detached threads ended\n", ended, DETACHED); ++failures; } |
| 85 | + |
| 86 | + if (pthread_key_create(&key, destroy) != 0) { puts("FAIL: pthread_key_create"); ++failures; } |
| 87 | + pthread_t joinable; |
| 88 | + void* result = 0; |
| 89 | + if (pthread_create(&joinable, 0, keyed, 0) != 0 || pthread_join(joinable, &result) != 0) { |
| 90 | + puts("FAIL: the joinable thread did not run"); |
| 91 | + ++failures; |
| 92 | + } |
| 93 | + printf("joinable: its error value %ld, destructor ran %d time(s)\n", (long)result, destroyed); |
| 94 | + if ((long)result != 7) { puts("FAIL: the joinable thread's error value"); ++failures; } |
| 95 | + if (destroyed != 1) { puts("FAIL: the key's destructor"); ++failures; } |
| 96 | + |
| 97 | + printf("-- failures: %d --\n", failures); |
| 98 | + return failures == 0 ? 0 : 1; |
| 99 | +} |
0 commit comments