1#include "pthread_impl.h" 2#include <semaphore.h> 3#include <unistd.h> 4#include <dirent.h> 5#include <string.h> 6#include <ctype.h> 7#include "futex.h" 8#include "atomic.h" 9#include "../dirent/__dirent.h" 10 11static struct chain { 12 struct chain *next; 13 int tid; 14 sem_t target_sem, caller_sem; 15} *volatile head; 16 17static volatile int synccall_lock[2]; 18static volatile int target_tid; 19static void (*callback)(void *), *context; 20static volatile int dummy = 0; 21weak_alias(dummy, __block_new_threads); 22 23static void handler(int sig) 24{ 25 struct chain ch; 26 int old_errno = errno; 27 28 sem_init(&ch.target_sem, 0, 0); 29 sem_init(&ch.caller_sem, 0, 0); 30 31 ch.tid = __syscall(SYS_gettid); 32 33 do ch.next = head; 34 while (a_cas_p(&head, ch.next, &ch) != ch.next); 35 36 if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000)) 37 __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE); 38 39 sem_wait(&ch.target_sem); 40 callback(context); 41 sem_post(&ch.caller_sem); 42 sem_wait(&ch.target_sem); 43 44 errno = old_errno; 45} 46 47void __synccall(void (*func)(void *), void *ctx) 48{ 49 sigset_t oldmask; 50 int cs, i, r, pid, self;; 51 DIR dir = {0}; 52 struct dirent *de; 53 struct sigaction sa = { .sa_flags = 0, .sa_handler = handler }; 54 struct chain *cp, *next; 55 struct timespec ts; 56 57 /* Blocking signals in two steps, first only app-level signals 58 * before taking the lock, then all signals after taking the lock, 59 * is necessary to achieve AS-safety. Blocking them all first would 60 * deadlock if multiple threads called __synccall. Waiting to block 61 * any until after the lock would allow re-entry in the same thread 62 * with the lock already held. */ 63 __block_app_sigs(&oldmask); 64 LOCK(synccall_lock); 65 __block_all_sigs(0); 66 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); 67 68 head = 0; 69 70 if (!libc.threaded) goto single_threaded; 71 72 callback = func; 73 context = ctx; 74 75 /* This atomic store ensures that any signaled threads will see the 76 * above stores, and prevents more than a bounded number of threads, 77 * those already in pthread_create, from creating new threads until 78 * the value is cleared to zero again. */ 79 a_store(&__block_new_threads, 1); 80 81 /* Block even implementation-internal signals, so that nothing 82 * interrupts the SIGSYNCCALL handlers. The main possible source 83 * of trouble is asynchronous cancellation. */ 84 memset(&sa.sa_mask, -1, sizeof sa.sa_mask); 85 __libc_sigaction(SIGSYNCCALL, &sa, 0); 86 87 pid = __syscall(SYS_getpid); 88 self = __syscall(SYS_gettid); 89 90 /* Since opendir is not AS-safe, the DIR needs to be setup manually 91 * in automatic storage. Thankfully this is easy. */ 92 dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC); 93 if (dir.fd < 0) goto out; 94 95 /* Initially send one signal per counted thread. But since we can't 96 * synchronize with thread creation/exit here, there could be too 97 * few signals. This initial signaling is just an optimization, not 98 * part of the logic. */ 99 for (i=libc.threads_minus_1; i; i--) 100 __syscall(SYS_kill, pid, SIGSYNCCALL); 101 102 /* Loop scanning the kernel-provided thread list until it shows no 103 * threads that have not already replied to the signal. */ 104 for (;;) { 105 int miss_cnt = 0; 106 while ((de = readdir(&dir))) { 107 if (!isdigit(de->d_name[0])) continue; 108 int tid = atoi(de->d_name); 109 if (tid == self || !tid) continue; 110 111 /* Set the target thread as the PI futex owner before 112 * checking if it's in the list of caught threads. If it 113 * adds itself to the list after we check for it, then 114 * it will see its own tid in the PI futex and perform 115 * the unlock operation. */ 116 a_store(&target_tid, tid); 117 118 /* Thread-already-caught is a success condition. */ 119 for (cp = head; cp && cp->tid != tid; cp=cp->next); 120 if (cp) continue; 121 122 r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL); 123 124 /* Target thread exit is a success condition. */ 125 if (r == ESRCH) continue; 126 127 /* The FUTEX_LOCK_PI operation is used to loan priority 128 * to the target thread, which otherwise may be unable 129 * to run. Timeout is necessary because there is a race 130 * condition where the tid may be reused by a different 131 * process. */ 132 clock_gettime(CLOCK_REALTIME, &ts); 133 ts.tv_nsec += 10000000; 134 if (ts.tv_nsec >= 1000000000) { 135 ts.tv_sec++; 136 ts.tv_nsec -= 1000000000; 137 } 138 r = -__syscall(SYS_futex, &target_tid, 139 FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts); 140 141 /* Obtaining the lock means the thread responded. ESRCH 142 * means the target thread exited, which is okay too. */ 143 if (!r || r == ESRCH) continue; 144 145 miss_cnt++; 146 } 147 if (!miss_cnt) break; 148 rewinddir(&dir); 149 } 150 close(dir.fd); 151 152 /* Serialize execution of callback in caught threads. */ 153 for (cp=head; cp; cp=cp->next) { 154 sem_post(&cp->target_sem); 155 sem_wait(&cp->caller_sem); 156 } 157 158 sa.sa_handler = SIG_IGN; 159 __libc_sigaction(SIGSYNCCALL, &sa, 0); 160 161single_threaded: 162 func(ctx); 163 164 /* Only release the caught threads once all threads, including the 165 * caller, have returned from the callback function. */ 166 for (cp=head; cp; cp=next) { 167 next = cp->next; 168 sem_post(&cp->target_sem); 169 } 170 171out: 172 a_store(&__block_new_threads, 0); 173 __wake(&__block_new_threads, -1, 1); 174 175 pthread_setcancelstate(cs, 0); 176 UNLOCK(synccall_lock); 177 __restore_sigs(&oldmask); 178} 179