Thread snapshots on Windows can have varying size

In a future CL we will make use of InitializeContext2 which can produce
contexts of varying sizes - this makes the existing use of a union for
wow/x64 contexts no longer feasible.

The context union in process_reader_win is replaced with a (moveable,
copyable) helper struct which currently only knows how to allocate
the replaced WOW or CONTEXT sized unions.

As this field is no longer a member of the Thread struct it cannot
be passed into other functions as a reference, so instead a pointer
is used in these functions.

Bug: 1250098
Change-Id: Ied3fe971c0073bbdafc071217e1bb0f72350bb4e
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3538668
Commit-Queue: Alex Gough <ajgo@chromium.org>
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
Alex Gough 2022-05-14 22:40:02 -07:00 committed by Crashpad LUCI CQ
parent 4581a355b1
commit a5b7e504c6
9 changed files with 165 additions and 113 deletions

View File

@ -36,12 +36,12 @@ static_assert(sizeof(CPUContextX86::Fsave) ==
#endif // ARCH_CPU_X86
template <typename T>
bool HasContextPart(const T& context, uint32_t bits) {
return (context.ContextFlags & bits) == bits;
bool HasContextPart(const T* context, uint32_t bits) {
return (context->ContextFlags & bits) == bits;
}
template <class T>
void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
void CommonInitializeX86Context(const T* context, CPUContextX86* out) {
// This function assumes that the WOW64_CONTEXT_* and x86 CONTEXT_* values
// for ContextFlags are identical. This can be tested when targeting 32-bit
// x86.
@ -72,54 +72,54 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
<< "non-x86 context";
if (HasContextPart(context, WOW64_CONTEXT_CONTROL)) {
out->ebp = context.Ebp;
out->eip = context.Eip;
out->cs = static_cast<uint16_t>(context.SegCs);
out->eflags = context.EFlags;
out->esp = context.Esp;
out->ss = static_cast<uint16_t>(context.SegSs);
out->ebp = context->Ebp;
out->eip = context->Eip;
out->cs = static_cast<uint16_t>(context->SegCs);
out->eflags = context->EFlags;
out->esp = context->Esp;
out->ss = static_cast<uint16_t>(context->SegSs);
}
if (HasContextPart(context, WOW64_CONTEXT_INTEGER)) {
out->eax = context.Eax;
out->ebx = context.Ebx;
out->ecx = context.Ecx;
out->edx = context.Edx;
out->edi = context.Edi;
out->esi = context.Esi;
out->eax = context->Eax;
out->ebx = context->Ebx;
out->ecx = context->Ecx;
out->edx = context->Edx;
out->edi = context->Edi;
out->esi = context->Esi;
}
if (HasContextPart(context, WOW64_CONTEXT_SEGMENTS)) {
out->ds = static_cast<uint16_t>(context.SegDs);
out->es = static_cast<uint16_t>(context.SegEs);
out->fs = static_cast<uint16_t>(context.SegFs);
out->gs = static_cast<uint16_t>(context.SegGs);
out->ds = static_cast<uint16_t>(context->SegDs);
out->es = static_cast<uint16_t>(context->SegEs);
out->fs = static_cast<uint16_t>(context->SegFs);
out->gs = static_cast<uint16_t>(context->SegGs);
}
if (HasContextPart(context, WOW64_CONTEXT_DEBUG_REGISTERS)) {
out->dr0 = context.Dr0;
out->dr1 = context.Dr1;
out->dr2 = context.Dr2;
out->dr3 = context.Dr3;
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
// https://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr4 = context->Dr6;
out->dr5 = context->Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
out->dr6 = context->Dr6;
out->dr7 = context->Dr7;
}
if (HasContextPart(context, WOW64_CONTEXT_EXTENDED_REGISTERS)) {
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
static_assert(sizeof(out->fxsave) == sizeof(context->ExtendedRegisters),
"fxsave types must be equivalent");
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
memcpy(&out->fxsave, &context->ExtendedRegisters, sizeof(out->fxsave));
} else if (HasContextPart(context, WOW64_CONTEXT_FLOATING_POINT)) {
// The static_assert that validates this cast cant be here because it
// relies on field names that vary based on the template parameter.
CPUContextX86::FsaveToFxsave(
*reinterpret_cast<const CPUContextX86::Fsave*>(&context.FloatSave),
*reinterpret_cast<const CPUContextX86::Fsave*>(&context->FloatSave),
&out->fxsave);
}
}
@ -128,101 +128,101 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
#if defined(ARCH_CPU_X86)
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out) {
void InitializeX86Context(const CONTEXT* context, CPUContextX86* out) {
CommonInitializeX86Context(context, out);
}
#elif defined(ARCH_CPU_X86_64)
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
void InitializeX86Context(const WOW64_CONTEXT* context, CPUContextX86* out) {
CommonInitializeX86Context(context, out);
}
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
void InitializeX64Context(const CONTEXT* context, CPUContextX86_64* out) {
memset(out, 0, sizeof(*out));
LOG_IF(ERROR, !HasContextPart(context, CONTEXT_AMD64)) << "non-x64 context";
if (HasContextPart(context, CONTEXT_CONTROL)) {
out->cs = context.SegCs;
out->rflags = context.EFlags;
out->rip = context.Rip;
out->rsp = context.Rsp;
out->cs = context->SegCs;
out->rflags = context->EFlags;
out->rip = context->Rip;
out->rsp = context->Rsp;
// SegSs ignored.
}
if (HasContextPart(context, CONTEXT_INTEGER)) {
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->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->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->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;
}
if (HasContextPart(context, CONTEXT_SEGMENTS)) {
out->fs = context.SegFs;
out->gs = context.SegGs;
out->fs = context->SegFs;
out->gs = context->SegGs;
// SegDs ignored.
// SegEs ignored.
}
if (HasContextPart(context, CONTEXT_DEBUG_REGISTERS)) {
out->dr0 = context.Dr0;
out->dr1 = context.Dr1;
out->dr2 = context.Dr2;
out->dr3 = context.Dr3;
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
// https://en.wikipedia.org/wiki/X86_debug_register.
out->dr4 = context.Dr6;
out->dr5 = context.Dr7;
out->dr4 = context->Dr6;
out->dr5 = context->Dr7;
out->dr6 = context.Dr6;
out->dr7 = context.Dr7;
out->dr6 = context->Dr6;
out->dr7 = context->Dr7;
}
if (HasContextPart(context, CONTEXT_FLOATING_POINT)) {
static_assert(sizeof(out->fxsave) == sizeof(context.FltSave),
static_assert(sizeof(out->fxsave) == sizeof(context->FltSave),
"types must be equivalent");
memcpy(&out->fxsave, &context.FltSave, sizeof(out->fxsave));
memcpy(&out->fxsave, &context->FltSave, sizeof(out->fxsave));
}
}
#elif defined(ARCH_CPU_ARM64)
void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out) {
void InitializeARM64Context(const CONTEXT* context, CPUContextARM64* out) {
memset(out, 0, sizeof(*out));
LOG_IF(ERROR, !HasContextPart(context, CONTEXT_ARM64)) << "non-arm64 context";
if (HasContextPart(context, CONTEXT_CONTROL)) {
out->spsr = context.Cpsr;
out->pc = context.Pc;
out->regs[30] = context.Lr;
out->sp = context.Sp;
out->regs[29] = context.Fp;
out->spsr = context->Cpsr;
out->pc = context->Pc;
out->regs[30] = context->Lr;
out->sp = context->Sp;
out->regs[29] = context->Fp;
}
if (HasContextPart(context, CONTEXT_INTEGER)) {
memcpy(&out->regs[0], &context.X0, 18 * sizeof(context.X0));
memcpy(&out->regs[0], &context->X0, 18 * sizeof(context->X0));
// Don't copy x18 which is reserved as platform register.
memcpy(&out->regs[19], &context.X19, 10 * sizeof(context.X0));
memcpy(&out->regs[19], &context->X19, 10 * sizeof(context->X0));
}
if (HasContextPart(context, CONTEXT_FLOATING_POINT)) {
static_assert(sizeof(out->fpsimd) == sizeof(context.V),
static_assert(sizeof(out->fpsimd) == sizeof(context->V),
"types must be equivalent");
memcpy(&out->fpsimd, &context.V, sizeof(out->fpsimd));
memcpy(&out->fpsimd, &context->V, sizeof(out->fpsimd));
}
}

View File

@ -29,7 +29,7 @@ struct CPUContextARM64;
//! \brief Initializes a CPUContextX86 structure from a native context structure
//! on Windows.
void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
void InitializeX86Context(const CONTEXT* context, CPUContextX86* out);
#endif // ARCH_CPU_X86
@ -37,11 +37,12 @@ void InitializeX86Context(const CONTEXT& context, CPUContextX86* out);
//! \brief Initializes a CPUContextX86 structure from a native context structure
//! on Windows.
void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out);
void InitializeX86Context(const WOW64_CONTEXT* context, CPUContextX86* out);
//! \brief Initializes a CPUContextX86_64 structure from a native context
//! structure on Windows.
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out);
//! Only reads a max of sizeof(CONTEXT) so will not initialize extended values.
void InitializeX64Context(const CONTEXT* context, CPUContextX86_64* out);
#endif // ARCH_CPU_X86_64
@ -49,7 +50,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out);
//! \brief Initializes a CPUContextARM64 structure from a native context
//! structure on Windows.
void InitializeARM64Context(const CONTEXT& context, CPUContextARM64* out);
void InitializeARM64Context(const CONTEXT* context, CPUContextARM64* out);
#endif // ARCH_CPU_ARM64

View File

@ -41,7 +41,7 @@ void TestInitializeX86Context() {
// directly from the supplied thread, float, and debug state parameters.
{
CPUContextX86 cpu_context_x86 = {};
InitializeX86Context(context, &cpu_context_x86);
InitializeX86Context(&context, &cpu_context_x86);
EXPECT_EQ(cpu_context_x86.eax, 1u);
EXPECT_EQ(cpu_context_x86.fxsave.ftw, 2u);
EXPECT_EQ(cpu_context_x86.dr0, 3u);
@ -73,7 +73,7 @@ void TestInitializeX86Context_FsaveWithoutFxsave() {
{
CPUContextX86 cpu_context_x86 = {};
InitializeX86Context(context, &cpu_context_x86);
InitializeX86Context(&context, &cpu_context_x86);
EXPECT_EQ(cpu_context_x86.eax, 1u);
@ -117,7 +117,7 @@ TEST(CPUContextWin, InitializeX64Context) {
// set directly from the supplied thread, float, and debug state parameters.
{
CPUContextX86_64 cpu_context_x86_64 = {};
InitializeX64Context(context, &cpu_context_x86_64);
InitializeX64Context(&context, &cpu_context_x86_64);
EXPECT_EQ(cpu_context_x86_64.rax, 10u);
EXPECT_EQ(cpu_context_x86_64.fxsave.ftw, 11u);
EXPECT_EQ(cpu_context_x86_64.dr0, 12u);

View File

@ -36,7 +36,7 @@ using Context32 = CONTEXT;
using Context32 = WOW64_CONTEXT;
#endif
void NativeContextToCPUContext32(const Context32& context_record,
void NativeContextToCPUContext32(const Context32* context_record,
CPUContext* context,
CPUContextUnion* context_union) {
context->architecture = kCPUArchitectureX86;
@ -46,7 +46,7 @@ void NativeContextToCPUContext32(const Context32& context_record,
#endif // ARCH_CPU_X86_FAMILY
#if defined(ARCH_CPU_64_BITS)
void NativeContextToCPUContext64(const CONTEXT& context_record,
void NativeContextToCPUContext64(const CONTEXT* context_record,
CPUContext* context,
CPUContextUnion* context_union) {
#if defined(ARCH_CPU_X86_64)
@ -190,7 +190,7 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
ProcessReaderWin* process_reader,
WinVMAddress exception_pointers_address,
DWORD exception_thread_id,
void (*native_to_cpu_context)(const ContextType& context_record,
void (*native_to_cpu_context)(const ContextType* context_record,
CPUContext* context,
CPUContextUnion* context_union)) {
ExceptionPointersType exception_pointers;
@ -232,10 +232,9 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
for (const auto& thread : process_reader->Threads()) {
if (thread.id == blame_thread_id) {
thread_id_ = blame_thread_id;
native_to_cpu_context(
*reinterpret_cast<const ContextType*>(&thread.context),
&context_,
&context_union_);
native_to_cpu_context(thread.context.context<const ContextType>(),
&context_,
&context_union_);
exception_address_ = context_.InstructionPointer();
break;
}
@ -266,7 +265,7 @@ bool ExceptionSnapshotWin::InitializeFromExceptionPointers(
return false;
}
native_to_cpu_context(context_record, &context_, &context_union_);
native_to_cpu_context(&context_record, &context_, &context_union_);
}
return true;

View File

@ -93,7 +93,7 @@ class ExceptionSnapshotWin final : public ExceptionSnapshot {
ProcessReaderWin* process_reader,
WinVMAddress exception_pointers_address,
DWORD exception_thread_id,
void (*native_to_cpu_context)(const ContextType& context_record,
void (*native_to_cpu_context)(const ContextType* context_record,
CPUContext* context,
CPUContextUnion* context_union));

View File

@ -143,7 +143,7 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
DCHECK(suspension_state == ProcessSuspensionState::kRunning);
thread->suspend_count = 0;
DCHECK(!is_64_reading_32);
CaptureContext(&thread->context.native);
thread->context.InitializeFromCurrentThread();
} else {
DWORD previous_suspend_count = SuspendThread(thread_handle);
if (previous_suspend_count == static_cast<DWORD>(-1)) {
@ -162,25 +162,18 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
(suspension_state == ProcessSuspensionState::kSuspended ? 1 : 0);
}
memset(&thread->context, 0, sizeof(thread->context));
#if defined(ARCH_CPU_32_BITS)
const bool is_native = true;
#elif defined(ARCH_CPU_64_BITS)
const bool is_native = !is_64_reading_32;
if (is_64_reading_32) {
thread->context.wow64.ContextFlags = CONTEXT_ALL;
if (!Wow64GetThreadContext(thread_handle, &thread->context.wow64)) {
PLOG(ERROR) << "Wow64GetThreadContext";
if (!thread->context.InitializeWow64(thread_handle))
return false;
}
}
#endif
if (is_native) {
thread->context.native.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, &thread->context.native)) {
PLOG(ERROR) << "GetThreadContext";
if (!thread->context.InitializeNative(thread_handle))
return false;
}
}
if (!ResumeThread(thread_handle)) {
@ -194,6 +187,39 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
} // namespace
ProcessReaderWin::ThreadContext::ThreadContext()
: offset_(0), initialized_(false), data_() {}
void ProcessReaderWin::ThreadContext::InitializeFromCurrentThread() {
data_.resize(sizeof(CONTEXT));
initialized_ = true;
CaptureContext(context<CONTEXT>());
}
bool ProcessReaderWin::ThreadContext::InitializeNative(HANDLE thread_handle) {
data_.resize(sizeof(CONTEXT));
initialized_ = true;
context<CONTEXT>()->ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(thread_handle, context<CONTEXT>())) {
PLOG(ERROR) << "GetThreadContext";
return false;
}
return true;
}
#if defined(ARCH_CPU_64_BITS)
bool ProcessReaderWin::ThreadContext::InitializeWow64(HANDLE thread_handle) {
data_.resize(sizeof(WOW64_CONTEXT));
initialized_ = true;
context<WOW64_CONTEXT>()->ContextFlags = CONTEXT_ALL;
if (!Wow64GetThreadContext(thread_handle, context<WOW64_CONTEXT>())) {
PLOG(ERROR) << "Wow64GetThreadContext";
return false;
}
return true;
}
#endif
ProcessReaderWin::Thread::Thread()
: context(),
id(0),
@ -203,8 +229,7 @@ ProcessReaderWin::Thread::Thread()
stack_region_size(0),
suspend_count(0),
priority_class(0),
priority(0) {
}
priority(0) {}
ProcessReaderWin::ProcessReaderWin()
: process_(INVALID_HANDLE_VALUE),

View File

@ -40,17 +40,39 @@ enum class ProcessSuspensionState : bool {
//! \brief Accesses information about another process, identified by a `HANDLE`.
class ProcessReaderWin {
public:
//! \brief Helper to make the context copyable and resizable.
class ThreadContext {
public:
ThreadContext();
~ThreadContext() {}
template <typename T>
T* context() const {
DCHECK(initialized_);
return reinterpret_cast<T*>(
const_cast<unsigned char*>(data_.data() + offset_));
}
#if defined(ARCH_CPU_64_BITS)
bool InitializeWow64(HANDLE thread_handle);
#endif
void InitializeFromCurrentThread();
bool InitializeNative(HANDLE thread_handle);
private:
// This is usually 0 but Windows might cause it to be positive when
// fetching the extended context. This needs to be adjusted after
// calls to InitializeContext2().
size_t offset_;
bool initialized_;
std::vector<unsigned char> data_;
};
//! \brief Contains information about a thread that belongs to a process.
struct Thread {
Thread();
~Thread() {}
union {
CONTEXT native;
#if defined(ARCH_CPU_64_BITS)
WOW64_CONTEXT wow64;
#endif
} context;
ThreadContext context;
uint64_t id;
WinVMAddress teb_address;
WinVMSize teb_size;

View File

@ -111,7 +111,8 @@ TEST(ProcessReaderWin, SelfOneThread) {
ASSERT_GE(threads.size(), 1u);
EXPECT_EQ(threads[0].id, GetCurrentThreadId());
EXPECT_NE(ProgramCounterFromCONTEXT(&threads[0].context.native), nullptr);
EXPECT_NE(ProgramCounterFromCONTEXT(threads[0].context.context<CONTEXT>()),
nullptr);
EXPECT_EQ(threads[0].suspend_count, 0u);
}

View File

@ -67,21 +67,25 @@ bool ThreadSnapshotWin::Initialize(
#if defined(ARCH_CPU_X86)
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(process_reader_thread.context.native, context_.x86);
InitializeX86Context(process_reader_thread.context.context<CONTEXT>(),
context_.x86);
#elif defined(ARCH_CPU_X86_64)
if (process_reader->Is64Bit()) {
context_.architecture = kCPUArchitectureX86_64;
context_.x86_64 = &context_union_.x86_64;
InitializeX64Context(process_reader_thread.context.native, context_.x86_64);
InitializeX64Context(process_reader_thread.context.context<CONTEXT>(),
context_.x86_64);
} else {
context_.architecture = kCPUArchitectureX86;
context_.x86 = &context_union_.x86;
InitializeX86Context(process_reader_thread.context.wow64, context_.x86);
InitializeX86Context(process_reader_thread.context.context<WOW64_CONTEXT>(),
context_.x86);
}
#elif defined(ARCH_CPU_ARM64)
context_.architecture = kCPUArchitectureARM64;
context_.arm64 = &context_union_.arm64;
InitializeARM64Context(process_reader_thread.context.native, context_.arm64);
InitializeARM64Context(process_reader_thread.context.context<CONTEXT>(),
context_.arm64);
#else
#error Unsupported Windows Arch
#endif // ARCH_CPU_X86