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 |