1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
base / profiler / suspendable_thread_delegate_win.cc [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/profiler/suspendable_thread_delegate_win.h"
#include <windows.h>
#include <winternl.h>
#include <vector>
#include "base/check.h"
#include "base/debug/alias.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/profiler/native_unwinder_win.h"
#include "build/build_config.h"
// IMPORTANT NOTE: Some functions within this implementation are invoked while
// the target thread is suspended so it must not do any allocation from the
// heap, including indirectly via use of DCHECK/CHECK or other logging
// statements. Otherwise this code can deadlock on heap locks acquired by the
// target thread before it was suspended. These functions are commented with "NO
// HEAP ALLOCATIONS".
namespace base {
namespace {
// The thread environment block internal type.
struct TEB {
NT_TIB Tib;
// Rest of struct is ignored.
};
win::ScopedHandle GetCurrentThreadHandle() {
HANDLE thread;
CHECK(::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentThread(),
::GetCurrentProcess(), &thread, 0, FALSE,
DUPLICATE_SAME_ACCESS));
return win::ScopedHandle(thread);
}
win::ScopedHandle GetThreadHandle(PlatformThreadId thread_id) {
// TODO(crbug.com/40620762): Move this logic to
// GetSamplingProfilerCurrentThreadToken() and pass the handle in
// SamplingProfilerThreadToken.
if (thread_id == ::GetCurrentThreadId())
return GetCurrentThreadHandle();
// TODO(http://crbug.com/947459): Remove the test_handle* CHECKs once we
// understand which flag is triggering the failure.
DWORD flags = 0;
base::debug::Alias(&flags);
flags |= THREAD_GET_CONTEXT;
win::ScopedHandle test_handle1(::OpenThread(flags, FALSE, thread_id));
CHECK(test_handle1.is_valid());
flags |= THREAD_QUERY_INFORMATION;
win::ScopedHandle test_handle2(::OpenThread(flags, FALSE, thread_id));
CHECK(test_handle2.is_valid());
flags |= THREAD_SUSPEND_RESUME;
win::ScopedHandle handle(::OpenThread(flags, FALSE, thread_id));
CHECK(handle.is_valid());
return handle;
}
// Returns the thread environment block pointer for |thread_handle|.
const TEB* GetThreadEnvironmentBlock(PlatformThreadId thread_id,
HANDLE thread_handle) {
// TODO(crbug.com/40620762): Move this logic to
// GetSamplingProfilerCurrentThreadToken() and pass the TEB* in
// SamplingProfilerThreadToken.
if (thread_id == ::GetCurrentThreadId())
return reinterpret_cast<TEB*>(NtCurrentTeb());
// Define types not in winternl.h needed to invoke NtQueryInformationThread().
constexpr auto ThreadBasicInformation = static_cast<THREADINFOCLASS>(0);
struct THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
// RAW_PTR_EXCLUSION: Filled in by the OS so cannot use raw_ptr<>.
RAW_PTR_EXCLUSION TEB* Teb;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
LONG Priority;
LONG BasePriority;
};
THREAD_BASIC_INFORMATION basic_info = {0};
NTSTATUS status = ::NtQueryInformationThread(
thread_handle, ThreadBasicInformation, &basic_info,
sizeof(THREAD_BASIC_INFORMATION), nullptr);
if (status != 0)
return nullptr;
return basic_info.Teb;
}
// Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
// ALLOCATIONS.
bool PointsToGuardPage(uintptr_t stack_pointer) {
MEMORY_BASIC_INFORMATION memory_info;
SIZE_T result = ::VirtualQuery(reinterpret_cast<LPCVOID>(stack_pointer),
&memory_info, sizeof(memory_info));
return result != 0 && (memory_info.Protect & PAGE_GUARD);
}
// ScopedDisablePriorityBoost -------------------------------------------------
// Disables priority boost on a thread for the lifetime of the object.
class ScopedDisablePriorityBoost {
public:
ScopedDisablePriorityBoost(HANDLE thread_handle);
ScopedDisablePriorityBoost(const ScopedDisablePriorityBoost&) = delete;
ScopedDisablePriorityBoost& operator=(const ScopedDisablePriorityBoost&) =
delete;
~ScopedDisablePriorityBoost();
private:
HANDLE thread_handle_;
BOOL got_previous_boost_state_;
BOOL boost_state_was_disabled_;
};
// NO HEAP ALLOCATIONS.
ScopedDisablePriorityBoost::ScopedDisablePriorityBoost(HANDLE thread_handle)
: thread_handle_(thread_handle),
got_previous_boost_state_(false),
boost_state_was_disabled_(false) {
got_previous_boost_state_ =
::GetThreadPriorityBoost(thread_handle_, &boost_state_was_disabled_);
if (got_previous_boost_state_) {
// Confusingly, TRUE disables priority boost.
::SetThreadPriorityBoost(thread_handle_, TRUE);
}
}
ScopedDisablePriorityBoost::~ScopedDisablePriorityBoost() {
if (got_previous_boost_state_)
::SetThreadPriorityBoost(thread_handle_, boost_state_was_disabled_);
}
} // namespace
// ScopedSuspendThread --------------------------------------------------------
// NO HEAP ALLOCATIONS after ::SuspendThread.
SuspendableThreadDelegateWin::ScopedSuspendThread::ScopedSuspendThread(
HANDLE thread_handle)
: thread_handle_(thread_handle),
was_successful_(::SuspendThread(thread_handle) !=
static_cast<DWORD>(-1)) {}
// NO HEAP ALLOCATIONS. The CHECK is OK because it provides a more noisy failure
// mode than deadlocking.
SuspendableThreadDelegateWin::ScopedSuspendThread::~ScopedSuspendThread() {
if (!was_successful_)
return;
// Disable the priority boost that the thread would otherwise receive on
// resume. We do this to avoid artificially altering the dynamics of the
// executing application any more than we already are by suspending and
// resuming the thread.
//
// Note that this can racily disable a priority boost that otherwise would
// have been given to the thread, if the thread is waiting on other wait
// conditions at the time of SuspendThread and those conditions are satisfied
// before priority boost is reenabled. The measured length of this window is
// ~100us, so this should occur fairly rarely.
ScopedDisablePriorityBoost disable_priority_boost(thread_handle_);
bool resume_thread_succeeded =
::ResumeThread(thread_handle_) != static_cast<DWORD>(-1);
PCHECK(resume_thread_succeeded) << "ResumeThread failed";
}
bool SuspendableThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
return was_successful_;
}
// SuspendableThreadDelegateWin
// ----------------------------------------------------------
SuspendableThreadDelegateWin::SuspendableThreadDelegateWin(
SamplingProfilerThreadToken thread_token)
: thread_id_(thread_token.id),
thread_handle_(GetThreadHandle(thread_token.id)),
thread_stack_base_address_(reinterpret_cast<uintptr_t>(
GetThreadEnvironmentBlock(thread_token.id, thread_handle_.get())
->Tib.StackBase)) {}
SuspendableThreadDelegateWin::~SuspendableThreadDelegateWin() = default;
std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
SuspendableThreadDelegateWin::CreateScopedSuspendThread() {
return std::make_unique<ScopedSuspendThread>(thread_handle_.get());
}
PlatformThreadId SuspendableThreadDelegateWin::GetThreadId() const {
return thread_id_;
}
// NO HEAP ALLOCATIONS.
bool SuspendableThreadDelegateWin::GetThreadContext(CONTEXT* thread_context) {
*thread_context = {0};
thread_context->ContextFlags = CONTEXT_FULL;
return ::GetThreadContext(thread_handle_.get(), thread_context) != 0;
}
// NO HEAP ALLOCATIONS.
uintptr_t SuspendableThreadDelegateWin::GetStackBaseAddress() const {
return thread_stack_base_address_;
}
// Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
// ALLOCATIONS.
bool SuspendableThreadDelegateWin::CanCopyStack(uintptr_t stack_pointer) {
// Dereferencing a pointer in the guard page in a thread that doesn't own the
// stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a crash. This
// occurs very rarely, but reliably over the population.
return !PointsToGuardPage(stack_pointer);
}
std::vector<uintptr_t*> SuspendableThreadDelegateWin::GetRegistersToRewrite(
CONTEXT* thread_context) {
// Return the set of non-volatile registers.
return {
#if defined(ARCH_CPU_X86_64)
&thread_context->R12, &thread_context->R13, &thread_context->R14,
&thread_context->R15, &thread_context->Rdi, &thread_context->Rsi,
&thread_context->Rbx, &thread_context->Rbp, &thread_context->Rsp
#elif defined(ARCH_CPU_ARM64)
&thread_context->X19, &thread_context->X20, &thread_context->X21,
&thread_context->X22, &thread_context->X23, &thread_context->X24,
&thread_context->X25, &thread_context->X26, &thread_context->X27,
&thread_context->X28, &thread_context->Fp, &thread_context->Lr,
&thread_context->Sp
#endif
};
}
} // namespace base