win: support native x86 CONTEXT structures with x87 but no SSE context

When no SSE (fxsave) context is available but x87 (fsave) context is, use the
x87 context.

This also embeds the x87 FPU opcode from the fxsave fop field in bits 16-26 of
the fsave error_selector field, true to the layout of the fsave structure. See
Intel SDM volume 1 (253665-061) 8.1.10 and figure 8-9.

BUG=crashpad:161
TEST=crashpad_snapshot_test CPUContextX86.*:CPUContextWin.*

Change-Id: I0bf7ed995c152f124166eaa20104d228d3468f76
Reviewed-on: https://chromium-review.googlesource.com/442144
Reviewed-by: Scott Graham <scottmg@chromium.org>
This commit is contained in:
Mark Mentovai 2017-02-14 16:27:22 -05:00
parent c1b305244a
commit d98a4de718
7 changed files with 208 additions and 47 deletions

View File

@ -100,14 +100,18 @@ void MinidumpContextX86Writer::InitializeFromSnapshot(
context_snapshot->fxsave.ftw, context_snapshot->fxsave.ftw,
context_snapshot->fxsave.st_mm); context_snapshot->fxsave.st_mm);
context_.float_save.error_offset = context_snapshot->fxsave.fpu_ip; context_.float_save.error_offset = context_snapshot->fxsave.fpu_ip;
context_.float_save.error_selector = context_snapshot->fxsave.fpu_cs; context_.float_save.error_selector =
(context_snapshot->fxsave.fop << 16) | context_snapshot->fxsave.fpu_cs;
context_.float_save.data_offset = context_snapshot->fxsave.fpu_dp; context_.float_save.data_offset = context_snapshot->fxsave.fpu_dp;
context_.float_save.data_selector = context_snapshot->fxsave.fpu_ds; context_.float_save.data_selector = context_snapshot->fxsave.fpu_ds;
for (size_t index = 0, offset = 0; CPUContextX86::X87Register* context_float_save_st =
reinterpret_cast<CPUContextX86::X87Register*>(
context_.float_save.register_area);
for (size_t index = 0;
index < arraysize(context_snapshot->fxsave.st_mm); index < arraysize(context_snapshot->fxsave.st_mm);
offset += sizeof(context_snapshot->fxsave.st_mm[index].st), ++index) { ++index) {
memcpy(&context_.float_save.register_area[offset], memcpy(&context_float_save_st[index],
&context_snapshot->fxsave.st_mm[index].st, &context_snapshot->fxsave.st_mm[index].st,
sizeof(context_snapshot->fxsave.st_mm[index].st)); sizeof(context_snapshot->fxsave.st_mm[index].st));
} }

View File

