mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 07:14:10 +08:00
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:
parent
f357afc43e
commit
5a21de6a1b
@ -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);
|
||||
|
@ -34,6 +34,7 @@ class ProcessReaderWin {
|
||||
Thread();
|
||||
~Thread() {}
|
||||
|
||||
CONTEXT context;
|
||||
uint64_t id;
|
||||
WinVMAddress teb;
|
||||
WinVMAddress stack_region_address;
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user