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
3 // found in the LICENSE file.
5 #include "base/profiler/stack_sampling_profiler.h"
12 #include "base/logging.h"
13 #include "base/time/time.h"
14 #include "base/win/pe_image.h"
15 #include "base/win/scoped_handle.h"
21 class NativeStackSamplerWin
: public StackSamplingProfiler::NativeStackSampler
{
23 explicit NativeStackSamplerWin(win::ScopedHandle thread_handle
);
24 ~NativeStackSamplerWin() override
;
26 // StackSamplingProfiler::NativeStackSampler:
27 void ProfileRecordingStarting(
28 StackSamplingProfiler::Profile
* profile
) override
;
29 void RecordStackSample(StackSamplingProfiler::Sample
* sample
) override
;
30 void ProfileRecordingStopped() override
;
33 static bool GetModuleInfo(HMODULE module
,
34 StackSamplingProfiler::Module
* module_info
);
36 void CopyToSample(const void* const instruction_pointers
[],
37 const HMODULE modules
[],
39 StackSamplingProfiler::Sample
* sample
,
40 std::vector
<StackSamplingProfiler::Module
>* module_infos
);
42 win::ScopedHandle thread_handle_
;
43 // Weak. Points to the profile being recorded between
44 // ProfileRecordingStarting() and ProfileRecordingStopped().
45 StackSamplingProfiler::Profile
* current_profile_
;
46 // Maps a module to the module's index within current_profile_->modules.
47 std::map
<HMODULE
, int> profile_module_index_
;
49 DISALLOW_COPY_AND_ASSIGN(NativeStackSamplerWin
);
52 // Walk the stack represented by |context| from the current frame downwards,
53 // recording the instruction pointers for each frame in |instruction_pointers|.
54 int RecordStack(CONTEXT
* context
,
56 const void* instruction_pointers
[],
57 bool* last_frame_is_unknown_function
) {
59 *last_frame_is_unknown_function
= false;
61 IMAGEHLP_SYMBOL64 sym
;
62 sym
.SizeOfStruct
= sizeof(sym
);
63 sym
.MaxNameLength
= 0;
65 for (int i
= 0; i
< max_stack_size
; ++i
) {
66 // Try to look up unwind metadata for the current function.
68 PRUNTIME_FUNCTION runtime_function
=
69 RtlLookupFunctionEntry(context
->Rip
, &image_base
, nullptr);
71 instruction_pointers
[i
] = reinterpret_cast<void*>(context
->Rip
);
73 if (runtime_function
) {
74 KNONVOLATILE_CONTEXT_POINTERS nvcontext
= {0};
76 ULONG64 establisher_frame
;
77 RtlVirtualUnwind(0, image_base
, context
->Rip
, runtime_function
, context
,
78 &handler_data
, &establisher_frame
, &nvcontext
);
80 // If we don't have a RUNTIME_FUNCTION, then we've encountered
81 // a leaf function. Adjust the stack appropriately.
82 context
->Rip
= *reinterpret_cast<PDWORD64
>(context
->Rsp
);
84 *last_frame_is_unknown_function
= true;
90 return max_stack_size
;
96 // Fills in |modules| corresponding to the pointers to code in |addresses|. The
97 // modules are returned with reference counts incremented should be freed with
99 void FindModulesForAddresses(const void* const addresses
[], HMODULE modules
[],
101 bool last_frame_is_unknown_function
) {
102 const int module_frames
= last_frame_is_unknown_function
? stack_depth
- 1 :
104 for (int i
= 0; i
< module_frames
; ++i
) {
105 HMODULE module
= NULL
;
106 if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
,
107 reinterpret_cast<LPCTSTR
>(addresses
[i
]),
109 // HMODULE is the base address of the module.
110 DCHECK_LT(reinterpret_cast<const void*>(module
), addresses
[i
]);
116 // Free the modules returned by FindModulesForAddresses.
117 void FreeModules(int stack_depth
, HMODULE modules
[]) {
118 for (int i
= 0; i
< stack_depth
; ++i
) {
120 ::FreeLibrary(modules
[i
]);
124 // Disables priority boost on a thread for the lifetime of the object.
125 class ScopedDisablePriorityBoost
{
127 ScopedDisablePriorityBoost(HANDLE thread_handle
);
128 ~ScopedDisablePriorityBoost();
131 HANDLE thread_handle_
;
132 BOOL got_previous_boost_state_
;
133 BOOL boost_state_was_disabled_
;
135 DISALLOW_COPY_AND_ASSIGN(ScopedDisablePriorityBoost
);
138 ScopedDisablePriorityBoost::ScopedDisablePriorityBoost(HANDLE thread_handle
)
139 : thread_handle_(thread_handle
),
140 got_previous_boost_state_(false),
141 boost_state_was_disabled_(false) {
142 got_previous_boost_state_
=
143 ::GetThreadPriorityBoost(thread_handle_
, &boost_state_was_disabled_
);
144 if (got_previous_boost_state_
&& !boost_state_was_disabled_
) {
145 // Confusingly, TRUE disables priority boost ...
146 ::SetThreadPriorityBoost(thread_handle_
, TRUE
);
150 ScopedDisablePriorityBoost::~ScopedDisablePriorityBoost() {
151 if (got_previous_boost_state_
&& !boost_state_was_disabled_
) {
152 // ... and FALSE enables priority boost.
153 ::SetThreadPriorityBoost(thread_handle_
, FALSE
);
157 // Suspends the thread with |thread_handle|, records the stack into
158 // |instruction_pointers|, then resumes the thread. Returns the size of the
160 int SuspendThreadAndRecordStack(HANDLE thread_handle
, int max_stack_size
,
161 const void* instruction_pointers
[],
162 bool* last_frame_is_unknown_function
) {
164 if (RtlVirtualUnwind
== nullptr || RtlLookupFunctionEntry
== nullptr)
168 if (::SuspendThread(thread_handle
) == -1) {
169 LOG(ERROR
) << "SuspendThread failed: " << GetLastError();
173 CONTEXT thread_context
= {0};
174 thread_context
.ContextFlags
= CONTEXT_FULL
;
175 if (!::GetThreadContext(thread_handle
, &thread_context
)) {
176 LOG(ERROR
) << "GetThreadContext failed: " << GetLastError();
179 int stack_depth
= RecordStack(&thread_context
, max_stack_size
,
180 instruction_pointers
,
181 last_frame_is_unknown_function
);
184 ScopedDisablePriorityBoost
disable_priority_boost(thread_handle
);
185 if (::ResumeThread(thread_handle
) == -1)
186 LOG(ERROR
) << "ResumeThread failed: " << GetLastError();
194 scoped_ptr
<StackSamplingProfiler::NativeStackSampler
>
195 StackSamplingProfiler::NativeStackSampler::Create(PlatformThreadId thread_id
) {
197 // Get the thread's handle.
198 HANDLE thread_handle
= ::OpenThread(
199 THREAD_GET_CONTEXT
| THREAD_SUSPEND_RESUME
| THREAD_QUERY_INFORMATION
,
202 DCHECK(thread_handle
) << "OpenThread failed";
204 return scoped_ptr
<NativeStackSampler
>(new NativeStackSamplerWin(
205 win::ScopedHandle(thread_handle
)));
207 return scoped_ptr
<NativeStackSampler
>();
211 NativeStackSamplerWin::NativeStackSamplerWin(win::ScopedHandle thread_handle
)
212 : thread_handle_(thread_handle
.Take()) {
214 if (RtlVirtualUnwind
== nullptr && RtlLookupFunctionEntry
== nullptr) {
215 const HMODULE nt_dll_handle
= ::GetModuleHandle(L
"ntdll.dll");
216 // This should always be non-null, but handle just in case.
218 reinterpret_cast<void*&>(RtlVirtualUnwind
) =
219 ::GetProcAddress(nt_dll_handle
, "RtlVirtualUnwind");
220 reinterpret_cast<void*&>(RtlLookupFunctionEntry
) =
221 ::GetProcAddress(nt_dll_handle
, "RtlLookupFunctionEntry");
227 NativeStackSamplerWin::~NativeStackSamplerWin() {
230 void NativeStackSamplerWin::ProfileRecordingStarting(
231 StackSamplingProfiler::Profile
* profile
) {
232 current_profile_
= profile
;
233 profile_module_index_
.clear();
236 void NativeStackSamplerWin::RecordStackSample(
237 StackSamplingProfiler::Sample
* sample
) {
238 DCHECK(current_profile_
);
240 const int max_stack_size
= 64;
241 const void* instruction_pointers
[max_stack_size
] = {0};
242 HMODULE modules
[max_stack_size
] = {0};
244 bool last_frame_is_unknown_function
= false;
245 int stack_depth
= SuspendThreadAndRecordStack(
246 thread_handle_
.Get(), max_stack_size
, instruction_pointers
,
247 &last_frame_is_unknown_function
);
248 FindModulesForAddresses(instruction_pointers
, modules
, stack_depth
,
249 last_frame_is_unknown_function
);
250 CopyToSample(instruction_pointers
, modules
, stack_depth
, sample
,
251 ¤t_profile_
->modules
);
252 FreeModules(stack_depth
, modules
);
255 void NativeStackSamplerWin::ProfileRecordingStopped() {
256 current_profile_
= nullptr;
260 bool NativeStackSamplerWin::GetModuleInfo(
262 StackSamplingProfiler::Module
* module_info
) {
263 wchar_t module_name
[MAX_PATH
];
264 DWORD result_length
=
265 GetModuleFileName(module
, module_name
, arraysize(module_name
));
266 if (result_length
== 0)
269 module_info
->filename
= base::FilePath(module_name
);
271 module_info
->base_address
= reinterpret_cast<const void*>(module
);
275 win::PEImage(module
).GetDebugId(&guid
, &age
);
276 module_info
->id
.insert(module_info
->id
.end(),
277 reinterpret_cast<char*>(&guid
),
278 reinterpret_cast<char*>(&guid
+ 1));
279 module_info
->id
.insert(module_info
->id
.end(),
280 reinterpret_cast<char*>(&age
),
281 reinterpret_cast<char*>(&age
+ 1));
286 void NativeStackSamplerWin::CopyToSample(
287 const void* const instruction_pointers
[],
288 const HMODULE modules
[],
290 StackSamplingProfiler::Sample
* sample
,
291 std::vector
<StackSamplingProfiler::Module
>* module_infos
) {
293 sample
->reserve(stack_depth
);
295 for (int i
= 0; i
< stack_depth
; ++i
) {
296 sample
->push_back(StackSamplingProfiler::Frame());
297 StackSamplingProfiler::Frame
& frame
= sample
->back();
299 frame
.instruction_pointer
= instruction_pointers
[i
];
301 // Record an invalid module index if we don't have a valid module.
303 frame
.module_index
= -1;
307 auto loc
= profile_module_index_
.find(modules
[i
]);
308 if (loc
== profile_module_index_
.end()) {
309 StackSamplingProfiler::Module module_info
;
310 // Record an invalid module index if we have a module but can't find
311 // information on it.
312 if (!GetModuleInfo(modules
[i
], &module_info
)) {
313 frame
.module_index
= -1;
316 module_infos
->push_back(module_info
);
317 loc
= profile_module_index_
.insert(std::make_pair(
318 modules
[i
], static_cast<int>(module_infos
->size() - 1))).first
;
321 frame
.module_index
= loc
->second
;