Chromium Code Reviews| 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 status = crashpad::NtQuerySystemInformation( | 61 status = crashpad::NtQuerySystemInformation( |
| 62 static_cast<SYSTEM_INFORMATION_CLASS>( | 62 SystemProcessInformation, |
| 63 kSystemExtendedProcessInformation), | |
| 64 reinterpret_cast<void*>(buffer->get()), | 63 reinterpret_cast<void*>(buffer->get()), |
| 65 buffer_size, | 64 buffer_size, |
| 66 &buffer_size); | 65 &buffer_size); |
| 67 if (status == STATUS_BUFFER_TOO_SMALL || | 66 if (status == STATUS_BUFFER_TOO_SMALL || |
| 68 status == STATUS_INFO_LENGTH_MISMATCH) { | 67 status == STATUS_INFO_LENGTH_MISMATCH) { |
| 69 // Add a little extra to try to avoid an additional loop iteration. We're | 68 // 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 | 69 // racing with system-wide process creation between here and the next call |
| 71 // to NtQuerySystemInformation(). | 70 // to NtQuerySystemInformation(). |
| 72 buffer_size += 4096; | 71 buffer_size += 4096; |
| 73 buffer->reset(new uint8_t[buffer_size]); | 72 buffer->reset(new uint8_t[buffer_size]); |
| 74 } else { | 73 } else { |
| 75 break; | 74 break; |
| 76 } | 75 } |
| 77 } | 76 } |
| 78 | 77 |
| 79 if (!NT_SUCCESS(status)) { | 78 if (!NT_SUCCESS(status)) { |
| 80 LOG(ERROR) << "NtQuerySystemInformation failed: " << std::hex << status; | 79 NTSTATUS_LOG(ERROR, status) << "NtQuerySystemInformation"; |
| 81 return nullptr; | 80 return nullptr; |
| 82 } | 81 } |
| 83 | 82 |
| 84 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process = | 83 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process = |
| 85 reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>( | 84 reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>( |
| 86 buffer->get()); | 85 buffer->get()); |
| 87 DWORD process_id = GetProcessId(process_handle); | 86 DWORD process_id = GetProcessId(process_handle); |
| 88 do { | 87 do { |
| 89 if (process->UniqueProcessId == process_id) | 88 if (process->UniqueProcessId == process_id) |
| 90 return process; | 89 return process; |
| 91 } while (process = NextProcess(process)); | 90 } while (process = NextProcess(process)); |
| 92 | 91 |
| 93 LOG(ERROR) << "process " << process_id << " not found"; | 92 LOG(ERROR) << "process " << process_id << " not found"; |
| 94 return nullptr; | 93 return nullptr; |
| 95 } | 94 } |
| 96 | 95 |
| 97 template <class Traits> | 96 template <class Traits> |
| 98 HANDLE OpenThread(const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION< | 97 HANDLE OpenThread( |
| 99 Traits>& thread_info) { | 98 const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info) { |
| 100 HANDLE handle; | 99 HANDLE handle; |
| 101 ACCESS_MASK query_access = THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME; | 100 ACCESS_MASK query_access = |
| 101 THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION; | |
| 102 OBJECT_ATTRIBUTES object_attributes; | 102 OBJECT_ATTRIBUTES object_attributes; |
| 103 InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); | 103 InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); |
| 104 NTSTATUS status = crashpad::NtOpenThread( | 104 NTSTATUS status = crashpad::NtOpenThread( |
| 105 &handle, query_access, &object_attributes, &thread_info.ClientId); | 105 &handle, query_access, &object_attributes, &thread_info.ClientId); |
| 106 if (!NT_SUCCESS(status)) { | 106 if (!NT_SUCCESS(status)) { |
| 107 LOG(ERROR) << "NtOpenThread failed"; | 107 NTSTATUS_LOG(ERROR, status) << "NtOpenThread"; |
| 108 return nullptr; | 108 return nullptr; |
| 109 } | 109 } |
| 110 return handle; | 110 return handle; |
| 111 } | 111 } |
| 112 | 112 |
| 113 // It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a | 113 // 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 | 114 // 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. | 115 // fill out these two pieces of semi-unrelated data in the same function. |
| 116 template <class Traits> | 116 template <class Traits> |
| 117 void FillThreadContextAndSuspendCount( | 117 bool FillThreadContextAndSuspendCount(HANDLE thread_handle, |
| 118 const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<Traits>& | 118 ProcessReaderWin::Thread* thread, |
| 119 thread_info, | 119 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 | 120 // 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. | 121 // binaries, as we won't be walking ourselves, in general. |
| 124 bool is_current_thread = thread_info.ClientId.UniqueThread == | 122 bool is_current_thread = thread->id == |
| 125 reinterpret_cast<process_types::TEB<Traits>*>( | 123 reinterpret_cast<process_types::TEB<Traits>*>( |
| 126 NtCurrentTeb())->ClientId.UniqueThread; | 124 NtCurrentTeb())->ClientId.UniqueThread; |
| 127 | 125 |
| 128 ScopedKernelHANDLE thread_handle(OpenThread(thread_info)); | |
| 129 | |
| 130 // TODO(scottmg): Handle cross-bitness in this function. | 126 // TODO(scottmg): Handle cross-bitness in this function. |
| 131 | 127 |
| 132 if (is_current_thread) { | 128 if (is_current_thread) { |
| 133 DCHECK(suspension_state == ProcessSuspensionState::kRunning); | 129 DCHECK(suspension_state == ProcessSuspensionState::kRunning); |
| 134 thread->suspend_count = 0; | 130 thread->suspend_count = 0; |
| 135 RtlCaptureContext(&thread->context); | 131 RtlCaptureContext(&thread->context); |
| 136 } else { | 132 } else { |
| 137 DWORD previous_suspend_count = SuspendThread(thread_handle.get()); | 133 DWORD previous_suspend_count = SuspendThread(thread_handle); |
| 138 if (previous_suspend_count == -1) { | 134 if (previous_suspend_count == -1) { |
| 139 PLOG(ERROR) << "SuspendThread failed"; | 135 PLOG(ERROR) << "SuspendThread failed"; |
| 140 return; | 136 return false; |
| 141 } | 137 } |
| 142 DCHECK(previous_suspend_count > 0 || | 138 DCHECK(previous_suspend_count > 0 || |
| 143 suspension_state == ProcessSuspensionState::kRunning); | 139 suspension_state == ProcessSuspensionState::kRunning); |
| 144 thread->suspend_count = | 140 thread->suspend_count = |
| 145 previous_suspend_count - | 141 previous_suspend_count - |
| 146 (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); | 142 (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0); |
| 147 | 143 |
| 148 memset(&thread->context, 0, sizeof(thread->context)); | 144 memset(&thread->context, 0, sizeof(thread->context)); |
| 149 thread->context.ContextFlags = CONTEXT_ALL; | 145 thread->context.ContextFlags = CONTEXT_ALL; |
| 150 if (!GetThreadContext(thread_handle.get(), &thread->context)) { | 146 if (!GetThreadContext(thread_handle, &thread->context)) { |
|
Mark Mentovai
2015/09/18 03:30:51
I was looking at the documentation for GetThreadCo
scottmg
2015/09/18 03:50:09
2/3rds of the way through the comment sludge... :)
| |
| 151 PLOG(ERROR) << "GetThreadContext failed"; | 147 PLOG(ERROR) << "GetThreadContext failed"; |
| 152 return; | 148 return false; |
| 153 } | 149 } |
| 154 | 150 |
| 155 if (!ResumeThread(thread_handle.get())) { | 151 if (!ResumeThread(thread_handle)) { |
| 156 PLOG(ERROR) << "ResumeThread failed"; | 152 PLOG(ERROR) << "ResumeThread failed"; |
| 153 return false; | |
| 157 } | 154 } |
| 158 } | 155 } |
| 156 | |
| 157 return true; | |
| 159 } | 158 } |
| 160 | 159 |
| 161 } // namespace | 160 } // namespace |
| 162 | 161 |
| 163 ProcessReaderWin::Thread::Thread() | 162 ProcessReaderWin::Thread::Thread() |
| 164 : context(), | 163 : context(), |
| 165 id(0), | 164 id(0), |
| 166 teb(0), | 165 teb(0), |
| 167 stack_region_address(0), | 166 stack_region_address(0), |
| 168 stack_region_size(0), | 167 stack_region_size(0), |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 191 process_ = process; | 190 process_ = process; |
| 192 suspension_state_ = suspension_state; | 191 suspension_state_ = suspension_state; |
| 193 process_info_.Initialize(process); | 192 process_info_.Initialize(process); |
| 194 | 193 |
| 195 INITIALIZATION_STATE_SET_VALID(initialized_); | 194 INITIALIZATION_STATE_SET_VALID(initialized_); |
| 196 return true; | 195 return true; |
| 197 } | 196 } |
| 198 | 197 |
| 199 bool ProcessReaderWin::ReadMemory(WinVMAddress at, | 198 bool ProcessReaderWin::ReadMemory(WinVMAddress at, |
| 200 WinVMSize num_bytes, | 199 WinVMSize num_bytes, |
| 201 void* into) { | 200 void* into) const { |
| 202 SIZE_T bytes_read; | 201 SIZE_T bytes_read; |
| 203 if (!ReadProcessMemory(process_, | 202 if (!ReadProcessMemory(process_, |
| 204 reinterpret_cast<void*>(at), | 203 reinterpret_cast<void*>(at), |
| 205 into, | 204 into, |
| 206 base::checked_cast<SIZE_T>(num_bytes), | 205 base::checked_cast<SIZE_T>(num_bytes), |
| 207 &bytes_read) || | 206 &bytes_read) || |
| 208 num_bytes != bytes_read) { | 207 num_bytes != bytes_read) { |
| 209 PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of " | 208 PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of " |
| 210 << num_bytes << " bytes failed"; | 209 << num_bytes << " bytes failed"; |
| 211 return false; | 210 return false; |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 236 } | 235 } |
| 237 | 236 |
| 238 const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() { | 237 const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() { |
| 239 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | 238 INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| 240 | 239 |
| 241 if (initialized_threads_) | 240 if (initialized_threads_) |
| 242 return threads_; | 241 return threads_; |
| 243 | 242 |
| 244 initialized_threads_ = true; | 243 initialized_threads_ = true; |
| 245 | 244 |
| 245 if (process_info_.Is64Bit()) | |
| 246 ReadThreadData<process_types::internal::Traits64>(); | |
| 247 else | |
| 248 ReadThreadData<process_types::internal::Traits32>(); | |
| 249 | |
| 250 return threads_; | |
| 251 } | |
| 252 | |
| 253 const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() { | |
| 254 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
| 255 | |
| 256 if (!process_info_.Modules(&modules_)) { | |
| 257 LOG(ERROR) << "couldn't retrieve modules"; | |
| 258 } | |
| 259 | |
| 260 return modules_; | |
| 261 } | |
| 262 | |
| 263 template <class Traits> | |
| 264 void ProcessReaderWin::ReadThreadData() { | |
| 246 DCHECK(threads_.empty()); | 265 DCHECK(threads_.empty()); |
| 247 | 266 |
| 248 #if ARCH_CPU_32_BITS | |
| 249 using SizeTraits = process_types::internal::Traits32; | |
| 250 #else | |
| 251 using SizeTraits = process_types::internal::Traits64; | |
| 252 #endif | |
| 253 scoped_ptr<uint8_t[]> buffer; | 267 scoped_ptr<uint8_t[]> buffer; |
| 254 process_types::SYSTEM_PROCESS_INFORMATION<SizeTraits>* process_information = | 268 process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process_information = |
| 255 GetProcessInformation<SizeTraits>(process_, &buffer); | 269 GetProcessInformation<Traits>(process_, &buffer); |
| 256 if (!process_information) | 270 if (!process_information) |
| 257 return threads_; | 271 return; |
| 258 | 272 |
| 259 for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) { | 273 for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) { |
| 260 const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<SizeTraits>& | 274 const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info = |
| 261 thread_info = process_information->Threads[i]; | 275 process_information->Threads[i]; |
| 262 Thread thread; | 276 ProcessReaderWin::Thread thread; |
| 263 thread.id = thread_info.ClientId.UniqueThread; | 277 thread.id = thread_info.ClientId.UniqueThread; |
| 264 | 278 |
| 265 FillThreadContextAndSuspendCount(thread_info, &thread, suspension_state_); | 279 ScopedKernelHANDLE thread_handle(OpenThread(thread_info)); |
| 280 if (!thread_handle.is_valid()) | |
| 281 continue; | |
| 282 | |
| 283 if (!FillThreadContextAndSuspendCount<Traits>( | |
| 284 thread_handle.get(), &thread, suspension_state_)) { | |
| 285 continue; | |
| 286 } | |
| 266 | 287 |
| 267 // TODO(scottmg): I believe we could reverse engineer the PriorityClass from | 288 // TODO(scottmg): I believe we could reverse engineer the PriorityClass from |
| 268 // the Priority, BasePriority, and | 289 // the Priority, BasePriority, and |
| 269 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 . | 290 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 . |
| 270 // MinidumpThreadWriter doesn't handle it yet in any case, so investigate | 291 // MinidumpThreadWriter doesn't handle it yet in any case, so investigate |
| 271 // both of those at the same time if it's useful. | 292 // both of those at the same time if it's useful. |
| 272 thread.priority_class = NORMAL_PRIORITY_CLASS; | 293 thread.priority_class = NORMAL_PRIORITY_CLASS; |
| 273 | 294 |
| 274 thread.priority = thread_info.Priority; | 295 thread.priority = thread_info.Priority; |
| 275 thread.teb = thread_info.TebBase; | |
| 276 | 296 |
| 277 // While there are semi-documented fields in the thread structure called | 297 process_types::THREAD_BASIC_INFORMATION<Traits> thread_basic_info; |
| 278 // StackBase and StackLimit, they don't appear to be correct in practice (or | 298 NTSTATUS status = crashpad::NtQueryInformationThread( |
| 279 // at least, I don't know how to interpret them). Instead, read the TIB | 299 thread_handle.get(), |
| 280 // (Thread Information Block) which is the first element of the TEB, and use | 300 static_cast<THREADINFOCLASS>(ThreadBasicInformation), |
| 281 // its stack fields. | 301 &thread_basic_info, |
| 282 process_types::NT_TIB<SizeTraits> tib; | 302 sizeof(thread_basic_info), |
| 283 if (ReadMemory(thread_info.TebBase, sizeof(tib), &tib)) { | 303 nullptr); |
| 304 if (!NT_SUCCESS(status)) { | |
| 305 NTSTATUS_LOG(ERROR, status) << "NtQueryInformationThread"; | |
| 306 continue; | |
| 307 } | |
| 308 | |
| 309 // Read the TIB (Thread Information Block) which is the first element of the | |
| 310 // TEB, for its stack fields. | |
| 311 process_types::NT_TIB<Traits> tib; | |
| 312 if (ReadMemory(thread_basic_info.TebBaseAddress, sizeof(tib), &tib)) { | |
| 284 // Note, "backwards" because of direction of stack growth. | 313 // Note, "backwards" because of direction of stack growth. |
| 285 thread.stack_region_address = tib.StackLimit; | 314 thread.stack_region_address = tib.StackLimit; |
| 286 thread.stack_region_size = tib.StackBase - tib.StackLimit; | 315 if (tib.StackLimit > tib.StackBase) { |
| 316 LOG(ERROR) << "invalid stack range: " << tib.StackBase << " - " | |
| 317 << tib.StackLimit; | |
| 318 thread.stack_region_size = 0; | |
| 319 } else { | |
| 320 thread.stack_region_size = tib.StackBase - tib.StackLimit; | |
| 321 } | |
| 287 } | 322 } |
| 288 threads_.push_back(thread); | 323 threads_.push_back(thread); |
| 289 } | 324 } |
| 290 | |
| 291 return threads_; | |
| 292 } | |
| 293 | |
| 294 const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() { | |
| 295 INITIALIZATION_STATE_DCHECK_VALID(initialized_); | |
| 296 | |
| 297 if (!process_info_.Modules(&modules_)) { | |
| 298 LOG(ERROR) << "couldn't retrieve modules"; | |
| 299 } | |
| 300 | |
| 301 return modules_; | |
| 302 } | 325 } |
| 303 | 326 |
| 304 } // namespace crashpad | 327 } // namespace crashpad |
| OLD | NEW |