Write compacted xsave contexts in minidumps

Adds new structures and offsets for minidump extended contexts. This
information will be captured from threads in a later CL so this CL
does not yet write different dumps, except in testing.

Minidump format for extended compacted contexts has been determined by
experiment. Offsets for where to write various parts of the context
are hardcoded to 0x550 as this matches values seen in Windows. Offsets
for misc_info_5 match those seen in working minidumps that can be opened
in windbg. Our hope is that while these could change in future, CPU
and OS vendors are unlikely to change them.

See doc[0] for a discussion of these fields and offsets in the minidump.

See "MANAGING STATE USING THE XSAVE FEATURE SET" Chapter 13 in the
Intel SDM[1]. Many of the offsets and sizes of the extended features
are provided by cpu specific values. We can access these in Windows
using the SDK, and transfer these to the saved extended context
which in turn is understandable by windbg.

Further information is available from AMD Ch. 18 "Shadow Stacks"[2].

    [0] https://docs.google.com/document/d/1Dn8n97r5B7kxYouvujNnPIYd_7QeVHpahSRmB92Qn6g/edit#heading=h.hivqj2jg39y
    [1] https://software.intel.com/content/www/us/en/develop/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html.
    [2] https://www.amd.com/system/files/TechDocs/24593.pdf

Bug: 1250098
Change-Id: Ia9041acc379c6d38329ee99737a2a0a77f7a1ee0
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3536964
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
Commit-Queue: Alex Gough <ajgo@chromium.org>
This commit is contained in:
Alex Gough 2022-05-14 22:40:24 -07:00 committed by Crashpad LUCI CQ
parent 25222891c7
commit 9e0051aba6
10 changed files with 295 additions and 5 deletions

View File

@ -1100,6 +1100,11 @@ enum MINIDUMP_TYPE {
//! MINIDUMP_MEMORY_DESCRIPTOR containing the 256 bytes centered around
//! the exception address or the instruction pointer.
MiniDumpNormal = 0x00000000,
//! \brief A minidump with extended contexts.
//!
//! Contains Normal plus a MISC_INFO_5 structure describing the contexts.
MiniDumpWithAvxXStateContext = 0x00200000,
};
#endif // CRASHPAD_COMPAT_NON_WIN_DBGHELP_H_

View File

