//===-- 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(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 BlockingMutex mu_for_thread_context(LINKER_INITIALIZED); static LowLevelAllocator allocator_for_thread_context; static ThreadContextBase *GetMemprofThreadContext(u32 tid) { BlockingMutexLock 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, kMaxNumberOfThreads, kMaxNumberOfThreads); initialized = true; } return *memprof_thread_registry; } MemprofThreadContext *GetThreadContextByTidLocked(u32 tid) { return static_cast( 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(*reinterpret_cast(thread), 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_, &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 */ 0, /* 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() == 0, &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(TSDGet()); if (!context) return nullptr; return context->thread; } void SetCurrentThread(MemprofThread *t) { CHECK(t->context()); VReport(2, "SetCurrentThread: %p for thread %p\n", 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(TSDGet()); if (context && (context->tid == 0)) context->os_id = GetTid(); } } // namespace __memprof