mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
56c8359b27
commit
c8592b847b
@ -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<int>(x86_THREAD_STATE32_COUNT), context->tsh.count);
|
||||
ASSERT_EQ(x86_THREAD_STATE32, context.tsh.flavor);
|
||||
ASSERT_EQ(implicit_cast<int>(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<int>(x86_THREAD_STATE64_COUNT), context->tsh.count);
|
||||
ASSERT_EQ(x86_THREAD_STATE64, context.tsh.flavor);
|
||||
ASSERT_EQ(implicit_cast<int>(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<uintptr_t>(&context_2)),
|
||||
std::min(reinterpret_cast<uintptr_t>(&pc),
|
||||
reinterpret_cast<uintptr_t>(&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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -18,12 +18,13 @@
|
||||
#include <windows.h>
|
||||
|
||||
#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)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
}],
|
||||
],
|
||||
},
|
||||
|
@ -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',
|
||||
|
532
util/win/capture_context.asm
Normal file
532
util/win/capture_context.asm
Normal 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 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
|
47
util/win/capture_context.h
Normal file
47
util/win/capture_context.h
Normal 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 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_
|
182
util/win/capture_context_test.cc
Normal file
182
util/win/capture_context_test.cc
Normal 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 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<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, 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
|
Loading…
x
Reference in New Issue
Block a user