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 .
This commit is contained in:
Mark Mentovai 2015-09-30 14:10:08 -04:00
parent 56c8359b27
commit c8592b847b
10 changed files with 819 additions and 42 deletions

View File

@ -32,13 +32,13 @@ namespace {
// fatal gtest assertions. For other fields, where its possible to reason about // fatal gtest assertions. For other fields, where its possible to reason about
// their validity based solely on their contents, sanity-checks via nonfatal // their validity based solely on their contents, sanity-checks via nonfatal
// gtest assertions. // gtest assertions.
void SanityCheckContext(NativeCPUContext* context) { void SanityCheckContext(const NativeCPUContext& context) {
#if defined(ARCH_CPU_X86) #if defined(ARCH_CPU_X86)
ASSERT_EQ(x86_THREAD_STATE32, context->tsh.flavor); ASSERT_EQ(x86_THREAD_STATE32, context.tsh.flavor);
ASSERT_EQ(implicit_cast<int>(x86_THREAD_STATE32_COUNT), context->tsh.count); ASSERT_EQ(implicit_cast<int>(x86_THREAD_STATE32_COUNT), context.tsh.count);
#elif defined(ARCH_CPU_X86_64) #elif defined(ARCH_CPU_X86_64)
ASSERT_EQ(x86_THREAD_STATE64, context->tsh.flavor); ASSERT_EQ(x86_THREAD_STATE64, context.tsh.flavor);
ASSERT_EQ(implicit_cast<int>(x86_THREAD_STATE64_COUNT), context->tsh.count); ASSERT_EQ(implicit_cast<int>(x86_THREAD_STATE64_COUNT), context.tsh.count);
#endif #endif
#if defined(ARCH_CPU_X86_FAMILY) #if defined(ARCH_CPU_X86_FAMILY)
@ -47,46 +47,46 @@ void SanityCheckContext(NativeCPUContext* context) {
// that the high bits are all clear. // that the high bits are all clear.
// //
// Many bit positions in the flags register are reserved and will always read // 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 // a known value. Most reserved bits are always 0, but bit 1 is always 1.
// that the reserved bits are all set to their expected values. Note that the // Check that the reserved bits are all set to their expected values. Note
// set of reserved bits may be relaxed over time with newer CPUs, and that // that the set of reserved bits may be relaxed over time with newer CPUs, and
// this test may need to be changed to reflect these developments. The current // that this test may need to be changed to reflect these developments. The
// set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel Software // current set of reserved bits are 1, 3, 5, 15, and 22 and higher. See Intel
// Developers Manual, Volume 1: Basic Architecture (253665-051), 3.4.3 // Software Developers Manual, Volume 1: Basic Architecture (253665-051),
// “EFLAGS Register”, and AMD Architecture Programmers Manual, Volume 2: // 3.4.3 “EFLAGS Register”, and AMD Architecture Programmers Manual, Volume
// System Programming (24593-3.24), 3.1.6 “RFLAGS Register”. // 2: System Programming (24593-3.24), 3.1.6 “RFLAGS Register”.
#if defined(ARCH_CPU_X86) #if defined(ARCH_CPU_X86)
EXPECT_EQ(0u, context->uts.ts32.__cs & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__cs & ~0xffff);
EXPECT_EQ(0u, context->uts.ts32.__ds & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__ds & ~0xffff);
EXPECT_EQ(0u, context->uts.ts32.__es & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__es & ~0xffff);
EXPECT_EQ(0u, context->uts.ts32.__fs & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__fs & ~0xffff);
EXPECT_EQ(0u, context->uts.ts32.__gs & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__gs & ~0xffff);
EXPECT_EQ(0u, context->uts.ts32.__ss & ~0xffff); EXPECT_EQ(0u, context.uts.ts32.__ss & ~0xffff);
EXPECT_EQ(2u, context->uts.ts32.__eflags & 0xffc0802a); EXPECT_EQ(2u, context.uts.ts32.__eflags & 0xffc0802a);
#elif defined(ARCH_CPU_X86_64) #elif defined(ARCH_CPU_X86_64)
EXPECT_EQ(0u, context->uts.ts64.__cs & ~UINT64_C(0xffff)); 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.__fs & ~UINT64_C(0xffff));
EXPECT_EQ(0u, context->uts.ts64.__gs & ~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(2u, context.uts.ts64.__rflags & UINT64_C(0xffffffffffc0802a));
#endif #endif
#endif #endif
} }
// A CPU-independent function to return the program counter. // 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) #if defined(ARCH_CPU_X86)
return context->uts.ts32.__eip; return context.uts.ts32.__eip;
#elif defined(ARCH_CPU_X86_64) #elif defined(ARCH_CPU_X86_64)
return context->uts.ts64.__rip; return context.uts.ts64.__rip;
#endif #endif
} }
// A CPU-independent function to return the stack pointer. // 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) #if defined(ARCH_CPU_X86)
return context->uts.ts32.__esp; return context.uts.ts32.__esp;
#elif defined(ARCH_CPU_X86_64) #elif defined(ARCH_CPU_X86_64)
return context->uts.ts64.__rsp; return context.uts.ts64.__rsp;
#endif #endif
} }
@ -96,13 +96,13 @@ void TestCaptureContext() {
{ {
SCOPED_TRACE("context_1"); 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 functions address. The // The program counter reference value is this functions address. The
// captured program counter should be slightly greater than or equal to the // captured program counter should be slightly greater than or equal to the
// reference program counter. // reference program counter.
uintptr_t pc = ProgramCounterFromContext(&context_1); uintptr_t pc = ProgramCounterFromContext(context_1);
#if !__has_feature(address_sanitizer) #if !__has_feature(address_sanitizer)
// AddressSanitizer can cause enough code bloat that the “nearby” check would // AddressSanitizer can cause enough code bloat that the “nearby” check would
// likely fail. // likely fail.
@ -125,7 +125,7 @@ void TestCaptureContext() {
reinterpret_cast<uintptr_t>(&context_2)), reinterpret_cast<uintptr_t>(&context_2)),
std::min(reinterpret_cast<uintptr_t>(&pc), std::min(reinterpret_cast<uintptr_t>(&pc),
reinterpret_cast<uintptr_t>(&sp))); reinterpret_cast<uintptr_t>(&sp)));
sp = StackPointerFromContext(&context_1); sp = StackPointerFromContext(context_1);
EXPECT_LT(kReferenceSP - sp, 512u); EXPECT_LT(kReferenceSP - sp, 512u);
// Capture the context again, expecting that the stack pointer stays the same // Capture the context again, expecting that the stack pointer stays the same
@ -136,11 +136,11 @@ void TestCaptureContext() {
{ {
SCOPED_TRACE("context_2"); 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_EQ(sp, StackPointerFromContext(context_2));
EXPECT_GT(ProgramCounterFromContext(&context_2), pc); EXPECT_GT(ProgramCounterFromContext(context_2), pc);
} }
TEST(CaptureContextMac, CaptureContext) { TEST(CaptureContextMac, CaptureContext) {

View File

@ -93,8 +93,8 @@ class CrashpadClient {
//! \brief Requests that the handler capture a dump even though there hasn't //! \brief Requests that the handler capture a dump even though there hasn't
//! been a crash. //! been a crash.
//! //!
//! \param[in] context A CONTEXT, generally captured by `RtlCaptureContext()` //! \param[in] context A `CONTEXT`, generally captured by CaptureContext() or
//! or similar. //! similar.
static void DumpWithoutCrash(const CONTEXT& context); static void DumpWithoutCrash(const CONTEXT& context);
#endif #endif

View File

@ -18,12 +18,13 @@
#include <windows.h> #include <windows.h>
#include "client/crashpad_client.h" #include "client/crashpad_client.h"
#include "util/win/capture_context.h"
//! \brief Captures the CPU context and captures a dump without an exception. //! \brief Captures the CPU context and captures a dump without an exception.
#define CRASHPAD_SIMULATE_CRASH() \ #define CRASHPAD_SIMULATE_CRASH() \
do { \ do { \
CONTEXT context; \ CONTEXT context; \
RtlCaptureContext(&context); \ crashpad::CaptureContext(&context); \
crashpad::CrashpadClient::DumpWithoutCrash(context); \ crashpad::CrashpadClient::DumpWithoutCrash(context); \
} while (false) } while (false)

View File

@ -192,8 +192,7 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate {
snapshot.Initialize(process, ProcessSuspensionState::kSuspended); snapshot.Initialize(process, ProcessSuspensionState::kSuspended);
snapshot.InitializeException(exception_information_address); snapshot.InitializeException(exception_information_address);
EXPECT_TRUE(snapshot.Exception()); EXPECT_TRUE(snapshot.Exception());
EXPECT_EQ(0, snapshot.Exception()->Exception()); EXPECT_EQ(0x517a7ed, snapshot.Exception()->Exception());
EXPECT_EQ(0, snapshot.Exception()->ExceptionAddress());
// Verify the dump was captured at the expected location with some slop // Verify the dump was captured at the expected location with some slop
// space. // space.
@ -203,6 +202,9 @@ class SimulateDelegate : public ExceptionHandlerServer::Delegate {
EXPECT_LT(snapshot.Exception()->Context()->InstructionPointer(), EXPECT_LT(snapshot.Exception()->Context()->InstructionPointer(),
dump_near_ + kAllowedOffset); dump_near_ + kAllowedOffset);
EXPECT_EQ(snapshot.Exception()->Context()->InstructionPointer(),
snapshot.Exception()->ExceptionAddress());
SetEvent(completed_test_event_); SetEvent(completed_test_event_);
return 0; return 0;

View File

@ -18,6 +18,7 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "util/win/capture_context.h"
#include "util/win/nt_internals.h" #include "util/win/nt_internals.h"
#include "util/win/ntstatus_logging.h" #include "util/win/ntstatus_logging.h"
#include "util/win/process_structs.h" #include "util/win/process_structs.h"
@ -128,7 +129,7 @@ bool FillThreadContextAndSuspendCount(HANDLE thread_handle,
DCHECK(suspension_state == ProcessSuspensionState::kRunning); DCHECK(suspension_state == ProcessSuspensionState::kRunning);
thread->suspend_count = 0; thread->suspend_count = 0;
DCHECK(!is_64_reading_32); DCHECK(!is_64_reading_32);
RtlCaptureContext(&thread->context.native); CaptureContext(&thread->context.native);
} else { } else {
DWORD previous_suspend_count = SuspendThread(thread_handle); DWORD previous_suspend_count = SuspendThread(thread_handle);
if (previous_suspend_count == -1) { if (previous_suspend_count == -1) {

View File

@ -149,6 +149,8 @@
'thread/thread_posix.cc', 'thread/thread_posix.cc',
'thread/thread_win.cc', 'thread/thread_win.cc',
'win/address_types.h', 'win/address_types.h',
'win/capture_context.asm',
'win/capture_context.h',
'win/checked_win_address_range.h', 'win/checked_win_address_range.h',
'win/exception_handler_server.cc', 'win/exception_handler_server.cc',
'win/exception_handler_server.h', 'win/exception_handler_server.h',
@ -240,6 +242,15 @@
'-lwinhttp.lib', '-lwinhttp.lib',
], ],
}, },
'conditions': [
['target_arch=="ia32"', {
'msvs_settings': {
'MASM': {
'UseSafeExceptionHandlers': 'true',
},
},
}],
],
}], }],
], ],
}, },

