win: Retrieve thread context for x64

Retrieve context and save to thread context. NtQueryInformationThread
is no longer required (right now?) because to retrieve the CONTEXT, the
thread needs to be Suspend/ResumeThread'd anyway, and the return value
of SuspendThread is the previous SuspendCount.

I haven't handle the x86 case yet -- that would ideally be via
Wow64GetThreadContext (I think) but unfortunately that's Vista+, so I'll
likely need to to a bit of fiddling to get that sorted out. (It's actually
likely going to be NtQueryInformationThread again, but one thing at a
time for now.)

R=cpu@chromium.org, rsesek@chromium.org
TBR=mark@chromium.org
BUG=crashpad:1

Review URL: https://codereview.chromium.org/1133203002
This commit is contained in:
Scott Graham 2015-05-14 17:37:02 -07:00
parent f357afc43e
commit 5a21de6a1b
7 changed files with 155 additions and 64 deletions

View File

@ -64,22 +64,6 @@ NTSTATUS NtOpenThread(PHANDLE thread_handle,
static_cast<const void*>(client_id)); static_cast<const void*>(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<decltype(::NtQueryInformationThread)*>(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 // Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of
// ntstatus.h. // ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
@ -153,63 +137,69 @@ process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation(
} }
template <class Traits> template <class Traits>
uint32_t GetThreadSuspendCount( HANDLE OpenThread(const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<
const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<Traits>& Traits>& thread_info) {
thread_info) { HANDLE handle;
// Wait reason values are from KWAIT_REASON in wdm.h. We don't need all of ACCESS_MASK query_access = THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME;
// 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; OBJECT_ATTRIBUTES object_attributes;
InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr); InitializeObjectAttributes(&object_attributes, nullptr, 0, nullptr, nullptr);
NTSTATUS status = crashpad::NtOpenThread( 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)) { if (!NT_SUCCESS(status)) {
LOG(WARNING) << "couldn't open thread to retrieve suspend count"; LOG(ERROR) << "NtOpenThread failed";
// Fall back to something semi-reasonable. We know we're suspended at this return nullptr;
// point, so just return 1. }
return 1; return handle;
} }
// Take ownership of this handle so we close on exit. NtClose and CloseHandle // It's necessary to suspend the thread to grab CONTEXT. SuspendThread has a
// are identical. // side-effect of returning the SuspendCount of the thread on success, so we
ScopedKernelHANDLE handle(thread_handle); // fill out these two pieces of semi-unrelated data in the same function.
template <class Traits>
void FillThreadContextAndSuspendCount(
const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<Traits>&
thread_info,
ProcessReaderWin::Thread* thread) {
// From ntddk.h. winternl.h defines THREADINFOCLASS, but only one value. // Don't suspend the thread if it's this thread. This is really only for test
const int kThreadSuspendCount = 35; // binaries, as we won't be walking ourselves, in general.
ULONG suspend_count; bool is_current_thread = thread_info.ClientId.UniqueThread ==
status = crashpad::NtQueryInformationThread( reinterpret_cast<process_types::TEB<Traits>*>(
handle.get(), NtCurrentTeb())->ClientId.UniqueThread;
static_cast<THREADINFOCLASS>(kThreadSuspendCount),
&suspend_count, ScopedKernelHANDLE thread_handle(OpenThread(thread_info));
sizeof(suspend_count),
nullptr); // TODO(scottmg): Handle cross-bitness in this function.
if (!NT_SUCCESS(status)) {
LOG(WARNING) << "NtQueryInformationThread failed" << std::hex << status; if (is_current_thread) {
return 1; 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;
} }
return suspend_count; if (!ResumeThread(thread_handle.get())) {
PLOG(ERROR) << "ResumeThread failed";
}
}
} }
} // namespace } // namespace
ProcessReaderWin::Thread::Thread() ProcessReaderWin::Thread::Thread()
: id(0), : context(),
id(0),
teb(0), teb(0),
stack_region_address(0), stack_region_address(0),
stack_region_size(0), stack_region_size(0),
@ -250,7 +240,8 @@ bool ProcessReaderWin::ReadMemory(WinVMAddress at,
base::checked_cast<SIZE_T>(num_bytes), base::checked_cast<SIZE_T>(num_bytes),
&bytes_read) || &bytes_read) ||
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 false;
} }
return true; return true;
@ -304,7 +295,8 @@ const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() {
thread_info = process_information->Threads[i]; thread_info = process_information->Threads[i];
Thread thread; Thread thread;
thread.id = thread_info.ClientId.UniqueThread; 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 // TODO(scottmg): I believe we could reverse engineer the PriorityClass from
// the Priority, BasePriority, and // the Priority, BasePriority, and
@ -323,8 +315,8 @@ const std::vector<ProcessReaderWin::Thread>& ProcessReaderWin::Threads() {
// its stack fields. // its stack fields.
process_types::NT_TIB<SizeTraits> tib; process_types::NT_TIB<SizeTraits> tib;
if (ReadMemory(thread_info.TebBase, sizeof(tib), &tib)) { if (ReadMemory(thread_info.TebBase, sizeof(tib), &tib)) {
thread.stack_region_address = tib.StackBase;
// Note, "backwards" because of direction of stack growth. // Note, "backwards" because of direction of stack growth.
thread.stack_region_address = tib.StackLimit;
thread.stack_region_size = tib.StackBase - tib.StackLimit; thread.stack_region_size = tib.StackBase - tib.StackLimit;
} }
threads_.push_back(thread); threads_.push_back(thread);

View File

@ -34,6 +34,7 @@ class ProcessReaderWin {
Thread(); Thread();
~Thread() {} ~Thread() {}
CONTEXT context;
uint64_t id; uint64_t id;
WinVMAddress teb; WinVMAddress teb;
WinVMAddress stack_region_address; WinVMAddress stack_region_address;

View File

@ -43,6 +43,28 @@ TEST(ProcessReaderWin, SelfBasic) {
EXPECT_STREQ(kTestMemory, buffer); EXPECT_STREQ(kTestMemory, buffer);
} }
TEST(ProcessReaderWin, SelfOneThread) {
ProcessReaderWin process_reader;
ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess()));
const std::vector<ProcessReaderWin::Thread>& 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
} // namespace test } // namespace test
} // namespace crashpad } // namespace crashpad

