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