Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" | 5 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <iterator> | 8 #include <iterator> |
| 9 | 9 |
| 10 #include "base/allocator/features.h" | |
| 10 #include "base/atomicops.h" | 11 #include "base/atomicops.h" |
| 12 #include "base/debug/stack_trace.h" | |
| 11 #include "base/threading/thread_local_storage.h" | 13 #include "base/threading/thread_local_storage.h" |
| 12 #include "base/trace_event/heap_profiler_allocation_context.h" | 14 #include "base/trace_event/heap_profiler_allocation_context.h" |
| 13 | 15 |
| 14 namespace base { | 16 namespace base { |
| 15 namespace trace_event { | 17 namespace trace_event { |
| 16 | 18 |
| 17 subtle::Atomic32 AllocationContextTracker::capture_enabled_ = 0; | 19 subtle::Atomic32 AllocationContextTracker::capture_enabled_ = 0; |
| 18 | 20 |
| 19 namespace { | 21 namespace { |
| 20 | 22 |
| 21 const size_t kMaxStackDepth = 128u; | 23 const size_t kMaxStackDepth = 128u; |
| 22 AllocationContextTracker* const kInitializingSentinel = | 24 AllocationContextTracker* const kInitializingSentinel = |
| 23 reinterpret_cast<AllocationContextTracker*>(-1); | 25 reinterpret_cast<AllocationContextTracker*>(-1); |
| 24 | 26 |
| 25 ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; | 27 ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; |
| 26 | 28 |
| 27 // This function is added to the TLS slot to clean up the instance when the | 29 // This function is added to the TLS slot to clean up the instance when the |
| 28 // thread exits. | 30 // thread exits. |
| 29 void DestructAllocationContextTracker(void* alloc_ctx_tracker) { | 31 void DestructAllocationContextTracker(void* alloc_ctx_tracker) { |
| 30 delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); | 32 delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); |
| 31 } | 33 } |
| 32 | 34 |
| 35 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATION_TRACING) && !defined(OS_NACL) | |
| 36 | |
| 37 // Walks stack frames and collects a trace. Unlike base::debug::StackTrace | |
| 38 // (which uses unwinding) this function uses frame pointers and is fast. | |
| 39 // However, by default frame pointers are not enabled in optimized builds | |
| 40 // unless -fno-omit-frame-pointer flag is specified. | |
| 41 // Adapted from TCMalloc's stacktrace_x86-inl.h | |
| 42 size_t WalkStackFrames(const void** trace, | |
|
Primiano Tucci (use gerrit)
2016/04/01 15:56:28
would be nice to make this available as a general
Dmitry Skiba
2016/04/04 22:38:34
Yes, I would prefer to make it available through S
| |
| 43 size_t max_depth, | |
| 44 size_t skip_count) { | |
| 45 uintptr_t sp = reinterpret_cast<uintptr_t>( | |
| 46 __builtin_frame_address(0)); | |
| 47 | |
| 48 size_t n = 0; | |
| 49 while (sp && n < max_depth) { | |
| 50 if (reinterpret_cast<void**>(sp)[1] == nullptr) { | |
| 51 // In 64-bit code, we often see a frame that | |
| 52 // points to itself and has a return address of 0. | |
| 53 break; | |
| 54 } | |
| 55 | |
| 56 if (skip_count > 0) { | |
| 57 skip_count--; | |
| 58 } else { | |
| 59 trace[n] = reinterpret_cast<void**>(sp)[1]; | |
| 60 n++; | |
| 61 } | |
| 62 | |
| 63 { | |
| 64 uintptr_t next_sp = reinterpret_cast<uintptr_t*>(sp)[0]; | |
| 65 | |
| 66 // With the stack growing downwards, older stack frame must be | |
| 67 // at a greater address that the current one. | |
| 68 if (next_sp <= sp) break; | |
| 69 | |
| 70 // Assume stack frames larger than 100,000 bytes are bogus. | |
| 71 if (next_sp - sp > 100000) break; | |
| 72 | |
| 73 // Check alignment. | |
| 74 if (next_sp & (sizeof(void*) - 1)) break; | |
| 75 | |
| 76 #ifdef __i386__ | |
| 77 // On 64-bit machines, the stack pointer can be very close to | |
| 78 // 0xffffffff, so we explicitly check for a pointer into the | |
| 79 // last two pages in the address space | |
| 80 if (next_sp >= 0xffffe000) break; | |
| 81 #endif | |
| 82 | |
| 83 sp = next_sp; | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 return n; | |
| 88 } | |
| 89 | |
| 90 #endif // USE_EXPERIMENTAL_ALLOCATION_TRACING | |
| 91 | |
| 33 } // namespace | 92 } // namespace |
| 34 | 93 |
| 35 // static | 94 // static |
| 36 AllocationContextTracker* | 95 AllocationContextTracker* |
| 37 AllocationContextTracker::GetInstanceForCurrentThread() { | 96 AllocationContextTracker::GetInstanceForCurrentThread() { |
| 38 AllocationContextTracker* tracker = | 97 AllocationContextTracker* tracker = |
| 39 static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get()); | 98 static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get()); |
| 40 if (tracker == kInitializingSentinel) | 99 if (tracker == kInitializingSentinel) |
| 41 return nullptr; // Re-entrancy case. | 100 return nullptr; // Re-entrancy case. |
| 42 | 101 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 57 // static | 116 // static |
| 58 void AllocationContextTracker::SetCaptureEnabled(bool enabled) { | 117 void AllocationContextTracker::SetCaptureEnabled(bool enabled) { |
| 59 // When enabling capturing, also initialize the TLS slot. This does not create | 118 // When enabling capturing, also initialize the TLS slot. This does not create |
| 60 // a TLS instance yet. | 119 // a TLS instance yet. |
| 61 if (enabled && !g_tls_alloc_ctx_tracker.initialized()) | 120 if (enabled && !g_tls_alloc_ctx_tracker.initialized()) |
| 62 g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); | 121 g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); |
| 63 | 122 |
| 64 // Release ordering ensures that when a thread observes |capture_enabled_| to | 123 // Release ordering ensures that when a thread observes |capture_enabled_| to |
| 65 // be true through an acquire load, the TLS slot has been initialized. | 124 // be true through an acquire load, the TLS slot has been initialized. |
| 66 subtle::Release_Store(&capture_enabled_, enabled); | 125 subtle::Release_Store(&capture_enabled_, enabled); |
| 126 | |
| 127 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATION_TRACING) && !defined(OS_NACL) | |
| 128 if (enabled) { | |
| 129 // Dummy StackTrace usage, just in case stack trace machinery needs to | |
| 130 // allocate memory during initialization (e.g. backtrace() does that). | |
|
Primiano Tucci (use gerrit)
2016/04/01 15:56:28
where is backtrace() called from?
Dmitry Skiba
2016/04/04 22:38:34
From StackTrace constructor. StackTrace is actuall
| |
| 131 debug::StackTrace warmup; | |
| 132 } | |
| 133 #endif | |
| 67 } | 134 } |
| 68 | 135 |
| 69 void AllocationContextTracker::PushPseudoStackFrame(StackFrame frame) { | 136 void AllocationContextTracker::PushPseudoStackFrame(StackFrame frame) { |
| 70 // Impose a limit on the height to verify that every push is popped, because | 137 // Impose a limit on the height to verify that every push is popped, because |
| 71 // in practice the pseudo stack never grows higher than ~20 frames. | 138 // in practice the pseudo stack never grows higher than ~20 frames. |
| 72 if (pseudo_stack_.size() < kMaxStackDepth) | 139 if (pseudo_stack_.size() < kMaxStackDepth) |
| 73 pseudo_stack_.push_back(frame); | 140 pseudo_stack_.push_back(frame); |
| 74 else | 141 else |
| 75 NOTREACHED(); | 142 NOTREACHED(); |
| 76 } | 143 } |
| 77 | 144 |
| 78 // static | |
| 79 void AllocationContextTracker::PopPseudoStackFrame(StackFrame frame) { | 145 void AllocationContextTracker::PopPseudoStackFrame(StackFrame frame) { |
| 80 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in | 146 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in |
| 81 // scope, the frame was never pushed, so it is possible that pop is called | 147 // scope, the frame was never pushed, so it is possible that pop is called |
| 82 // on an empty stack. | 148 // on an empty stack. |
| 83 if (pseudo_stack_.empty()) | 149 if (pseudo_stack_.empty()) |
| 84 return; | 150 return; |
| 85 | 151 |
| 86 // Assert that pushes and pops are nested correctly. This DCHECK can be | 152 // Assert that pushes and pops are nested correctly. This DCHECK can be |
| 87 // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call | 153 // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call |
| 88 // without a corresponding TRACE_EVENT_BEGIN). | 154 // without a corresponding TRACE_EVENT_BEGIN). |
| 89 DCHECK_EQ(frame, pseudo_stack_.back()) | 155 DCHECK_EQ(frame, pseudo_stack_.back()) |
| 90 << "Encountered an unmatched TRACE_EVENT_END"; | 156 << "Encountered an unmatched TRACE_EVENT_END"; |
| 91 | 157 |
| 92 pseudo_stack_.pop_back(); | 158 pseudo_stack_.pop_back(); |
| 93 } | 159 } |
| 94 | 160 |
| 95 // static | |
| 96 AllocationContext AllocationContextTracker::GetContextSnapshot() { | 161 AllocationContext AllocationContextTracker::GetContextSnapshot() { |
| 97 AllocationContext ctx; | 162 AllocationContext ctx; |
| 98 | 163 |
| 99 // Fill the backtrace. | 164 #if BUILDFLAG(USE_EXPERIMENTAL_ALLOCATION_TRACING) && !defined(OS_NACL) |
| 165 // Ignore pseudo stack and return the real thing | |
| 166 | |
| 167 // base::debug::StackTrace::StackTrace() | |
| 168 // base::trace_event::AllocationContextTracker::GetContextSnapshot() | |
| 169 // base::trace_event::MallocDumpProvider::InsertAllocation(...) | |
| 170 // base::trace_event::(anonymous namespace)::HookXXX(...) | |
| 171 // ShimXXX | |
| 172 const size_t known_frame_count = 5; | |
| 173 | |
| 174 const size_t max_backtrace_count = arraysize(ctx.backtrace.frames); | |
| 175 size_t backtrace_count = WalkStackFrames(ctx.backtrace.frames, | |
| 176 max_backtrace_count, | |
| 177 known_frame_count + 1); | |
| 178 | |
| 179 std::reverse(ctx.backtrace.frames, | |
|
Primiano Tucci (use gerrit)
2016/04/01 15:56:28
I wonder if we could avoid this reverse and just w
Dmitry Skiba
2016/04/04 22:38:34
OK
| |
| 180 ctx.backtrace.frames + backtrace_count); | |
| 181 | |
| 182 std::fill(ctx.backtrace.frames + backtrace_count, | |
| 183 ctx.backtrace.frames + max_backtrace_count, | |
| 184 nullptr); | |
| 185 | |
| 186 ctx.backtrace.frame_type = STACK_FRAME_TYPE_PC; | |
| 187 | |
| 188 #else | |
| 189 // Return pseudo stack trace | |
| 190 | |
| 100 { | 191 { |
| 101 auto src = pseudo_stack_.begin(); | 192 auto src = pseudo_stack_.begin(); |
| 102 auto dst = std::begin(ctx.backtrace.frames); | 193 auto dst = std::begin(ctx.backtrace.frames); |
| 103 auto src_end = pseudo_stack_.end(); | 194 auto src_end = pseudo_stack_.end(); |
| 104 auto dst_end = std::end(ctx.backtrace.frames); | 195 auto dst_end = std::end(ctx.backtrace.frames); |
| 105 | 196 |
| 106 // Copy as much of the bottom of the pseudo stack into the backtrace as | 197 // Copy as much of the bottom of the pseudo stack into the backtrace as |
| 107 // possible. | 198 // possible. |
| 108 for (; src != src_end && dst != dst_end; src++, dst++) | 199 for (; src != src_end && dst != dst_end; src++, dst++) |
| 109 *dst = *src; | 200 *dst = *src; |
| 110 | 201 |
| 111 // If there is room for more, fill the remaining slots with empty frames. | 202 // If there is room for more, fill the remaining slots with empty frames. |
| 112 std::fill(dst, dst_end, nullptr); | 203 std::fill(dst, dst_end, nullptr); |
| 113 } | 204 } |
| 114 | 205 |
| 206 ctx.backtrace.frame_type = STACK_FRAME_TYPE_SYMBOL; | |
| 207 | |
| 208 #endif // USE_EXPERIMENTAL_ALLOCATION_TRACING | |
| 209 | |
| 115 ctx.type_name = nullptr; | 210 ctx.type_name = nullptr; |
| 116 | 211 |
| 117 return ctx; | 212 return ctx; |
| 118 } | 213 } |
| 119 | 214 |
| 120 } // namespace trace_event | 215 } // namespace trace_event |
| 121 } // namespace base | 216 } // namespace base |
| OLD | NEW |