From 658cd3e1a729e3bd183873b81230da44ed195946 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Mon, 11 May 2015 13:29:52 -0700 Subject: [PATCH] win: Add thread snapshot and memory snapshot for stacks The next big piece of functionality in snapshot. There's a bit more grubbing around in the NT internals than would be nice, and it has made me start to question the value avoiding MinidumpWriteDump. But this seems to extract most of the data we need (I haven't pulled the cpu context yet, but I hope that won't be too hard.) R=mark@chromium.org BUG=crashpad:1 Review URL: https://codereview.chromium.org/1131473005 --- snapshot/mac/memory_snapshot_mac.cc | 1 + snapshot/snapshot.gyp | 4 + snapshot/win/memory_snapshot_win.cc | 70 ++++++++ snapshot/win/memory_snapshot_win.h | 68 +++++++ snapshot/win/process_reader_win.cc | 256 +++++++++++++++++++++++++++ snapshot/win/process_reader_win.h | 22 +++ snapshot/win/process_snapshot_win.cc | 26 ++- snapshot/win/process_snapshot_win.h | 5 +- snapshot/win/thread_snapshot_win.cc | 75 ++++++++ snapshot/win/thread_snapshot_win.h | 74 ++++++++ util/win/process_structs.h | 101 +++++++++++ 11 files changed, 695 insertions(+), 7 deletions(-) create mode 100644 snapshot/win/memory_snapshot_win.cc create mode 100644 snapshot/win/memory_snapshot_win.h create mode 100644 snapshot/win/thread_snapshot_win.cc create mode 100644 snapshot/win/thread_snapshot_win.h diff --git a/snapshot/mac/memory_snapshot_mac.cc b/snapshot/mac/memory_snapshot_mac.cc index b0cce244..4ded2512 100644 --- a/snapshot/mac/memory_snapshot_mac.cc +++ b/snapshot/mac/memory_snapshot_mac.cc @@ -14,6 +14,7 @@ #include "snapshot/mac/memory_snapshot_mac.h" +#include "base/memory/scoped_ptr.h" #include "util/mach/task_memory.h" namespace crashpad { diff --git a/snapshot/snapshot.gyp b/snapshot/snapshot.gyp index dcb386f6..0eda1b6b 100644 --- a/snapshot/snapshot.gyp +++ b/snapshot/snapshot.gyp @@ -88,6 +88,8 @@ 'system_snapshot.h', 'thread_snapshot.h', 'win/module_snapshot_win.cc', + 'win/memory_snapshot_win.cc', + 'win/memory_snapshot_win.h', 'win/module_snapshot_win.h', 'win/pe_image_reader.cc', 'win/pe_image_reader.h', @@ -97,6 +99,8 @@ 'win/process_snapshot_win.h', 'win/system_snapshot_win.cc', 'win/system_snapshot_win.h', + 'win/thread_snapshot_win.cc', + 'win/thread_snapshot_win.h', ], 'conditions': [ ['OS=="win"', { diff --git a/snapshot/win/memory_snapshot_win.cc b/snapshot/win/memory_snapshot_win.cc new file mode 100644 index 00000000..1861a885 --- /dev/null +++ b/snapshot/win/memory_snapshot_win.cc @@ -0,0 +1,70 @@ +// 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/memory_snapshot_win.h" + +#include "base/memory/scoped_ptr.h" + +namespace crashpad { +namespace internal { + +MemorySnapshotWin::MemorySnapshotWin() + : MemorySnapshot(), + process_reader_(nullptr), + address_(0), + size_(0), + initialized_() { +} + +MemorySnapshotWin::~MemorySnapshotWin() { +} + +void MemorySnapshotWin::Initialize(ProcessReaderWin* process_reader, + uint64_t address, + uint64_t size) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + process_reader_ = process_reader; + address_ = address; + DLOG_IF(WARNING, size >= std::numeric_limits::max()) + << "size overflow"; + size_ = static_cast(size); + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +uint64_t MemorySnapshotWin::Address() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return address_; +} + +size_t MemorySnapshotWin::Size() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return size_; +} + +bool MemorySnapshotWin::Read(Delegate* delegate) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (size_ == 0) { + return delegate->MemorySnapshotDelegateRead(nullptr, size_); + } + + scoped_ptr buffer(new uint8_t[size_]); + if (!process_reader_->ReadMemory(address_, size_, buffer.get())) { + return false; + } + return delegate->MemorySnapshotDelegateRead(buffer.get(), size_); +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/win/memory_snapshot_win.h b/snapshot/win/memory_snapshot_win.h new file mode 100644 index 00000000..b6d2074e --- /dev/null +++ b/snapshot/win/memory_snapshot_win.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ +#define CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ + +#include +#include + +#include "base/basictypes.h" +#include "snapshot/memory_snapshot.h" +#include "snapshot/win/process_reader_win.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief A MemorySnapshot of a memory region in a process on the running +//! system, when the system runs Windows. +class MemorySnapshotWin final : public MemorySnapshot { + public: + MemorySnapshotWin(); + ~MemorySnapshotWin() override; + + //! \brief Initializes the object. + //! + //! Memory is read lazily. No attempt is made to read the memory snapshot data + //! until Read() is called, and the memory snapshot data is discared when + //! Read() returns. + //! + //! \param[in] process_reader A reader for the process being snapshotted. + //! \param[in] address The base address of the memory region to snapshot, in + //! the snapshot process' address space. + //! \param[in] size The size of the memory region to snapshot. + void Initialize(ProcessReaderWin* process_reader, + uint64_t address, + uint64_t size); + + // MemorySnapshot: + + uint64_t Address() const override; + size_t Size() const override; + bool Read(Delegate* delegate) const override; + + private: + ProcessReaderWin* process_reader_; // weak + uint64_t address_; + size_t size_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(MemorySnapshotWin); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_MEMORY_SNAPSHOT_WIN_H_ diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index 84e176b8..a8774b01 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -14,15 +14,216 @@ #include "snapshot/win/process_reader_win.h" +#include + +#include "base/memory/scoped_ptr.h" #include "base/numerics/safe_conversions.h" +#include "util/win/process_structs.h" +#include "util/win/scoped_handle.h" #include "util/win/time.h" namespace crashpad { +namespace { + +NTSTATUS NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS system_information_class, + PVOID system_information, + ULONG system_information_length, + PULONG return_length) { + static decltype(::NtQuerySystemInformation)* nt_query_system_information = + reinterpret_cast(GetProcAddress( + LoadLibrary(L"ntdll.dll"), "NtQuerySystemInformation")); + DCHECK(nt_query_system_information); + return nt_query_system_information(system_information_class, + system_information, + system_information_length, + return_length); +} + +// The 4th argument is CLIENT_ID*, but as we can't typedef that, we simply cast +// to void* here. +typedef NTSTATUS(WINAPI* NtOpenThreadFunction)( + PHANDLE ThreadHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + const void* ClientId); + +template +NTSTATUS NtOpenThread(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID* client_id) { + static NtOpenThreadFunction nt_open_thread = + reinterpret_cast( + GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtOpenThread")); + DCHECK(nt_open_thread); + return nt_open_thread(thread_handle, + desired_access, + object_attributes, + static_cast(client_id)); +} + +NTSTATUS NtQueryInformationThread(HANDLE thread_handle, + THREADINFOCLASS thread_information_class, + PVOID thread_information, + ULONG thread_information_length, + PULONG return_length) { + static decltype(::NtQueryInformationThread)* nt_query_information_thread = + reinterpret_cast(GetProcAddress( + LoadLibrary(L"ntdll.dll"), "NtQueryInformationThread")); + DCHECK(nt_query_information_thread); + return nt_query_information_thread(thread_handle, + thread_information_class, + thread_information, + thread_information_length, + return_length); +} + +// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of +// ntstatus.h. +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) + +// 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 +process_types::SYSTEM_PROCESS_INFORMATION* NextProcess( + process_types::SYSTEM_PROCESS_INFORMATION* process) { + ULONG offset = process->NextEntryOffset; + if (offset == 0) + return nullptr; + return reinterpret_cast*>( + reinterpret_cast(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 +process_types::SYSTEM_PROCESS_INFORMATION* GetProcessInformation( + HANDLE process_handle, + scoped_ptr* 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) { + const int kSystemExtendedProcessInformation = 57; + status = crashpad::NtQuerySystemInformation( + static_cast( + kSystemExtendedProcessInformation), + reinterpret_cast(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)) { + LOG(ERROR) << "NtQuerySystemInformation failed: " << std::hex << status; + return nullptr; + } + + process_types::SYSTEM_PROCESS_INFORMATION* process = + reinterpret_cast*>( + buffer->get()); + DWORD process_id = GetProcessId(process_handle); + do { + if (process->UniqueProcessId == process_id) + return process; + } while (process = NextProcess(process)); + + LOG(ERROR) << "process " << process_id << " not found"; + return nullptr; +} + +template +uint32_t GetThreadSuspendCount( + const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION& + thread_info) { + // Wait reason values are from KWAIT_REASON in wdm.h. We don't need all of + // them, so just declare the one we need. + const ULONG kWaitReasonSuspended = 5; + + // Kernel mode enumerations for thread state come from + // http://www.nirsoft.net/kernel_struct/vista/KTHREAD_STATE.html and + // https://msdn.microsoft.com/en-us/library/system.diagnostics.threadstate(v=vs.110).aspx + const ULONG kThreadStateWaiting = 5; + const ULONG kThreadStateGateWait = 8; + + bool suspended = (thread_info.ThreadState == kThreadStateWaiting || + thread_info.ThreadState == kThreadStateGateWait) && + thread_info.WaitReason == kWaitReasonSuspended; + if (!suspended) + return 0; + + HANDLE thread_handle; + ACCESS_MASK query_access = THREAD_QUERY_LIMITED_INFORMATION; + OBJECT_ATTRIBUTES object_attributes; + InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); + NTSTATUS status = crashpad::NtOpenThread( + &thread_handle, query_access, &object_attributes, &thread_info.ClientId); + if (!NT_SUCCESS(status)) { + LOG(WARNING) << "couldn't open thread to retrieve suspend count"; + // Fall back to something semi-reasonable. We know we're suspended at this + // point, so just return 1. + return 1; + } + + // Take ownership of this handle so we close on exit. NtClose and CloseHandle + // are identical. + ScopedKernelHANDLE handle(thread_handle); + + // From ntddk.h. winternl.h defines THREADINFOCLASS, but only one value. + const int kThreadSuspendCount = 35; + ULONG suspend_count; + status = crashpad::NtQueryInformationThread( + handle.get(), + static_cast(kThreadSuspendCount), + &suspend_count, + sizeof(suspend_count), + nullptr); + if (!NT_SUCCESS(status)) { + LOG(WARNING) << "NtQueryInformationThread failed" << std::hex << status; + return 1; + } + + return suspend_count; +} + +} // namespace + +ProcessReaderWin::Thread::Thread() + : id(0), + teb(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_(), + initialized_threads_(false), initialized_() { } @@ -77,6 +278,61 @@ bool ProcessReaderWin::CPUTimes(timeval* user_time, return true; } +const std::vector& ProcessReaderWin::Threads() { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + if (initialized_threads_) + return threads_; + + initialized_threads_ = true; + + DCHECK(threads_.empty()); + +#if ARCH_CPU_32_BITS + using SizeTraits = process_types::internal::Traits32; +#else + using SizeTraits = process_types::internal::Traits64; +#endif + scoped_ptr buffer; + process_types::SYSTEM_PROCESS_INFORMATION* process_information = + GetProcessInformation(process_, &buffer); + if (!process_information) + return threads_; + + for (unsigned long i = 0; i < process_information->NumberOfThreads; ++i) { + const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION& + thread_info = process_information->Threads[i]; + Thread thread; + thread.id = thread_info.ClientId.UniqueThread; + thread.suspend_count = GetThreadSuspendCount(thread_info); + + // 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; + thread.teb = thread_info.TebBase; + + // While there are semi-documented fields in the thread structure called + // StackBase and StackLimit, they don't appear to be correct in practice (or + // at least, I don't know how to interpret them). Instead, read the TIB + // (Thread Information Block) which is the first element of the TEB, and use + // its stack fields. + process_types::NT_TIB tib; + if (ReadMemory(thread_info.TebBase, sizeof(tib), &tib)) { + thread.stack_region_address = tib.StackBase; + // Note, "backwards" because of direction of stack growth. + thread.stack_region_size = tib.StackBase - tib.StackLimit; + } + threads_.push_back(thread); + } + + return threads_; +} + const std::vector& ProcessReaderWin::Modules() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h index ff687885..0e9ada52 100644 --- a/snapshot/win/process_reader_win.h +++ b/snapshot/win/process_reader_win.h @@ -18,6 +18,8 @@ #include #include +#include + #include "util/misc/initialization_state_dcheck.h" #include "util/win/address_types.h" #include "util/win/process_info.h" @@ -27,6 +29,20 @@ namespace crashpad { //! \brief Accesses information about another process, identified by a HANDLE. class ProcessReaderWin { public: + //! \brief Contains information about a thread that belongs to a process. + struct Thread { + Thread(); + ~Thread() {} + + uint64_t id; + WinVMAddress teb; + WinVMAddress stack_region_address; + WinVMSize stack_region_size; + uint32_t suspend_count; + uint32_t priority_class; + uint32_t priority; + }; + ProcessReaderWin(); ~ProcessReaderWin(); @@ -66,6 +82,10 @@ class ProcessReaderWin { //! \return `true` on success, `false` on failure, with a warning logged. bool CPUTimes(timeval* user_time, timeval* system_time) const; + //! \return The threads that are in the process. The first element (at index + //! `0`) corresponds to the main thread. + const std::vector& Threads(); + //! \return The modules loaded in the process. The first element (at index //! `0`) corresponds to the main executable. const std::vector& Modules(); @@ -73,7 +93,9 @@ class ProcessReaderWin { private: HANDLE process_; ProcessInfo process_info_; + std::vector threads_; std::vector modules_; + bool initialized_threads_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessReaderWin); diff --git a/snapshot/win/process_snapshot_win.cc b/snapshot/win/process_snapshot_win.cc index 16623069..b25e8caa 100644 --- a/snapshot/win/process_snapshot_win.cc +++ b/snapshot/win/process_snapshot_win.cc @@ -22,7 +22,7 @@ namespace crashpad { ProcessSnapshotWin::ProcessSnapshotWin() : ProcessSnapshot(), system_(), - // TODO(scottmg): threads_(), + threads_(), modules_(), // TODO(scottmg): exception_(), process_reader_(), @@ -46,7 +46,7 @@ bool ProcessSnapshotWin::Initialize(HANDLE process) { system_.Initialize(&process_reader_); - // TODO(scottmg): InitializeThreads(); + InitializeThreads(); InitializeModules(); INITIALIZATION_STATE_SET_VALID(initialized_); @@ -131,8 +131,12 @@ const SystemSnapshot* ProcessSnapshotWin::System() const { } std::vector ProcessSnapshotWin::Threads() const { - CHECK(false) << "TODO(scottmg)"; - return std::vector(); + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + std::vector threads; + for (internal::ThreadSnapshotWin* thread : threads_) { + threads.push_back(thread); + } + return threads; } std::vector ProcessSnapshotWin::Modules() const { @@ -145,10 +149,22 @@ std::vector ProcessSnapshotWin::Modules() const { } const ExceptionSnapshot* ProcessSnapshotWin::Exception() const { - CHECK(false) << "TODO(scottmg)"; + CHECK(false) << "TODO(scottmg): Exception()"; return nullptr; } +void ProcessSnapshotWin::InitializeThreads() { + const std::vector& process_reader_threads = + process_reader_.Threads(); + for (const ProcessReaderWin::Thread& process_reader_thread : + process_reader_threads) { + auto thread = make_scoped_ptr(new internal::ThreadSnapshotWin()); + if (thread->Initialize(&process_reader_, process_reader_thread)) { + threads_.push_back(thread.release()); + } + } +} + void ProcessSnapshotWin::InitializeModules() { const std::vector& process_reader_modules = process_reader_.Modules(); diff --git a/snapshot/win/process_snapshot_win.h b/snapshot/win/process_snapshot_win.h index 8bb0bf48..97bb1c69 100644 --- a/snapshot/win/process_snapshot_win.h +++ b/snapshot/win/process_snapshot_win.h @@ -33,6 +33,7 @@ #include "snapshot/thread_snapshot.h" #include "snapshot/win/module_snapshot_win.h" #include "snapshot/win/system_snapshot_win.h" +#include "snapshot/win/thread_snapshot_win.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" #include "util/stdlib/pointer_container.h" @@ -104,13 +105,13 @@ class ProcessSnapshotWin final : public ProcessSnapshot { private: // Initializes threads_ on behalf of Initialize(). - // TODO(scottmg): void InitializeThreads(); + void InitializeThreads(); // Initializes modules_ on behalf of Initialize(). void InitializeModules(); internal::SystemSnapshotWin system_; - // TODO(scottmg): PointerVector threads_; + PointerVector threads_; PointerVector modules_; // TODO(scottmg): scoped_ptr exception_; ProcessReaderWin process_reader_; diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc new file mode 100644 index 00000000..689e79ca --- /dev/null +++ b/snapshot/win/thread_snapshot_win.cc @@ -0,0 +1,75 @@ +// 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/thread_snapshot_win.h" + +#include "base/logging.h" +#include "snapshot/win/process_reader_win.h" + +namespace crashpad { +namespace internal { + +ThreadSnapshotWin::ThreadSnapshotWin() + : ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() { +} + +ThreadSnapshotWin::~ThreadSnapshotWin() { +} + +bool ThreadSnapshotWin::Initialize( + ProcessReaderWin* process_reader, + const ProcessReaderWin::Thread& process_reader_thread) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + thread_ = process_reader_thread; + stack_.Initialize( + process_reader, thread_.stack_region_address, thread_.stack_region_size); + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +const CPUContext* ThreadSnapshotWin::Context() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + LOG(ERROR) << "TODO(scottmg): CPUContext"; + return &context_; +} + +const MemorySnapshot* ThreadSnapshotWin::Stack() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return &stack_; +} + +uint64_t ThreadSnapshotWin::ThreadID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_.id; +} + +int ThreadSnapshotWin::SuspendCount() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_.suspend_count; +} + +int ThreadSnapshotWin::Priority() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_.priority; +} + +uint64_t ThreadSnapshotWin::ThreadSpecificDataAddress() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return thread_.teb; +} + +} // namespace internal +} // namespace crashpad diff --git a/snapshot/win/thread_snapshot_win.h b/snapshot/win/thread_snapshot_win.h new file mode 100644 index 00000000..19e8066c --- /dev/null +++ b/snapshot/win/thread_snapshot_win.h @@ -0,0 +1,74 @@ +// 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. + +#ifndef CRASHPAD_SNAPSHOT_WIN_THREAD_SNAPSHOT_WIN_H_ +#define CRASHPAD_SNAPSHOT_WIN_THREAD_SNAPSHOT_WIN_H_ + +#include + +#include "base/basictypes.h" +#include "snapshot/cpu_context.h" +#include "snapshot/memory_snapshot.h" +#include "snapshot/thread_snapshot.h" +#include "snapshot/win/memory_snapshot_win.h" +#include "snapshot/win/process_reader_win.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { + +class ProcessReaderWin; + +namespace internal { + +//! \brief A ThreadSnapshot of a thread in a running (or crashed) process on a +//! Windows system. +class ThreadSnapshotWin final : public ThreadSnapshot { + public: + ThreadSnapshotWin(); + ~ThreadSnapshotWin() override; + + //! \brief Initializes the object. + //! + //! \param[in] process_reader A ProcessReaderWin for the process containing + //! the thread. + //! \param[in] process_reader_thread The thread within the ProcessReaderWin + //! for which the snapshot should be created. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + bool Initialize(ProcessReaderWin* process_reader, + const ProcessReaderWin::Thread& process_reader_thread); + + // ThreadSnapshot: + + const CPUContext* Context() const override; + const MemorySnapshot* Stack() const override; + uint64_t ThreadID() const override; + int SuspendCount() const override; + int Priority() const override; + uint64_t ThreadSpecificDataAddress() const override; + + private: + CPUContext context_; + MemorySnapshotWin stack_; + ProcessReaderWin::Thread thread_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSnapshotWin); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_SNAPSHOT_WIN_THREAD_SNAPSHOT_WIN_H_ diff --git a/util/win/process_structs.h b/util/win/process_structs.h index ece60896..d88b33d1 100644 --- a/util/win/process_structs.h +++ b/util/win/process_structs.h @@ -281,6 +281,107 @@ struct PEB { DWORD FlsHighIndex; }; +template +struct NT_TIB { + typename Traits::Pointer ExceptionList; + typename Traits::Pointer StackBase; + typename Traits::Pointer StackLimit; + typename Traits::Pointer SubSystemTib; + union { + typename Traits::Pointer FiberData; + BYTE Version[4]; + }; + typename Traits::Pointer ArbitraryUserPointer; + typename Traits::Pointer Self; +}; + +// See https://msdn.microsoft.com/en-us/library/gg750647.aspx. +template +struct CLIENT_ID { + typename Traits::Pointer UniqueProcess; + typename Traits::Pointer UniqueThread; +}; + +// See https://msdn.microsoft.com/en-us/library/gg750724.aspx for the base +// structure, and +// http://processhacker.sourceforge.net/doc/struct___s_y_s_t_e_m___e_x_t_e_n_d_e_d___t_h_r_e_a_d___i_n_f_o_r_m_a_t_i_o_n.html +// for the extension part. +template +struct SYSTEM_EXTENDED_THREAD_INFORMATION { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + union { + ULONG WaitTime; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer StartAddress; + CLIENT_ID ClientId; + LONG Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + union { + ULONG WaitReason; + typename Traits::Pad padding_for_x64_1; + }; + typename Traits::Pointer StackBase; // These don't appear to be correct. + typename Traits::Pointer StackLimit; + typename Traits::Pointer Win32StartAddress; + typename Traits::Pointer TebBase; + typename Traits::Pointer Reserved; + typename Traits::Pointer Reserved2; + typename Traits::Pointer Reserved3; +}; + +// See http://undocumented.ntinternals.net/source/usermode/undocumented%20functions/system%20information/structures/system_process_information.html +template +struct SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER Reserved[3]; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + union { + LONG BasePriority; + typename Traits::Pad padding_for_x64_0; + }; + union { + DWORD UniqueProcessId; + typename Traits::Pad padding_for_x64_1; + }; + union { + DWORD InheritedFromUniqueProcessId; + typename Traits::Pad padding_for_x64_2; + }; + ULONG HandleCount; + ULONG Reserved2[3]; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + union { + ULONG PageFaultCount; + typename Traits::Pad padding_for_x64_3; + }; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + SYSTEM_EXTENDED_THREAD_INFORMATION Threads[1]; +}; + #pragma pack(pop) //! \}