From c8592b847b99f7f8643c342999c3f8f4f1e9b2e8 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 30 Sep 2015 14:10:08 -0400 Subject: [PATCH] win: Add and use a custom CaptureContext() implementation RtlCaptureContext() is buggy and limited. BUG=crashpad:53 R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1377963002 . --- client/capture_context_mac_test.cc | 72 +-- client/crashpad_client.h | 4 +- client/simulate_crash_win.h | 3 +- snapshot/win/exception_snapshot_win_test.cc | 6 +- snapshot/win/process_reader_win.cc | 3 +- util/util.gyp | 11 + util/util_test.gyp | 1 + util/win/capture_context.asm | 532 ++++++++++++++++++++ util/win/capture_context.h | 47 ++ util/win/capture_context_test.cc | 182 +++++++ 10 files changed, 819 insertions(+), 42 deletions(-) create mode 100644 util/win/capture_context.asm create mode 100644 util/win/capture_context.h create mode 100644 util/win/capture_context_test.cc diff --git a/client/capture_context_mac_test.cc b/client/capture_context_mac_test.cc index 5b0b643e..436ac5ad 100644 --- a/client/capture_context_mac_test.cc +++ b/client/capture_context_mac_test.cc @@ -32,13 +32,13 @@ namespace { // fatal gtest assertions. For other fields, where it’s possible to reason about // their validity based solely on their contents, sanity-checks via nonfatal // gtest assertions. -void SanityCheckContext(NativeCPUContext* context) { +void SanityCheckContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - ASSERT_EQ(x86_THREAD_STATE32, context->tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), context->tsh.count); + ASSERT_EQ(x86_THREAD_STATE32, context.tsh.flavor); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE32_COUNT), context.tsh.count); #elif defined(ARCH_CPU_X86_64) - ASSERT_EQ(x86_THREAD_STATE64, context->tsh.flavor); - ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), context->tsh.count); + ASSERT_EQ(x86_THREAD_STATE64, context.tsh.flavor); + ASSERT_EQ(implicit_cast(x86_THREAD_STATE64_COUNT), context.tsh.count); #endif #if defined(ARCH_CPU_X86_FAMILY) @@ -47,46 +47,46 @@ void SanityCheckContext(NativeCPUContext* context) { // that the high bits are all clear. // // Many bit positions in the flags register are reserved and will always read - // a known value. Most reservd bits are always 0, but bit 1 is always 1. Check - // that the reserved bits are all set to their expected values. Note that the - // set of reserved bits may be relaxed over time with newer CPUs, and that - // this test may need to be changed to reflect these developments. The current - // set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel Software - // Developer’s Manual, Volume 1: Basic Architecture (253665-051), 3.4.3 - // “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume 2: - // System Programming (24593-3.24), 3.1.6 “RFLAGS Register”. + // a known value. Most reserved bits are always 0, but bit 1 is always 1. + // Check that the reserved bits are all set to their expected values. Note + // that the set of reserved bits may be relaxed over time with newer CPUs, and + // that this test may need to be changed to reflect these developments. The + // current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel + // Software Developer’s Manual, Volume 1: Basic Architecture (253665-051), + // 3.4.3 “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume + // 2: System Programming (24593-3.24), 3.1.6 “RFLAGS Register”. #if defined(ARCH_CPU_X86) - EXPECT_EQ(0u, context->uts.ts32.__cs & ~0xffff); - EXPECT_EQ(0u, context->uts.ts32.__ds & ~0xffff); - EXPECT_EQ(0u, context->uts.ts32.__es & ~0xffff); - EXPECT_EQ(0u, context->uts.ts32.__fs & ~0xffff); - EXPECT_EQ(0u, context->uts.ts32.__gs & ~0xffff); - EXPECT_EQ(0u, context->uts.ts32.__ss & ~0xffff); - EXPECT_EQ(2u, context->uts.ts32.__eflags & 0xffc0802a); + EXPECT_EQ(0u, context.uts.ts32.__cs & ~0xffff); + EXPECT_EQ(0u, context.uts.ts32.__ds & ~0xffff); + EXPECT_EQ(0u, context.uts.ts32.__es & ~0xffff); + EXPECT_EQ(0u, context.uts.ts32.__fs & ~0xffff); + EXPECT_EQ(0u, context.uts.ts32.__gs & ~0xffff); + EXPECT_EQ(0u, context.uts.ts32.__ss & ~0xffff); + EXPECT_EQ(2u, context.uts.ts32.__eflags & 0xffc0802a); #elif defined(ARCH_CPU_X86_64) - EXPECT_EQ(0u, context->uts.ts64.__cs & ~UINT64_C(0xffff)); - EXPECT_EQ(0u, context->uts.ts64.__fs & ~UINT64_C(0xffff)); - EXPECT_EQ(0u, context->uts.ts64.__gs & ~UINT64_C(0xffff)); - EXPECT_EQ(2u, context->uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a)); + EXPECT_EQ(0u, context.uts.ts64.__cs & ~UINT64_C(0xffff)); + EXPECT_EQ(0u, context.uts.ts64.__fs & ~UINT64_C(0xffff)); + EXPECT_EQ(0u, context.uts.ts64.__gs & ~UINT64_C(0xffff)); + EXPECT_EQ(2u, context.uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a)); #endif #endif } // A CPU-independent function to return the program counter. -uintptr_t ProgramCounterFromContext(NativeCPUContext* context) { +uintptr_t ProgramCounterFromContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - return context->uts.ts32.__eip; + return context.uts.ts32.__eip; #elif defined(ARCH_CPU_X86_64) - return context->uts.ts64.__rip; + return context.uts.ts64.__rip; #endif } // A CPU-independent function to return the stack pointer. -uintptr_t StackPointerFromContext(NativeCPUContext* context) { +uintptr_t StackPointerFromContext(const NativeCPUContext& context) { #if defined(ARCH_CPU_X86) - return context->uts.ts32.__esp; + return context.uts.ts32.__esp; #elif defined(ARCH_CPU_X86_64) - return context->uts.ts64.__rsp; + return context.uts.ts64.__rsp; #endif } @@ -96,13 +96,13 @@ void TestCaptureContext() { { SCOPED_TRACE("context_1"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(&context_1)); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1)); } // The program counter reference value is this function’s address. The // captured program counter should be slightly greater than or equal to the // reference program counter. - uintptr_t pc = ProgramCounterFromContext(&context_1); + uintptr_t pc = ProgramCounterFromContext(context_1); #if !__has_feature(address_sanitizer) // AddressSanitizer can cause enough code bloat that the “nearby” check would // likely fail. @@ -125,7 +125,7 @@ void TestCaptureContext() { reinterpret_cast(&context_2)), std::min(reinterpret_cast(&pc), reinterpret_cast(&sp))); - sp = StackPointerFromContext(&context_1); + sp = StackPointerFromContext(context_1); EXPECT_LT(kReferenceSP - sp, 512u); // Capture the context again, expecting that the stack pointer stays the same @@ -136,11 +136,11 @@ void TestCaptureContext() { { SCOPED_TRACE("context_2"); - ASSERT_NO_FATAL_FAILURE(SanityCheckContext(&context_2)); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2)); } - EXPECT_EQ(sp, StackPointerFromContext(&context_2)); - EXPECT_GT(ProgramCounterFromContext(&context_2), pc); + EXPECT_EQ(sp, StackPointerFromContext(context_2)); + EXPECT_GT(ProgramCounterFromContext(context_2), pc); } TEST(CaptureContextMac, CaptureContext) { diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 60186a40..7095de04 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -93,8 +93,8 @@ class CrashpadClient { //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. //! - //! \param[in] context A CONTEXT, generally captured by `RtlCaptureContext()` - //! or similar. + //! \param[in] context A `CONTEXT`, generally captured by CaptureContext() or + //! similar. static void DumpWithoutCrash(const CONTEXT& context); #endif diff --git a/client/simulate_crash_win.h b/client/simulate_crash_win.h index eaae8d43..81c0af93 100644 --- a/client/simulate_crash_win.h +++ b/client/simulate_crash_win.h @@ -18,12 +18,13 @@ #include #include "client/crashpad_client.h" +#include "util/win/capture_context.h" //! \brief Captures the CPU context and captures a dump without an exception. #define CRASHPAD_SIMULATE_CRASH() \ do { \ CONTEXT context; \ - RtlCaptureContext(&context); \ + crashpad::CaptureContext(&context); \ crashpad::CrashpadClient::DumpWithoutCrash(context); \ } while (false) diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc index 4108a575..3fc8c6d1 100644 --- a/snapshot/win/exception_snapshot_win_test.cc +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -192,8 +192,7 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate { snapshot.Initialize(process, ProcessSuspensionState::kSuspended); snapshot.InitializeException(exception_information_address); EXPECT_TRUE(snapshot.Exception()); - EXPECT_EQ(0, snapshot.Exception()->Exception()); - EXPECT_EQ(0, snapshot.Exception()->ExceptionAddress()); + EXPECT_EQ(0x517a7ed, snapshot.Exception()->Exception()); // Verify the dump was captured at the expected location with some slop // space. @@ -203,6 +202,9 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate { EXPECT_LT(snapshot.Exception()->Context()->InstructionPointer(), dump_near_ + kAllowedOffset); + EXPECT_EQ(snapshot.Exception()->Context()->InstructionPointer(), + snapshot.Exception()->ExceptionAddress()); + SetEvent(completed_test_event_); return 0; diff --git a/snapshot/win/process_reader_win.cc b/snapshot/win/process_reader_win.cc index bcdf4483..20e17ed9 100644 --- a/snapshot/win/process_reader_win.cc +++ b/snapshot/win/process_reader_win.cc @@ -18,6 +18,7 @@ #include "base/memory/scoped_ptr.h" #include "base/numerics/safe_conversions.h" +#include "util/win/capture_context.h" #include "util/win/nt_internals.h" #include "util/win/ntstatus_logging.h" #include "util/win/process_structs.h" @@ -128,7 +129,7 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle, DCHECK(suspension_state == ProcessSuspensionState::kRunning); thread->suspend_count = 0; DCHECK(!is_64_reading_32); - RtlCaptureContext(&thread->context.native); + CaptureContext(&thread->context.native); } else { DWORD previous_suspend_count = SuspendThread(thread_handle); if (previous_suspend_count == -1) { diff --git a/util/util.gyp b/util/util.gyp index 099c3103..02e3b02c 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -149,6 +149,8 @@ 'thread/thread_posix.cc', 'thread/thread_win.cc', 'win/address_types.h', + 'win/capture_context.asm', + 'win/capture_context.h', 'win/checked_win_address_range.h', 'win/exception_handler_server.cc', 'win/exception_handler_server.h', @@ -240,6 +242,15 @@ '-lwinhttp.lib', ], }, + 'conditions': [ + ['target_arch=="ia32"', { + 'msvs_settings': { + 'MASM': { + 'UseSafeExceptionHandlers': 'true', + }, + }, + }], + ], }], ], }, diff --git a/util/util_test.gyp b/util/util_test.gyp index 1010b5e5..2641ff52 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -79,6 +79,7 @@ 'synchronization/semaphore_test.cc', 'thread/thread_log_messages_test.cc', 'thread/thread_test.cc', + 'win/capture_context_test.cc', 'win/exception_handler_server_test.cc', 'win/process_info_test.cc', 'win/scoped_process_suspend_test.cc', diff --git a/util/win/capture_context.asm b/util/win/capture_context.asm new file mode 100644 index 00000000..933f8828 --- /dev/null +++ b/util/win/capture_context.asm @@ -0,0 +1,532 @@ +; Copyright 2015 The Crashpad Authors. All rights reserved. +; +; 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. + +; Detect ml64 assembling for x86_64 by checking for rax. +ifdef rax +_M_X64 equ 1 +else +_M_IX86 equ 1 +endif + +ifdef _M_IX86 +.586 +.XMM +.model flat +endif + +offsetof macro structure, field + exitm +endm + +; The CONTEXT structure definitions that follow are based on those in . +; Field names are prefixed (as in c_Rax) to avoid colliding with the predefined +; register names (such as Rax). + +ifdef _M_IX86 + +CONTEXT_i386 equ 10000h +CONTEXT_CONTROL equ CONTEXT_i386 or 1h +CONTEXT_INTEGER equ CONTEXT_i386 or 2h +CONTEXT_SEGMENTS equ CONTEXT_i386 or 4h +CONTEXT_FLOATING_POINT equ CONTEXT_i386 or 8h +CONTEXT_DEBUG_REGISTERS equ CONTEXT_i386 or 10h +CONTEXT_EXTENDED_REGISTERS equ CONTEXT_i386 or 20h +CONTEXT_XSTATE equ CONTEXT_i386 or 40h + +MAXIMUM_SUPPORTED_EXTENSION equ 512 + +CONTEXT struct + c_ContextFlags dword ? + + c_Dr0 dword ? + c_Dr1 dword ? + c_Dr2 dword ? + c_Dr3 dword ? + c_Dr6 dword ? + c_Dr7 dword ? + + struct c_FloatSave + f_ControlWord dword ? + f_StatusWord dword ? + f_TagWord dword ? + f_ErrorOffset dword ? + f_ErrorSelector dword ? + f_DataOffset dword ? + f_DataSelector dword ? + f_RegisterArea byte 80 dup(?) + + union + f_Spare0 dword ? ; As in FLOATING_SAVE_AREA. + f_Cr0NpxState dword ? ; As in WOW64_FLOATING_SAVE_AREA. + ends + ends + + c_SegGs dword ? + c_SegFs dword ? + c_SegEs dword ? + c_SegDs dword ? + + c_Edi dword ? + c_Esi dword ? + c_Ebx dword ? + c_Edx dword ? + c_Ecx dword ? + c_Eax dword ? + + c_Ebp dword ? + + c_Eip dword ? + c_SegCs dword ? + + c_EFlags dword ? + + c_Esp dword ? + c_SegSs dword ? + + c_ExtendedRegisters byte MAXIMUM_SUPPORTED_EXTENSION dup(?) +CONTEXT ends + +elseifdef _M_X64 + +M128A struct 16 + m_Low qword ? + m_High qword ? +M128A ends + +CONTEXT_AMD64 equ 100000h +CONTEXT_CONTROL equ CONTEXT_AMD64 or 1h +CONTEXT_INTEGER equ CONTEXT_AMD64 or 2h +CONTEXT_SEGMENTS equ CONTEXT_AMD64 or 4h +CONTEXT_FLOATING_POINT equ CONTEXT_AMD64 or 8h +CONTEXT_DEBUG_REGISTERS equ CONTEXT_AMD64 or 10h +CONTEXT_XSTATE equ CONTEXT_AMD64 or 40h + +CONTEXT struct 16 + c_P1Home qword ? + c_P2Home qword ? + c_P3Home qword ? + c_P4Home qword ? + c_P5Home qword ? + c_P6Home qword ? + + c_ContextFlags dword ? + c_MxCsr dword ? + + c_SegCs word ? + c_SegDs word ? + c_SegEs word ? + c_SegFs word ? + c_SegGs word ? + c_SegSs word ? + + c_EFlags dword ? + + c_Dr0 qword ? + c_Dr1 qword ? + c_Dr2 qword ? + c_Dr3 qword ? + c_Dr6 qword ? + c_Dr7 qword ? + + c_Rax qword ? + c_Rcx qword ? + c_Rdx qword ? + c_Rbx qword ? + c_Rsp qword ? + c_Rbp qword ? + c_Rsi qword ? + c_Rdi qword ? + c_R8 qword ? + c_R9 qword ? + c_R10 qword ? + c_R11 qword ? + c_R12 qword ? + c_R13 qword ? + c_R14 qword ? + c_R15 qword ? + + c_Rip qword ? + + union + struct c_FltSave + f_ControlWord word ? + f_StatusWord word ? + f_TagWord byte ? + f_Reserved1 byte ? + f_ErrorOpcode word ? + f_ErrorOffset dword ? + f_ErrorSelector word ? + f_Reserved2 word ? + f_DataOffset dword ? + f_DataSelector word ? + f_Reserved3 word ? + f_MxCsr dword ? + f_MxCsr_Mask dword ? + f_FloatRegisters M128A 8 dup() + f_XmmRegisters M128A 16 dup() + f_Reserved4 byte 96 dup(?) + ends + struct + fx_Header M128A 2 dup() + fx_Legacy M128A 8 dup() + fx_Xmm0 M128A + fx_Xmm1 M128A + fx_Xmm2 M128A + fx_Xmm3 M128A + fx_Xmm4 M128A + fx_Xmm5 M128A + fx_Xmm6 M128A + fx_Xmm7 M128A + fx_Xmm8 M128A + fx_Xmm9 M128A + fx_Xmm10 M128A + fx_Xmm11 M128A + fx_Xmm12 M128A + fx_Xmm13 M128A + fx_Xmm14 M128A + fx_Xmm15 M128A + ends + ends + + c_VectorRegister M128A 26 dup() + c_VectorControl qword ? + + c_DebugControl qword ? + c_LastBranchToRip qword ? + c_LastBranchFromRip qword ? + c_LastExceptionToRip qword ? + c_LastExceptionFromRip qword ? +CONTEXT ends + +endif + +; namespace crashpad { +; void CaptureContext(CONTEXT* context) +; } // namespace crashpad +ifdef _M_IX86 +CAPTURECONTEXT_SYMBOL equ ?CaptureContext@crashpad@@YAXPAU_CONTEXT@@@Z +elseifdef _M_X64 +CAPTURECONTEXT_SYMBOL equ ?CaptureContext@crashpad@@YAXPEAU_CONTEXT@@@Z +endif + +_TEXT segment +public CAPTURECONTEXT_SYMBOL + +ifdef _M_IX86 + +CAPTURECONTEXT_SYMBOL proc + + push ebp + mov ebp, esp + + ; pushfd first, because some instructions affect eflags. eflags will be in + ; [ebp-4]. + pushfd + + ; Save the original value of ebx, and use ebx to hold the CONTEXT* argument. + ; The original value of ebx will be in [ebp-8]. + push ebx + mov ebx, [ebp+8] + + ; General-purpose registers whose values haven’t changed can be captured + ; directly. + mov [ebx.CONTEXT].c_Edi, edi + mov [ebx.CONTEXT].c_Esi, esi + mov [ebx.CONTEXT].c_Edx, edx + mov [ebx.CONTEXT].c_Ecx, ecx + mov [ebx.CONTEXT].c_Eax, eax + + ; Now that the original value of edx has been saved, it can be repurposed to + ; hold other registers’ values. + + ; The original ebx was saved on the stack above. + mov edx, dword ptr [ebp-8] + mov [ebx.CONTEXT].c_Ebx, edx + + ; The original ebp was saved on the stack in this function’s prologue. + mov edx, dword ptr [ebp] + mov [ebx.CONTEXT].c_Ebp, edx + + ; eip can’t be accessed directly, but the return address saved on the stack + ; by the call instruction that reached this function can be used. + mov edx, dword ptr [ebp+4] + mov [ebx.CONTEXT].c_Eip, edx + + ; The original eflags was saved on the stack above. + mov edx, dword ptr [ebp-4] + mov [ebx.CONTEXT].c_EFlags, edx + + ; esp was saved in ebp in this function’s prologue, but the caller’s esp is 8 + ; more than this value: 4 for the original ebp saved on the stack in this + ; function’s prologue, and 4 for the return address saved on the stack by the + ; call instruction that reached this function. + lea edx, [ebp+8] + mov [ebx.CONTEXT].c_Esp, edx + + ; The segment registers are 16 bits wide, but CONTEXT declares them as + ; unsigned 32-bit values, so zero the top half. + xor edx, edx + mov dx, gs + mov [ebx.CONTEXT].c_SegGs, edx + mov dx, fs + mov [ebx.CONTEXT].c_SegFs, edx + mov dx, es + mov [ebx.CONTEXT].c_SegEs, edx + mov dx, ds + mov [ebx.CONTEXT].c_SegDs, edx + mov dx, cs + mov [ebx.CONTEXT].c_SegCs, edx + mov dx, ss + mov [ebx.CONTEXT].c_SegSs, edx + + ; Prepare for the string move that will populate the ExtendedRegisters area, + ; or the string store that will zero it. + cld + + ; Use cpuid 1 to check whether fxsave is supported. If it is, perform it + ; before fnsave because fxsave is a less-destructive operation. + mov esi, ebx + mov eax, 1 + cpuid + mov ebx, esi + + test edx, 01000000 ; FXSR + jnz $FXSave + + ; fxsave is not supported. Set ContextFlags to not include + ; CONTEXT_EXTENDED_REGISTERS, and zero the ExtendedRegisters area. + mov [ebx.CONTEXT].c_ContextFlags, CONTEXT_i386 or \ + CONTEXT_CONTROL or \ + CONTEXT_INTEGER or \ + CONTEXT_SEGMENTS or \ + CONTEXT_FLOATING_POINT + lea edi, [ebx.CONTEXT].c_ExtendedRegisters + xor eax, eax + mov ecx, MAXIMUM_SUPPORTED_EXTENSION / sizeof(dword) ; 128 + rep stosd + jmp $FXSaveDone + +$FXSave: + ; fxsave is supported. Set ContextFlags to include CONTEXT_EXTENDED_REGISTERS. + mov [ebx.CONTEXT].c_ContextFlags, CONTEXT_i386 or \ + CONTEXT_CONTROL or \ + CONTEXT_INTEGER or \ + CONTEXT_SEGMENTS or \ + CONTEXT_FLOATING_POINT or \ + CONTEXT_EXTENDED_REGISTERS + + ; fxsave requires a 16 byte-aligned destination memory area. Nothing + ; guarantees the alignment of a CONTEXT structure, so create a temporary + ; aligned fxsave destination on the stack. + and esp, 0fffffff0h + sub esp, MAXIMUM_SUPPORTED_EXTENSION + + ; Zero out the temporary fxsave area before performing the fxsave. Some of the + ; fxsave area may not be written by fxsave, and some is definitely not written + ; by fxsave. + mov edi, esp + xor eax, eax + mov ecx, MAXIMUM_SUPPORTED_EXTENSION / sizeof(dword) ; 128 + rep stosd + + fxsave [esp] + + ; Copy the temporary fxsave area into the CONTEXT structure. + lea edi, [ebx.CONTEXT].c_ExtendedRegisters + mov esi, esp + mov ecx, MAXIMUM_SUPPORTED_EXTENSION / sizeof(dword) ; 128 + rep movsd + + ; Free the stack space used for the temporary fxsave area. + lea esp, [ebp-8] + + ; TODO(mark): AVX/xsave support. + ; https://code.google.com/p/crashpad/issues/detail?id=58 + +$FXSaveDone: + ; fnsave reinitializes the FPU with an implicit finit operation, so use frstor + ; to restore the original state. + fnsave [ebx.CONTEXT].c_FloatSave + frstor [ebx.CONTEXT].c_FloatSave + + ; cr0 is inaccessible from user code, and this field would not be used anyway. + mov [ebx.CONTEXT].c_FloatSave.f_Cr0NpxState, 0 + + ; The debug registers can’t be read from user code, so zero them out in the + ; CONTEXT structure. context->ContextFlags doesn’t indicate that they are + ; present. + mov [ebx.CONTEXT].c_Dr0, 0 + mov [ebx.CONTEXT].c_Dr1, 0 + mov [ebx.CONTEXT].c_Dr2, 0 + mov [ebx.CONTEXT].c_Dr3, 0 + mov [ebx.CONTEXT].c_Dr6, 0 + mov [ebx.CONTEXT].c_Dr7, 0 + + ; Clean up by restoring clobbered registers, even those considered volatile + ; by the ABI, so that the captured context represents the state at this + ; function’s exit. + mov edi, [ebx.CONTEXT].c_Edi + mov esi, [ebx.CONTEXT].c_Esi + mov edx, [ebx.CONTEXT].c_Edx + mov ecx, [ebx.CONTEXT].c_Ecx + mov eax, [ebx.CONTEXT].c_Eax + pop ebx + popfd + + pop ebp + + ret + +CAPTURECONTEXT_SYMBOL endp + +elseifdef _M_X64 + +CAPTURECONTEXT_SYMBOL proc frame + + push rbp + .pushreg rbp + mov rbp, rsp + .setframe rbp, 0 + + ; Note that 16-byte stack alignment is not maintained because this function + ; does not call out to any other. + + ; pushfq first, because some instructions affect rflags. rflags will be in + ; [rbp-8]. + pushfq + .allocstack 8 + .endprolog + + mov [rcx.CONTEXT].c_ContextFlags, CONTEXT_AMD64 or \ + CONTEXT_CONTROL or \ + CONTEXT_INTEGER or \ + CONTEXT_SEGMENTS or \ + CONTEXT_FLOATING_POINT + + ; General-purpose registers whose values haven’t changed can be captured + ; directly. + mov [rcx.CONTEXT].c_Rax, rax + mov [rcx.CONTEXT].c_Rdx, rdx + mov [rcx.CONTEXT].c_Rbx, rbx + mov [rcx.CONTEXT].c_Rsi, rsi + mov [rcx.CONTEXT].c_Rdi, rdi + mov [rcx.CONTEXT].c_R8, r8 + mov [rcx.CONTEXT].c_R9, r9 + mov [rcx.CONTEXT].c_R10, r10 + mov [rcx.CONTEXT].c_R11, r11 + mov [rcx.CONTEXT].c_R12, r12 + mov [rcx.CONTEXT].c_R13, r13 + mov [rcx.CONTEXT].c_R14, r14 + mov [rcx.CONTEXT].c_R15, r15 + + ; Because of the calling convention, there’s no way to recover the value of + ; the caller’s rcx as it existed prior to calling this function. This + ; function captures a snapshot of the register state at its return, which + ; involves rcx containing a pointer to its first argument. + mov [rcx.CONTEXT].c_Rcx, rcx + + ; Now that the original value of rax has been saved, it can be repurposed to + ; hold other registers’ values. + + ; Save mxcsr. This is duplicated in context->FltSave.MxCsr, saved by fxsave + ; below. + stmxcsr [rcx.CONTEXT].c_MxCsr + + ; Segment registers. + mov [rcx.CONTEXT].c_SegCs, cs + mov [rcx.CONTEXT].c_SegDs, ds + mov [rcx.CONTEXT].c_SegEs, es + mov [rcx.CONTEXT].c_SegFs, fs + mov [rcx.CONTEXT].c_SegGs, gs + mov [rcx.CONTEXT].c_SegSs, ss + + ; The original rflags was saved on the stack above. Note that the CONTEXT + ; structure only stores eflags, the low 32 bits. The high 32 bits in rflags + ; are reserved. + mov rax, qword ptr [rbp-8] + mov [rcx.CONTEXT].c_EFlags, eax + + ; rsp was saved in rbp in this function’s prologue, but the caller’s rsp is + ; 16 more than this value: 8 for the original rbp saved on the stack in this + ; function’s prologue, and 8 for the return address saved on the stack by the + ; call instruction that reached this function. + lea rax, [rbp+16] + mov [rcx.CONTEXT].c_Rsp, rax + + ; The original rbp was saved on the stack in this function’s prologue. + mov rax, qword ptr [rbp] + mov [rcx.CONTEXT].c_Rbp, rax + + ; rip can’t be accessed directly, but the return address saved on the stack by + ; the call instruction that reached this function can be used. + mov rax, qword ptr [rbp+8] + mov [rcx.CONTEXT].c_Rip, rax + + ; Zero out the fxsave area before performing the fxsave. Some of the fxsave + ; area may not be written by fxsave, and some is definitely not written by + ; fxsave. This also zeroes out the rest of the CONTEXT structure to its end, + ; including the unused VectorRegister and VectorControl fields, and the debug + ; control register fields. + mov rbx, rcx + cld + lea rdi, [rcx.CONTEXT].c_FltSave + xor rax, rax + mov rcx, (sizeof(CONTEXT) - offsetof(CONTEXT, c_FltSave)) / \ + sizeof(qword) ; 122 + rep stosq + mov rcx, rbx + + ; Save the floating point (including SSE) state. The CONTEXT structure is + ; declared as 16-byte-aligned, which is correct for this operation. + fxsave [rcx.CONTEXT].c_FltSave + + ; TODO(mark): AVX/xsave support. + ; https://code.google.com/p/crashpad/issues/detail?id=58 + + ; The register parameter home address fields aren’t used, so zero them out. + mov [rcx.CONTEXT].c_P1Home, 0 + mov [rcx.CONTEXT].c_P2Home, 0 + mov [rcx.CONTEXT].c_P3Home, 0 + mov [rcx.CONTEXT].c_P4Home, 0 + mov [rcx.CONTEXT].c_P5Home, 0 + mov [rcx.CONTEXT].c_P6Home, 0 + + ; The debug registers can’t be read from user code, so zero them out in the + ; CONTEXT structure. context->ContextFlags doesn’t indicate that they are + ; present. + mov [rcx.CONTEXT].c_Dr0, 0 + mov [rcx.CONTEXT].c_Dr1, 0 + mov [rcx.CONTEXT].c_Dr2, 0 + mov [rcx.CONTEXT].c_Dr3, 0 + mov [rcx.CONTEXT].c_Dr6, 0 + mov [rcx.CONTEXT].c_Dr7, 0 + + ; Clean up by restoring clobbered registers, even those considered volatile by + ; the ABI, so that the captured context represents the state at this + ; function’s exit. + mov rax, [rcx.CONTEXT].c_Rax + mov rbx, [rcx.CONTEXT].c_Rbx + mov rdi, [rcx.CONTEXT].c_Rdi + popfq + + pop rbp + + ret + +CAPTURECONTEXT_SYMBOL endp + +endif + +_TEXT ends +end diff --git a/util/win/capture_context.h b/util/win/capture_context.h new file mode 100644 index 00000000..2f501f80 --- /dev/null +++ b/util/win/capture_context.h @@ -0,0 +1,47 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_ +#define CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_ + +#include + +namespace crashpad { + +//! \brief Saves the CPU context. +//! +//! The CPU context will be captured as accurately and completely as possible, +//! containing an atomic snapshot at the point of this function’s return. This +//! function does not modify any registers. +//! +//! This function captures all integer registers as well as the floating-point +//! and vector (SSE) state. It does not capture debug registers, which are +//! inaccessible by user code. +//! +//! This function is a replacement for `RtlCaptureContext()`, which contains +//! bugs and limitations. On 32-bit x86, `RtlCaptureContext()` requires that +//! `ebp` be used as a frame pointer, and returns `ebp`, `esp`, and `eip` out of +//! sync with the other registers. Both the 32-bit x86 and 64-bit x86_64 +//! versions of `RtlCaptureContext()` capture only the state of the integer +//! registers, ignoring floating-point and vector state. +//! +//! \param[out] context The structure to store the context in. +//! +//! \note On x86_64, the value for `rcx` will be populated with the address of +//! this function’s argument, as mandated by the ABI. +void CaptureContext(CONTEXT* context); + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_ diff --git a/util/win/capture_context_test.cc b/util/win/capture_context_test.cc new file mode 100644 index 00000000..270ecb39 --- /dev/null +++ b/util/win/capture_context_test.cc @@ -0,0 +1,182 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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 "util/win/capture_context.h" + +#include + +#include + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +// If the context structure has fields that tell whether it’s valid, such as +// magic numbers or size fields, sanity-checks those fields for validity with +// fatal gtest assertions. For other fields, where it’s possible to reason about +// their validity based solely on their contents, sanity-checks via nonfatal +// gtest assertions. +void SanityCheckContext(const CONTEXT& context) { +#if defined(ARCH_CPU_X86) + const uint32_t must_have = CONTEXT_i386 | + CONTEXT_CONTROL | + CONTEXT_INTEGER | + CONTEXT_SEGMENTS | + CONTEXT_FLOATING_POINT; + ASSERT_EQ(must_have, context.ContextFlags & must_have); + const uint32_t may_have = CONTEXT_EXTENDED_REGISTERS; + ASSERT_EQ(0, context.ContextFlags & ~(must_have | may_have)); +#elif defined(ARCH_CPU_X86_64) + ASSERT_EQ(CONTEXT_AMD64 | + CONTEXT_CONTROL | + CONTEXT_INTEGER | + CONTEXT_SEGMENTS | + CONTEXT_FLOATING_POINT, + context.ContextFlags); +#endif + +#if defined(ARCH_CPU_X86_FAMILY) + // Many bit positions in the flags register are reserved and will always read + // a known value. Most reserved bits are always 0, but bit 1 is always 1. + // Check that the reserved bits are all set to their expected values. Note + // that the set of reserved bits may be relaxed over time with newer CPUs, and + // that this test may need to be changed to reflect these developments. The + // current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel + // Software Developer’s Manual, Volume 1: Basic Architecture (253665-055), + // 3.4.3 “EFLAGS Register”, and AMD Architecture Programmer’s Manual, Volume + // 2: System Programming (24593-3.25), 3.1.6 “RFLAGS Register”. + EXPECT_EQ(2u, context.EFlags & 0xffc0802a); + + // CaptureContext() doesn’t capture debug registers, so make sure they read 0. + EXPECT_EQ(0, context.Dr0); + EXPECT_EQ(0, context.Dr1); + EXPECT_EQ(0, context.Dr2); + EXPECT_EQ(0, context.Dr3); + EXPECT_EQ(0, context.Dr6); + EXPECT_EQ(0, context.Dr7); +#endif + +#if defined(ARCH_CPU_X86) + // fxsave doesn’t write these bytes. + for (size_t i = 464; i < arraysize(context.ExtendedRegisters); ++i) { + SCOPED_TRACE(i); + EXPECT_EQ(0, context.ExtendedRegisters[i]); + } +#elif defined(ARCH_CPU_X86_64) + // mxcsr shows up twice in the context structure. Make sure the values are + // identical. + EXPECT_EQ(context.MxCsr, context.FltSave.MxCsr); + + // fxsave doesn’t write these bytes. + for (size_t i = 0; i < arraysize(context.FltSave.Reserved4); ++i) { + SCOPED_TRACE(i); + EXPECT_EQ(0, context.FltSave.Reserved4[i]); + } + + // CaptureContext() doesn’t use these fields. + EXPECT_EQ(0, context.P1Home); + EXPECT_EQ(0, context.P2Home); + EXPECT_EQ(0, context.P3Home); + EXPECT_EQ(0, context.P4Home); + EXPECT_EQ(0, context.P5Home); + EXPECT_EQ(0, context.P6Home); + for (size_t i = 0; i < arraysize(context.VectorRegister); ++i) { + SCOPED_TRACE(i); + EXPECT_EQ(0, context.VectorRegister[i].Low); + EXPECT_EQ(0, context.VectorRegister[i].High); + } + EXPECT_EQ(0, context.VectorControl); + EXPECT_EQ(0, context.DebugControl); + EXPECT_EQ(0, context.LastBranchToRip); + EXPECT_EQ(0, context.LastBranchFromRip); + EXPECT_EQ(0, context.LastExceptionToRip); + EXPECT_EQ(0, context.LastExceptionFromRip); +#endif +} + +// A CPU-independent function to return the program counter. +uintptr_t ProgramCounterFromContext(const CONTEXT& context) { +#if defined(ARCH_CPU_X86) + return context.Eip; +#elif defined(ARCH_CPU_X86_64) + return context.Rip; +#endif +} + +// A CPU-independent function to return the stack pointer. +uintptr_t StackPointerFromContext(const CONTEXT& context) { +#if defined(ARCH_CPU_X86) + return context.Esp; +#elif defined(ARCH_CPU_X86_64) + return context.Rsp; +#endif +} + +void TestCaptureContext() { + CONTEXT context_1; + CaptureContext(&context_1); + + { + SCOPED_TRACE("context_1"); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_1)); + } + + // The program counter reference value is this function’s address. The + // captured program counter should be slightly greater than or equal to the + // reference program counter. + uintptr_t pc = ProgramCounterFromContext(context_1); + + // Declare sp and context_2 here because all local variables need to be + // declared before computing the stack pointer reference value, so that the + // reference value can be the lowest value possible. + uintptr_t sp; + CONTEXT context_2; + + // The stack pointer reference value is the lowest address of a local variable + // in this function. The captured program counter will be slightly less than + // or equal to the reference stack pointer. + const uintptr_t kReferenceSP = + std::min(std::min(reinterpret_cast(&context_1), + reinterpret_cast(&context_2)), + std::min(reinterpret_cast(&pc), + reinterpret_cast(&sp))); + sp = StackPointerFromContext(context_1); + EXPECT_LT(kReferenceSP - sp, 512u); + + // Capture the context again, expecting that the stack pointer stays the same + // and the program counter increases. Strictly speaking, there’s no guarantee + // that these conditions will hold, although they do for known compilers even + // under typical optimization. + CaptureContext(&context_2); + + { + SCOPED_TRACE("context_2"); + ASSERT_NO_FATAL_FAILURE(SanityCheckContext(context_2)); + } + + EXPECT_EQ(sp, StackPointerFromContext(context_2)); + EXPECT_GT(ProgramCounterFromContext(context_2), pc); +} + +TEST(CaptureContextWin, CaptureContext) { + ASSERT_NO_FATAL_FAILURE(TestCaptureContext()); +} + +} // namespace +} // namespace test +} // namespace crashpad