|
| 1 | +//===- nsan_threads.cpp ---------------------------------------------------===// |
| 2 | +// |
| 3 | +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | +// See https://llvm.org/LICENSE.txt for license information. |
| 5 | +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | +// |
| 7 | +//===----------------------------------------------------------------------===// |
| 8 | +// Thread management. |
| 9 | +//===----------------------------------------------------------------------===// |
| 10 | + |
| 11 | +#include "nsan_thread.h" |
| 12 | + |
| 13 | +#include <pthread.h> |
| 14 | + |
| 15 | +#include "nsan.h" |
| 16 | +#include "sanitizer_common/sanitizer_tls_get_addr.h" |
| 17 | + |
| 18 | +using namespace __nsan; |
| 19 | + |
| 20 | +NsanThread *NsanThread::Create(thread_callback_t start_routine, void *arg) { |
| 21 | + uptr PageSize = GetPageSizeCached(); |
| 22 | + uptr size = RoundUpTo(sizeof(NsanThread), PageSize); |
| 23 | + NsanThread *thread = (NsanThread *)MmapOrDie(size, __func__); |
| 24 | + thread->start_routine_ = start_routine; |
| 25 | + thread->arg_ = arg; |
| 26 | + thread->destructor_iterations_ = GetPthreadDestructorIterations(); |
| 27 | + |
| 28 | + return thread; |
| 29 | +} |
| 30 | + |
| 31 | +void NsanThread::SetThreadStackAndTls() { |
| 32 | + uptr tls_size = 0; |
| 33 | + uptr stack_size = 0; |
| 34 | + GetThreadStackAndTls(IsMainThread(), &stack_.bottom, &stack_size, &tls_begin_, |
| 35 | + &tls_size); |
| 36 | + stack_.top = stack_.bottom + stack_size; |
| 37 | + tls_end_ = tls_begin_ + tls_size; |
| 38 | + |
| 39 | + int local; |
| 40 | + CHECK(AddrIsInStack((uptr)&local)); |
| 41 | +} |
| 42 | + |
| 43 | +void NsanThread::ClearShadowForThreadStackAndTLS() { |
| 44 | + __nsan_set_value_unknown((const u8 *)stack_.bottom, |
| 45 | + stack_.top - stack_.bottom); |
| 46 | + if (tls_begin_ != tls_end_) |
| 47 | + __nsan_set_value_unknown((const u8 *)tls_begin_, tls_end_ - tls_begin_); |
| 48 | + DTLS *dtls = DTLS_Get(); |
| 49 | + CHECK_NE(dtls, 0); |
| 50 | + ForEachDVT(dtls, [](const DTLS::DTV &dtv, int id) { |
| 51 | + __nsan_set_value_unknown((const u8 *)dtv.beg, dtv.size); |
| 52 | + }); |
| 53 | +} |
| 54 | + |
| 55 | +void NsanThread::Init() { |
| 56 | + SetThreadStackAndTls(); |
| 57 | + ClearShadowForThreadStackAndTLS(); |
| 58 | +} |
| 59 | + |
| 60 | +void NsanThread::TSDDtor(void *tsd) { |
| 61 | + NsanThread *t = (NsanThread *)tsd; |
| 62 | + t->Destroy(); |
| 63 | +} |
| 64 | + |
| 65 | +void NsanThread::Destroy() { |
| 66 | + // We also clear the shadow on thread destruction because |
| 67 | + // some code may still be executing in later TSD destructors |
| 68 | + // and we don't want it to have any poisoned stack. |
| 69 | + ClearShadowForThreadStackAndTLS(); |
| 70 | + uptr size = RoundUpTo(sizeof(NsanThread), GetPageSizeCached()); |
| 71 | + UnmapOrDie(this, size); |
| 72 | + DTLS_Destroy(); |
| 73 | +} |
| 74 | + |
| 75 | +thread_return_t NsanThread::ThreadStart() { |
| 76 | + if (!start_routine_) { |
| 77 | + // start_routine_ == 0 if we're on the main thread or on one of the |
| 78 | + // OS X libdispatch worker threads. But nobody is supposed to call |
| 79 | + // ThreadStart() for the worker threads. |
| 80 | + return 0; |
| 81 | + } |
| 82 | + |
| 83 | + return start_routine_(arg_); |
| 84 | +} |
| 85 | + |
| 86 | +NsanThread::StackBounds NsanThread::GetStackBounds() const { |
| 87 | + if (!stack_switching_) |
| 88 | + return {stack_.bottom, stack_.top}; |
| 89 | + const uptr cur_stack = GET_CURRENT_FRAME(); |
| 90 | + // Note: need to check next stack first, because FinishSwitchFiber |
| 91 | + // may be in process of overwriting stack_.top/bottom_. But in such case |
| 92 | + // we are already on the next stack. |
| 93 | + if (cur_stack >= next_stack_.bottom && cur_stack < next_stack_.top) |
| 94 | + return {next_stack_.bottom, next_stack_.top}; |
| 95 | + return {stack_.bottom, stack_.top}; |
| 96 | +} |
| 97 | + |
| 98 | +uptr NsanThread::stack_top() { return GetStackBounds().top; } |
| 99 | + |
| 100 | +uptr NsanThread::stack_bottom() { return GetStackBounds().bottom; } |
| 101 | + |
| 102 | +bool NsanThread::AddrIsInStack(uptr addr) { |
| 103 | + const auto bounds = GetStackBounds(); |
| 104 | + return addr >= bounds.bottom && addr < bounds.top; |
| 105 | +} |
| 106 | + |
| 107 | +void NsanThread::StartSwitchFiber(uptr bottom, uptr size) { |
| 108 | + CHECK(!stack_switching_); |
| 109 | + next_stack_.bottom = bottom; |
| 110 | + next_stack_.top = bottom + size; |
| 111 | + stack_switching_ = true; |
| 112 | +} |
| 113 | + |
| 114 | +void NsanThread::FinishSwitchFiber(uptr *bottom_old, uptr *size_old) { |
| 115 | + CHECK(stack_switching_); |
| 116 | + if (bottom_old) |
| 117 | + *bottom_old = stack_.bottom; |
| 118 | + if (size_old) |
| 119 | + *size_old = stack_.top - stack_.bottom; |
| 120 | + stack_.bottom = next_stack_.bottom; |
| 121 | + stack_.top = next_stack_.top; |
| 122 | + stack_switching_ = false; |
| 123 | + next_stack_.top = 0; |
| 124 | + next_stack_.bottom = 0; |
| 125 | +} |
| 126 | + |
| 127 | +static pthread_key_t tsd_key; |
| 128 | +static bool tsd_key_inited; |
| 129 | + |
| 130 | +void __nsan::NsanTSDInit(void (*destructor)(void *tsd)) { |
| 131 | + CHECK(!tsd_key_inited); |
| 132 | + tsd_key_inited = true; |
| 133 | + CHECK_EQ(0, pthread_key_create(&tsd_key, destructor)); |
| 134 | +} |
| 135 | + |
| 136 | +static THREADLOCAL NsanThread *nsan_current_thread; |
| 137 | + |
| 138 | +NsanThread *__nsan::GetCurrentThread() { return nsan_current_thread; } |
| 139 | + |
| 140 | +void __nsan::SetCurrentThread(NsanThread *t) { |
| 141 | + // Make sure we do not reset the current NsanThread. |
| 142 | + CHECK_EQ(0, nsan_current_thread); |
| 143 | + nsan_current_thread = t; |
| 144 | + // Make sure that NsanTSDDtor gets called at the end. |
| 145 | + CHECK(tsd_key_inited); |
| 146 | + pthread_setspecific(tsd_key, t); |
| 147 | +} |
| 148 | + |
| 149 | +void __nsan::NsanTSDDtor(void *tsd) { |
| 150 | + NsanThread *t = (NsanThread *)tsd; |
| 151 | + if (t->destructor_iterations_ > 1) { |
| 152 | + t->destructor_iterations_--; |
| 153 | + CHECK_EQ(0, pthread_setspecific(tsd_key, tsd)); |
| 154 | + return; |
| 155 | + } |
| 156 | + nsan_current_thread = nullptr; |
| 157 | + // Make sure that signal handler can not see a stale current thread pointer. |
| 158 | + atomic_signal_fence(memory_order_seq_cst); |
| 159 | + NsanThread::TSDDtor(tsd); |
| 160 | +} |
0 commit comments