forked from OSchip/llvm-project
220 lines
7.2 KiB
C++
220 lines
7.2 KiB
C++
//===-- 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
|