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