OLD | NEW |
---|---|
1 // Copyright 2015 The Crashpad Authors. All rights reserved. | 1 // Copyright 2015 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "snapshot/win/process_reader_win.h" | 15 #include "snapshot/win/process_reader_win.h" |
16 | 16 |
17 #include <winternl.h> | 17 #include <winternl.h> |
18 | 18 |
19 #include "base/memory/scoped_ptr.h" | 19 #include "base/memory/scoped_ptr.h" |
20 #include "base/numerics/safe_conversions.h" | 20 #include "base/numerics/safe_conversions.h" |
21 #include "util/win/nt_internals.h" | 21 #include "util/win/nt_internals.h" |
22 #include "util/win/ntstatus_logging.h" | |
22 #include "util/win/process_structs.h" | 23 #include "util/win/process_structs.h" |
23 #include "util/win/scoped_handle.h" | 24 #include "util/win/scoped_handle.h" |
24 #include "util/win/time.h" | 25 #include "util/win/time.h" |
25 | 26 |
26 namespace crashpad { | 27 namespace crashpad { |
27 | 28 |
28 namespace { | 29 namespace { |
29 | 30 |
30 // Gets a pointer to the process information structure after a given one, or | 31 // Gets a pointer to the process information structure after a given one, or |
31 // null when iteration is complete, assuming they've been retrieved in a block | 32 // null when iteration is complete, assuming they've been retrieved in a block |
(...skipping 18 matching lines...) Expand all Loading... | |
50 template <class Traits> | 51 template <class Traits> |
51 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation( | 52 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation( |
52 HANDLE process_handle, | 53 HANDLE process_handle, |
53 scoped_ptr<uint8_t[]>* buffer) { | 54 scoped_ptr<uint8_t[]>* buffer) { |
54 ULONG buffer_size = 16384; | 55 ULONG buffer_size = 16384; |
55 buffer->reset(new uint8_t[buffer_size]); | 56 buffer->reset(new uint8_t[buffer_size]); |
56 NTSTATUS status; | 57 NTSTATUS status; |
57 // This must be in retry loop, as we're racing with process creation on the | 58 // This must be in retry loop, as we're racing with process creation on the |
58 // system to find a buffer large enough to hold all process information. | 59 // system to find a buffer large enough to hold all process information. |
59 for (int tries = 0; tries < 20; ++tries) { | 60 for (int tries = 0; tries < 20; ++tries) { |
60 const int kSystemExtendedProcessInformation = 57; | 61 const int kSystemProcessInformation = 5; |
Mark Mentovai
2015/09/16 02:57:19
Before this, kSystemExtendedProcessInformation had
scottmg
2015/09/16 18:05:06
Aha, good catch, done.
| |
61 status = crashpad::NtQuerySystemInformation( | 62 status = crashpad::NtQuerySystemInformation( |
62 static_cast<SYSTEM_INFORMATION_CLASS>( | 63 static_cast<SYSTEM_INFORMATION_CLASS>(kSystemProcessInformation), |
Mark Mentovai
2015/09/16 02:57:19
And then you won’t need a cast anymore!
scottmg
2015/09/16 18:05:06
Done.
| |
63 kSystemExtendedProcessInformation), | |
64 reinterpret_cast<void*>(buffer->get()), | 64 reinterpret_cast<void*>(buffer->get()), |
65 buffer_size, | 65 buffer_size, |
66 &buffer_size); | 66 &buffer_size); |
67 if (status == STATUS_BUFFER_TOO_SMALL || | 67 if (status == STATUS_BUFFER_TOO_SMALL || |
68 status == STATUS_INFO_LENGTH_MISMATCH) { | 68 status == STATUS_INFO_LENGTH_MISMATCH) { |
69 // Add a little extra to try to avoid an additional loop iteration. We're | 69 // Add a little extra to try to avoid an additional loop iteration. We're |
70 // racing with system-wide process creation between here and the next call | 70 // racing with system-wide process creation between here and the next call |
71 // to NtQuerySystemInformation(). | 71 // to NtQuerySystemInformation(). |
72 buffer_size += 4096; | 72 buffer_size += 4096; |
73 buffer->reset(new uint8_t[buffer_size]); | 73 buffer->reset(new uint8_t[buffer_size]); |
74 } else { | 74 } else { |
75 break; | 75 break; |
76 } | 76 } |
77 } | 77 } |
78 | 78 |
79 if (!NT_SUCCESS(status)) { | 79 if (!NT_SUCCESS(status)) { |
80 LOG(ERROR) << "NtQuerySystemInformation failed: " << std::hex << status; | 80 NTSTATUS_LOG(ERROR, status) << "NtQuerySystemInformation"; |
81 return nullptr; | 81 return nullptr; |
82 } | 82 } |
83 | 83 |
84 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process = | 84 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process = |
85 reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>( | 85 reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>( |
86 buffer->get()); | 86 buffer->get()); |
87 DWORD process_id = GetProcessId(process_handle); | 87 DWORD process_id = GetProcessId(process_handle); |
88 do { | 88 do { |
89 if (process->UniqueProcessId == process_id) | 89 if (process->UniqueProcessId == process_id) |
90 return process; | 90 return process; |
91 } while (process = NextProcess(process)); | 91 } while (process = NextProcess(process)); |
92 | 92 |
93 LOG(ERROR) << "process " << process_id << " not found"; | 93 LOG(ERROR) << "process " << process_id << " not found"; |
94 return nullptr; | 94 return nullptr; |
95 } | 95 } |
96 | 96 |
97 template <class Traits> | 97 template <class Traits> |
98 HANDLE OpenThread(const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION< | 98 HANDLE OpenThread( |
99 Traits>& thread_info) { | 99 const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info) { |
100 HANDLE handle; | 100 HANDLE handle; |
101 ACCESS_MASK query_access = THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME; | 101 ACCESS_MASK query_access = |
102 THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION; | |
102 OBJECT_ATTRIBUTES object_attributes; | 103 OBJECT_ATTRIBUTES object_attributes; |
103 InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); | 104 InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); |
104 NTSTATUS status = crashpad::NtOpenThread( | 105 NTSTATUS status = crashpad::NtOpenThread( |
105 &handle, query_access, &object_attributes, &thread_info.ClientId); | 106 &handle, query_access, &object_attributes, &thread_info.ClientId); |
106 if (!NT_SUCCESS(status)) { | 107 if (!NT_SUCCESS(status)) { |
107 LOG(ERROR) << "NtOpenThread failed"; | 108 NTSTATUS_LOG(ERROR, status) << "NtOpenThread"; |
108 return nullptr; | 109 return nullptr; |
109 } | 110 } |
110 return handle; | 111 return handle; |
111 } | 112 } |
112 | 113 |
113 // It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a | 114 // It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a |
114 // side-effect of returning the SuspendCount of the thread on success, so we | 115 // side-effect of returning the SuspendCount of the thread on success, so we |
115 // fill out these two pieces of semi-unrelated data in the same function. | 116 // fill out these two pieces of semi-unrelated data in the same function. |
116 template <class Traits> | 117 template <class Traits> |
117 void FillThreadContextAndSuspendCount( | 118 void FillThreadContextAndSuspendCount(HANDLE thread_handle, |
118 const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<Traits>& | 119 ProcessReaderWin::Thread* thread, |
119 thread_info, | 120 ProcessSuspensionState suspension_state) { |
120 ProcessReaderWin::Thread* thread, | |
121 ProcessSuspensionState suspension_state) { | |
122 // Don't suspend the thread if it's this thread. This is really only for test | 121 // Don't suspend the thread if it's this thread. This is really only for test |
123 // binaries, as we won't be walking ourselves, in general. | 122 // binaries, as we won't be walking ourselves, in general. |
124 bool is_current_thread = thread_info.ClientId.UniqueThread == | 123 bool is_current_thread = thread->id == |
125 reinterpret_cast<process_types::TEB<Traits>*>( | 124 reinterpret_cast<process_types::TEB<Traits>*>( |
126 NtCurrentTeb())->ClientId.UniqueThread; | 125 NtCurrentTeb())->ClientId.UniqueThread; |
127 | 126 |
128 ScopedKernelHANDLE thread_handle(OpenThread(thread_info)); | |
129 | |
130 // TODO(scottmg): Handle cross-bitness in this function. | 127 // TODO(scottmg): Handle cross-bitness in this function. |
131 | 128 |
132 if (is_current_thread) { | 129 if (is_current_thread) { |
133 DCHECK(suspension_state == ProcessSuspensionState::kRunning); | 130 DCHECK(suspension_state == ProcessSuspensionState::kRunning); |
134 thread->suspend_count = 0; | 131 thread->suspend_count = 0; |
135 RtlCaptureContext(&thread->context); | 132 RtlCaptureContext(&thread->context); |
136 } else { | 133 } else { |
137 DWORD previous_suspend_count = SuspendThread(thread_handle.get()); | 134 DWORD previous_suspend_count = SuspendThread(thread_handle); |
138 if (previous_suspend_count == -1) { | 135 if (previous_suspend_count == -1) { |
139 PLOG(ERROR) << "SuspendThread failed"; | 136 PLOG(ERROR) << "SuspendThread failed"; |
140 return; | 137 return; |
141 } | 138 } |
142 DCHECK(previous_suspend_count > 0 || | 139 DCHECK(previous_suspend_count > 0 || |
143 suspension_state == ProcessSuspensionState::kRunning); | 140 suspension_state == ProcessSuspensionState::kRunning); |
144 thread->suspend_count = | 141 thread->suspend_count = |
145 previous_suspend_count - | 142 previous_suspend_count - |
146 (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); | 143 (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); |
147 | 144 |
148 memset(&thread->context, 0, sizeof(thread->context)); | 145 memset(&thread->context, 0, sizeof(thread->context)); |
149 thread->context.ContextFlags = CONTEXT_ALL; | 146 thread->context.ContextFlags = CONTEXT_ALL; |
150 if (!GetThreadContext(thread_handle.get(), &thread->context)) { | 147 if (!GetThreadContext(thread_handle, &thread->context)) { |
151 PLOG(ERROR) << "GetThreadContext failed"; | 148 PLOG(ERROR) << "GetThreadContext failed"; |
152 return; | 149 return; |
153 } | 150 } |
154 | 151 |
155 if (!ResumeThread(thread_handle.get())) { | 152 if (!ResumeThread(thread_handle)) { |
156 PLOG(ERROR) << "ResumeThread failed"; | 153 PLOG(ERROR) << "ResumeThread failed"; |
157 } | 154 } |
158 } | 155 } |
159 } | 156 } |
160 | 157 |
161 } // namespace | 158 } // namespace |
162 | 159 |
160 template <class Traits> | |
scottmg
2015/09/15 21:13:34
On XP, the "extended" version of this structure do
| |
161 void ReadThreadData(ProcessReaderWin* process_reader) { | |
Mark Mentovai
2015/09/16 02:57:19
This belongs the unnamed namespace.
scottmg
2015/09/16 18:05:06
It wasn't because it needed a friend declaration i
| |
162 DCHECK(process_reader->threads_.empty()); | |
163 | |
164 scoped_ptr<uint8_t[]> buffer; | |
165 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process_information = | |
166 GetProcessInformation<Traits>(process_reader->process_, &buffer); | |
167 if (!process_information) | |
168 return; | |
169 | |
170 for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) { | |
171 const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info = | |
172 process_information->Threads[i]; | |
173 ProcessReaderWin::Thread thread; | |
174 thread.id = thread_info.ClientId.UniqueThread; | |
175 | |
176 ScopedKernelHANDLE thread_handle(OpenThread(thread_info)); | |
177 if (!thread_handle.is_valid()) | |
178 continue; | |
179 | |
180 FillThreadContextAndSuspendCount<Traits>( | |
Mark Mentovai
2015/09/16 02:57:19
(This and the next comment describe things found i
scottmg
2015/09/16 18:05:06
Done.
| |
181 thread_handle.get(), &thread, process_reader->suspension_state_); | |
182 | |
183 // TODO(scottmg): I believe we could reverse engineer the PriorityClass from | |
184 // the Priority, BasePriority, and | |
185 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 . | |
186 // MinidumpThreadWriter doesn't handle it yet in any case, so investigate | |
187 // both of those at the same time if it's useful. | |
188 thread.priority_class = NORMAL_PRIORITY_CLASS; | |
189 | |
190 thread.priority = thread_info.Priority; | |
191 | |
192 process_types::THREAD_BASIC_INFORMATION<Traits> thread_basic_info; | |
193 NTSTATUS status = crashpad::NtQueryInformationThread( | |
194 thread_handle.get(), | |
195 static_cast<THREADINFOCLASS>(ThreadBasicInformation), | |
196 &thread_basic_info, | |
197 sizeof(thread_basic_info), | |
198 nullptr); | |
199 if (!NT_SUCCESS(status)) { | |
200 NTSTATUS_LOG(ERROR, status) << "NtQueryInformationThread"; | |
201 continue; | |
202 } | |
203 | |
204 // Read the TIB (Thread Information Block) which is the first element of the | |
205 // TEB, for its stack fields. | |
206 process_types::NT_TIB<Traits> tib; | |
207 if (process_reader->ReadMemory( | |
208 thread_basic_info.TebBaseAddress, sizeof(tib), &tib)) { | |
209 // Note, "backwards" because of direction of stack growth. | |
Mark Mentovai
2015/09/16 02:57:19
Might be good to actually check that limit <= base
scottmg
2015/09/16 18:05:06
Done.
| |
210 thread.stack_region_address = tib.StackLimit; | |
211 thread.stack_region_size = tib.StackBase - tib.StackLimit; | |
212 } | |
213 process_reader->threads_.push_back(thread); | |
214 } | |
215 } | |
216 | |
163 ProcessReaderWin::Thread::Thread() | 217 ProcessReaderWin::Thread::Thread() |
164 : context(), | 218 : context(), |
165 id(0), | 219 id(0), |
166 teb(0), | 220 teb(0), |
167 stack_region_address(0), | 221 stack_region_address(0), |
168 stack_region_size(0), | 222 stack_region_size(0), |
169 suspend_count(0), | 223 suspend_count(0), |
170 priority_class(0), | 224 priority_class(0), |
171 priority(0) { | 225 priority(0) { |
172 } | 226 } |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
236 } | 290 } |
237 | 291 |
238 const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() { | 292 const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() { |
239 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 293 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
240 | 294 |
241 if (initialized_threads_) | 295 if (initialized_threads_) |
242 return threads_; | 296 return threads_; |
243 | 297 |
244 initialized_threads_ = true; | 298 initialized_threads_ = true; |
245 | 299 |
246 DCHECK(threads_.empty()); | 300 if (process_info_.Is64Bit()) |
247 | 301 ReadThreadData<process_types::internal::Traits64>(this); |
248 #if ARCH_CPU_32_BITS | 302 else |
249 using SizeTraits = process_types::internal::Traits32; | 303 ReadThreadData<process_types::internal::Traits32>(this); |
250 #else | |
251 using SizeTraits = process_types::internal::Traits64; | |
252 #endif | |
253 scoped_ptr<uint8_t[]> buffer; | |
254 process_types::SYSTEM_PROCESS_INFORMATION<SizeTraits>* process_information = | |
255 GetProcessInformation<SizeTraits>(process_, &buffer); | |
256 if (!process_information) | |
257 return threads_; | |
258 | |
259 for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) { | |
260 const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<SizeTraits>& | |
261 thread_info = process_information->Threads[i]; | |
262 Thread thread; | |
263 thread.id = thread_info.ClientId.UniqueThread; | |
264 | |
265 FillThreadContextAndSuspendCount(thread_info, &thread, suspension_state_); | |
266 | |
267 // TODO(scottmg): I believe we could reverse engineer the PriorityClass from | |
268 // the Priority, BasePriority, and | |
269 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 . | |
270 // MinidumpThreadWriter doesn't handle it yet in any case, so investigate | |
271 // both of those at the same time if it's useful. | |
272 thread.priority_class = NORMAL_PRIORITY_CLASS; | |
273 | |
274 thread.priority = thread_info.Priority; | |
275 thread.teb = thread_info.TebBase; | |
276 | |
277 // While there are semi-documented fields in the thread structure called | |
278 // StackBase and StackLimit, they don't appear to be correct in practice (or | |
279 // at least, I don't know how to interpret them). Instead, read the TIB | |
280 // (Thread Information Block) which is the first element of the TEB, and use | |
281 // its stack fields. | |
282 process_types::NT_TIB<SizeTraits> tib; | |
283 if (ReadMemory(thread_info.TebBase, sizeof(tib), &tib)) { | |
284 // Note, "backwards" because of direction of stack growth. | |
285 thread.stack_region_address = tib.StackLimit; | |
286 thread.stack_region_size = tib.StackBase - tib.StackLimit; | |
287 } | |
288 threads_.push_back(thread); | |
289 } | |
290 | 304 |
291 return threads_; | 305 return threads_; |
292 } | 306 } |
293 | 307 |
294 const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() { | 308 const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() { |
295 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 309 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
296 | 310 |
297 if (!process_info_.Modules(&modules_)) { | 311 if (!process_info_.Modules(&modules_)) { |
298 LOG(ERROR) << "couldn't retrieve modules"; | 312 LOG(ERROR) << "couldn't retrieve modules"; |
299 } | 313 } |
300 | 314 |
301 return modules_; | 315 return modules_; |
302 } | 316 } |
303 | 317 |
304 } // namespace crashpad | 318 } // namespace crashpad |
OLD | NEW |