diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index a8774b01..09e04594 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -64,22 +64,6 @@ NTSTATUS NtOpenThread(PHANDLE thread_handle, 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) @@ -153,63 +137,69 @@ process_types::SYSTEM_PROCESS_INFORMATION* GetProcessInformation( } 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; +HANDLE OpenThread(const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION< + Traits>& thread_info) { + HANDLE handle; + ACCESS_MASK query_access = THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME; OBJECT_ATTRIBUTES object_attributes; InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); NTSTATUS status = crashpad::NtOpenThread( - &thread_handle, query_access, &object_attributes, &thread_info.ClientId); + &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; + LOG(ERROR) << "NtOpenThread failed"; + return nullptr; } + return handle; +} - // Take ownership of this handle so we close on exit. NtClose and CloseHandle - // are identical. - ScopedKernelHANDLE handle(thread_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 +void FillThreadContextAndSuspendCount( + const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION& + thread_info, + ProcessReaderWin::Thread* thread) { - // 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; + // 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_info.ClientId.UniqueThread == + reinterpret_cast*>( + NtCurrentTeb())->ClientId.UniqueThread; + + ScopedKernelHANDLE thread_handle(OpenThread(thread_info)); + + // TODO(scottmg): Handle cross-bitness in this function. + + if (is_current_thread) { + thread->suspend_count = 0; + RtlCaptureContext(&thread->context); + } else { + DWORD previous_suspend_count = SuspendThread(thread_handle.get()); + if (previous_suspend_count == -1) { + PLOG(ERROR) << "SuspendThread failed"; + return; + } + thread->suspend_count = previous_suspend_count; + + memset(&thread->context, 0, sizeof(thread->context)); + thread->context.ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(thread_handle.get(), &thread->context)) { + PLOG(ERROR) << "GetThreadContext failed"; + return; + } + + if (!ResumeThread(thread_handle.get())) { + PLOG(ERROR) << "ResumeThread failed"; + } } - - return suspend_count; } } // namespace ProcessReaderWin::Thread::Thread() - : id(0), + : context(), + id(0), teb(0), stack_region_address(0), stack_region_size(0), @@ -250,7 +240,8 @@ bool ProcessReaderWin::ReadMemory(WinVMAddress at, base::checked_cast(num_bytes), &bytes_read) || num_bytes != bytes_read) { - PLOG(ERROR) << "ReadMemory at " << at << " of " << num_bytes << " failed"; + PLOG(ERROR) << "ReadMemory at 0x" << std::hex << at << std::dec << " of " + << num_bytes << " bytes failed"; return false; } return true; @@ -304,7 +295,8 @@ const std::vector& ProcessReaderWin::Threads() { thread_info = process_information->Threads[i]; Thread thread; thread.id = thread_info.ClientId.UniqueThread; - thread.suspend_count = GetThreadSuspendCount(thread_info); + + FillThreadContextAndSuspendCount(thread_info, &thread); // TODO(scottmg): I believe we could reverse engineer the PriorityClass from // the Priority, BasePriority, and @@ -323,8 +315,8 @@ const std::vector& ProcessReaderWin::Threads() { // 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_address = tib.StackLimit; thread.stack_region_size = tib.StackBase - tib.StackLimit; } threads_.push_back(thread); diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h index 0e9ada52..a60fdac4 100644 --- a/snapshot/win/process_reader_win.h +++ b/snapshot/win/process_reader_win.h @@ -34,6 +34,7 @@ class ProcessReaderWin { Thread(); ~Thread() {} + CONTEXT context; uint64_t id; WinVMAddress teb; WinVMAddress stack_region_address; diff --git a/snapshot/win/process_reader_win_test.cc b/snapshot/win/process_reader_win_test.cc index e7c478ea..38de7865 100644 --- a/snapshot/win/process_reader_win_test.cc +++ b/snapshot/win/process_reader_win_test.cc @@ -43,6 +43,28 @@ TEST(ProcessReaderWin, SelfBasic) { EXPECT_STREQ(kTestMemory, buffer); } +TEST(ProcessReaderWin, SelfOneThread) { + ProcessReaderWin process_reader; + ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess())); + + const std::vector& threads = + process_reader.Threads(); + + // If other tests ran in this process previously, threads may have been + // created and may still be running. This check must look for at least one + // thread, not exactly one thread. + ASSERT_GE(threads.size(), 1u); + + EXPECT_EQ(GetThreadId(GetCurrentThread()), threads[0].id); +#if defined(ARCH_CPU_64_BITS) + EXPECT_NE(0, threads[0].context.Rip); +#else + EXPECT_NE(0, threads[0].context.Eip); +#endif + + EXPECT_EQ(0, threads[0].suspend_count); +} + } // namespace } // namespace test } // namespace crashpad diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc index 689e79ca..83ad54ef 100644 --- a/snapshot/win/thread_snapshot_win.cc +++ b/snapshot/win/thread_snapshot_win.cc @@ -20,6 +20,55 @@ namespace crashpad { namespace internal { +namespace { + +void InitializeX64Context(const CONTEXT& context, + CPUContextX86_64* out) { + out->rax = context.Rax; + out->rbx = context.Rbx; + out->rcx = context.Rcx; + out->rdx = context.Rdx; + out->rdi = context.Rdi; + out->rsi = context.Rsi; + out->rbp = context.Rbp; + out->rsp = context.Rsp; + out->r8 = context.R8; + out->r9 = context.R9; + out->r10 = context.R10; + out->r11 = context.R11; + out->r12 = context.R12; + out->r13 = context.R13; + out->r14 = context.R14; + out->r15 = context.R15; + out->rip = context.Rip; + out->rflags = context.EFlags; + out->cs = context.SegCs; + out->fs = context.SegFs; + out->gs = context.SegGs; + + out->dr0 = context.Dr0; + out->dr1 = context.Dr1; + out->dr2 = context.Dr2; + out->dr3 = context.Dr3; + // DR4 and DR5 are obsolete synonyms for DR6 and DR7, see + // http://en.wikipedia.org/wiki/X86_debug_register. + out->dr4 = context.Dr6; + out->dr5 = context.Dr7; + out->dr6 = context.Dr6; + out->dr7 = context.Dr7; + + static_assert(sizeof(out->fxsave) == sizeof(context.FltSave), + "types must be equivalent"); + memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave)); +} + +void InitializeX86Context(const CONTEXT& context, + CPUContextX86* out) { + CHECK(false) << "TODO(scottmg) InitializeX86Context()"; +} + +} // namespace + ThreadSnapshotWin::ThreadSnapshotWin() : ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() { } @@ -36,13 +85,24 @@ bool ThreadSnapshotWin::Initialize( stack_.Initialize( process_reader, thread_.stack_region_address, thread_.stack_region_size); +#if defined(ARCH_CPU_X86_FAMILY) + if (process_reader->Is64Bit()) { + context_.architecture = kCPUArchitectureX86_64; + context_.x86_64 = &context_union_.x86_64; + InitializeX64Context(process_reader_thread.context, context_.x86_64); + } else { + context_.architecture = kCPUArchitectureX86; + context_.x86 = &context_union_.x86; + InitializeX86Context(process_reader_thread.context, context_.x86); + } +#endif + INITIALIZATION_STATE_SET_VALID(initialized_); return true; } const CPUContext* ThreadSnapshotWin::Context() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - LOG(ERROR) << "TODO(scottmg): CPUContext"; return &context_; } diff --git a/snapshot/win/thread_snapshot_win.h b/snapshot/win/thread_snapshot_win.h index 19e8066c..9829707f 100644 --- a/snapshot/win/thread_snapshot_win.h +++ b/snapshot/win/thread_snapshot_win.h @@ -60,6 +60,12 @@ class ThreadSnapshotWin final : public ThreadSnapshot { uint64_t ThreadSpecificDataAddress() const override; private: +#if defined(ARCH_CPU_X86_FAMILY) + union { + CPUContextX86 x86; + CPUContextX86_64 x86_64; + } context_union_; +#endif CPUContext context_; MemorySnapshotWin stack_; ProcessReaderWin::Thread thread_; diff --git a/tools/generate_dump.cc b/tools/generate_dump.cc index 818d232d..5da9f044 100644 --- a/tools/generate_dump.cc +++ b/tools/generate_dump.cc @@ -199,6 +199,7 @@ int GenerateDumpMain(int argc, char* argv[]) { MinidumpFileWriter minidump; minidump.InitializeFromSnapshot(&process_snapshot); if (!minidump.WriteEverything(&file_writer)) { + file_writer.Close(); if (unlink(options.dump_path.c_str()) != 0) { PLOG(ERROR) << "unlink"; } diff --git a/util/win/process_structs.h b/util/win/process_structs.h index d88b33d1..6cd06b0f 100644 --- a/util/win/process_structs.h +++ b/util/win/process_structs.h @@ -302,6 +302,15 @@ struct CLIENT_ID { typename Traits::Pointer UniqueThread; }; +// This is a partial definition of the TEB, as we do not currently use many +// fields of it. See http://www.nirsoft.net/kernel_struct/vista/TEB.html. +template +struct TEB { + NT_TIB NtTib; + typename Traits::Pointer EnvironmentPointer; + CLIENT_ID ClientId; +}; + // 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