View File

@ -20,6 +20,55 @@
namespace crashpad { namespace crashpad {
namespace internal { 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() ThreadSnapshotWin::ThreadSnapshotWin()
: ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() { : ThreadSnapshot(), context_(), stack_(), thread_(), initialized_() {
} }
@ -36,13 +85,24 @@ bool ThreadSnapshotWin::Initialize(
stack_.Initialize( stack_.Initialize(
process_reader, thread_.stack_region_address, thread_.stack_region_size); 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_); INITIALIZATION_STATE_SET_VALID(initialized_);
return true; return true;
} }
const CPUContext* ThreadSnapshotWin::Context() const { const CPUContext* ThreadSnapshotWin::Context() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_); INITIALIZATION_STATE_DCHECK_VALID(initialized_);
LOG(ERROR) << "TODO(scottmg): CPUContext";
return &context_; return &context_;
} }

View File

@ -60,6 +60,12 @@ class ThreadSnapshotWin final : public ThreadSnapshot {
uint64_t ThreadSpecificDataAddress() const override; uint64_t ThreadSpecificDataAddress() const override;
private: private:
#if defined(ARCH_CPU_X86_FAMILY)
union {
CPUContextX86 x86;
CPUContextX86_64 x86_64;
} context_union_;
#endif
CPUContext context_; CPUContext context_;
MemorySnapshotWin stack_; MemorySnapshotWin stack_;
ProcessReaderWin::Thread thread_; ProcessReaderWin::Thread thread_;

View File

@ -199,6 +199,7 @@ int GenerateDumpMain(int argc, char* argv[]) {
MinidumpFileWriter minidump; MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(&process_snapshot); minidump.InitializeFromSnapshot(&process_snapshot);
if (!minidump.WriteEverything(&file_writer)) { if (!minidump.WriteEverything(&file_writer)) {
file_writer.Close();
if (unlink(options.dump_path.c_str()) != 0) { if (unlink(options.dump_path.c_str()) != 0) {
PLOG(ERROR) << "unlink"; PLOG(ERROR) << "unlink";
} }

View File

@ -302,6 +302,15 @@ struct CLIENT_ID {
typename Traits::Pointer UniqueThread; 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 <class Traits>
struct TEB {
NT_TIB<Traits> NtTib;
typename Traits::Pointer EnvironmentPointer;
CLIENT_ID<Traits> ClientId;
};
// See https://msdn.microsoft.com/en-us/library/gg750724.aspx for the base // See https://msdn.microsoft.com/en-us/library/gg750724.aspx for the base
// structure, and // 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 // 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