| //===-- memprof_thread.cpp -----------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is a part of MemProfiler, a memory profiler. |
| // |
| // Thread-related code. |
| //===----------------------------------------------------------------------===// |
| #include "memprof_thread.h" |
| #include "memprof_allocator.h" |
| #include "memprof_interceptors.h" |
| #include "memprof_mapping.h" |
| #include "memprof_stack.h" |
| #include "sanitizer_common/sanitizer_common.h" |
| #include "sanitizer_common/sanitizer_placement_new.h" |
| #include "sanitizer_common/sanitizer_stackdepot.h" |
| #include "sanitizer_common/sanitizer_tls_get_addr.h" |
| |
| namespace __memprof { |
| |
| // MemprofThreadContext implementation. |
| |
| void MemprofThreadContext::OnCreated(void *arg) { |
| CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg); |
| if (args->stack) |
| stack_id = StackDepotPut(*args->stack); |
| thread = args->thread; |
| thread->set_context(this); |
| } |
| |
| void MemprofThreadContext::OnFinished() { |
| // Drop the link to the MemprofThread object. |
| thread = nullptr; |
| } |
| |
| static ALIGNED(16) char thread_registry_placeholder[sizeof(ThreadRegistry)]; |
| static ThreadRegistry *memprof_thread_registry; |
| |
| static Mutex mu_for_thread_context; |
| static LowLevelAllocator allocator_for_thread_context; |
| |
| static ThreadContextBase *GetMemprofThreadContext(u32 tid) { |
| Lock lock(&mu_for_thread_context); |
| return new (allocator_for_thread_context) MemprofThreadContext(tid); |
| } |
| |
| ThreadRegistry &memprofThreadRegistry() { |
| static bool initialized; |
| // Don't worry about thread_safety - this should be called when there is |
| // a single thread. |
| if (!initialized) { |
| // Never reuse MemProf threads: we store pointer to MemprofThreadContext |
| // in TSD and can't reliably tell when no more TSD destructors will |
| // be called. It would be wrong to reuse MemprofThreadContext for another |
| // thread before all TSD destructors will be called for it. |
| memprof_thread_registry = new (thread_registry_placeholder) |
| ThreadRegistry(GetMemprofThreadContext); |
| initialized = true; |
| } |
| return *memprof_thread_registry; |
| } |
| |
| MemprofThreadContext *GetThreadContextByTidLocked(u32 tid) { |
| return static_cast<MemprofThreadContext *>( |
| memprofThreadRegistry().GetThreadLocked(tid)); |
| } |
| |
| // MemprofThread implementation. |
| |
| MemprofThread *MemprofThread::Create(thread_callback_t start_routine, void *arg, |
| u32 parent_tid, StackTrace *stack, |
| bool detached) { |
| uptr PageSize = GetPageSizeCached(); |
| uptr size = RoundUpTo(sizeof(MemprofThread), PageSize); |
| MemprofThread *thread = (MemprofThread *)MmapOrDie(size, __func__); |
| thread->start_routine_ = start_routine; |
| thread->arg_ = arg; |
| MemprofThreadContext::CreateThreadContextArgs args = {thread, stack}; |
| memprofThreadRegistry().CreateThread(0, detached, parent_tid, &args); |
| |
| return thread; |
| } |
| |
| void MemprofThread::TSDDtor(void *tsd) { |
| MemprofThreadContext *context = (MemprofThreadContext *)tsd; |
| VReport(1, "T%d TSDDtor\n", context->tid); |
| if (context->thread) |
| context->thread->Destroy(); |
| } |
| |
| void MemprofThread::Destroy() { |
| int tid = this->tid(); |
| VReport(1, "T%d exited\n", tid); |
| |
| malloc_storage().CommitBack(); |
| memprofThreadRegistry().FinishThread(tid); |
| FlushToDeadThreadStats(&stats_); |
| uptr size = RoundUpTo(sizeof(MemprofThread), GetPageSizeCached()); |
| UnmapOrDie(this, size); |
| DTLS_Destroy(); |
| } |
| |
| inline MemprofThread::StackBounds MemprofThread::GetStackBounds() const { |
| if (stack_bottom_ >= stack_top_) |
| return {0, 0}; |
| return {stack_bottom_, stack_top_}; |
| } |
| |
| uptr MemprofThread::stack_top() { return GetStackBounds().top; } |
| |
| uptr MemprofThread::stack_bottom() { return GetStackBounds().bottom; } |
| |
| uptr MemprofThread::stack_size() { |
| const auto bounds = GetStackBounds(); |
| return bounds.top - bounds.bottom; |
| } |
| |
| void MemprofThread::Init(const InitOptions *options) { |
| CHECK_EQ(this->stack_size(), 0U); |
| SetThreadStackAndTls(options); |
| if (stack_top_ != stack_bottom_) { |
| CHECK_GT(this->stack_size(), 0U); |
| CHECK(AddrIsInMem(stack_bottom_)); |
| CHECK(AddrIsInMem(stack_top_ - 1)); |
| } |
| int local = 0; |
| VReport(1, "T%d: stack [%p,%p) size 0x%zx; local=%p\n", tid(), |
| (void *)stack_bottom_, (void *)stack_top_, stack_top_ - stack_bottom_, |
| (void *)&local); |
| } |
| |
| thread_return_t |
| MemprofThread::ThreadStart(tid_t os_id, |
| atomic_uintptr_t *signal_thread_is_registered) { |
| Init(); |
| memprofThreadRegistry().StartThread(tid(), os_id, ThreadType::Regular, |
| nullptr); |
| if (signal_thread_is_registered) |
| atomic_store(signal_thread_is_registered, 1, memory_order_release); |
| |
| if (!start_routine_) { |
| // start_routine_ == 0 if we're on the main thread or on one of the |
| // OS X libdispatch worker threads. But nobody is supposed to call |
| // ThreadStart() for the worker threads. |
| CHECK_EQ(tid(), 0); |
| return 0; |
| } |
| |
| return start_routine_(arg_); |
| } |
| |
| MemprofThread *CreateMainThread() { |
| MemprofThread *main_thread = MemprofThread::Create( |
| /* start_routine */ nullptr, /* arg */ nullptr, /* parent_tid */ kMainTid, |
| /* stack */ nullptr, /* detached */ true); |
| SetCurrentThread(main_thread); |
| main_thread->ThreadStart(internal_getpid(), |
| /* signal_thread_is_registered */ nullptr); |
| return main_thread; |
| } |
| |
| // This implementation doesn't use the argument, which is just passed down |
| // from the caller of Init (which see, above). It's only there to support |
| // OS-specific implementations that need more information passed through. |
| void MemprofThread::SetThreadStackAndTls(const InitOptions *options) { |
| DCHECK_EQ(options, nullptr); |
| uptr tls_size = 0; |
| uptr stack_size = 0; |
| GetThreadStackAndTls(tid() == kMainTid, &stack_bottom_, &stack_size, |
| &tls_begin_, &tls_size); |
| stack_top_ = stack_bottom_ + stack_size; |
| tls_end_ = tls_begin_ + tls_size; |
| dtls_ = DTLS_Get(); |
| |
| if (stack_top_ != stack_bottom_) { |
| int local; |
| CHECK(AddrIsInStack((uptr)&local)); |
| } |
| } |
| |
| bool MemprofThread::AddrIsInStack(uptr addr) { |
| const auto bounds = GetStackBounds(); |
| return addr >= bounds.bottom && addr < bounds.top; |
| } |
| |
| MemprofThread *GetCurrentThread() { |
| MemprofThreadContext *context = |
| reinterpret_cast<MemprofThreadContext *>(TSDGet()); |
| if (!context) |
| return nullptr; |
| return context->thread; |
| } |
| |
| void SetCurrentThread(MemprofThread *t) { |
| CHECK(t->context()); |
| VReport(2, "SetCurrentThread: %p for thread %p\n", (void *)t->context(), |
| (void *)GetThreadSelf()); |
| // Make sure we do not reset the current MemprofThread. |
| CHECK_EQ(0, TSDGet()); |
| TSDSet(t->context()); |
| CHECK_EQ(t->context(), TSDGet()); |
| } |
| |
| u32 GetCurrentTidOrInvalid() { |
| MemprofThread *t = GetCurrentThread(); |
| return t ? t->tid() : kInvalidTid; |
| } |
| |
| void EnsureMainThreadIDIsCorrect() { |
| MemprofThreadContext *context = |
| reinterpret_cast<MemprofThreadContext *>(TSDGet()); |
| if (context && (context->tid == kMainTid)) |
| context->os_id = GetTid(); |
| } |
| } // namespace __memprof |