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  CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg);
29  if (args->stack)
30    stack_id = StackDepotPut(*args->stack);
31  thread = args->thread;
32  thread->set_context(this);
33}
34
35void MemprofThreadContext::OnFinished() {
36  // Drop the link to the MemprofThread object.
37  thread = nullptr;
38}
39
40static ALIGNED(16) char thread_registry_placeholder[sizeof(ThreadRegistry)];
41static ThreadRegistry *memprof_thread_registry;
42
43static Mutex mu_for_thread_context;
44static LowLevelAllocator allocator_for_thread_context;
45
46static ThreadContextBase *GetMemprofThreadContext(u32 tid) {
47  Lock lock(&mu_for_thread_context);
48  return new (allocator_for_thread_context) MemprofThreadContext(tid);
49}
50
51ThreadRegistry &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
67MemprofThreadContext *GetThreadContextByTidLocked(u32 tid) {
68  return static_cast<MemprofThreadContext *>(
69      memprofThreadRegistry().GetThreadLocked(tid));
70}
71
72// MemprofThread implementation.
73
74MemprofThread *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(sizeof(MemprofThread), PageSize);
79  MemprofThread *thread = (MemprofThread *)MmapOrDie(size, __func__);
80  thread->start_routine_ = start_routine;
81  thread->arg_ = arg;
82  MemprofThreadContext::CreateThreadContextArgs args = {thread, stack};
83  memprofThreadRegistry().CreateThread(0, detached, parent_tid, &args);
84
85  return thread;
86}
87
88void 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
95void 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_);
102  uptr size = RoundUpTo(sizeof(MemprofThread), GetPageSizeCached());
103  UnmapOrDie(this, size);
104  DTLS_Destroy();
105}
106
107inline MemprofThread::StackBounds MemprofThread::GetStackBounds() const {
108  if (stack_bottom_ >= stack_top_)
109    return {0, 0};
110  return {stack_bottom_, stack_top_};
111}
112
113uptr MemprofThread::stack_top() { return GetStackBounds().top; }
114
115uptr MemprofThread::stack_bottom() { return GetStackBounds().bottom; }
116
117uptr MemprofThread::stack_size() {
118  const auto bounds = GetStackBounds();
119  return bounds.top - bounds.bottom;
120}
121
122void 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
136thread_return_t
137MemprofThread::ThreadStart(tid_t os_id,
138                           atomic_uintptr_t *signal_thread_is_registered) {
139  Init();
140  memprofThreadRegistry().StartThread(tid(), os_id, ThreadType::Regular,
141                                      nullptr);
142  if (signal_thread_is_registered)
143    atomic_store(signal_thread_is_registered, 1, 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
156MemprofThread *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(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.
169void MemprofThread::SetThreadStackAndTls(const InitOptions *options) {
170  DCHECK_EQ(options, nullptr);
171  uptr tls_size = 0;
172  uptr stack_size = 0;
173  GetThreadStackAndTls(tid() == kMainTid, &stack_bottom_, &stack_size,
174                       &tls_begin_, &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
185bool MemprofThread::AddrIsInStack(uptr addr) {
186  const auto bounds = GetStackBounds();
187  return addr >= bounds.bottom && addr < bounds.top;
188}
189
190MemprofThread *GetCurrentThread() {
191  MemprofThreadContext *context =
192      reinterpret_cast<MemprofThreadContext *>(TSDGet());
193  if (!context)
194    return nullptr;
195  return context->thread;
196}
197
198void 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(t->context());
205  CHECK_EQ(t->context(), TSDGet());
206}
207
208u32 GetCurrentTidOrInvalid() {
209  MemprofThread *t = GetCurrentThread();
210  return t ? t->tid() : kInvalidTid;
211}
212
213void 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