@ -155,6 +155,17 @@
//! Architecture (253665-060), 13.4.2 “XSAVE Header”.
#define MAXIMUM_XSTATE_FEATURES (64)
//! \anchor XSTATE_x
//! \name XSTATE_*
//!
//! \brief Offsets and constants for extended state.
//! \{
#define XSTATE_COMPACTION_ENABLE (63)
#define XSTATE_COMPACTION_ENABLE_MASK (1ull << XSTATE_COMPACTION_ENABLE)
#define XSTATE_CET_U (11)
#define XSTATE_MASK_CET_U (1ull << XSTATE_CET_U)
//! \}
//! \brief The location of a single state component within an XSAVE area.
struct XSTATE_FEATURE {
//! \brief The location of a state component within a CPU-specific context

View File

@ -225,7 +225,7 @@ enum MinidumpContextAMD64Flags : uint32_t {
//! \brief Indicates the validity of `xsave` data (`CONTEXT_XSTATE`).
//!
//! The context contains `xsave` data. This is used with an extended context
//! structure not currently defined here.
//! structure which is partly implemented for CET state only.
kMinidumpContextAMD64Xstate = kMinidumpContextAMD64 | 0x00000040,
//! \brief Indicates the validity of control, integer, and floating-point
@ -386,6 +386,51 @@ struct MinidumpContextARM {
uint32_t extra[8];
};
//! \brief CONTEXT_CHUNK
struct MinidumpContextChunk {
int32_t offset;
uint32_t size;
};
//! \brief CONTEXT_EX
struct MinidumpContextExHeader {
MinidumpContextChunk all;
MinidumpContextChunk legacy;
MinidumpContextChunk xstate;
};
//! \brief XSAVE_AREA_HEADER
struct MinidumpXSaveAreaHeader {
uint64_t mask;
uint64_t compaction_mask;
uint64_t xsave_header_reserved[6];
};
//! \brief Offset of first xsave feature in the full extended context.
//!
//! This is used to calculate the final size of the extended context, and
//! can be validated by calling InitializeContext2 with one XSTATE feature,
//! and LocateXStateFeature to determine the first offset.
//! Also see "MANAGING STATE USING THE XSAVE FEATURE SET", Ch. 13, Intel SDM.
constexpr uint32_t kMinidumpAMD64XSaveOffset = 0x550;
//! \brief Offset of first xsave feature within the extended context area.
//!
//! 0x240 is the size of the legacy area (512) + the xsave header(64 bytes)
//! Intel SDM 13.4.1. This is not where the item is in the extended compacted
//! context, but is the offset recorded in the minidump. It needs to be correct
//! there. See https://windows-internals.com/cet-on-windows/ for some discussion
//! "CONTEXT_XSTATE: Extended processor state chunk. The state is stored in the
//! same format the XSAVE operation stores it with exception of the first 512
//! bytes, i.e. starting from XSAVE_AREA_HEADER." This may vary by cpuid.
constexpr uint32_t kXSaveAreaFirstOffset = 0x240;
//! \brief XSAVE_CET_U_FORMAT
struct MinidumpAMD64XSaveFormatCetU {
uint64_t cetmsr;
uint64_t ssp;
};
//! \brief 64-bit ARM-specifc flags for MinidumpContextARM64::context_flags.
enum MinidumpContextARM64Flags : uint32_t {
//! \brief Identifies the context structure as 64-bit ARM.

View File

@ -118,6 +118,11 @@ size_t MinidumpContextWriter::SizeOfObject() {
return ContextSize();
}
size_t MinidumpContextWriter::FreezeAndGetSizeOfObject() {
Freeze();
return SizeOfObject();
}
MinidumpContextX86Writer::MinidumpContextX86Writer()
: MinidumpContextWriter(), context_() {
context_.context_flags = kMinidumpContextX86;
@ -213,7 +218,14 @@ void MinidumpContextAMD64Writer::InitializeFromSnapshot(
DCHECK_EQ(state(), kStateMutable);
DCHECK_EQ(context_.context_flags, kMinidumpContextAMD64);
context_.context_flags = kMinidumpContextAMD64All;
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;
@ -247,6 +259,15 @@ void MinidumpContextAMD64Writer::InitializeFromSnapshot(
// 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<MinidumpXSaveAMD64CetU>();
cet_u->InitializeFromSnapshot(context_snapshot);
xsave_entries_.push_back(std::move(cet_u));
}
}
size_t MinidumpContextAMD64Writer::Alignment() {
@ -258,14 +279,96 @@ size_t MinidumpContextAMD64Writer::Alignment() {
bool MinidumpContextAMD64Writer::WriteObject(FileWriterInterface* file_writer) {
DCHECK_EQ(state(), kStateWritable);
// Note: all sizes here come from our constants, not from untrustworthy data.
std::vector<unsigned char> data(ContextSize());
unsigned char* const buf = data.data();
return file_writer->Write(&context_, sizeof(context_));
// 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}};
MinidumpXSaveAreaHeader xsave_header = {0};
// CONTEXT_EX goes directly after the CONTEXT. |offset| is relative to
// &CONTEXT_EX.
context_ex.all.offset = -static_cast<int32_t>(sizeof(context_));
context_ex.all.size = static_cast<uint32_t>(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<int32_t>(
kMinidumpAMD64XSaveOffset - sizeof(MinidumpXSaveAreaHeader) -
sizeof(context_));
context_ex.xstate.size =
static_cast<uint32_t>(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;
}
}
return sizeof(context_);
bool MinidumpXSaveAMD64CetU::InitializeFromSnapshot(
const CPUContextX86_64* context_snapshot) {
DCHECK_EQ(context_snapshot->xstate.cet_u.cetmsr, 1ull);
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()

View File

@ -27,6 +27,7 @@ namespace crashpad {
struct CPUContext;
struct CPUContextX86;
struct CPUContextX86_64;
class MinidumpMiscInfoWriter;
//! \brief The base class for writers of CPU context structures in minidump
//! files.
@ -49,6 +50,12 @@ class MinidumpContextWriter : public internal::MinidumpWritable {
static std::unique_ptr<MinidumpContextWriter> CreateFromSnapshot(
const CPUContext* context_snapshot);
//! \brief Returns the size of the context structure that this object will
//! write.
//!
//! \note This method will force this to #kStateFrozen, if it is not already.
size_t FreezeAndGetSizeOfObject();
protected:
MinidumpContextWriter() : MinidumpWritable() {}
@ -105,6 +112,36 @@ class MinidumpContextX86Writer final : public MinidumpContextWriter {
MinidumpContextX86 context_;
};
//! \brief Wraps an xsave feature that knows where and how big it is.
class MinidumpXSaveFeatureAMD64 {
public:
virtual ~MinidumpXSaveFeatureAMD64() = default;
// Number of bytes that will be written. May need to vary by CPUID (see
// Intel 13.5).
virtual size_t Size() const = 0;
// Intel 13.4.2 XCOMP_BV.
virtual uint8_t XCompBVBit() const = 0;
// Write data to dst. Does not write padding.
virtual bool Copy(void* dst) const = 0;
};
//! \brief XSAVE_CET_U_FORMAT
class MinidumpXSaveAMD64CetU final : public MinidumpXSaveFeatureAMD64 {
public:
MinidumpXSaveAMD64CetU() {}
~MinidumpXSaveAMD64CetU() {}
MinidumpXSaveAMD64CetU(const MinidumpXSaveAMD64CetU&) = delete;
MinidumpXSaveAMD64CetU& operator=(const MinidumpXSaveAMD64CetU&) = delete;
size_t Size() const override { return sizeof(cet_u_); }
uint8_t XCompBVBit() const override { return XSTATE_CET_U; }
bool Copy(void* dst) const override;
bool InitializeFromSnapshot(const CPUContextX86_64* context_snapshot);
private:
MinidumpAMD64XSaveFormatCetU cet_u_;
};
//! \brief The writer for a MinidumpContextAMD64 structure in a minidump file.
class MinidumpContextAMD64Writer final : public MinidumpContextWriter {
public:
@ -157,6 +194,8 @@ class MinidumpContextAMD64Writer final : public MinidumpContextWriter {
private:
MinidumpContextAMD64 context_;
// These should be in order of XCompBVBit().
std::vector<std::unique_ptr<MinidumpXSaveFeatureAMD64>> xsave_entries_;
};
//! \brief The writer for a MinidumpContextARM structure in a minidump file.

View File

@ -153,6 +153,30 @@ TEST(MinidumpContextWriter, AMD64_FromSnapshot) {
context, ExpectMinidumpContextAMD64, kSeed);
}
TEST(MinidumpContextWriter, AMD64_CetFromSnapshot) {
constexpr uint32_t kSeed = 77;
CPUContextX86_64 context_x86_64;
CPUContext context;
context.x86_64 = &context_x86_64;
InitializeCPUContextX86_64(&context, kSeed);
context_x86_64.xstate.enabled_features |= XSTATE_MASK_CET_U;
context_x86_64.xstate.cet_u.cetmsr = 1;
context_x86_64.xstate.cet_u.ssp = kSeed * kSeed;
// We cannot use FromSnapshotTest as we write more than the fixed context.
std::unique_ptr<MinidumpContextWriter> context_writer =
MinidumpContextWriter::CreateFromSnapshot(&context);
ASSERT_TRUE(context_writer);
StringFile string_file;
ASSERT_TRUE(context_writer->WriteEverything(&string_file));
const MinidumpContextAMD64* observed =
MinidumpWritableAtRVA<MinidumpContextAMD64>(string_file.string(), 0);
ASSERT_TRUE(observed);
ExpectMinidumpContextAMD64(kSeed, observed, true);
}
TEST(MinidumpContextWriter, ARM_Zeros) {
EmptyContextTest<MinidumpContextARMWriter, MinidumpContextARM>(
ExpectMinidumpContextARM);

View File

@ -79,6 +79,9 @@ void MinidumpFileWriter::InitializeFromSnapshot(
auto misc_info = std::make_unique<MinidumpMiscInfoWriter>();
misc_info->InitializeFromSnapshot(process_snapshot);
if (misc_info->HasXStateData())
header_.Flags = header_.Flags | MiniDumpWithAvxXStateContext;
add_stream_result = AddStream(std::move(misc_info));
DCHECK(add_stream_result);

View File

@ -23,10 +23,13 @@
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "minidump/minidump_context_writer.h"
#include "minidump/minidump_writer_util.h"
#include "package.h"
#include "snapshot/cpu_context.h"
#include "snapshot/process_snapshot.h"
#include "snapshot/system_snapshot.h"
#include "snapshot/thread_snapshot.h"
#include "util/file/file_writer.h"
#include "util/numeric/in_range_cast.h"
#include "util/numeric/safe_assignment.h"
@ -95,6 +98,43 @@ int AvailabilityVersionToMacOSVersionNumber(int availability) {
}
#endif // BUILDFLAG(IS_MAC)
bool MaybeSetXStateData(const ProcessSnapshot* process_snapshot,
XSTATE_CONFIG_FEATURE_MSC_INFO* xstate) {
// Cannot set xstate data if there are no threads.
auto threads = process_snapshot->Threads();
if (threads.size() == 0)
return false;
// All threads should be the same as we request contexts in the same way.
auto context = threads.at(0)->Context();
// Only support AMD64.
if (context->architecture != kCPUArchitectureX86_64)
return false;
// If no extended features, then we will just write the standard context.
if (context->x86_64->xstate.enabled_features == 0)
return false;
xstate->SizeOfInfo = sizeof(*xstate);
// Needs to match the size of the context we'll write or the dump is invalid,
// so ask the first thread how large it will be.
auto context_writer = MinidumpContextWriter::CreateFromSnapshot(context);
xstate->ContextSize =
static_cast<uint32_t>(context_writer->FreezeAndGetSizeOfObject());
// Note: This isn't the same as xstateenabledfeatures!
xstate->EnabledFeatures =
context->x86_64->xstate.enabled_features | XSTATE_COMPACTION_ENABLE_MASK;
// Note: if other XSAVE entries are to be supported they will be in order,
// and may have different offsets depending on what is saved.
if (context->x86_64->xstate.enabled_features & XSTATE_MASK_CET_U) {
xstate->Features[XSTATE_CET_U].Offset = kXSaveAreaFirstOffset;
xstate->Features[XSTATE_CET_U].Size = sizeof(MinidumpAMD64XSaveFormatCetU);
}
return true;
}
} // namespace
namespace internal {
@ -235,6 +275,11 @@ void MinidumpMiscInfoWriter::InitializeFromSnapshot(
SetBuildString(BuildString(system_snapshot),
internal::MinidumpMiscInfoDebugBuildString());
XSTATE_CONFIG_FEATURE_MSC_INFO xstate{};
if (MaybeSetXStateData(process_snapshot, &xstate)) {
SetXStateData(xstate);
}
}
void MinidumpMiscInfoWriter::SetProcessID(uint32_t process_id) {
@ -353,6 +398,10 @@ void MinidumpMiscInfoWriter::SetXStateData(
has_xstate_data_ = true;
}
bool MinidumpMiscInfoWriter::HasXStateData() const {
return has_xstate_data_;
}
void MinidumpMiscInfoWriter::SetProcessCookie(uint32_t process_cookie) {
DCHECK_EQ(state(), kStateMutable);

View File

@ -118,6 +118,9 @@ class MinidumpMiscInfoWriter final : public internal::MinidumpStreamWriter {
//! \brief Sets MINIDUMP_MISC_INFO_5::XStateData.
void SetXStateData(const XSTATE_CONFIG_FEATURE_MSC_INFO& xstate_data);
//! \brief Will this write extended context information?
bool HasXStateData() const;
//! \brief Sets the field referenced by #MINIDUMP_MISC5_PROCESS_COOKIE.
void SetProcessCookie(uint32_t process_cookie);

View File

@ -383,7 +383,15 @@ void ExpectMinidumpContextAMD64(
MinidumpContextAMD64 expected;
InitializeMinidumpContextAMD64(&expected, expect_seed);
EXPECT_EQ(observed->context_flags, expected.context_flags);
// Allow context_flags to include xstate bit - this is added if we will write
// an extended context, but is not generated in the fixed context for testing.
if ((observed->context_flags & kMinidumpContextAMD64Xstate) ==
kMinidumpContextAMD64Xstate) {
EXPECT_EQ(observed->context_flags,
(expected.context_flags | kMinidumpContextAMD64Xstate));
} else {
EXPECT_EQ(observed->context_flags, expected.context_flags);
}
if (snapshot) {
EXPECT_EQ(observed->p1_home, 0u);