| Index: third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
|
| diff --git a/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..373124d61930f04e3fed8c82f6f0735362eaef7f
|
| --- /dev/null
|
| +++ b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
|
| @@ -0,0 +1,413 @@
|
| +// Copyright 2015 The Crashpad Authors. All rights reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +#include "snapshot/win/process_reader_win.h"
|
| +
|
| +#include <winternl.h>
|
| +
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/numerics/safe_conversions.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "util/win/capture_context.h"
|
| +#include "util/win/nt_internals.h"
|
| +#include "util/win/ntstatus_logging.h"
|
| +#include "util/win/process_structs.h"
|
| +#include "util/win/scoped_handle.h"
|
| +#include "util/win/time.h"
|
| +
|
| +namespace crashpad {
|
| +
|
| +namespace {
|
| +
|
| +// Gets a pointer to the process information structure after a given one, or
|
| +// null when iteration is complete, assuming they've been retrieved in a block
|
| +// via NtQuerySystemInformation().
|
| +template <class Traits>
|
| +process_types::SYSTEM_PROCESS_INFORMATION<Traits>* NextProcess(
|
| + process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process) {
|
| + ULONG offset = process->NextEntryOffset;
|
| + if (offset == 0)
|
| + return nullptr;
|
| + return reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>(
|
| + reinterpret_cast<uint8_t*>(process) + offset);
|
| +}
|
| +
|
| +//! \brief Retrieves the SYSTEM_PROCESS_INFORMATION for a given process.
|
| +//!
|
| +//! The returned pointer points into the memory block stored by \a buffer.
|
| +//! Ownership of \a buffer is transferred to the caller.
|
| +//!
|
| +//! \return Pointer to the process' data, or nullptr if it was not found or on
|
| +//! error. On error, a message will be logged.
|
| +template <class Traits>
|
| +process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation(
|
| + HANDLE process_handle,
|
| + scoped_ptr<uint8_t[]>* buffer) {
|
| + ULONG buffer_size = 16384;
|
| + buffer->reset(new uint8_t[buffer_size]);
|
| + NTSTATUS status;
|
| + // This must be in retry loop, as we're racing with process creation on the
|
| + // system to find a buffer large enough to hold all process information.
|
| + for (int tries = 0; tries < 20; ++tries) {
|
| + status = crashpad::NtQuerySystemInformation(
|
| + SystemProcessInformation,
|
| + reinterpret_cast<void*>(buffer->get()),
|
| + buffer_size,
|
| + &buffer_size);
|
| + if (status == STATUS_BUFFER_TOO_SMALL ||
|
| + status == STATUS_INFO_LENGTH_MISMATCH) {
|
| + // Add a little extra to try to avoid an additional loop iteration. We're
|
| + // racing with system-wide process creation between here and the next call
|
| + // to NtQuerySystemInformation().
|
| + buffer_size += 4096;
|
| + buffer->reset(new uint8_t[buffer_size]);
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (!NT_SUCCESS(status)) {
|
| + NTSTATUS_LOG(ERROR, status) << "NtQuerySystemInformation";
|
| + return nullptr;
|
| + }
|
| +
|
| + process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process =
|
| + reinterpret_cast<process_types::SYSTEM_PROCESS_INFORMATION<Traits>*>(
|
| + buffer->get());
|
| + DWORD process_id = GetProcessId(process_handle);
|
| + for (;;) {
|
| + if (process->UniqueProcessId == process_id)
|
| + return process;
|
| + process = NextProcess(process);
|
| + if (!process)
|
| + break;
|
| + }
|
| +
|
| + LOG(ERROR) << "process " << process_id << " not found";
|
| + return nullptr;
|
| +}
|
| +
|
| +template <class Traits>
|
| +HANDLE OpenThread(
|
| + const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info) {
|
| + HANDLE handle;
|
| + ACCESS_MASK query_access =
|
| + THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION;
|
| + OBJECT_ATTRIBUTES object_attributes;
|
| + InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr);
|
| + NTSTATUS status = crashpad::NtOpenThread(
|
| + &handle, query_access, &object_attributes, &thread_info.ClientId);
|
| + if (!NT_SUCCESS(status)) {
|
| + NTSTATUS_LOG(ERROR, status) << "NtOpenThread";
|
| + return nullptr;
|
| + }
|
| + return handle;
|
| +}
|
| +
|
| +// It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a
|
| +// side-effect of returning the SuspendCount of the thread on success, so we
|
| +// fill out these two pieces of semi-unrelated data in the same function.
|
| +template <class Traits>
|
| +bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
|
| + ProcessReaderWin::Thread* thread,
|
| + ProcessSuspensionState suspension_state,
|
| + bool is_64_reading_32) {
|
| + // Don't suspend the thread if it's this thread. This is really only for test
|
| + // binaries, as we won't be walking ourselves, in general.
|
| + bool is_current_thread = thread->id ==
|
| + reinterpret_cast<process_types::TEB<Traits>*>(
|
| + NtCurrentTeb())->ClientId.UniqueThread;
|
| +
|
| + if (is_current_thread) {
|
| + DCHECK(suspension_state == ProcessSuspensionState::kRunning);
|
| + thread->suspend_count = 0;
|
| + DCHECK(!is_64_reading_32);
|
| + CaptureContext(&thread->context.native);
|
| + } else {
|
| + DWORD previous_suspend_count = SuspendThread(thread_handle);
|
| + if (previous_suspend_count == -1) {
|
| + PLOG(ERROR) << "SuspendThread";
|
| + return false;
|
| + }
|
| + DCHECK(previous_suspend_count > 0 ||
|
| + suspension_state == ProcessSuspensionState::kRunning);
|
| + thread->suspend_count =
|
| + previous_suspend_count -
|
| + (suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
|
| +
|
| + memset(&thread->context, 0, sizeof(thread->context));
|
| +#if defined(ARCH_CPU_32_BITS)
|
| + const bool is_native = true;
|
| +#elif defined(ARCH_CPU_64_BITS)
|
| + const bool is_native = !is_64_reading_32;
|
| + if (is_64_reading_32) {
|
| + thread->context.wow64.ContextFlags = CONTEXT_ALL;
|
| + if (!Wow64GetThreadContext(thread_handle, &thread->context.wow64)) {
|
| + PLOG(ERROR) << "Wow64GetThreadContext";
|
| + return false;
|
| + }
|
| + }
|
| +#endif
|
| + if (is_native) {
|
| + thread->context.native.ContextFlags = CONTEXT_ALL;
|
| + if (!GetThreadContext(thread_handle, &thread->context.native)) {
|
| + PLOG(ERROR) << "GetThreadContext";
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + if (!ResumeThread(thread_handle)) {
|
| + PLOG(ERROR) << "ResumeThread";
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +ProcessReaderWin::Thread::Thread()
|
| + : context(),
|
| + id(0),
|
| + teb_address(0),
|
| + teb_size(0),
|
| + stack_region_address(0),
|
| + stack_region_size(0),
|
| + suspend_count(0),
|
| + priority_class(0),
|
| + priority(0) {
|
| +}
|
| +
|
| +ProcessReaderWin::ProcessReaderWin()
|
| + : process_(INVALID_HANDLE_VALUE),
|
| + process_info_(),
|
| + threads_(),
|
| + modules_(),
|
| + suspension_state_(),
|
| + initialized_threads_(false),
|
| + initialized_() {
|
| +}
|
| +
|
| +ProcessReaderWin::~ProcessReaderWin() {
|
| +}
|
| +
|
| +bool ProcessReaderWin::Initialize(HANDLE process,
|
| + ProcessSuspensionState suspension_state) {
|
| + INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
| +
|
| + process_ = process;
|
| + suspension_state_ = suspension_state;
|
| + process_info_.Initialize(process);
|
| +
|
| + INITIALIZATION_STATE_SET_VALID(initialized_);
|
| + return true;
|
| +}
|
| +
|
| +bool ProcessReaderWin::ReadMemory(WinVMAddress at,
|
| + WinVMSize num_bytes,
|
| + void* into) const {
|
| + if (num_bytes == 0)
|
| + return 0;
|
| +
|
| + SIZE_T bytes_read;
|
| + if (!ReadProcessMemory(process_,
|
| + reinterpret_cast<void*>(at),
|
| + into,
|
| + base::checked_cast<SIZE_T>(num_bytes),
|
| + &bytes_read) ||
|
| + num_bytes != bytes_read) {
|
| + PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of "
|
| + << num_bytes << " bytes failed";
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +WinVMSize ProcessReaderWin::ReadAvailableMemory(WinVMAddress at,
|
| + WinVMSize num_bytes,
|
| + void* into) const {
|
| + if (num_bytes == 0)
|
| + return 0;
|
| +
|
| + auto ranges = process_info_.GetReadableRanges(
|
| + CheckedRange<WinVMAddress, WinVMSize>(at, num_bytes));
|
| +
|
| + // We only read up until the first unavailable byte, so we only read from the
|
| + // first range. If we have no ranges, then no bytes were accessible anywhere
|
| + // in the range.
|
| + if (ranges.empty()) {
|
| + LOG(ERROR) << base::StringPrintf(
|
| + "range at 0x%llx, size 0x%llx completely inaccessible", at, num_bytes);
|
| + return 0;
|
| + }
|
| +
|
| + // If the start address was adjusted, we couldn't read even the first
|
| + // requested byte.
|
| + if (ranges.front().base() != at) {
|
| + LOG(ERROR) << base::StringPrintf(
|
| + "start of range at 0x%llx, size 0x%llx inaccessible", at, num_bytes);
|
| + return 0;
|
| + }
|
| +
|
| + DCHECK_LE(ranges.front().size(), num_bytes);
|
| +
|
| + // If we fail on a normal read, then something went very wrong.
|
| + if (!ReadMemory(ranges.front().base(), ranges.front().size(), into))
|
| + return 0;
|
| +
|
| + return ranges.front().size();
|
| +}
|
| +
|
| +bool ProcessReaderWin::StartTime(timeval* start_time) const {
|
| + FILETIME creation, exit, kernel, user;
|
| + if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) {
|
| + PLOG(ERROR) << "GetProcessTimes";
|
| + return false;
|
| + }
|
| + *start_time = FiletimeToTimevalEpoch(creation);
|
| + return true;
|
| +}
|
| +
|
| +bool ProcessReaderWin::CPUTimes(timeval* user_time,
|
| + timeval* system_time) const {
|
| + FILETIME creation, exit, kernel, user;
|
| + if (!GetProcessTimes(process_, &creation, &exit, &kernel, &user)) {
|
| + PLOG(ERROR) << "GetProcessTimes";
|
| + return false;
|
| + }
|
| + *user_time = FiletimeToTimevalInterval(user);
|
| + *system_time = FiletimeToTimevalInterval(kernel);
|
| + return true;
|
| +}
|
| +
|
| +const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() {
|
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
| +
|
| + if (initialized_threads_)
|
| + return threads_;
|
| +
|
| + initialized_threads_ = true;
|
| +
|
| +#if defined(ARCH_CPU_64_BITS)
|
| + ReadThreadData<process_types::internal::Traits64>(process_info_.IsWow64());
|
| +#else
|
| + ReadThreadData<process_types::internal::Traits32>(false);
|
| +#endif
|
| +
|
| + return threads_;
|
| +}
|
| +
|
| +const std::vector<ProcessInfo::Module>& ProcessReaderWin::Modules() {
|
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
| +
|
| + if (!process_info_.Modules(&modules_)) {
|
| + LOG(ERROR) << "couldn't retrieve modules";
|
| + }
|
| +
|
| + return modules_;
|
| +}
|
| +
|
| +const ProcessInfo& ProcessReaderWin::GetProcessInfo() const {
|
| + INITIALIZATION_STATE_DCHECK_VALID(initialized_);
|
| + return process_info_;
|
| +}
|
| +
|
| +template <class Traits>
|
| +void ProcessReaderWin::ReadThreadData(bool is_64_reading_32) {
|
| + DCHECK(threads_.empty());
|
| +
|
| + scoped_ptr<uint8_t[]> buffer;
|
| + process_types::SYSTEM_PROCESS_INFORMATION<Traits>* process_information =
|
| + GetProcessInformation<Traits>(process_, &buffer);
|
| + if (!process_information)
|
| + return;
|
| +
|
| + for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) {
|
| + const process_types::SYSTEM_THREAD_INFORMATION<Traits>& thread_info =
|
| + process_information->Threads[i];
|
| + ProcessReaderWin::Thread thread;
|
| + thread.id = thread_info.ClientId.UniqueThread;
|
| +
|
| + ScopedKernelHANDLE thread_handle(OpenThread(thread_info));
|
| + if (!thread_handle.is_valid())
|
| + continue;
|
| +
|
| + if (!FillThreadContextAndSuspendCount<Traits>(thread_handle.get(),
|
| + &thread,
|
| + suspension_state_,
|
| + is_64_reading_32)) {
|
| + continue;
|
| + }
|
| +
|
| + // TODO(scottmg): I believe we could reverse engineer the PriorityClass from
|
| + // the Priority, BasePriority, and
|
| + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms685100 .
|
| + // MinidumpThreadWriter doesn't handle it yet in any case, so investigate
|
| + // both of those at the same time if it's useful.
|
| + thread.priority_class = NORMAL_PRIORITY_CLASS;
|
| +
|
| + thread.priority = thread_info.Priority;
|
| +
|
| + process_types::THREAD_BASIC_INFORMATION<Traits> thread_basic_info;
|
| + NTSTATUS status = crashpad::NtQueryInformationThread(
|
| + thread_handle.get(),
|
| + static_cast<THREADINFOCLASS>(ThreadBasicInformation),
|
| + &thread_basic_info,
|
| + sizeof(thread_basic_info),
|
| + nullptr);
|
| + if (!NT_SUCCESS(status)) {
|
| + NTSTATUS_LOG(ERROR, status) << "NtQueryInformationThread";
|
| + continue;
|
| + }
|
| +
|
| + // Read the TIB (Thread Information Block) which is the first element of the
|
| + // TEB, for its stack fields.
|
| + process_types::NT_TIB<Traits> tib;
|
| + thread.teb_address = thread_basic_info.TebBaseAddress;
|
| + thread.teb_size = sizeof(process_types::TEB<Traits>);
|
| + if (ReadMemory(thread.teb_address, sizeof(tib), &tib)) {
|
| + WinVMAddress base = 0;
|
| + WinVMAddress limit = 0;
|
| + // If we're reading a WOW64 process, then the TIB we just retrieved is the
|
| + // x64 one. The first word of the x64 TIB points at the x86 TIB. See
|
| + // https://msdn.microsoft.com/en-us/library/dn424783.aspx
|
| + if (is_64_reading_32) {
|
| + process_types::NT_TIB<process_types::internal::Traits32> tib32;
|
| + thread.teb_address = tib.Wow64Teb;
|
| + thread.teb_size =
|
| + sizeof(process_types::TEB<process_types::internal::Traits32>);
|
| + if (ReadMemory(thread.teb_address, sizeof(tib32), &tib32)) {
|
| + base = tib32.StackBase;
|
| + limit = tib32.StackLimit;
|
| + }
|
| + } else {
|
| + base = tib.StackBase;
|
| + limit = tib.StackLimit;
|
| + }
|
| +
|
| + // Note, "backwards" because of direction of stack growth.
|
| + thread.stack_region_address = limit;
|
| + if (limit > base) {
|
| + LOG(ERROR) << "invalid stack range: " << base << " - " << limit;
|
| + thread.stack_region_size = 0;
|
| + } else {
|
| + thread.stack_region_size = base - limit;
|
| + }
|
| + }
|
| + threads_.push_back(thread);
|
| + }
|
| +}
|
| +
|
| +} // namespace crashpad
|
|
|