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