@ -73,7 +73,8 @@ void InitializeMinidumpContextX86(MinidumpContextX86* context, uint32_t seed) {
context->float_save.tag_word = CPUContextX86::FxsaveToFsaveTagWord( context->float_save.tag_word = CPUContextX86::FxsaveToFsaveTagWord(
context->fxsave.fsw, context->fxsave.ftw, context->fxsave.st_mm); context->fxsave.fsw, context->fxsave.ftw, context->fxsave.st_mm);
context->float_save.error_offset = context->fxsave.fpu_ip; context->float_save.error_offset = context->fxsave.fpu_ip;
context->float_save.error_selector = context->fxsave.fpu_cs; context->float_save.error_selector =
(context->fxsave.fop << 16) | context->fxsave.fpu_cs;
context->float_save.data_offset = context->fxsave.fpu_dp; context->float_save.data_offset = context->fxsave.fpu_dp;
context->float_save.data_selector = context->fxsave.fpu_ds; context->float_save.data_selector = context->fxsave.fpu_ds;
for (size_t st_mm_index = 0; for (size_t st_mm_index = 0;

View File

@ -19,18 +19,22 @@
namespace crashpad { namespace crashpad {
namespace {
enum {
kX87TagValid = 0,
kX87TagZero,
kX87TagSpecial,
kX87TagEmpty,
};
} // namespace
// static // static
uint16_t CPUContextX86::FxsaveToFsaveTagWord( uint16_t CPUContextX86::FxsaveToFsaveTagWord(
uint16_t fsw, uint16_t fsw,
uint8_t fxsave_tag, uint8_t fxsave_tag,
const CPUContextX86::X87OrMMXRegister st_mm[8]) { const CPUContextX86::X87OrMMXRegister st_mm[8]) {
enum {
kX87TagValid = 0,
kX87TagZero,
kX87TagSpecial,
kX87TagEmpty,
};
// The x87 tag word (in both abridged and full form) identifies physical // The x87 tag word (in both abridged and full form) identifies physical
// registers, but |st_mm| is arranged in logical stack order. In order to map // registers, but |st_mm| is arranged in logical stack order. In order to map
// physical tag word bits to the logical stack registers they correspond to, // physical tag word bits to the logical stack registers they correspond to,
@ -85,6 +89,17 @@ uint16_t CPUContextX86::FxsaveToFsaveTagWord(
return fsave_tag; return fsave_tag;
} }
// static
uint8_t CPUContextX86::FsaveToFxsaveTagWord(uint16_t fsave_tag) {
uint8_t fxsave_tag = 0;
for (int physical_index = 0; physical_index < 8; ++physical_index) {
const uint8_t fsave_bits = (fsave_tag >> (physical_index * 2)) & 0x3;
const bool fxsave_bit = fsave_bits != kX87TagEmpty;
fxsave_tag |= fxsave_bit << physical_index;
}
return fxsave_tag;
}
uint64_t CPUContext::InstructionPointer() const { uint64_t CPUContext::InstructionPointer() const {
switch (architecture) { switch (architecture) {
case kCPUArchitectureX86: case kCPUArchitectureX86:

View File

@ -75,6 +75,8 @@ struct CPUContextX86 {
//! Manual, Volume 2: System Programming (24593-3.24), “FXSAVE Format for x87 //! Manual, Volume 2: System Programming (24593-3.24), “FXSAVE Format for x87
//! Tag Word”. //! Tag Word”.
//! //!
//! \sa FsaveToFxsaveTagWord()
//!
//! \param[in] fsw The FPU status word, used to map logical \a st_mm registers //! \param[in] fsw The FPU status word, used to map logical \a st_mm registers
//! to their physical counterparts. This can be taken from //! to their physical counterparts. This can be taken from
//! CPUContextX86::Fxsave::fsw. //! CPUContextX86::Fxsave::fsw.
@ -87,6 +89,16 @@ struct CPUContextX86 {
static uint16_t FxsaveToFsaveTagWord( static uint16_t FxsaveToFsaveTagWord(
uint16_t fsw, uint8_t fxsave_tag, const X87OrMMXRegister st_mm[8]); uint16_t fsw, uint8_t fxsave_tag, const X87OrMMXRegister st_mm[8]);
//! \breif Converts x87 floating-point tag words from `fsave` (full, 16-bit)
//! to `fxsave` (abridged, 8-bit) form.
//!
//! This function performs the inverse operation of FxsaveToFsaveTagWord().
//!
//! \param[in] fsave_tag The full FPU tag word.
//!
//! \return The abridged FPU tag word.
static uint8_t FsaveToFxsaveTagWord(uint16_t fsave_tag);
// Integer registers. // Integer registers.
uint32_t eax; uint32_t eax;
uint32_t ebx; uint32_t ebx;

View File

@ -161,6 +161,16 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) {
CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm)); CPUContextX86::FxsaveToFsaveTagWord(fsw, fxsave_tag, st_mm));
} }
TEST(CPUContextX86, FsaveToFxsaveTagWord) {
// The register sets that these x87 tag words might apply to are given in the
// FxsaveToFsaveTagWord test above.
EXPECT_EQ(0x0f, CPUContextX86::FsaveToFxsaveTagWord(0xff22));
EXPECT_EQ(0xf0, CPUContextX86::FsaveToFxsaveTagWord(0xa9ff));
EXPECT_EQ(0x5a, CPUContextX86::FsaveToFxsaveTagWord(0xeebb));
EXPECT_EQ(0x1f, CPUContextX86::FsaveToFxsaveTagWord(0xfe90));
EXPECT_EQ(0x00, CPUContextX86::FsaveToFxsaveTagWord(0xffff));
}
} // namespace } // namespace
} // namespace test } // namespace test
} // namespace crashpad } // namespace crashpad

View File

@ -24,16 +24,43 @@ namespace crashpad {
namespace { namespace {
template <typename T>
bool HasContextPart(const T& context, uint32_t bits) {
return (context.ContextFlags & bits) == bits;
}
template <class T> template <class T>
void CommonInitializeX86Context(const T& context, CPUContextX86* out) { void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
LOG_IF(ERROR, !(context.ContextFlags & WOW64_CONTEXT_i386)) // This function assumes that the WOW64_CONTEXT_* and x86 CONTEXT_* values
<< "non-x86 context"; // for ContextFlags are identical. This can be tested when targeting 32-bit
// x86.
#if defined(ARCH_CPU_X86)
static_assert(sizeof(CONTEXT) == sizeof(WOW64_CONTEXT),
"type mismatch: CONTEXT");
#define ASSERT_WOW64_EQUIVALENT(x) \
do { \
static_assert(x == WOW64_##x, "value mismatch: " #x); \
} while (false)
ASSERT_WOW64_EQUIVALENT(CONTEXT_i386);
ASSERT_WOW64_EQUIVALENT(CONTEXT_i486);
ASSERT_WOW64_EQUIVALENT(CONTEXT_CONTROL);
ASSERT_WOW64_EQUIVALENT(CONTEXT_INTEGER);
ASSERT_WOW64_EQUIVALENT(CONTEXT_SEGMENTS);
ASSERT_WOW64_EQUIVALENT(CONTEXT_FLOATING_POINT);
ASSERT_WOW64_EQUIVALENT(CONTEXT_DEBUG_REGISTERS);
ASSERT_WOW64_EQUIVALENT(CONTEXT_EXTENDED_REGISTERS);
ASSERT_WOW64_EQUIVALENT(CONTEXT_FULL);
ASSERT_WOW64_EQUIVALENT(CONTEXT_ALL);
ASSERT_WOW64_EQUIVALENT(CONTEXT_XSTATE);
#undef ASSERT_WOW64_EQUIVALENT
#endif // ARCH_CPU_X86
memset(out, 0, sizeof(*out)); memset(out, 0, sizeof(*out));
// We assume in this function that the WOW64_CONTEXT_* and x86 CONTEXT_* LOG_IF(ERROR, !HasContextPart(context, WOW64_CONTEXT_i386))
// values for ContextFlags are identical. << "non-x86 context";
if (context.ContextFlags & WOW64_CONTEXT_CONTROL) { if (HasContextPart(context, WOW64_CONTEXT_CONTROL)) {
out->ebp = context.Ebp; out->ebp = context.Ebp;
out->eip = context.Eip; out->eip = context.Eip;
out->cs = static_cast<uint16_t>(context.SegCs); out->cs = static_cast<uint16_t>(context.SegCs);
@ -42,7 +69,7 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
out->ss = static_cast<uint16_t>(context.SegSs); out->ss = static_cast<uint16_t>(context.SegSs);
} }
if (context.ContextFlags & WOW64_CONTEXT_INTEGER) { if (HasContextPart(context, WOW64_CONTEXT_INTEGER)) {
out->eax = context.Eax; out->eax = context.Eax;
out->ebx = context.Ebx; out->ebx = context.Ebx;
out->ecx = context.Ecx; out->ecx = context.Ecx;
@ -51,14 +78,14 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
out->esi = context.Esi; out->esi = context.Esi;
} }
if (context.ContextFlags & WOW64_CONTEXT_SEGMENTS) { if (HasContextPart(context, WOW64_CONTEXT_SEGMENTS)) {
out->ds = static_cast<uint16_t>(context.SegDs); out->ds = static_cast<uint16_t>(context.SegDs);
out->es = static_cast<uint16_t>(context.SegEs); out->es = static_cast<uint16_t>(context.SegEs);
out->fs = static_cast<uint16_t>(context.SegFs); out->fs = static_cast<uint16_t>(context.SegFs);
out->gs = static_cast<uint16_t>(context.SegGs); out->gs = static_cast<uint16_t>(context.SegGs);
} }
if (context.ContextFlags & WOW64_CONTEXT_DEBUG_REGISTERS) { if (HasContextPart(context, WOW64_CONTEXT_DEBUG_REGISTERS)) {
out->dr0 = context.Dr0; out->dr0 = context.Dr0;
out->dr1 = context.Dr1; out->dr1 = context.Dr1;
out->dr2 = context.Dr2; out->dr2 = context.Dr2;
@ -71,12 +98,28 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) {
out->dr7 = context.Dr7; out->dr7 = context.Dr7;
} }
if (context.ContextFlags & WOW64_CONTEXT_EXTENDED_REGISTERS) { if (HasContextPart(context, WOW64_CONTEXT_EXTENDED_REGISTERS)) {
static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters), static_assert(sizeof(out->fxsave) == sizeof(context.ExtendedRegisters),
"types must be equivalent"); "types must be equivalent");
memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave)); memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave));
} else if (context.ContextFlags & WOW64_CONTEXT_FLOATING_POINT) { } else if (HasContextPart(context, WOW64_CONTEXT_FLOATING_POINT)) {
CHECK(false) << "TODO(scottmg): extract x87 data"; out->fxsave.fcw = static_cast<uint16_t>(context.FloatSave.ControlWord);
out->fxsave.fsw = static_cast<uint16_t>(context.FloatSave.StatusWord);
out->fxsave.ftw = CPUContextX86::FsaveToFxsaveTagWord(
static_cast<uint16_t>(context.FloatSave.TagWord));
out->fxsave.fop = context.FloatSave.ErrorSelector >> 16;
out->fxsave.fpu_ip = context.FloatSave.ErrorOffset;
out->fxsave.fpu_cs = static_cast<uint16_t>(context.FloatSave.ErrorSelector);
out->fxsave.fpu_dp = context.FloatSave.DataOffset;
out->fxsave.fpu_ds = static_cast<uint16_t>(context.FloatSave.DataSelector);
const CPUContextX86::X87Register* context_floatsave_st =
reinterpret_cast<const CPUContextX86::X87Register*>(
context.FloatSave.RegisterArea);
for (size_t index = 0; index < arraysize(out->fxsave.st_mm); ++index) {
memcpy(out->fxsave.st_mm[index].st,
context_floatsave_st[index],
sizeof(out->fxsave.st_mm[index].st));
}
} }
} }
@ -91,9 +134,9 @@ void InitializeX86Context(const WOW64_CONTEXT& context, CPUContextX86* out) {
void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) { void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
memset(out, 0, sizeof(*out)); memset(out, 0, sizeof(*out));
LOG_IF(ERROR, !(context.ContextFlags & CONTEXT_AMD64)) << "non-x64 context"; LOG_IF(ERROR, !HasContextPart(context, CONTEXT_AMD64)) << "non-x64 context";
if (context.ContextFlags & CONTEXT_CONTROL) { if (HasContextPart(context, CONTEXT_CONTROL)) {
out->cs = context.SegCs; out->cs = context.SegCs;
out->rflags = context.EFlags; out->rflags = context.EFlags;
out->rip = context.Rip; out->rip = context.Rip;
@ -101,7 +144,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
// SegSs ignored. // SegSs ignored.
} }
if (context.ContextFlags & CONTEXT_INTEGER) { if (HasContextPart(context, CONTEXT_INTEGER)) {
out->rax = context.Rax; out->rax = context.Rax;
out->rbx = context.Rbx; out->rbx = context.Rbx;
out->rcx = context.Rcx; out->rcx = context.Rcx;
@ -119,14 +162,14 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
out->r15 = context.R15; out->r15 = context.R15;
} }
if (context.ContextFlags & CONTEXT_SEGMENTS) { if (HasContextPart(context, CONTEXT_SEGMENTS)) {
out->fs = context.SegFs; out->fs = context.SegFs;
out->gs = context.SegGs; out->gs = context.SegGs;
// SegDs ignored. // SegDs ignored.
// SegEs ignored. // SegEs ignored.
} }
if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS) { if (HasContextPart(context, CONTEXT_DEBUG_REGISTERS)) {
out->dr0 = context.Dr0; out->dr0 = context.Dr0;
out->dr1 = context.Dr1; out->dr1 = context.Dr1;
out->dr2 = context.Dr2; out->dr2 = context.Dr2;
@ -139,7 +182,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) {
out->dr7 = context.Dr7; out->dr7 = context.Dr7;
} }
if (context.ContextFlags & CONTEXT_FLOATING_POINT) { 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"); "types must be equivalent");
memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave)); memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave));

