// Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "minidump/minidump_context_writer.h" #include #include #include #include #include "base/compiler_specific.h" #include "base/logging.h" #include "build/build_config.h" #include "snapshot/cpu_context.h" #include "util/file/file_writer.h" #include "util/stdlib/aligned_allocator.h" namespace crashpad { namespace { // Sanity-check complex structures to ensure interoperability. static_assert(sizeof(MinidumpContextX86) == 716, "MinidumpContextX86 size"); static_assert(sizeof(MinidumpContextAMD64) == 1232, "MinidumpContextAMD64 size"); // These structures can also be checked against definitions in the Windows SDK. #if BUILDFLAG(IS_WIN) #if defined(ARCH_CPU_X86_FAMILY) static_assert(sizeof(MinidumpContextX86) == sizeof(WOW64_CONTEXT), "WOW64_CONTEXT size"); #if defined(ARCH_CPU_X86) static_assert(sizeof(MinidumpContextX86) == sizeof(CONTEXT), "CONTEXT size"); #elif defined(ARCH_CPU_X86_64) static_assert(sizeof(MinidumpContextAMD64) == sizeof(CONTEXT), "CONTEXT size"); #endif #endif // ARCH_CPU_X86_FAMILY #endif // BUILDFLAG(IS_WIN) } // namespace MinidumpContextWriter::~MinidumpContextWriter() { } // static std::unique_ptr MinidumpContextWriter::CreateFromSnapshot(const CPUContext* context_snapshot) { std::unique_ptr context; switch (context_snapshot->architecture) { case kCPUArchitectureX86: { MinidumpContextX86Writer* context_x86 = new MinidumpContextX86Writer(); context.reset(context_x86); context_x86->InitializeFromSnapshot(context_snapshot->x86); break; } case kCPUArchitectureX86_64: { MinidumpContextAMD64Writer* context_amd64 = new MinidumpContextAMD64Writer(); context.reset(context_amd64); context_amd64->InitializeFromSnapshot(context_snapshot->x86_64); break; } case kCPUArchitectureARM: { context = std::make_unique(); reinterpret_cast(context.get()) ->InitializeFromSnapshot(context_snapshot->arm); break; } case kCPUArchitectureARM64: { context = std::make_unique(); reinterpret_cast(context.get()) ->InitializeFromSnapshot(context_snapshot->arm64); break; } case kCPUArchitectureMIPSEL: { context = std::make_unique(); reinterpret_cast(context.get()) ->InitializeFromSnapshot(context_snapshot->mipsel); break; } case kCPUArchitectureMIPS64EL: { context = std::make_unique(); reinterpret_cast(context.get()) ->InitializeFromSnapshot(context_snapshot->mips64); break; } case kCPUArchitectureRISCV64: { context = std::make_unique(); reinterpret_cast(context.get()) ->InitializeFromSnapshot(context_snapshot->riscv64); break; } default: { LOG(ERROR) << "unknown context architecture " << context_snapshot->architecture; break; } } return context; } size_t MinidumpContextWriter::SizeOfObject() { DCHECK_GE(state(), kStateFrozen); return ContextSize(); } size_t MinidumpContextWriter::FreezeAndGetSizeOfObject() { Freeze(); return SizeOfObject(); } MinidumpContextX86Writer::MinidumpContextX86Writer() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextX86; } MinidumpContextX86Writer::~MinidumpContextX86Writer() { } void MinidumpContextX86Writer::InitializeFromSnapshot( const CPUContextX86* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextX86); context_.context_flags = kMinidumpContextX86All; context_.dr0 = context_snapshot->dr0; context_.dr1 = context_snapshot->dr1; context_.dr2 = context_snapshot->dr2; context_.dr3 = context_snapshot->dr3; context_.dr6 = context_snapshot->dr6; context_.dr7 = context_snapshot->dr7; // The contents of context_.fsave effectively alias everything in // context_.fxsave that’s related to x87 FPU state. context_.fsave doesn’t // carry state specific to SSE (or later), such as mxcsr and the xmm // registers. CPUContextX86::FxsaveToFsave(context_snapshot->fxsave, &context_.fsave); context_.gs = context_snapshot->gs; context_.fs = context_snapshot->fs; context_.es = context_snapshot->es; context_.ds = context_snapshot->ds; context_.edi = context_snapshot->edi; context_.esi = context_snapshot->esi; context_.ebx = context_snapshot->ebx; context_.edx = context_snapshot->edx; context_.ecx = context_snapshot->ecx; context_.eax = context_snapshot->eax; context_.ebp = context_snapshot->ebp; context_.eip = context_snapshot->eip; context_.cs = context_snapshot->cs; context_.eflags = context_snapshot->eflags; context_.esp = context_snapshot->esp; context_.ss = context_snapshot->ss; // This is effectively a memcpy() of a big structure. context_.fxsave = context_snapshot->fxsave; } bool MinidumpContextX86Writer::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextX86Writer::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } static_assert(alignof(MinidumpContextAMD64) >= 16, "MinidumpContextAMD64 alignment"); static_assert(alignof(MinidumpContextAMD64Writer) >= alignof(MinidumpContextAMD64), "MinidumpContextAMD64Writer alignment"); MinidumpContextAMD64Writer::MinidumpContextAMD64Writer() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextAMD64; } MinidumpContextAMD64Writer::~MinidumpContextAMD64Writer() { } // static void* MinidumpContextAMD64Writer::operator new(size_t size) { // MinidumpContextAMD64 requests an alignment of 16, which can be larger than // what standard new provides. This may trigger MSVC warning C4316. As a // workaround to this language deficiency, provide a custom allocation // function to allocate a block meeting the alignment requirement. return AlignedAllocate(alignof(MinidumpContextAMD64Writer), size); } // static void MinidumpContextAMD64Writer::operator delete(void* pointer) { return AlignedFree(pointer); } void MinidumpContextAMD64Writer::InitializeFromSnapshot( const CPUContextX86_64* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextAMD64); if (context_snapshot->xstate.enabled_features != 0) { // Extended context. context_.context_flags = kMinidumpContextAMD64All | kMinidumpContextAMD64Xstate; } else { // Fixed size context - no xsave components. context_.context_flags = kMinidumpContextAMD64All; } context_.mx_csr = context_snapshot->fxsave.mxcsr; context_.cs = context_snapshot->cs; context_.fs = context_snapshot->fs; context_.gs = context_snapshot->gs; // The top 32 bits of rflags are reserved/unused. context_.eflags = static_cast(context_snapshot->rflags); context_.dr0 = context_snapshot->dr0; context_.dr1 = context_snapshot->dr1; context_.dr2 = context_snapshot->dr2; context_.dr3 = context_snapshot->dr3; context_.dr6 = context_snapshot->dr6; context_.dr7 = context_snapshot->dr7; context_.rax = context_snapshot->rax; context_.rcx = context_snapshot->rcx; context_.rdx = context_snapshot->rdx; context_.rbx = context_snapshot->rbx; context_.rsp = context_snapshot->rsp; context_.rbp = context_snapshot->rbp; context_.rsi = context_snapshot->rsi; context_.rdi = context_snapshot->rdi; context_.r8 = context_snapshot->r8; context_.r9 = context_snapshot->r9; context_.r10 = context_snapshot->r10; context_.r11 = context_snapshot->r11; context_.r12 = context_snapshot->r12; context_.r13 = context_snapshot->r13; context_.r14 = context_snapshot->r14; context_.r15 = context_snapshot->r15; context_.rip = context_snapshot->rip; // This is effectively a memcpy() of a big structure. context_.fxsave = context_snapshot->fxsave; // If XSave features are being recorded store in xsave_entries in xcomp_bv // order. We will not see features we do not support as we provide flags // to the OS when first obtaining a snapshot. if (context_snapshot->xstate.enabled_features & XSTATE_MASK_CET_U) { auto cet_u = std::make_unique(); cet_u->InitializeFromSnapshot(context_snapshot); xsave_entries_.push_back(std::move(cet_u)); } } size_t MinidumpContextAMD64Writer::Alignment() { DCHECK_GE(state(), kStateFrozen); // Match the alignment of MinidumpContextAMD64. return 16; } bool MinidumpContextAMD64Writer::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); // Note: all sizes here come from our constants, not from untrustworthy data. std::vector data(ContextSize()); unsigned char* const buf = data.data(); // CONTEXT always comes first. DCHECK_LE(sizeof(context_), data.size()); memcpy(buf, &context_, sizeof(context_)); if (xsave_entries_.size() > 0) { MinidumpContextExHeader context_ex = {{0, 0}, {0, 0}, {0, 0}}; MinidumpXSaveAreaHeader xsave_header = {0, 0, {}}; // CONTEXT_EX goes directly after the CONTEXT. |offset| is relative to // &CONTEXT_EX. context_ex.all.offset = -static_cast(sizeof(context_)); context_ex.all.size = static_cast(ContextSize()); context_ex.legacy.offset = context_ex.all.offset; context_ex.legacy.size = sizeof(context_); // Then... there is a gap. // // In the compacted format the XSave area header goes just before // the first xsave entry. It has a total size given by the header // + (padded) sizes of all the entries. context_ex.xstate.offset = static_cast( kMinidumpAMD64XSaveOffset - sizeof(MinidumpXSaveAreaHeader) - sizeof(context_)); context_ex.xstate.size = static_cast(sizeof(MinidumpXSaveAreaHeader) + ContextSize() - kMinidumpAMD64XSaveOffset); // Store CONTEXT_EX now it is complete. DCHECK_LE(sizeof(context_) + sizeof(context_ex), data.size()); memcpy(&buf[sizeof(context_)], &context_ex, sizeof(context_ex)); // Calculate flags for xsave header & write entries (they will be // *after* the xsave header). size_t cursor = kMinidumpAMD64XSaveOffset; for (auto const& entry : xsave_entries_) { xsave_header.mask |= 1ull << entry->XCompBVBit(); DCHECK_LE(cursor + entry->Size(), data.size()); entry->Copy(&buf[cursor]); cursor += entry->Size(); } xsave_header.compaction_mask = xsave_header.mask | XSTATE_COMPACTION_ENABLE_MASK; // Store xsave header at its calculated offset. It is before the entries // above, but we need to add the |mask| bits before writing it. DCHECK_LE( context_ex.xstate.offset + sizeof(context_) + sizeof(xsave_header), data.size()); memcpy(&buf[context_ex.xstate.offset + sizeof(context_)], &xsave_header, sizeof(xsave_header)); } if (!file_writer->Write(data.data(), data.size())) return false; return true; } size_t MinidumpContextAMD64Writer::ContextSize() const { DCHECK_GE(state(), kStateFrozen); if (xsave_entries_.size() == 0) { return sizeof(context_); } else { DCHECK_EQ(context_.context_flags, kMinidumpContextAMD64All | kMinidumpContextAMD64Xstate); DCHECK(xsave_entries_.size() != 0); size_t size = kMinidumpAMD64XSaveOffset; for (auto& entry : xsave_entries_) { size += entry->Size(); } return size; } } bool MinidumpXSaveAMD64CetU::InitializeFromSnapshot( const CPUContextX86_64* context_snapshot) { #ifdef SENTRY_DISABLED DCHECK_EQ(context_snapshot->xstate.cet_u.cetmsr, 1ull); #else // TODO(supervacuus): this DCHECK led to multiple user inquiries because it // ends up killing the crashpad_handler (when using a DEBUG build) which in // turn keeps the crashpad client waiting indefinitely. // // It seems that crashpad devs put a DCHECK here because they already check // at the call-site that the CET_U flag is enabled in the XSAVE feature set. // However, that this flag is set, only means that the CET_U registers in // XSAVE are valid, not necessarily that the SH_STK_EN bit is set. // // I couldn't find anything in the Intel (SDM 13.1) or AMD (PR 11.5.2, // 18.11/12/13) CET/SS spec that would signal that SH_STK_EN(=cetmsr[0]) // cannot be 0 at this point. Ideally, if SH_STK_EN is not set, then SSP // should be set to 0 too (which means both are in their initial state). But // even that should not lead to fatally exit the crashpad_handler (even in // DEBUG), but rather produce a log and result in something that can be // analysed in the backend. // // Any validation based on these register contents must check SH_STK_EN // anyway or check SSP for !NULL and as a valid base like it is done here: // https://chromium.googlesource.com/crashpad/crashpad/+/6278690abe6ef0dda047e67dc1d0c49ce7af3811/snapshot/win/thread_snapshot_win.cc#130 if (!(context_snapshot->xstate.cet_u.cetmsr & 1ull)) { LOG(WARNING) << "CET MSR enabled flag is not set (" << context_snapshot->xstate.cet_u.cetmsr << "); SSP = " << context_snapshot->xstate.cet_u.ssp; } #endif cet_u_.cetmsr = context_snapshot->xstate.cet_u.cetmsr; cet_u_.ssp = context_snapshot->xstate.cet_u.ssp; return true; } bool MinidumpXSaveAMD64CetU::Copy(void* dst) const { memcpy(dst, &cet_u_, sizeof(cet_u_)); return true; } MinidumpContextARMWriter::MinidumpContextARMWriter() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextARM; } MinidumpContextARMWriter::~MinidumpContextARMWriter() = default; void MinidumpContextARMWriter::InitializeFromSnapshot( const CPUContextARM* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextARM); context_.context_flags = kMinidumpContextARMAll; static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), "GPRS size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); context_.fp = context_snapshot->fp; context_.ip = context_snapshot->ip; context_.sp = context_snapshot->sp; context_.lr = context_snapshot->lr; context_.pc = context_snapshot->pc; context_.cpsr = context_snapshot->cpsr; context_.fpscr = context_snapshot->vfp_regs.fpscr; static_assert(sizeof(context_.vfp) == sizeof(context_snapshot->vfp_regs.vfp), "VFP size mismatch"); memcpy(context_.vfp, context_snapshot->vfp_regs.vfp, sizeof(context_.vfp)); memset(context_.extra, 0, sizeof(context_.extra)); } bool MinidumpContextARMWriter::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextARMWriter::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } MinidumpContextARM64Writer::MinidumpContextARM64Writer() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextARM64; } MinidumpContextARM64Writer::~MinidumpContextARM64Writer() = default; void MinidumpContextARM64Writer::InitializeFromSnapshot( const CPUContextARM64* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextARM64); context_.context_flags = kMinidumpContextARM64Full; static_assert( sizeof(context_.regs) == sizeof(context_snapshot->regs) - 2 * sizeof(context_snapshot->regs[0]), "GPRs size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); context_.fp = context_snapshot->regs[29]; context_.lr = context_snapshot->regs[30]; context_.sp = context_snapshot->sp; context_.pc = context_snapshot->pc; context_.cpsr = context_snapshot->spsr; static_assert(sizeof(context_.fpsimd) == sizeof(context_snapshot->fpsimd), "FPSIMD size mismatch"); memcpy(context_.fpsimd, context_snapshot->fpsimd, sizeof(context_.fpsimd)); context_.fpcr = context_snapshot->fpcr; context_.fpsr = context_snapshot->fpsr; memset(context_.bcr, 0, sizeof(context_.bcr)); memset(context_.bvr, 0, sizeof(context_.bvr)); memset(context_.wcr, 0, sizeof(context_.wcr)); memset(context_.wvr, 0, sizeof(context_.wvr)); } bool MinidumpContextARM64Writer::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextARM64Writer::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } MinidumpContextMIPSWriter::MinidumpContextMIPSWriter() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextMIPS; } MinidumpContextMIPSWriter::~MinidumpContextMIPSWriter() = default; void MinidumpContextMIPSWriter::InitializeFromSnapshot( const CPUContextMIPS* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS); context_.context_flags = kMinidumpContextMIPSAll; static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), "GPRs size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); context_.mdhi = context_snapshot->mdhi; context_.mdlo = context_snapshot->mdlo; context_.epc = context_snapshot->cp0_epc; context_.badvaddr = context_snapshot->cp0_badvaddr; context_.status = context_snapshot->cp0_status; context_.cause = context_snapshot->cp0_cause; static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs), "FPRs size mismatch"); memcpy(&context_.fpregs, &context_snapshot->fpregs, sizeof(context_.fpregs)); context_.fpcsr = context_snapshot->fpcsr; context_.fir = context_snapshot->fir; for (size_t index = 0; index < 3; ++index) { context_.hi[index] = context_snapshot->hi[index]; context_.lo[index] = context_snapshot->lo[index]; } context_.dsp_control = context_snapshot->dsp_control; } bool MinidumpContextMIPSWriter::WriteObject(FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextMIPSWriter::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } MinidumpContextMIPS64Writer::MinidumpContextMIPS64Writer() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextMIPS64; } MinidumpContextMIPS64Writer::~MinidumpContextMIPS64Writer() = default; void MinidumpContextMIPS64Writer::InitializeFromSnapshot( const CPUContextMIPS64* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextMIPS64); context_.context_flags = kMinidumpContextMIPS64All; static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), "GPRs size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); context_.mdhi = context_snapshot->mdhi; context_.mdlo = context_snapshot->mdlo; context_.epc = context_snapshot->cp0_epc; context_.badvaddr = context_snapshot->cp0_badvaddr; context_.status = context_snapshot->cp0_status; context_.cause = context_snapshot->cp0_cause; static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs), "FPRs size mismatch"); memcpy(context_.fpregs.dregs, context_snapshot->fpregs.dregs, sizeof(context_.fpregs.dregs)); context_.fpcsr = context_snapshot->fpcsr; context_.fir = context_snapshot->fir; for (size_t index = 0; index < 3; ++index) { context_.hi[index] = context_snapshot->hi[index]; context_.lo[index] = context_snapshot->lo[index]; } context_.dsp_control = context_snapshot->dsp_control; } bool MinidumpContextMIPS64Writer::WriteObject( FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextMIPS64Writer::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } MinidumpContextRISCV64Writer::MinidumpContextRISCV64Writer() : MinidumpContextWriter(), context_() { context_.context_flags = kMinidumpContextRISCV64; context_.version = MinidumpContextRISCV64::kVersion; } MinidumpContextRISCV64Writer::~MinidumpContextRISCV64Writer() = default; void MinidumpContextRISCV64Writer::InitializeFromSnapshot( const CPUContextRISCV64* context_snapshot) { DCHECK_EQ(state(), kStateMutable); DCHECK_EQ(context_.context_flags, kMinidumpContextRISCV64); context_.context_flags = kMinidumpContextRISCV64All; context_.version = MinidumpContextRISCV64::kVersion; context_.pc = context_snapshot->pc; static_assert(sizeof(context_.regs) == sizeof(context_snapshot->regs), "GPRs size mismatch"); memcpy(context_.regs, context_snapshot->regs, sizeof(context_.regs)); static_assert(sizeof(context_.fpregs) == sizeof(context_snapshot->fpregs), "FPRs size mismatch"); memcpy(context_.fpregs, context_snapshot->fpregs, sizeof(context_.fpregs)); context_.fcsr = context_snapshot->fcsr; } bool MinidumpContextRISCV64Writer::WriteObject( FileWriterInterface* file_writer) { DCHECK_EQ(state(), kStateWritable); return file_writer->Write(&context_, sizeof(context_)); } size_t MinidumpContextRISCV64Writer::ContextSize() const { DCHECK_GE(state(), kStateFrozen); return sizeof(context_); } } // namespace crashpad