View File

@ -79,6 +79,7 @@
'synchronization/semaphore_test.cc', 'synchronization/semaphore_test.cc',
'thread/thread_log_messages_test.cc', 'thread/thread_log_messages_test.cc',
'thread/thread_test.cc', 'thread/thread_test.cc',
'win/capture_context_test.cc',
'win/exception_handler_server_test.cc', 'win/exception_handler_server_test.cc',
'win/process_info_test.cc', 'win/process_info_test.cc',
'win/scoped_process_suspend_test.cc', 'win/scoped_process_suspend_test.cc',

View File

@ -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 <structure.&field>
endm
; The CONTEXT structure definitions that follow are based on those in <winnt.h>.
; 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 havent 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 functions prologue.
mov edx, dword ptr [ebp]
mov [ebx.CONTEXT].c_Ebp, edx
; eip cant 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 functions prologue, but the callers esp is 8
; more than this value: 4 for the original ebp saved on the stack in this
; functions 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 cant be read from user code, so zero them out in the
; CONTEXT structure. context->ContextFlags doesnt 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
; functions 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 havent 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, theres no way to recover the value of
; the callers 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 functions prologue, but the callers rsp is
; 16 more than this value: 8 for the original rbp saved on the stack in this
; functions 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 functions prologue.
mov rax, qword ptr [rbp]
mov [rcx.CONTEXT].c_Rbp, rax
; rip cant 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 arent 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 cant be read from user code, so zero them out in the
; CONTEXT structure. context->ContextFlags doesnt 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
; functions 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

View File

@ -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 <windows.h>
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 functions 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 functions argument, as mandated by the ABI.
void CaptureContext(CONTEXT* context);
} // namespace crashpad
#endif // CRASHPAD_CLIENT_CAPTURE_CONTEXT_WIN_H_

View File

@ -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 <stdint.h>
#include <algorithm>
#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 its valid, such as
// magic numbers or size fields, sanity-checks those fields for validity with
// fatal gtest assertions. For other fields, where its 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 Developers Manual, Volume 1: Basic Architecture (253665-055),
// 3.4.3 “EFLAGS Register”, and AMD Architecture Programmers Manual, Volume
// 2: System Programming (24593-3.25), 3.1.6 “RFLAGS Register”.
EXPECT_EQ(2u, context.EFlags & 0xffc0802a);
// CaptureContext() doesnt 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 doesnt 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 doesnt 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() doesnt 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 functions 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<uintptr_t>(&context_1),
reinterpret_cast<uintptr_t>(&context_2)),
std::min(reinterpret_cast<uintptr_t>(&pc),
reinterpret_cast<uintptr_t>(&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, theres 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