Pirama Arumuga Nainar | 799172d | 2016-03-03 15:50:30 -0800 | [diff] [blame^] | 1 | //===-- tsan_libdispatch_mac.cc -------------------------------------------===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // This file is a part of ThreadSanitizer (TSan), a race detector. |
| 11 | // |
| 12 | // Mac-specific libdispatch (GCD) support. |
| 13 | //===----------------------------------------------------------------------===// |
| 14 | |
| 15 | #include "sanitizer_common/sanitizer_platform.h" |
| 16 | #if SANITIZER_MAC |
| 17 | |
| 18 | #include "sanitizer_common/sanitizer_common.h" |
| 19 | #include "interception/interception.h" |
| 20 | #include "tsan_interceptors.h" |
| 21 | #include "tsan_platform.h" |
| 22 | #include "tsan_rtl.h" |
| 23 | |
| 24 | #include <Block.h> |
| 25 | #include <dispatch/dispatch.h> |
| 26 | #include <pthread.h> |
| 27 | |
| 28 | typedef long long_t; // NOLINT |
| 29 | |
| 30 | namespace __tsan { |
| 31 | |
| 32 | typedef struct { |
| 33 | dispatch_queue_t queue; |
| 34 | void *orig_context; |
| 35 | dispatch_function_t orig_work; |
| 36 | uptr object_to_acquire; |
| 37 | dispatch_object_t object_to_release; |
| 38 | } tsan_block_context_t; |
| 39 | |
| 40 | // The offsets of different fields of the dispatch_queue_t structure, exported |
| 41 | // by libdispatch.dylib. |
| 42 | extern "C" struct dispatch_queue_offsets_s { |
| 43 | const uint16_t dqo_version; |
| 44 | const uint16_t dqo_label; |
| 45 | const uint16_t dqo_label_size; |
| 46 | const uint16_t dqo_flags; |
| 47 | const uint16_t dqo_flags_size; |
| 48 | const uint16_t dqo_serialnum; |
| 49 | const uint16_t dqo_serialnum_size; |
| 50 | const uint16_t dqo_width; |
| 51 | const uint16_t dqo_width_size; |
| 52 | const uint16_t dqo_running; |
| 53 | const uint16_t dqo_running_size; |
| 54 | const uint16_t dqo_suspend_cnt; |
| 55 | const uint16_t dqo_suspend_cnt_size; |
| 56 | const uint16_t dqo_target_queue; |
| 57 | const uint16_t dqo_target_queue_size; |
| 58 | const uint16_t dqo_priority; |
| 59 | const uint16_t dqo_priority_size; |
| 60 | } dispatch_queue_offsets; |
| 61 | |
| 62 | static bool IsQueueSerial(dispatch_queue_t q) { |
| 63 | CHECK_EQ(dispatch_queue_offsets.dqo_width_size, 2); |
| 64 | uptr width = *(uint16_t *)(((uptr)q) + dispatch_queue_offsets.dqo_width); |
| 65 | CHECK_NE(width, 0); |
| 66 | return width == 1; |
| 67 | } |
| 68 | |
| 69 | static tsan_block_context_t *AllocContext(ThreadState *thr, uptr pc, |
| 70 | dispatch_queue_t queue, |
| 71 | void *orig_context, |
| 72 | dispatch_function_t orig_work) { |
| 73 | tsan_block_context_t *new_context = |
| 74 | (tsan_block_context_t *)user_alloc(thr, pc, sizeof(tsan_block_context_t)); |
| 75 | new_context->queue = queue; |
| 76 | new_context->orig_context = orig_context; |
| 77 | new_context->orig_work = orig_work; |
| 78 | new_context->object_to_acquire = (uptr)new_context; |
| 79 | new_context->object_to_release = nullptr; |
| 80 | return new_context; |
| 81 | } |
| 82 | |
| 83 | static void dispatch_callback_wrap_acquire(void *param) { |
| 84 | SCOPED_INTERCEPTOR_RAW(dispatch_async_f_callback_wrap); |
| 85 | tsan_block_context_t *context = (tsan_block_context_t *)param; |
| 86 | Acquire(thr, pc, context->object_to_acquire); |
| 87 | |
| 88 | // Extra retain/release is required for dispatch groups. We use the group |
| 89 | // itself to synchronize, but in a notification (dispatch_group_notify |
| 90 | // callback), it may be disposed already. To solve this, we retain the group |
| 91 | // and release it here. |
| 92 | if (context->object_to_release) dispatch_release(context->object_to_release); |
| 93 | |
| 94 | // In serial queues, work items can be executed on different threads, we need |
| 95 | // to explicitly synchronize on the queue itself. |
| 96 | if (IsQueueSerial(context->queue)) Acquire(thr, pc, (uptr)context->queue); |
| 97 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 98 | context->orig_work(context->orig_context); |
| 99 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 100 | if (IsQueueSerial(context->queue)) Release(thr, pc, (uptr)context->queue); |
| 101 | user_free(thr, pc, context); |
| 102 | } |
| 103 | |
| 104 | static void invoke_and_release_block(void *param) { |
| 105 | dispatch_block_t block = (dispatch_block_t)param; |
| 106 | block(); |
| 107 | Block_release(block); |
| 108 | } |
| 109 | |
| 110 | #define DISPATCH_INTERCEPT_B(name) \ |
| 111 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, dispatch_block_t block) { \ |
| 112 | SCOPED_TSAN_INTERCEPTOR(name, q, block); \ |
| 113 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 114 | dispatch_block_t heap_block = Block_copy(block); \ |
| 115 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 116 | tsan_block_context_t *new_context = \ |
| 117 | AllocContext(thr, pc, q, heap_block, &invoke_and_release_block); \ |
| 118 | Release(thr, pc, (uptr)new_context); \ |
| 119 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 120 | REAL(name##_f)(q, new_context, dispatch_callback_wrap_acquire); \ |
| 121 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 122 | } |
| 123 | |
| 124 | #define DISPATCH_INTERCEPT_F(name) \ |
| 125 | TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, void *context, \ |
| 126 | dispatch_function_t work) { \ |
| 127 | SCOPED_TSAN_INTERCEPTOR(name, q, context, work); \ |
| 128 | tsan_block_context_t *new_context = \ |
| 129 | AllocContext(thr, pc, q, context, work); \ |
| 130 | Release(thr, pc, (uptr)new_context); \ |
| 131 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \ |
| 132 | REAL(name)(q, new_context, dispatch_callback_wrap_acquire); \ |
| 133 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \ |
| 134 | } |
| 135 | |
| 136 | // We wrap dispatch_async, dispatch_sync and friends where we allocate a new |
| 137 | // context, which is used to synchronize (we release the context before |
| 138 | // submitting, and the callback acquires it before executing the original |
| 139 | // callback). |
| 140 | DISPATCH_INTERCEPT_B(dispatch_async) |
| 141 | DISPATCH_INTERCEPT_B(dispatch_barrier_async) |
| 142 | DISPATCH_INTERCEPT_F(dispatch_async_f) |
| 143 | DISPATCH_INTERCEPT_F(dispatch_barrier_async_f) |
| 144 | DISPATCH_INTERCEPT_B(dispatch_sync) |
| 145 | DISPATCH_INTERCEPT_B(dispatch_barrier_sync) |
| 146 | DISPATCH_INTERCEPT_F(dispatch_sync_f) |
| 147 | DISPATCH_INTERCEPT_F(dispatch_barrier_sync_f) |
| 148 | |
| 149 | // GCD's dispatch_once implementation has a fast path that contains a racy read |
| 150 | // and it's inlined into user's code. Furthermore, this fast path doesn't |
| 151 | // establish a proper happens-before relations between the initialization and |
| 152 | // code following the call to dispatch_once. We could deal with this in |
| 153 | // instrumented code, but there's not much we can do about it in system |
| 154 | // libraries. Let's disable the fast path (by never storing the value ~0 to |
| 155 | // predicate), so the interceptor is always called, and let's add proper release |
| 156 | // and acquire semantics. Since TSan does not see its own atomic stores, the |
| 157 | // race on predicate won't be reported - the only accesses to it that TSan sees |
| 158 | // are the loads on the fast path. Loads don't race. Secondly, dispatch_once is |
| 159 | // both a macro and a real function, we want to intercept the function, so we |
| 160 | // need to undefine the macro. |
| 161 | #undef dispatch_once |
| 162 | TSAN_INTERCEPTOR(void, dispatch_once, dispatch_once_t *predicate, |
| 163 | dispatch_block_t block) { |
| 164 | SCOPED_TSAN_INTERCEPTOR(dispatch_once, predicate, block); |
| 165 | atomic_uint32_t *a = reinterpret_cast<atomic_uint32_t *>(predicate); |
| 166 | u32 v = atomic_load(a, memory_order_acquire); |
| 167 | if (v == 0 && |
| 168 | atomic_compare_exchange_strong(a, &v, 1, memory_order_relaxed)) { |
| 169 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 170 | block(); |
| 171 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 172 | Release(thr, pc, (uptr)a); |
| 173 | atomic_store(a, 2, memory_order_release); |
| 174 | } else { |
| 175 | while (v != 2) { |
| 176 | internal_sched_yield(); |
| 177 | v = atomic_load(a, memory_order_acquire); |
| 178 | } |
| 179 | Acquire(thr, pc, (uptr)a); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | #undef dispatch_once_f |
| 184 | TSAN_INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *predicate, |
| 185 | void *context, dispatch_function_t function) { |
| 186 | SCOPED_TSAN_INTERCEPTOR(dispatch_once_f, predicate, context, function); |
| 187 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 188 | WRAP(dispatch_once)(predicate, ^(void) { |
| 189 | function(context); |
| 190 | }); |
| 191 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 192 | } |
| 193 | |
| 194 | TSAN_INTERCEPTOR(long_t, dispatch_semaphore_signal, |
| 195 | dispatch_semaphore_t dsema) { |
| 196 | SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_signal, dsema); |
| 197 | Release(thr, pc, (uptr)dsema); |
| 198 | return REAL(dispatch_semaphore_signal)(dsema); |
| 199 | } |
| 200 | |
| 201 | TSAN_INTERCEPTOR(long_t, dispatch_semaphore_wait, dispatch_semaphore_t dsema, |
| 202 | dispatch_time_t timeout) { |
| 203 | SCOPED_TSAN_INTERCEPTOR(dispatch_semaphore_wait, dsema, timeout); |
| 204 | long_t result = REAL(dispatch_semaphore_wait)(dsema, timeout); |
| 205 | if (result == 0) Acquire(thr, pc, (uptr)dsema); |
| 206 | return result; |
| 207 | } |
| 208 | |
| 209 | TSAN_INTERCEPTOR(long_t, dispatch_group_wait, dispatch_group_t group, |
| 210 | dispatch_time_t timeout) { |
| 211 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_wait, group, timeout); |
| 212 | long_t result = REAL(dispatch_group_wait)(group, timeout); |
| 213 | if (result == 0) Acquire(thr, pc, (uptr)group); |
| 214 | return result; |
| 215 | } |
| 216 | |
| 217 | TSAN_INTERCEPTOR(void, dispatch_group_leave, dispatch_group_t group) { |
| 218 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_leave, group); |
| 219 | Release(thr, pc, (uptr)group); |
| 220 | REAL(dispatch_group_leave)(group); |
| 221 | } |
| 222 | |
| 223 | TSAN_INTERCEPTOR(void, dispatch_group_async, dispatch_group_t group, |
| 224 | dispatch_queue_t queue, dispatch_block_t block) { |
| 225 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_async, group, queue, block); |
| 226 | dispatch_retain(group); |
| 227 | dispatch_group_enter(group); |
| 228 | WRAP(dispatch_async)(queue, ^(void) { |
| 229 | block(); |
| 230 | WRAP(dispatch_group_leave)(group); |
| 231 | dispatch_release(group); |
| 232 | }); |
| 233 | } |
| 234 | |
| 235 | TSAN_INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, |
| 236 | dispatch_queue_t queue, void *context, |
| 237 | dispatch_function_t work) { |
| 238 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_async_f, group, queue, context, work); |
| 239 | dispatch_retain(group); |
| 240 | dispatch_group_enter(group); |
| 241 | WRAP(dispatch_async)(queue, ^(void) { |
| 242 | work(context); |
| 243 | WRAP(dispatch_group_leave)(group); |
| 244 | dispatch_release(group); |
| 245 | }); |
| 246 | } |
| 247 | |
| 248 | TSAN_INTERCEPTOR(void, dispatch_group_notify, dispatch_group_t group, |
| 249 | dispatch_queue_t q, dispatch_block_t block) { |
| 250 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify, group, q, block); |
| 251 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); |
| 252 | dispatch_block_t heap_block = Block_copy(block); |
| 253 | SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); |
| 254 | tsan_block_context_t *new_context = |
| 255 | AllocContext(thr, pc, q, heap_block, &invoke_and_release_block); |
| 256 | new_context->object_to_acquire = (uptr)group; |
| 257 | |
| 258 | // Will be released in dispatch_callback_wrap_acquire. |
| 259 | new_context->object_to_release = group; |
| 260 | dispatch_retain(group); |
| 261 | |
| 262 | Release(thr, pc, (uptr)group); |
| 263 | REAL(dispatch_group_notify_f)(group, q, new_context, |
| 264 | dispatch_callback_wrap_acquire); |
| 265 | } |
| 266 | |
| 267 | TSAN_INTERCEPTOR(void, dispatch_group_notify_f, dispatch_group_t group, |
| 268 | dispatch_queue_t q, void *context, dispatch_function_t work) { |
| 269 | SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify_f, group, q, context, work); |
| 270 | tsan_block_context_t *new_context = AllocContext(thr, pc, q, context, work); |
| 271 | new_context->object_to_acquire = (uptr)group; |
| 272 | |
| 273 | // Will be released in dispatch_callback_wrap_acquire. |
| 274 | new_context->object_to_release = group; |
| 275 | dispatch_retain(group); |
| 276 | |
| 277 | Release(thr, pc, (uptr)group); |
| 278 | REAL(dispatch_group_notify_f)(group, q, new_context, |
| 279 | dispatch_callback_wrap_acquire); |
| 280 | } |
| 281 | |
| 282 | } // namespace __tsan |
| 283 | |
| 284 | #endif // SANITIZER_MAC |