View File

@ -16,6 +16,7 @@
#include <windows.h> #include <windows.h>
#include "base/macros.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "snapshot/cpu_context.h" #include "snapshot/cpu_context.h"
@ -24,6 +25,84 @@ namespace crashpad {
namespace test { namespace test {
namespace { namespace {
template <typename T>
void TestInitializeX86Context() {
T context = {0};
context.ContextFlags = WOW64_CONTEXT_INTEGER |
WOW64_CONTEXT_DEBUG_REGISTERS |
WOW64_CONTEXT_EXTENDED_REGISTERS;
context.Eax = 1;
context.Dr0 = 3;
context.ExtendedRegisters[4] = 2; // FTW
// Test the simple case, where everything in the CPUContextX86 argument is set
// directly from the supplied thread, float, and debug state parameters.
{
CPUContextX86 cpu_context_x86 = {};
InitializeX86Context(context, &cpu_context_x86);
EXPECT_EQ(1u, cpu_context_x86.eax);
EXPECT_EQ(2u, cpu_context_x86.fxsave.ftw);
EXPECT_EQ(3u, cpu_context_x86.dr0);
}
}
template <typename T>
void TestInitializeX86Context_FsaveWithoutFxsave() {
T context = {0};
context.ContextFlags = WOW64_CONTEXT_INTEGER |
WOW64_CONTEXT_FLOATING_POINT |
WOW64_CONTEXT_DEBUG_REGISTERS;
context.Eax = 1;
// In fields that are wider than they need to be, set the high bits to ensure
// that theyre masked off appropriately in the output.
context.FloatSave.ControlWord = 0xffff027f;
context.FloatSave.StatusWord = 0xffff0004;
context.FloatSave.TagWord = 0xffffa9ff;
context.FloatSave.ErrorOffset = 0x01234567;
context.FloatSave.ErrorSelector = 0x0bad0003;
context.FloatSave.DataOffset = 0x89abcdef;
context.FloatSave.DataSelector = 0xffff0007;
context.FloatSave.RegisterArea[77] = 0x80;
context.FloatSave.RegisterArea[78] = 0xff;
context.FloatSave.RegisterArea[79] = 0x7f;
context.Dr0 = 3;
{
CPUContextX86 cpu_context_x86 = {};
InitializeX86Context(context, &cpu_context_x86);
EXPECT_EQ(1u, cpu_context_x86.eax);
EXPECT_EQ(0x027f, cpu_context_x86.fxsave.fcw);
EXPECT_EQ(0x0004, cpu_context_x86.fxsave.fsw);
EXPECT_EQ(0x00f0, cpu_context_x86.fxsave.ftw);
EXPECT_EQ(0x0bad, cpu_context_x86.fxsave.fop);
EXPECT_EQ(0x01234567, cpu_context_x86.fxsave.fpu_ip);
EXPECT_EQ(0x0003, cpu_context_x86.fxsave.fpu_cs);
EXPECT_EQ(0x89abcdef, cpu_context_x86.fxsave.fpu_dp);
EXPECT_EQ(0x0007, cpu_context_x86.fxsave.fpu_ds);
for (size_t st_mm = 0; st_mm < 7; ++st_mm) {
for (size_t byte = 0;
byte < arraysize(cpu_context_x86.fxsave.st_mm[st_mm].st);
++byte) {
EXPECT_EQ(0x00, cpu_context_x86.fxsave.st_mm[st_mm].st[byte]);
}
}
for (size_t byte = 0; byte < 7; ++byte) {
EXPECT_EQ(0x00, cpu_context_x86.fxsave.st_mm[7].st[byte]);
}
EXPECT_EQ(0x80, cpu_context_x86.fxsave.st_mm[7].st[7]);
EXPECT_EQ(0xff, cpu_context_x86.fxsave.st_mm[7].st[8]);
EXPECT_EQ(0x7f, cpu_context_x86.fxsave.st_mm[7].st[9]);
EXPECT_EQ(3u, cpu_context_x86.dr0);
}
}
#if defined(ARCH_CPU_X86_FAMILY)
#if defined(ARCH_CPU_X86_64) #if defined(ARCH_CPU_X86_64)
TEST(CPUContextWin, InitializeX64Context) { TEST(CPUContextWin, InitializeX64Context) {
@ -45,28 +124,25 @@ TEST(CPUContextWin, InitializeX64Context) {
} }
} }
#else #endif // ARCH_CPU_X86_64
TEST(CPUContextWin, InitializeX86Context) { TEST(CPUContextWin, InitializeX86Context) {
CONTEXT context = {0}; #if defined(ARCH_CPU_X86)
context.ContextFlags = TestInitializeX86Context<CONTEXT>();
CONTEXT_INTEGER | CONTEXT_EXTENDED_REGISTERS | CONTEXT_DEBUG_REGISTERS; #else // ARCH_CPU_X86
context.Eax = 1; TestInitializeX86Context<WOW64_CONTEXT>();
context.ExtendedRegisters[4] = 2; // FTW. #endif // ARCH_CPU_X86
context.Dr0 = 3;
// Test the simple case, where everything in the CPUContextX86 argument is
// set directly from the supplied thread, float, and debug state parameters.
{
CPUContextX86 cpu_context_x86 = {};
InitializeX86Context(context, &cpu_context_x86);
EXPECT_EQ(1u, cpu_context_x86.eax);
EXPECT_EQ(2u, cpu_context_x86.fxsave.ftw);
EXPECT_EQ(3u, cpu_context_x86.dr0);
}
} }
#endif // ARCH_CPU_X86_64 TEST(CPUContextWin, InitializeX86Context_FsaveWithoutFxsave) {
#if defined(ARCH_CPU_X86)
TestInitializeX86Context_FsaveWithoutFxsave<CONTEXT>();
#else // ARCH_CPU_X86
TestInitializeX86Context_FsaveWithoutFxsave<WOW64_CONTEXT>();
#endif // ARCH_CPU_X86
}
#endif // ARCH_CPU_X86_FAMILY
} // namespace } // namespace
} // namespace test } // namespace test