From d98a4de718d9d03fdd270dc7a6add8596894ed40 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Tue, 14 Feb 2017 16:27:22 -0500 Subject: [PATCH] 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 --- minidump/minidump_context_writer.cc | 12 ++- minidump/test/minidump_context_test_util.cc | 3 +- snapshot/cpu_context.cc | 29 +++-- snapshot/cpu_context.h | 12 +++ snapshot/cpu_context_test.cc | 10 ++ snapshot/win/cpu_context_win.cc | 77 +++++++++++--- snapshot/win/cpu_context_win_test.cc | 112 ++++++++++++++++---- 7 files changed, 208 insertions(+), 47 deletions(-) diff --git a/minidump/minidump_context_writer.cc b/minidump/minidump_context_writer.cc index 4d3bf8a4..cc18c34a 100644 --- a/minidump/minidump_context_writer.cc +++ b/minidump/minidump_context_writer.cc @@ -100,14 +100,18 @@ void MinidumpContextX86Writer::InitializeFromSnapshot( context_snapshot->fxsave.ftw, context_snapshot->fxsave.st_mm); 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_selector = context_snapshot->fxsave.fpu_ds; - for (size_t index = 0, offset = 0; + CPUContextX86::X87Register* context_float_save_st = + reinterpret_cast( + context_.float_save.register_area); + for (size_t index = 0; index < arraysize(context_snapshot->fxsave.st_mm); - offset += sizeof(context_snapshot->fxsave.st_mm[index].st), ++index) { - memcpy(&context_.float_save.register_area[offset], + ++index) { + memcpy(&context_float_save_st[index], &context_snapshot->fxsave.st_mm[index].st, sizeof(context_snapshot->fxsave.st_mm[index].st)); } diff --git a/minidump/test/minidump_context_test_util.cc b/minidump/test/minidump_context_test_util.cc index 730c2b79..a4d1d26d 100644 --- a/minidump/test/minidump_context_test_util.cc +++ b/minidump/test/minidump_context_test_util.cc @@ -73,7 +73,8 @@ void InitializeMinidumpContextX86(MinidumpContextX86* context, uint32_t seed) { context->float_save.tag_word = CPUContextX86::FxsaveToFsaveTagWord( context->fxsave.fsw, context->fxsave.ftw, context->fxsave.st_mm); 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_selector = context->fxsave.fpu_ds; for (size_t st_mm_index = 0; diff --git a/snapshot/cpu_context.cc b/snapshot/cpu_context.cc index c11a031b..c846fd1f 100644 --- a/snapshot/cpu_context.cc +++ b/snapshot/cpu_context.cc @@ -19,18 +19,22 @@ namespace crashpad { +namespace { + +enum { + kX87TagValid = 0, + kX87TagZero, + kX87TagSpecial, + kX87TagEmpty, +}; + +} // namespace + // static uint16_t CPUContextX86::FxsaveToFsaveTagWord( uint16_t fsw, uint8_t fxsave_tag, const CPUContextX86::X87OrMMXRegister st_mm[8]) { - enum { - kX87TagValid = 0, - kX87TagZero, - kX87TagSpecial, - kX87TagEmpty, - }; - // 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 // physical tag word bits to the logical stack registers they correspond to, @@ -85,6 +89,17 @@ uint16_t CPUContextX86::FxsaveToFsaveTagWord( 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 { switch (architecture) { case kCPUArchitectureX86: diff --git a/snapshot/cpu_context.h b/snapshot/cpu_context.h index 67b298e8..e1989141 100644 --- a/snapshot/cpu_context.h +++ b/snapshot/cpu_context.h @@ -75,6 +75,8 @@ struct CPUContextX86 { //! Manual, Volume 2: System Programming (24593-3.24), “FXSAVE Format for x87 //! Tag Word”. //! + //! \sa FsaveToFxsaveTagWord() + //! //! \param[in] fsw The FPU status word, used to map logical \a st_mm registers //! to their physical counterparts. This can be taken from //! CPUContextX86::Fxsave::fsw. @@ -87,6 +89,16 @@ struct CPUContextX86 { static uint16_t FxsaveToFsaveTagWord( 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. uint32_t eax; uint32_t ebx; diff --git a/snapshot/cpu_context_test.cc b/snapshot/cpu_context_test.cc index 808ba615..042b14e7 100644 --- a/snapshot/cpu_context_test.cc +++ b/snapshot/cpu_context_test.cc @@ -161,6 +161,16 @@ TEST(CPUContextX86, FxsaveToFsaveTagWord) { 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 test } // namespace crashpad diff --git a/snapshot/win/cpu_context_win.cc b/snapshot/win/cpu_context_win.cc index 2450ef25..900fbda8 100644 --- a/snapshot/win/cpu_context_win.cc +++ b/snapshot/win/cpu_context_win.cc @@ -24,16 +24,43 @@ namespace crashpad { namespace { +template +bool HasContextPart(const T& context, uint32_t bits) { + return (context.ContextFlags & bits) == bits; +} + template void CommonInitializeX86Context(const T& context, CPUContextX86* out) { - LOG_IF(ERROR, !(context.ContextFlags & WOW64_CONTEXT_i386)) - << "non-x86 context"; + // This function assumes that the WOW64_CONTEXT_* and x86 CONTEXT_* values + // 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)); - // We assume in this function that the WOW64_CONTEXT_* and x86 CONTEXT_* - // values for ContextFlags are identical. + LOG_IF(ERROR, !HasContextPart(context, WOW64_CONTEXT_i386)) + << "non-x86 context"; - if (context.ContextFlags & WOW64_CONTEXT_CONTROL) { + if (HasContextPart(context, WOW64_CONTEXT_CONTROL)) { out->ebp = context.Ebp; out->eip = context.Eip; out->cs = static_cast(context.SegCs); @@ -42,7 +69,7 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) { out->ss = static_cast(context.SegSs); } - if (context.ContextFlags & WOW64_CONTEXT_INTEGER) { + if (HasContextPart(context, WOW64_CONTEXT_INTEGER)) { out->eax = context.Eax; out->ebx = context.Ebx; out->ecx = context.Ecx; @@ -51,14 +78,14 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) { out->esi = context.Esi; } - if (context.ContextFlags & WOW64_CONTEXT_SEGMENTS) { + 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); } - if (context.ContextFlags & WOW64_CONTEXT_DEBUG_REGISTERS) { + if (HasContextPart(context, WOW64_CONTEXT_DEBUG_REGISTERS)) { out->dr0 = context.Dr0; out->dr1 = context.Dr1; out->dr2 = context.Dr2; @@ -71,12 +98,28 @@ void CommonInitializeX86Context(const T& context, CPUContextX86* out) { 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), "types must be equivalent"); memcpy(&out->fxsave, &context.ExtendedRegisters, sizeof(out->fxsave)); - } else if (context.ContextFlags & WOW64_CONTEXT_FLOATING_POINT) { - CHECK(false) << "TODO(scottmg): extract x87 data"; + } else if (HasContextPart(context, WOW64_CONTEXT_FLOATING_POINT)) { + out->fxsave.fcw = static_cast(context.FloatSave.ControlWord); + out->fxsave.fsw = static_cast(context.FloatSave.StatusWord); + out->fxsave.ftw = CPUContextX86::FsaveToFxsaveTagWord( + static_cast(context.FloatSave.TagWord)); + out->fxsave.fop = context.FloatSave.ErrorSelector >> 16; + out->fxsave.fpu_ip = context.FloatSave.ErrorOffset; + out->fxsave.fpu_cs = static_cast(context.FloatSave.ErrorSelector); + out->fxsave.fpu_dp = context.FloatSave.DataOffset; + out->fxsave.fpu_ds = static_cast(context.FloatSave.DataSelector); + const CPUContextX86::X87Register* context_floatsave_st = + reinterpret_cast( + 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) { 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->rflags = context.EFlags; out->rip = context.Rip; @@ -101,7 +144,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) { // SegSs ignored. } - if (context.ContextFlags & CONTEXT_INTEGER) { + if (HasContextPart(context, CONTEXT_INTEGER)) { out->rax = context.Rax; out->rbx = context.Rbx; out->rcx = context.Rcx; @@ -119,14 +162,14 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) { out->r15 = context.R15; } - if (context.ContextFlags & CONTEXT_SEGMENTS) { + if (HasContextPart(context, CONTEXT_SEGMENTS)) { out->fs = context.SegFs; out->gs = context.SegGs; // SegDs ignored. // SegEs ignored. } - if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS) { + if (HasContextPart(context, CONTEXT_DEBUG_REGISTERS)) { out->dr0 = context.Dr0; out->dr1 = context.Dr1; out->dr2 = context.Dr2; @@ -139,7 +182,7 @@ void InitializeX64Context(const CONTEXT& context, CPUContextX86_64* out) { out->dr7 = context.Dr7; } - if (context.ContextFlags & CONTEXT_FLOATING_POINT) { + if (HasContextPart(context, CONTEXT_FLOATING_POINT)) { static_assert(sizeof(out->fxsave) == sizeof(context.FltSave), "types must be equivalent"); memcpy(&out->fxsave, &context.FltSave.ControlWord, sizeof(out->fxsave)); diff --git a/snapshot/win/cpu_context_win_test.cc b/snapshot/win/cpu_context_win_test.cc index 0c7484b7..1f8bfe98 100644 --- a/snapshot/win/cpu_context_win_test.cc +++ b/snapshot/win/cpu_context_win_test.cc @@ -16,6 +16,7 @@ #include +#include "base/macros.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/cpu_context.h" @@ -24,6 +25,84 @@ namespace crashpad { namespace test { namespace { +template +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 +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 they’re 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) TEST(CPUContextWin, InitializeX64Context) { @@ -45,28 +124,25 @@ TEST(CPUContextWin, InitializeX64Context) { } } -#else +#endif // ARCH_CPU_X86_64 TEST(CPUContextWin, InitializeX86Context) { - CONTEXT context = {0}; - context.ContextFlags = - CONTEXT_INTEGER | CONTEXT_EXTENDED_REGISTERS | CONTEXT_DEBUG_REGISTERS; - context.Eax = 1; - context.ExtendedRegisters[4] = 2; // FTW. - 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); - } +#if defined(ARCH_CPU_X86) + TestInitializeX86Context(); +#else // ARCH_CPU_X86 + TestInitializeX86Context(); +#endif // ARCH_CPU_X86 } -#endif // ARCH_CPU_X86_64 +TEST(CPUContextWin, InitializeX86Context_FsaveWithoutFxsave) { +#if defined(ARCH_CPU_X86) + TestInitializeX86Context_FsaveWithoutFxsave(); +#else // ARCH_CPU_X86 + TestInitializeX86Context_FsaveWithoutFxsave(); +#endif // ARCH_CPU_X86 +} + +#endif // ARCH_CPU_X86_FAMILY } // namespace } // namespace test