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