// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include <algorithm>
#include <iterator>
#include "base/atomicops.h"
#include "base/debug/leak_annotations.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_local_storage.h"
#include "base/trace_event/heap_profiler_allocation_context.h"
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include <sys/prctl.h>
#endif
namespace base {
namespace trace_event {
subtle::Atomic32 AllocationContextTracker::capture_mode_ =
static_cast<int32_t>(AllocationContextTracker::CaptureMode::DISABLED);
namespace {
const size_t kMaxStackDepth = 128u;
const size_t kMaxTaskDepth = 16u;
AllocationContextTracker* const kInitializingSentinel =
reinterpret_cast<AllocationContextTracker*>(-1);
ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER;
// This function is added to the TLS slot to clean up the instance when the
// thread exits.
void DestructAllocationContextTracker(void* alloc_ctx_tracker) {
delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker);
}
// Cannot call ThreadIdNameManager::GetName because it holds a lock and causes
// deadlock when lock is already held by ThreadIdNameManager before the current
// allocation. Gets the thread name from kernel if available or returns a string
// with id. This function intenionally leaks the allocated strings since they
// are used to tag allocations even after the thread dies.
const char* GetAndLeakThreadName() {
char name[16];
#if defined(OS_LINUX) || defined(OS_ANDROID)
// If the thread name is not set, try to get it from prctl. Thread name might
// not be set in cases where the thread started before heap profiling was
// enabled.
int err = prctl(PR_GET_NAME, name);
if (!err) {
return strdup(name);
}
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
// Use tid if we don't have a thread name.
snprintf(name, sizeof(name), "%lu",
static_cast<unsigned long>(PlatformThread::CurrentId()));
return strdup(name);
}
} // namespace
// static
AllocationContextTracker*
AllocationContextTracker::GetInstanceForCurrentThread() {
AllocationContextTracker* tracker =
static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get());
if (tracker == kInitializingSentinel)
return nullptr; // Re-entrancy case.
if (!tracker) {
g_tls_alloc_ctx_tracker.Set(kInitializingSentinel);
tracker = new AllocationContextTracker();
g_tls_alloc_ctx_tracker.Set(tracker);
}
return tracker;
}
AllocationContextTracker::AllocationContextTracker()
: thread_name_(nullptr), ignore_scope_depth_(0) {
pseudo_stack_.reserve(kMaxStackDepth);
task_contexts_.reserve(kMaxTaskDepth);
}
AllocationContextTracker::~AllocationContextTracker() {}
// static
void AllocationContextTracker::SetCurrentThreadName(const char* name) {
if (name && capture_mode() != CaptureMode::DISABLED) {
GetInstanceForCurrentThread()->thread_name_ = name;
}
}
// static
void AllocationContextTracker::SetCaptureMode(CaptureMode mode) {
// When enabling capturing, also initialize the TLS slot. This does not create
// a TLS instance yet.
if (mode != CaptureMode::DISABLED && !g_tls_alloc_ctx_tracker.initialized())
g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker);
// Release ordering ensures that when a thread observes |capture_mode_| to
// be true through an acquire load, the TLS slot has been initialized.
subtle::Release_Store(&capture_mode_, static_cast<int32_t>(mode));
}
void AllocationContextTracker::PushPseudoStackFrame(
AllocationContextTracker::PseudoStackFrame stack_frame) {
// Impose a limit on the height to verify that every push is popped, because
// in practice the pseudo stack never grows higher than ~20 frames.
if (pseudo_stack_.size() < kMaxStackDepth)
pseudo_stack_.push_back(stack_frame);
else
NOTREACHED();
}
void AllocationContextTracker::PopPseudoStackFrame(
AllocationContextTracker::PseudoStackFrame stack_frame) {
// Guard for stack underflow. If tracing was started with a TRACE_EVENT in
// scope, the frame was never pushed, so it is possible that pop is called
// on an empty stack.
if (pseudo_stack_.empty())
return;
// Assert that pushes and pops are nested correctly. This DCHECK can be
// hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call
// without a corresponding TRACE_EVENT_BEGIN).
DCHECK(stack_frame == pseudo_stack_.back())
<< "Encountered an unmatched TRACE_EVENT_END: "
<< stack_frame.trace_event_name
<< " vs event in stack: " << pseudo_stack_.back().trace_event_name;
pseudo_stack_.pop_back();
}
void AllocationContextTracker::PushCurrentTaskContext(const char* context) {
DCHECK(context);
if (task_contexts_.size() < kMaxTaskDepth)
task_contexts_.push_back(context);
else
NOTREACHED();
}
void AllocationContextTracker::PopCurrentTaskContext(const char* context) {
// Guard for stack underflow. If tracing was started with a TRACE_EVENT in
// scope, the context was never pushed, so it is possible that pop is called
// on an empty stack.
if (task_contexts_.empty())
return;
DCHECK_EQ(context, task_contexts_.back())
<< "Encountered an unmatched context end";
task_contexts_.pop_back();
}
// static
bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) {
if (ignore_scope_depth_)
return false;
CaptureMode mode = static_cast<CaptureMode>(
subtle::NoBarrier_Load(&capture_mode_));
auto* backtrace = std::begin(ctx->backtrace.frames);
auto* backtrace_end = std::end(ctx->backtrace.frames);
if (!thread_name_) {
// Ignore the string allocation made by GetAndLeakThreadName to avoid
// reentrancy.
ignore_scope_depth_++;
thread_name_ = GetAndLeakThreadName();
ANNOTATE_LEAKING_OBJECT_PTR(thread_name_);
DCHECK(thread_name_);
ignore_scope_depth_--;
}
// Add the thread name as the first entry in pseudo stack.
if (thread_name_) {
*backtrace++ = StackFrame::FromThreadName(thread_name_);
}
switch (mode) {
case CaptureMode::DISABLED:
{
break;
}
case CaptureMode::PSEUDO_STACK:
{
for (const PseudoStackFrame& stack_frame : pseudo_stack_) {
if (backtrace == backtrace_end) {
break;
}
*backtrace++ =
StackFrame::FromTraceEventName(stack_frame.trace_event_name);
}
break;
}
case CaptureMode::NATIVE_STACK:
{
// Backtrace contract requires us to return bottom frames, i.e.
// from main() and up. Stack unwinding produces top frames, i.e.
// from this point and up until main(). We request many frames to
// make sure we reach main(), and then copy bottom portion of them.
const void* frames[128];
static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount,
"not requesting enough frames to fill Backtrace");
#if HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_NACL)
size_t frame_count = debug::TraceStackFramePointers(
frames,
arraysize(frames),
1 /* exclude this function from the trace */ );
#else
size_t frame_count = 0;
NOTREACHED();
#endif
// Copy frames backwards
size_t backtrace_capacity = backtrace_end - backtrace;
int32_t top_frame_index = (backtrace_capacity >= frame_count)
? 0
: frame_count - backtrace_capacity;
for (int32_t i = frame_count - 1; i >= top_frame_index; --i) {
const void* frame = frames[i];
*backtrace++ = StackFrame::FromProgramCounter(frame);
}
break;
}
}
ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames);
// TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension
// (component name) in the heap profiler and not piggy back on the type name.
if (!task_contexts_.empty()) {
ctx->type_name = task_contexts_.back();
} else if (!pseudo_stack_.empty()) {
// If task context was unavailable, then the category names are taken from
// trace events.
ctx->type_name = pseudo_stack_.back().trace_event_category;
} else {
ctx->type_name = nullptr;
}
return true;
}
} // namespace trace_event
} // namespace base