1 | //===-- memprof_thread.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 | // |
9 | // This file is a part of MemProfiler, a memory profiler. |
10 | // |
11 | // Thread-related code. |
12 | //===----------------------------------------------------------------------===// |
13 | #include "memprof_thread.h" |
14 | #include "memprof_allocator.h" |
15 | #include "memprof_interceptors.h" |
16 | #include "memprof_mapping.h" |
17 | #include "memprof_stack.h" |
18 | #include "sanitizer_common/sanitizer_common.h" |
19 | #include "sanitizer_common/sanitizer_placement_new.h" |
20 | #include "sanitizer_common/sanitizer_stackdepot.h" |
21 | #include "sanitizer_common/sanitizer_tls_get_addr.h" |
22 | |
23 | namespace __memprof { |
24 | |
25 | // MemprofThreadContext implementation. |
26 | |
27 | void MemprofThreadContext::OnCreated(void *arg) { |
28 | CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg); |
29 | if (args->stack) |
30 | stack_id = StackDepotPut(stack: *args->stack); |
31 | thread = args->thread; |
32 | thread->set_context(this); |
33 | } |
34 | |
35 | void MemprofThreadContext::OnFinished() { |
36 | // Drop the link to the MemprofThread object. |
37 | thread = nullptr; |
38 | } |
39 | |
40 | static ALIGNED(16) char thread_registry_placeholder[sizeof(ThreadRegistry)]; |
41 | static ThreadRegistry *memprof_thread_registry; |
42 | |
43 | static Mutex mu_for_thread_context; |
44 | static LowLevelAllocator allocator_for_thread_context; |
45 | |
46 | static ThreadContextBase *GetMemprofThreadContext(u32 tid) { |
47 | Lock lock(&mu_for_thread_context); |
48 | return new (allocator_for_thread_context) MemprofThreadContext(tid); |
49 | } |
50 | |
51 | ThreadRegistry &memprofThreadRegistry() { |
52 | static bool initialized; |
53 | // Don't worry about thread_safety - this should be called when there is |
54 | // a single thread. |
55 | if (!initialized) { |
56 | // Never reuse MemProf threads: we store pointer to MemprofThreadContext |
57 | // in TSD and can't reliably tell when no more TSD destructors will |
58 | // be called. It would be wrong to reuse MemprofThreadContext for another |
59 | // thread before all TSD destructors will be called for it. |
60 | memprof_thread_registry = new (thread_registry_placeholder) |
61 | ThreadRegistry(GetMemprofThreadContext); |
62 | initialized = true; |
63 | } |
64 | return *memprof_thread_registry; |
65 | } |
66 | |
67 | MemprofThreadContext *GetThreadContextByTidLocked(u32 tid) { |
68 | return static_cast<MemprofThreadContext *>( |
69 | memprofThreadRegistry().GetThreadLocked(tid)); |
70 | } |
71 | |
72 | // MemprofThread implementation. |
73 | |
74 | MemprofThread *MemprofThread::Create(thread_callback_t start_routine, void *arg, |
75 | u32 parent_tid, StackTrace *stack, |
76 | bool detached) { |
77 | uptr PageSize = GetPageSizeCached(); |
78 | uptr size = RoundUpTo(size: sizeof(MemprofThread), boundary: PageSize); |
79 | MemprofThread *thread = (MemprofThread *)MmapOrDie(size, mem_type: __func__); |
80 | thread->start_routine_ = start_routine; |
81 | thread->arg_ = arg; |
82 | MemprofThreadContext::CreateThreadContextArgs args = {.thread: thread, .stack: stack}; |
83 | memprofThreadRegistry().CreateThread(user_id: 0, detached, parent_tid, arg: &args); |
84 | |
85 | return thread; |
86 | } |
87 | |
88 | void MemprofThread::TSDDtor(void *tsd) { |
89 | MemprofThreadContext *context = (MemprofThreadContext *)tsd; |
90 | VReport(1, "T%d TSDDtor\n" , context->tid); |
91 | if (context->thread) |
92 | context->thread->Destroy(); |
93 | } |
94 | |
95 | void MemprofThread::Destroy() { |
96 | int tid = this->tid(); |
97 | VReport(1, "T%d exited\n" , tid); |
98 | |
99 | malloc_storage().CommitBack(); |
100 | memprofThreadRegistry().FinishThread(tid); |
101 | FlushToDeadThreadStats(stats: &stats_); |
102 | uptr size = RoundUpTo(size: sizeof(MemprofThread), boundary: GetPageSizeCached()); |
103 | UnmapOrDie(addr: this, size); |
104 | DTLS_Destroy(); |
105 | } |
106 | |
107 | inline MemprofThread::StackBounds MemprofThread::GetStackBounds() const { |
108 | if (stack_bottom_ >= stack_top_) |
109 | return {.bottom: 0, .top: 0}; |
110 | return {.bottom: stack_bottom_, .top: stack_top_}; |
111 | } |
112 | |
113 | uptr MemprofThread::stack_top() { return GetStackBounds().top; } |
114 | |
115 | uptr MemprofThread::stack_bottom() { return GetStackBounds().bottom; } |
116 | |
117 | uptr MemprofThread::stack_size() { |
118 | const auto bounds = GetStackBounds(); |
119 | return bounds.top - bounds.bottom; |
120 | } |
121 | |
122 | void MemprofThread::Init(const InitOptions *options) { |
123 | CHECK_EQ(this->stack_size(), 0U); |
124 | SetThreadStackAndTls(options); |
125 | if (stack_top_ != stack_bottom_) { |
126 | CHECK_GT(this->stack_size(), 0U); |
127 | CHECK(AddrIsInMem(stack_bottom_)); |
128 | CHECK(AddrIsInMem(stack_top_ - 1)); |
129 | } |
130 | int local = 0; |
131 | VReport(1, "T%d: stack [%p,%p) size 0x%zx; local=%p\n" , tid(), |
132 | (void *)stack_bottom_, (void *)stack_top_, stack_top_ - stack_bottom_, |
133 | (void *)&local); |
134 | } |
135 | |
136 | thread_return_t |
137 | MemprofThread::ThreadStart(tid_t os_id, |
138 | atomic_uintptr_t *signal_thread_is_registered) { |
139 | Init(); |
140 | memprofThreadRegistry().StartThread(tid: tid(), os_id, thread_type: ThreadType::Regular, |
141 | arg: nullptr); |
142 | if (signal_thread_is_registered) |
143 | atomic_store(a: signal_thread_is_registered, v: 1, mo: memory_order_release); |
144 | |
145 | if (!start_routine_) { |
146 | // start_routine_ == 0 if we're on the main thread or on one of the |
147 | // OS X libdispatch worker threads. But nobody is supposed to call |
148 | // ThreadStart() for the worker threads. |
149 | CHECK_EQ(tid(), 0); |
150 | return 0; |
151 | } |
152 | |
153 | return start_routine_(arg_); |
154 | } |
155 | |
156 | MemprofThread *CreateMainThread() { |
157 | MemprofThread *main_thread = MemprofThread::Create( |
158 | /* start_routine */ nullptr, /* arg */ nullptr, /* parent_tid */ kMainTid, |
159 | /* stack */ nullptr, /* detached */ true); |
160 | SetCurrentThread(main_thread); |
161 | main_thread->ThreadStart(os_id: internal_getpid(), |
162 | /* signal_thread_is_registered */ nullptr); |
163 | return main_thread; |
164 | } |
165 | |
166 | // This implementation doesn't use the argument, which is just passed down |
167 | // from the caller of Init (which see, above). It's only there to support |
168 | // OS-specific implementations that need more information passed through. |
169 | void MemprofThread::SetThreadStackAndTls(const InitOptions *options) { |
170 | DCHECK_EQ(options, nullptr); |
171 | uptr tls_size = 0; |
172 | uptr stack_size = 0; |
173 | GetThreadStackAndTls(main: tid() == kMainTid, stk_addr: &stack_bottom_, stk_size: &stack_size, |
174 | tls_addr: &tls_begin_, tls_size: &tls_size); |
175 | stack_top_ = stack_bottom_ + stack_size; |
176 | tls_end_ = tls_begin_ + tls_size; |
177 | dtls_ = DTLS_Get(); |
178 | |
179 | if (stack_top_ != stack_bottom_) { |
180 | int local; |
181 | CHECK(AddrIsInStack((uptr)&local)); |
182 | } |
183 | } |
184 | |
185 | bool MemprofThread::AddrIsInStack(uptr addr) { |
186 | const auto bounds = GetStackBounds(); |
187 | return addr >= bounds.bottom && addr < bounds.top; |
188 | } |
189 | |
190 | MemprofThread *GetCurrentThread() { |
191 | MemprofThreadContext *context = |
192 | reinterpret_cast<MemprofThreadContext *>(TSDGet()); |
193 | if (!context) |
194 | return nullptr; |
195 | return context->thread; |
196 | } |
197 | |
198 | void SetCurrentThread(MemprofThread *t) { |
199 | CHECK(t->context()); |
200 | VReport(2, "SetCurrentThread: %p for thread %p\n" , (void *)t->context(), |
201 | (void *)GetThreadSelf()); |
202 | // Make sure we do not reset the current MemprofThread. |
203 | CHECK_EQ(0, TSDGet()); |
204 | TSDSet(tsd: t->context()); |
205 | CHECK_EQ(t->context(), TSDGet()); |
206 | } |
207 | |
208 | u32 GetCurrentTidOrInvalid() { |
209 | MemprofThread *t = GetCurrentThread(); |
210 | return t ? t->tid() : kInvalidTid; |
211 | } |
212 | |
213 | void EnsureMainThreadIDIsCorrect() { |
214 | MemprofThreadContext *context = |
215 | reinterpret_cast<MemprofThreadContext *>(TSDGet()); |
216 | if (context && (context->tid == kMainTid)) |
217 | context->os_id = GetTid(); |
218 | } |
219 | } // namespace __memprof |
220 | |