From a5b7e504c6fdba0e32558fe541174100e2ee94bb Mon Sep 17 00:00:00 2001 From: Alex Gough Date: Sat, 14 May 2022 22:40:02 -0700 Subject: [PATCH] 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 Reviewed-by: Joshua Peraza --- snapshot/win/cpu_context_win.cc | 148 ++++++++++++------------ snapshot/win/cpu_context_win.h | 9 +- snapshot/win/cpu_context_win_test.cc | 6 +- snapshot/win/exception_snapshot_win.cc | 15 ++- snapshot/win/exception_snapshot_win.h | 2 +- snapshot/win/process_reader_win.cc | 49 ++++++-- snapshot/win/process_reader_win.h | 34 +++++- snapshot/win/process_reader_win_test.cc | 3 +- snapshot/win/thread_snapshot_win.cc | 12 +- 9 files changed, 165 insertions(+), 113 deletions(-) diff --git a/snapshot/win/cpu_context_win.cc b/snapshot/win/cpu_context_win.cc index db2e8036..0d778382 100644 --- a/snapshot/win/cpu_context_win.cc +++ b/snapshot/win/cpu_context_win.cc @@ -36,12 +36,12 @@ static_assert(sizeof(CPUContextX86::Fsave) == #endif // ARCH_CPU_X86 template -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 -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(context.SegCs); - out->eflags = context.EFlags; - out->esp = context.Esp; - out->ss = static_cast(context.SegSs); + out->ebp = context->Ebp; + out->eip = context->Eip; + out->cs = static_cast(context->SegCs); + out->eflags = context->EFlags; + out->esp = context->Esp; + out->ss = static_cast(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(context.SegDs); - out->es = static_cast(context.SegEs); - out->fs = static_cast(context.SegFs); - out->gs = static_cast(context.SegGs); + out->ds = static_cast(context->SegDs); + out->es = static_cast(context->SegEs); + out->fs = static_cast(context->SegFs); + out->gs = static_cast(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 can’t be here because it // relies on field names that vary based on the template parameter. CPUContextX86::FsaveToFxsave( - *reinterpret_cast(&context.FloatSave), + *reinterpret_cast(&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)); } } diff --git a/snapshot/win/cpu_context_win.h b/snapshot/win/cpu_context_win.h index 9718f49c..69700cfe 100644 --- a/snapshot/win/cpu_context_win.h +++ b/snapshot/win/cpu_context_win.h @@ -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 diff --git a/snapshot/win/cpu_context_win_test.cc b/snapshot/win/cpu_context_win_test.cc index ab8b8b31..c6639e4b 100644 --- a/snapshot/win/cpu_context_win_test.cc +++ b/snapshot/win/cpu_context_win_test.cc @@ -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); diff --git a/snapshot/win/exception_snapshot_win.cc b/snapshot/win/exception_snapshot_win.cc index 29cf165d..4c9dcfb3 100644 --- a/snapshot/win/exception_snapshot_win.cc +++ b/snapshot/win/exception_snapshot_win.cc @@ -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(&thread.context), - &context_, - &context_union_); + native_to_cpu_context(thread.context.context(), + &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; diff --git a/snapshot/win/exception_snapshot_win.h b/snapshot/win/exception_snapshot_win.h index b8fa7353..7321d6a9 100644 --- a/snapshot/win/exception_snapshot_win.h +++ b/snapshot/win/exception_snapshot_win.h @@ -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)); diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index e3784cae..20149fae 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -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(-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()); +} + +bool ProcessReaderWin::ThreadContext::InitializeNative(HANDLE thread_handle) { + data_.resize(sizeof(CONTEXT)); + initialized_ = true; + context()->ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(thread_handle, 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()->ContextFlags = CONTEXT_ALL; + if (!Wow64GetThreadContext(thread_handle, 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), diff --git a/snapshot/win/process_reader_win.h b/snapshot/win/process_reader_win.h index 7875de04..dc41a207 100644 --- a/snapshot/win/process_reader_win.h +++ b/snapshot/win/process_reader_win.h @@ -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 + T* context() const { + DCHECK(initialized_); + return reinterpret_cast( + const_cast(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 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; diff --git a/snapshot/win/process_reader_win_test.cc b/snapshot/win/process_reader_win_test.cc index 709e56b2..15a6e2b7 100644 --- a/snapshot/win/process_reader_win_test.cc +++ b/snapshot/win/process_reader_win_test.cc @@ -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()), + nullptr); EXPECT_EQ(threads[0].suspend_count, 0u); } diff --git a/snapshot/win/thread_snapshot_win.cc b/snapshot/win/thread_snapshot_win.cc index c3894a78..3a47cf6e 100644 --- a/snapshot/win/thread_snapshot_win.cc +++ b/snapshot/win/thread_snapshot_win.cc @@ -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_.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_.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(), + 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_.arm64); #else #error Unsupported Windows Arch #endif // ARCH_CPU_X86