--- /dev/null
+/*
+ * PROJECT: ReactOS api tests
+ * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE: Test helper for x64 RtlCaptureContext
+ * COPYRIGHT: Copyright 2022 Timo Kreuzer <timo.kreuzer@reactos.org>
+ */
+
+#include <asm.inc>
+#include <ksamd64.inc>
+
+.code64
+
+EXTERN RtlCaptureContext:PROC
+
+FUNC ZeroContext
+
+ pushfq
+ .ALLOCSTACK 8
+ push rax
+ .PUSHREG rax
+ push rcx
+ .PUSHREG rcx
+ push rdi
+ .PUSHREG rdi
+ .ENDPROLOG
+
+ mov rdi, rcx
+ mov rcx, CONTEXT_FRAME_LENGTH
+ xor eax, eax
+ cld
+ rep stosb
+
+ pop rdi
+ pop rcx
+ pop rax
+ popfq
+ ret
+
+ENDFUNC
+
+//
+// VOID
+// RtlCaptureContextWrapper (
+// _Inout_ PCONTEXT InOutContext,
+// _Out_ PCONTEXT CapturedContext);
+//
+PUBLIC RtlCaptureContextWrapper
+FUNC RtlCaptureContextWrapper
+
+ // Generate a KEXCEPTION_FRAME on the stack
+ GENERATE_EXCEPTION_FRAME
+
+ // Save parameters
+ mov [rsp + ExP1Home], rcx
+ mov [rsp + ExP2Home], rdx
+
+ // Save current EFlags
+ pushfq
+ pop qword ptr [rsp + ExP3Home]
+
+ // Load integer registers from InOutContext
+ mov rax, [rcx + CxRax]
+ mov rdx, [rcx + CxRdx]
+ mov rbx, [rcx + CxRbx]
+ mov rbp, [rcx + CxRbp]
+ mov rsi, [rcx + CxRsi]
+ mov rdi, [rcx + CxRdi]
+ mov r8, [rcx + CxR8]
+ mov r9, [rcx + CxR9]
+ mov r10, [rcx + CxR10]
+ mov r11, [rcx + CxR11]
+ mov r12, [rcx + CxR12]
+ mov r13, [rcx + CxR13]
+ mov r14, [rcx + CxR14]
+ mov r15, [rcx + CxR15]
+
+ // Load floating point registers from InOutContext
+ fxrstor [rcx + CxFltSave]
+
+ // Load MxCsr (this overwrites the value from FltSave)
+ ldmxcsr [rcx + CxMxCsr]
+
+ // Load XMM registers
+ movaps xmm0, [rcx + CxXmm0]
+ movaps xmm1, [rcx + CxXmm1]
+ movaps xmm2, [rcx + CxXmm2]
+ movaps xmm3, [rcx + CxXmm3]
+ movaps xmm4, [rcx + CxXmm4]
+ movaps xmm5, [rcx + CxXmm5]
+ movaps xmm6, [rcx + CxXmm6]
+ movaps xmm7, [rcx + CxXmm7]
+ movaps xmm8, [rcx + CxXmm8]
+ movaps xmm9, [rcx + CxXmm9]
+ movaps xmm10, [rcx + CxXmm10]
+ movaps xmm11, [rcx + CxXmm11]
+ movaps xmm12, [rcx + CxXmm12]
+ movaps xmm13, [rcx + CxXmm13]
+ movaps xmm14, [rcx + CxXmm14]
+ movaps xmm15, [rcx + CxXmm15]
+
+ // Load EFlags
+ push qword ptr [rcx + CxEFlags]
+ popfq
+
+ // Capture the context
+ mov rcx, [rsp + ExP2Home]
+ call RtlCaptureContext
+ReturnAddress:
+
+ // Save the returned rcx
+ mov [rsp + ExP4Home], rcx
+
+ // Restore original rcx
+ mov rcx, [rsp + ExP1Home]
+
+ // Zero out the context (this does not clobber any registers/flags)
+ call ZeroContext
+
+ // Save returned Eflags
+ pushfq
+ pop qword ptr [rcx + CxEFlags]
+
+ // Restore original EFLags
+ push qword ptr [rsp + ExP3Home]
+ popfq
+
+ // Save returned integer registers to InOutContext
+ mov [rcx + CxRax], rax
+ mov [rcx + CxRdx], rdx
+ mov [rcx + CxRbx], rbx
+ mov [rcx + CxRbp], rbp
+ mov [rcx + CxRsi], rsi
+ mov [rcx + CxRdi], rdi
+ mov [rcx + CxR8], r8
+ mov [rcx + CxR9], r9
+ mov [rcx + CxR10], r10
+ mov [rcx + CxR11], r11
+ mov [rcx + CxR12], r12
+ mov [rcx + CxR13], r13
+ mov [rcx + CxR14], r14
+ mov [rcx + CxR15], r15
+
+ // Save the returned rcx in the context
+ mov rax, [rsp + ExP4Home]
+ mov [rcx + CxRcx], rax
+
+ // Save returned floating point registers to InOutContext
+ stmxcsr [rcx + CxMxCsr]
+ fxsave [rcx + CxFltSave]
+ movaps [rcx + CxXmm0], xmm0
+ movaps [rcx + CxXmm1], xmm1
+ movaps [rcx + CxXmm2], xmm2
+ movaps [rcx + CxXmm3], xmm3
+ movaps [rcx + CxXmm4], xmm4
+ movaps [rcx + CxXmm5], xmm5
+ movaps [rcx + CxXmm6], xmm6
+ movaps [rcx + CxXmm7], xmm7
+ movaps [rcx + CxXmm8], xmm8
+ movaps [rcx + CxXmm9], xmm9
+ movaps [rcx + CxXmm10], xmm10
+ movaps [rcx + CxXmm11], xmm11
+ movaps [rcx + CxXmm12], xmm12
+ movaps [rcx + CxXmm13], xmm13
+ movaps [rcx + CxXmm14], xmm14
+ movaps [rcx + CxXmm15], xmm15
+
+ // Save the expected return address
+ lea rax, ReturnAddress[rip]
+ mov [rcx + CxRip], rax
+
+ // Save the expected stored rsp
+ mov [rcx + CxRsp], rsp
+
+ // Restore the registers from the KEXCEPTION_FRAME
+ RESTORE_EXCEPTION_STATE
+
+ ret
+
+ENDFUNC
+
+
+END
--- /dev/null
+/*
+ * PROJECT: ReactOS api tests
+ * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE: Test for x64 RtlCaptureContext
+ * COPYRIGHT: Copyright 2022 Timo Kreuzer <timo.kreuzer@reactos.org>
+ */
+
+#include <rtltests.h>
+
+VOID
+RtlCaptureContextWrapper(
+ _Inout_ PCONTEXT InOutContext,
+ _Out_ PCONTEXT CapturedContext);
+
+START_TEST(RtlCaptureContext)
+{
+ CONTEXT OriginalContext, InOutContext, CapturedContext;
+ SIZE_T Index, MaxIndex;
+ PUSHORT Buffer;
+
+ /* Initialize a pattern */
+ MaxIndex = sizeof(OriginalContext) / sizeof(USHORT);
+ Buffer = (PUSHORT)&OriginalContext;
+ for (Index = 0; Index < MaxIndex; Index++)
+ {
+ Buffer[Index] = Index;
+ }
+
+ /* Set all valid bits in EFlags */
+ OriginalContext.EFlags = 0xE7F;
+
+ /* Set up a valid floating point state */
+ OriginalContext.FltSave.ControlWord = 0x27f;
+ OriginalContext.FltSave.StatusWord = 0x1234;
+ OriginalContext.FltSave.TagWord = 0xab;
+ OriginalContext.FltSave.Reserved1 = 0x75;
+ OriginalContext.FltSave.MxCsr = 0x1f80; // Valid Mask is 2FFFF
+ OriginalContext.FltSave.MxCsr_Mask = 0xabcde;
+
+ /* Set up a unique MxCsr. This one will overwrite FltSave.MxCsr */
+ OriginalContext.MxCsr = 0xffff; // Valid Mask is 0xFFFF
+
+ /* Copy the original buffer */
+ InOutContext = OriginalContext;
+
+ /* Fill the output buffer with bogus */
+ RtlFillMemory(&CapturedContext, sizeof(CapturedContext), 0xCC);
+
+ /* Call the wrapper function */
+ RtlCaptureContextWrapper(&InOutContext, &CapturedContext);
+
+ /* These fields are not changed */
+ ok_eq_hex64(CapturedContext.P1Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.P2Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.P3Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.P4Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.P5Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.P6Home, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr0, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr1, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr2, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr3, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr6, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.Dr7, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.VectorControl, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.DebugControl, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.LastBranchToRip, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.LastBranchFromRip, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.LastExceptionToRip, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.LastExceptionFromRip, 0xccccccccccccccccULL);
+ for (Index = 0; Index < ARRAYSIZE(CapturedContext.FltSave.Reserved4); Index++)
+ {
+ ok_eq_hex64(CapturedContext.FltSave.Reserved4[Index], 0xcc);
+ }
+ for (Index = 0; Index < ARRAYSIZE(CapturedContext.VectorRegister); Index++)
+ {
+ ok_eq_hex64(CapturedContext.VectorRegister[Index].Low, 0xccccccccccccccccULL);
+ ok_eq_hex64(CapturedContext.VectorRegister[Index].High, 0xccccccccccccccccULL);
+ }
+
+ /* ContextFlags is set */
+ ok_eq_hex(CapturedContext.ContextFlags, CONTEXT_FULL | CONTEXT_SEGMENTS);
+
+ /* These vallues are as passed in */
+ ok_eq_hex(CapturedContext.MxCsr, OriginalContext.MxCsr);
+ ok_eq_hex64(CapturedContext.Rax, OriginalContext.Rax);
+ ok_eq_hex64(CapturedContext.Rcx, (ULONG64)&CapturedContext);
+ ok_eq_hex64(CapturedContext.Rdx, OriginalContext.Rdx);
+ ok_eq_hex64(CapturedContext.Rbx, OriginalContext.Rbx);
+ ok_eq_hex64(CapturedContext.Rbp, OriginalContext.Rbp);
+ ok_eq_hex64(CapturedContext.Rsi, OriginalContext.Rsi);
+ ok_eq_hex64(CapturedContext.Rdi, OriginalContext.Rdi);
+ ok_eq_hex64(CapturedContext.R8, OriginalContext.R8);
+ ok_eq_hex64(CapturedContext.R9, OriginalContext.R9);
+ ok_eq_hex64(CapturedContext.R10, OriginalContext.R10);
+ ok_eq_hex64(CapturedContext.R11, OriginalContext.R11);
+ ok_eq_hex64(CapturedContext.R12, OriginalContext.R12);
+ ok_eq_hex64(CapturedContext.R13, OriginalContext.R13);
+ ok_eq_hex64(CapturedContext.R14, OriginalContext.R14);
+ ok_eq_hex64(CapturedContext.R15, OriginalContext.R15);
+
+ ok_eq_xmm(CapturedContext.Xmm0, OriginalContext.Xmm0);
+ ok_eq_xmm(CapturedContext.Xmm1, OriginalContext.Xmm1);
+ ok_eq_xmm(CapturedContext.Xmm2, OriginalContext.Xmm2);
+ ok_eq_xmm(CapturedContext.Xmm3, OriginalContext.Xmm3);
+ ok_eq_xmm(CapturedContext.Xmm4, OriginalContext.Xmm4);
+ ok_eq_xmm(CapturedContext.Xmm5, OriginalContext.Xmm5);
+ ok_eq_xmm(CapturedContext.Xmm6, OriginalContext.Xmm6);
+ ok_eq_xmm(CapturedContext.Xmm7, OriginalContext.Xmm7);
+ ok_eq_xmm(CapturedContext.Xmm8, OriginalContext.Xmm8);
+ ok_eq_xmm(CapturedContext.Xmm9, OriginalContext.Xmm9);
+ ok_eq_xmm(CapturedContext.Xmm10, OriginalContext.Xmm10);
+ ok_eq_xmm(CapturedContext.Xmm11, OriginalContext.Xmm11);
+ ok_eq_xmm(CapturedContext.Xmm12, OriginalContext.Xmm12);
+ ok_eq_xmm(CapturedContext.Xmm13, OriginalContext.Xmm13);
+ ok_eq_xmm(CapturedContext.Xmm14, OriginalContext.Xmm14);
+ ok_eq_xmm(CapturedContext.Xmm15, OriginalContext.Xmm15);
+
+ /* Some EFlags fields are cleared */
+ ok_eq_hex64(CapturedContext.EFlags, OriginalContext.EFlags & ~0x28);
+
+#ifndef KMT_KERNEL_MODE // User mode
+ ok_eq_hex(CapturedContext.FltSave.ControlWord, 0x27f);
+ ok_eq_hex(CapturedContext.FltSave.StatusWord, OriginalContext.FltSave.StatusWord);
+ ok_eq_hex(CapturedContext.FltSave.TagWord, OriginalContext.FltSave.TagWord);
+ ok_eq_hex(CapturedContext.FltSave.Reserved1, 0x00);
+ ok_eq_hex(CapturedContext.FltSave.MxCsr_Mask, 0xffff);
+ ok_eq_hex(CapturedContext.FltSave.ErrorOpcode, 0x00000083);
+ ok_eq_hex(CapturedContext.FltSave.ErrorOffset, 0x00850084);
+ ok_eq_hex(CapturedContext.FltSave.ErrorSelector, 0x0086);
+ ok_eq_hex(CapturedContext.FltSave.Reserved2, 0x0000);
+ ok_eq_hex(CapturedContext.FltSave.DataOffset, 0x00890088);
+ ok_eq_hex(CapturedContext.FltSave.DataSelector, 0x008a);
+ ok_eq_hex(CapturedContext.FltSave.Reserved3, 0x0000);
+#else
+ ok_eq_hex(CapturedContext.FltSave.ControlWord, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.StatusWord, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.TagWord, 0xcc);
+ ok_eq_hex(CapturedContext.FltSave.Reserved1, 0xcc);
+ ok_eq_hex(CapturedContext.FltSave.MxCsr_Mask, 0xcccccccc);
+ ok_eq_hex(CapturedContext.FltSave.ErrorOpcode, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.ErrorOffset, 0xcccccccc);
+ ok_eq_hex(CapturedContext.FltSave.ErrorSelector, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.Reserved2, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.DataOffset, 0xcccccccc);
+ ok_eq_hex(CapturedContext.FltSave.DataSelector, 0xcccc);
+ ok_eq_hex(CapturedContext.FltSave.Reserved3, 0xcccc);
+#endif
+
+ /* We get the value from OriginalContext.MxCsr, since we set that later in the wrapper */
+ ok_eq_hex(CapturedContext.FltSave.MxCsr, OriginalContext.MxCsr);
+
+ /* Legacy floating point registers are truncated to 10 bytes */
+ ok_eq_hex64(CapturedContext.Legacy[0].Low, OriginalContext.Legacy[0].Low);
+ ok_eq_hex64(CapturedContext.Legacy[0].High, OriginalContext.Legacy[0].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[1].Low, OriginalContext.Legacy[1].Low);
+ ok_eq_hex64(CapturedContext.Legacy[1].High, OriginalContext.Legacy[1].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[2].Low, OriginalContext.Legacy[2].Low);
+ ok_eq_hex64(CapturedContext.Legacy[2].High, OriginalContext.Legacy[2].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[3].Low, OriginalContext.Legacy[3].Low);
+ ok_eq_hex64(CapturedContext.Legacy[3].High, OriginalContext.Legacy[3].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[4].Low, OriginalContext.Legacy[4].Low);
+ ok_eq_hex64(CapturedContext.Legacy[4].High, OriginalContext.Legacy[4].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[5].Low, OriginalContext.Legacy[5].Low);
+ ok_eq_hex64(CapturedContext.Legacy[5].High, OriginalContext.Legacy[5].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[6].Low, OriginalContext.Legacy[6].Low);
+ ok_eq_hex64(CapturedContext.Legacy[6].High, OriginalContext.Legacy[6].High & 0xFF);
+ ok_eq_hex64(CapturedContext.Legacy[7].Low, OriginalContext.Legacy[7].Low);
+ ok_eq_hex64(CapturedContext.Legacy[7].High, OriginalContext.Legacy[7].High & 0xFF);
+
+ /* We don't pass in segments, but expect the default values */
+ ok_eq_hex(CapturedContext.SegCs, 0x33);
+ ok_eq_hex(CapturedContext.SegDs, 0x2b);
+ ok_eq_hex(CapturedContext.SegEs, 0x2b);
+ ok_eq_hex(CapturedContext.SegFs, 0x53);
+ ok_eq_hex(CapturedContext.SegGs, 0x2b);
+ ok_eq_hex(CapturedContext.SegSs, 0x2b);
+
+ /* For Rsp and Rip we get the expected value back from the asm wrapper */
+ ok_eq_hex64(CapturedContext.Rsp, InOutContext.Rsp);
+ ok_eq_hex64(CapturedContext.Rip, InOutContext.Rip);
+
+ /* Check that these registers are not modified by RtlCaptureContext */
+ ok_eq_xmm(InOutContext.Xmm0, OriginalContext.Xmm0);
+ ok_eq_xmm(InOutContext.Xmm1, OriginalContext.Xmm1);
+ ok_eq_xmm(InOutContext.Xmm2, OriginalContext.Xmm2);
+ ok_eq_xmm(InOutContext.Xmm3, OriginalContext.Xmm3);
+ ok_eq_xmm(InOutContext.Xmm4, OriginalContext.Xmm4);
+ ok_eq_xmm(InOutContext.Xmm5, OriginalContext.Xmm5);
+ ok_eq_xmm(InOutContext.Xmm6, OriginalContext.Xmm6);
+ ok_eq_xmm(InOutContext.Xmm7, OriginalContext.Xmm7);
+ ok_eq_xmm(InOutContext.Xmm8, OriginalContext.Xmm8);
+ ok_eq_xmm(InOutContext.Xmm9, OriginalContext.Xmm9);
+ ok_eq_xmm(InOutContext.Xmm10, OriginalContext.Xmm10);
+ ok_eq_xmm(InOutContext.Xmm11, OriginalContext.Xmm11);
+ ok_eq_xmm(InOutContext.Xmm12, OriginalContext.Xmm12);
+ ok_eq_xmm(InOutContext.Xmm13, OriginalContext.Xmm13);
+ ok_eq_xmm(InOutContext.Xmm14, OriginalContext.Xmm14);
+ ok_eq_xmm(InOutContext.Xmm15, OriginalContext.Xmm15);
+ ok_eq_hex64(InOutContext.Rdx, OriginalContext.Rdx);
+ ok_eq_hex64(InOutContext.Rbx, OriginalContext.Rbx);
+ ok_eq_hex64(InOutContext.Rbp, OriginalContext.Rbp);
+ ok_eq_hex64(InOutContext.Rsi, OriginalContext.Rsi);
+ ok_eq_hex64(InOutContext.Rdi, OriginalContext.Rdi);
+ ok_eq_hex64(InOutContext.R8, OriginalContext.R8);
+ ok_eq_hex64(InOutContext.R9, OriginalContext.R9);
+ ok_eq_hex64(InOutContext.R10, OriginalContext.R10);
+ ok_eq_hex64(InOutContext.R11, OriginalContext.R11);
+ ok_eq_hex64(InOutContext.R12, OriginalContext.R12);
+ ok_eq_hex64(InOutContext.R13, OriginalContext.R13);
+ ok_eq_hex64(InOutContext.R14, OriginalContext.R14);
+ ok_eq_hex64(InOutContext.R15, OriginalContext.R15);
+
+ /* Eflags is changed (parity is flaky) */
+ ok_eq_hex64(InOutContext.EFlags & ~0x04, OriginalContext.EFlags & 0x782);
+
+ /* MxCsr is the one we passed in in OriginalContext.MxCsr */
+ ok_eq_hex(InOutContext.MxCsr, OriginalContext.MxCsr);
+ ok_eq_hex(InOutContext.FltSave.MxCsr, OriginalContext.MxCsr);
+
+ /* Rcx still points to the captured context */
+ ok_eq_hex64(InOutContext.Rcx, (ULONG64)&CapturedContext);
+
+ /* Rax contains eflags */
+ ok_eq_hex64(InOutContext.Rax, CapturedContext.EFlags);
+
+ /* Second run with minimal EFLags/MxCsr */
+ OriginalContext.EFlags = 0x200;
+ OriginalContext.MxCsr = 0x0000;
+ InOutContext = OriginalContext;
+ RtlFillMemory(&CapturedContext, sizeof(CapturedContext), 0xCC);
+ RtlCaptureContextWrapper(&InOutContext, &CapturedContext);
+
+ /* Captured Eflags has reserved flag set (which is always 1) */
+ ok_eq_hex64(CapturedContext.EFlags, OriginalContext.EFlags | 2);
+
+ /* Parity flag is flaky */
+ ok_eq_hex64(InOutContext.EFlags & ~4, CapturedContext.EFlags);
+
+ /* MxCsr is captured/returned as passed in */
+ ok_eq_hex64(InOutContext.MxCsr, CapturedContext.MxCsr);
+ ok_eq_hex64(CapturedContext.MxCsr, OriginalContext.MxCsr);
+}