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));
}
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
// ntstatus.h.
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
@ -153,63 +137,69 @@ process_types::SYSTEM_PROCESS_INFORMATION<Traits>* GetProcessInformation(
}
template <class Traits>
uint32_t GetThreadSuspendCount(
const process_types::SYSTEM_EXTENDED_THREAD_INFORMATION<Traits>&
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 <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.
const int kThreadSuspendCount = 35;
ULONG suspend_count;
status = crashpad::NtQueryInformationThread(
handle.get(),
static_cast<THREADINFOCLASS>(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<process_types::TEB<Traits>*>(
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<SIZE_T>(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::Thread>& 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::Thread>& ProcessReaderWin::Threads() {
// its stack fields.
process_types::NT_TIB<SizeTraits> 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);

View File

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

View File

@ -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<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 test
} // namespace crashpad

View File

@ -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_;
}

View File

@ -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_;

View File

@ -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";
}

View File

@ -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 <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
// 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