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