mirror of
https://github.com/reactos/reactos.git
synced 2024-08-23 11:52:51 +00:00
402 lines
13 KiB
C
402 lines
13 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - 2.0 + (https ://spdx.org/licenses/GPL-2.0+)
|
|
* PURPOSE: AMD64 User-mode Callout Mechanisms (APC and Win32K Callbacks)
|
|
* COPYRIGHT: Timo Kreuzer(timo.kreuzer@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/*!
|
|
* \name KiInitializeUserApc
|
|
*
|
|
* \brief
|
|
* Prepares the current trap frame (which must have come from user mode)
|
|
* with the ntdll.KiUserApcDispatcher entrypoint, copying a CONTEXT
|
|
* record with the context from the old trap frame to the threads user
|
|
* mode stack.
|
|
*
|
|
* \param ExceptionFrame - Pointer to the Exception Frame
|
|
*
|
|
* \param TrapFrame Pointer to the Trap Frame.
|
|
*
|
|
* \param NormalRoutine - Pointer to the NormalRoutine to call.
|
|
*
|
|
* \param NormalContext - Pointer to the context to send to the Normal Routine.
|
|
*
|
|
* \param SystemArgument[1-2]
|
|
* Pointer to a set of two parameters that contain untyped data.
|
|
*
|
|
* \remarks
|
|
* This function is called from KiDeliverApc, when the trap frame came
|
|
* from user mode. This happens before a systemcall or interrupt exits back
|
|
* to usermode or when a thread is started from PspUserThreadstartup.
|
|
* The trap exit code will then leave to KiUserApcDispatcher which in turn
|
|
* calls the NormalRoutine, passing NormalContext, SystemArgument1 and
|
|
* SystemArgument2 as parameters. When that function returns, it calls
|
|
* NtContinue to return back to the kernel, where the old context that was
|
|
* saved on the usermode stack is restored and execution is transferred
|
|
* back to usermode, where the original trap originated from.
|
|
*
|
|
*--*/
|
|
VOID
|
|
NTAPI
|
|
KiInitializeUserApc(
|
|
_In_ PKEXCEPTION_FRAME ExceptionFrame,
|
|
_Inout_ PKTRAP_FRAME TrapFrame,
|
|
_In_ PKNORMAL_ROUTINE NormalRoutine,
|
|
_In_ PVOID NormalContext,
|
|
_In_ PVOID SystemArgument1,
|
|
_In_ PVOID SystemArgument2)
|
|
{
|
|
PCONTEXT Context;
|
|
EXCEPTION_RECORD ExceptionRecord;
|
|
|
|
/* Sanity check, that the trap frame is from user mode */
|
|
ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode);
|
|
|
|
/* Align the user tack to 16 bytes and allocate space for a CONTEXT structure */
|
|
Context = (PCONTEXT)ALIGN_DOWN_POINTER_BY(TrapFrame->Rsp, 16) - 1;
|
|
|
|
/* Protect with SEH */
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe the context */
|
|
ProbeForWrite(Context, sizeof(CONTEXT), 16);
|
|
|
|
/* Convert the current trap frame to a context */
|
|
Context->ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
|
|
KeTrapFrameToContext(TrapFrame, ExceptionFrame, Context);
|
|
|
|
/* Set parameters for KiUserApcDispatcher */
|
|
Context->P1Home = (ULONG64)NormalContext;
|
|
Context->P2Home = (ULONG64)SystemArgument1;
|
|
Context->P3Home = (ULONG64)SystemArgument2;
|
|
Context->P4Home = (ULONG64)NormalRoutine;
|
|
}
|
|
_SEH2_EXCEPT(ExceptionRecord = *_SEH2_GetExceptionInformation()->ExceptionRecord, EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Dispatch the exception */
|
|
ExceptionRecord.ExceptionAddress = (PVOID)TrapFrame->Rip;
|
|
KiDispatchException(&ExceptionRecord,
|
|
ExceptionFrame,
|
|
TrapFrame,
|
|
UserMode,
|
|
TRUE);
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Set the stack pointer to the context record */
|
|
TrapFrame->Rsp = (ULONG64)Context;
|
|
|
|
/* We jump to KiUserApcDispatcher in ntdll */
|
|
TrapFrame->Rip = (ULONG64)KeUserApcDispatcher;
|
|
|
|
/* Setup Ring 3 segments */
|
|
TrapFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;
|
|
TrapFrame->SegFs = KGDT64_R3_CMTEB | RPL_MASK;
|
|
TrapFrame->SegGs = KGDT64_R3_DATA | RPL_MASK;
|
|
TrapFrame->SegSs = KGDT64_R3_DATA | RPL_MASK;
|
|
|
|
/* Sanitize EFLAGS, enable interrupts */
|
|
TrapFrame->EFlags &= EFLAGS_USER_SANITIZE;
|
|
TrapFrame->EFlags |= EFLAGS_INTERRUPT_MASK;
|
|
}
|
|
|
|
/*
|
|
* Stack layout for KiUserModeCallout:
|
|
* ----------------------------------
|
|
* KCALLOUT_FRAME.ResultLength <= 2nd Parameter to KiCallUserMode
|
|
* KCALLOUT_FRAME.Result <= 1st Parameter to KiCallUserMode
|
|
* KCALLOUT_FRAME.ReturnAddress <= Return address of KiCallUserMode
|
|
* KCALLOUT_FRAME.Ebp \
|
|
* KCALLOUT_FRAME.Ebx | = non-volatile registers, pushed
|
|
* KCALLOUT_FRAME.Esi | by KiCallUserMode
|
|
* KCALLOUT_FRAME.Edi /
|
|
* KCALLOUT_FRAME.CallbackStack
|
|
* KCALLOUT_FRAME.TrapFrame
|
|
* KCALLOUT_FRAME.InitialStack <= CalloutFrame points here
|
|
* ----------------------------------
|
|
* ~~ optional alignment ~~
|
|
* ----------------------------------
|
|
* FX_SAVE_AREA
|
|
* ----------------------------------
|
|
* KTRAP_FRAME
|
|
* ----------------------------------
|
|
* ~~ begin of stack frame for KiUserModeCallout ~~
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
FASTCALL
|
|
KiUserModeCallout(
|
|
_Out_ PKCALLOUT_FRAME CalloutFrame)
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PKTRAP_FRAME TrapFrame;
|
|
KTRAP_FRAME CallbackTrapFrame;
|
|
PKIPCR Pcr;
|
|
ULONG_PTR InitialStack;
|
|
NTSTATUS Status;
|
|
|
|
/* Get the current thread */
|
|
CurrentThread = KeGetCurrentThread();
|
|
|
|
/* Check if we are at pasive level */
|
|
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
|
|
|
|
/* Check if we are attached or APCs are disabled */
|
|
ASSERT((CurrentThread->ApcStateIndex == OriginalApcEnvironment) &&
|
|
(CurrentThread->CombinedApcDisable == 0));
|
|
|
|
/* Align stack on a 16-byte boundary */
|
|
InitialStack = (ULONG_PTR)ALIGN_DOWN_POINTER_BY(CalloutFrame, 16);
|
|
|
|
/* Check if we have enough space on the stack */
|
|
if ((InitialStack - KERNEL_STACK_SIZE) < CurrentThread->StackLimit)
|
|
{
|
|
/* We don't, we'll have to grow our stack */
|
|
Status = MmGrowKernelStack((PVOID)InitialStack);
|
|
|
|
/* Quit if we failed */
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
}
|
|
|
|
/* Save the current callback stack and initial stack */
|
|
CalloutFrame->CallbackStack = (ULONG_PTR)CurrentThread->CallbackStack;
|
|
CalloutFrame->InitialStack = (ULONG_PTR)CurrentThread->InitialStack;
|
|
|
|
/* Get and save the trap frame */
|
|
TrapFrame = CurrentThread->TrapFrame;
|
|
CalloutFrame->TrapFrame = (ULONG_PTR)TrapFrame;
|
|
|
|
/* Set the new callback stack */
|
|
CurrentThread->CallbackStack = CalloutFrame;
|
|
|
|
/* Disable interrupts so we can fill the NPX State */
|
|
_disable();
|
|
|
|
/* Set the stack address */
|
|
CurrentThread->InitialStack = (PVOID)InitialStack;
|
|
|
|
/* Copy the trap frame to the new location */
|
|
CallbackTrapFrame = *TrapFrame;
|
|
|
|
/* Get PCR */
|
|
Pcr = (PKIPCR)KeGetPcr();
|
|
|
|
/* Set user-mode dispatcher address as EIP */
|
|
Pcr->TssBase->Rsp0 = InitialStack;
|
|
Pcr->Prcb.RspBase = InitialStack;
|
|
CallbackTrapFrame.Rip = (ULONG_PTR)KeUserCallbackDispatcher;
|
|
|
|
/* Bring interrupts back */
|
|
_enable();
|
|
|
|
/* Exit to user-mode */
|
|
KiServiceExit(&CallbackTrapFrame, 0);
|
|
}
|
|
|
|
VOID
|
|
KiSetupUserCalloutFrame(
|
|
_Out_ PUCALLOUT_FRAME UserCalloutFrame,
|
|
_In_ PKTRAP_FRAME TrapFrame,
|
|
_In_ ULONG ApiNumber,
|
|
_In_ PVOID Buffer,
|
|
_In_ ULONG BufferLength)
|
|
{
|
|
#ifdef _M_IX86
|
|
CalloutFrame->Reserved = 0;
|
|
CalloutFrame->ApiNumber = ApiNumber;
|
|
CalloutFrame->Buffer = (ULONG_PTR)NewStack;
|
|
CalloutFrame->Length = ArgumentLength;
|
|
#elif defined(_M_AMD64)
|
|
UserCalloutFrame->Buffer = (PVOID)(UserCalloutFrame + 1);
|
|
UserCalloutFrame->Length = BufferLength;
|
|
UserCalloutFrame->ApiNumber = ApiNumber;
|
|
UserCalloutFrame->MachineFrame.Rip = TrapFrame->Rip;
|
|
UserCalloutFrame->MachineFrame.Rsp = TrapFrame->Rsp;
|
|
#else
|
|
#error "KiSetupUserCalloutFrame not implemented!"
|
|
#endif
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
KeUserModeCallback(
|
|
IN ULONG RoutineIndex,
|
|
IN PVOID Argument,
|
|
IN ULONG ArgumentLength,
|
|
OUT PVOID *Result,
|
|
OUT PULONG ResultLength)
|
|
{
|
|
ULONG_PTR OldStack;
|
|
PUCHAR UserArguments;
|
|
PUCALLOUT_FRAME CalloutFrame;
|
|
PULONG_PTR UserStackPointer;
|
|
NTSTATUS CallbackStatus;
|
|
#ifdef _M_IX86
|
|
PEXCEPTION_REGISTRATION_RECORD ExceptionList;
|
|
#endif // _M_IX86
|
|
PTEB Teb;
|
|
ULONG GdiBatchCount = 0;
|
|
ASSERT(KeGetCurrentThread()->ApcState.KernelApcInProgress == FALSE);
|
|
ASSERT(KeGetPreviousMode() == UserMode);
|
|
|
|
/* Get the current user-mode stack */
|
|
UserStackPointer = KiGetUserModeStackAddress();
|
|
OldStack = *UserStackPointer;
|
|
|
|
/* Enter a SEH Block */
|
|
_SEH2_TRY
|
|
{
|
|
/* Calculate and align the stack. This is unaligned by 8 bytes, since the following
|
|
UCALLOUT_FRAME compensates for that and on entry we already have a full stack
|
|
frame with home space for the next call, i.e. we are already inside the function
|
|
body and the stack needs to be 16 byte aligned. */
|
|
UserArguments = (PUCHAR)ALIGN_DOWN_POINTER_BY(OldStack - ArgumentLength, 16) - 8;
|
|
|
|
/* The callout frame is below the arguments */
|
|
CalloutFrame = ((PUCALLOUT_FRAME)UserArguments) - 1;
|
|
|
|
/* Make sure it's all writable */
|
|
ProbeForWrite(CalloutFrame,
|
|
sizeof(PUCALLOUT_FRAME) + ArgumentLength,
|
|
sizeof(PVOID));
|
|
|
|
/* Copy the buffer into the stack */
|
|
RtlCopyMemory(UserArguments, Argument, ArgumentLength);
|
|
|
|
/* Write the arguments */
|
|
KiSetupUserCalloutFrame(CalloutFrame,
|
|
KeGetCurrentThread()->TrapFrame,
|
|
RoutineIndex,
|
|
UserArguments,
|
|
ArgumentLength);
|
|
|
|
/* Save the exception list */
|
|
Teb = KeGetCurrentThread()->Teb;
|
|
#ifdef _M_IX86
|
|
ExceptionList = Teb->NtTib.ExceptionList;
|
|
#endif // _M_IX86
|
|
|
|
/* Jump to user mode */
|
|
*UserStackPointer = (ULONG_PTR)CalloutFrame;
|
|
CallbackStatus = KiCallUserMode(Result, ResultLength);
|
|
if (CallbackStatus != STATUS_CALLBACK_POP_STACK)
|
|
{
|
|
#ifdef _M_IX86
|
|
/* Only restore the exception list if we didn't crash in ring 3 */
|
|
Teb->NtTib.ExceptionList = ExceptionList;
|
|
#endif // _M_IX86
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, pop the stack */
|
|
OldStack = *UserStackPointer;
|
|
}
|
|
|
|
/* Read the GDI Batch count */
|
|
GdiBatchCount = Teb->GdiBatchCount;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Get the SEH exception */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Check if we have GDI Batch operations */
|
|
if (GdiBatchCount)
|
|
{
|
|
*UserStackPointer -= 256;
|
|
KeGdiFlushUserBatch();
|
|
}
|
|
|
|
/* Restore stack and return */
|
|
*UserStackPointer = OldStack;
|
|
#ifdef _M_AMD64 // could probably move the update to TrapFrame->Rsp from the C handler to the asm code
|
|
__writegsqword(FIELD_OFFSET(KIPCR, UserRsp), OldStack);
|
|
#endif
|
|
return CallbackStatus;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtCallbackReturn(
|
|
_In_ PVOID Result,
|
|
_In_ ULONG ResultLength,
|
|
_In_ NTSTATUS CallbackStatus)
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PKCALLOUT_FRAME CalloutFrame;
|
|
PKTRAP_FRAME CallbackTrapFrame, TrapFrame;
|
|
PKIPCR Pcr;
|
|
|
|
/* Get the current thread and make sure we have a callback stack */
|
|
CurrentThread = KeGetCurrentThread();
|
|
CalloutFrame = CurrentThread->CallbackStack;
|
|
if (CalloutFrame == NULL)
|
|
{
|
|
return STATUS_NO_CALLBACK_ACTIVE;
|
|
}
|
|
|
|
/* Store the results in the callback stack */
|
|
*((PVOID*)CalloutFrame->OutputBuffer) = Result;
|
|
*((ULONG*)CalloutFrame->OutputLength) = ResultLength;
|
|
|
|
/* Get the trap frame */
|
|
CallbackTrapFrame = CurrentThread->TrapFrame;
|
|
|
|
/* Disable interrupts for NPX save and stack switch */
|
|
_disable();
|
|
|
|
/* Restore the exception list */
|
|
Pcr = (PKIPCR)KeGetPcr();
|
|
|
|
/* Get the previous trap frame */
|
|
TrapFrame = (PKTRAP_FRAME)CalloutFrame->TrapFrame;
|
|
|
|
/* Check if we failed in user mode */
|
|
if (CallbackStatus == STATUS_CALLBACK_POP_STACK)
|
|
{
|
|
*TrapFrame = *CallbackTrapFrame;
|
|
}
|
|
|
|
/* Clear DR7 */
|
|
TrapFrame->Dr7 = 0;
|
|
|
|
/* Check if debugging was active */
|
|
if (CurrentThread->Header.DebugActive & 0xFF)
|
|
{
|
|
/* Copy debug registers data from it */
|
|
TrapFrame->Dr0 = CallbackTrapFrame->Dr0;
|
|
TrapFrame->Dr1 = CallbackTrapFrame->Dr1;
|
|
TrapFrame->Dr2 = CallbackTrapFrame->Dr2;
|
|
TrapFrame->Dr3 = CallbackTrapFrame->Dr3;
|
|
TrapFrame->Dr6 = CallbackTrapFrame->Dr6;
|
|
TrapFrame->Dr7 = CallbackTrapFrame->Dr7;
|
|
}
|
|
|
|
/* Switch the stack back to the previous value */
|
|
Pcr->TssBase->Rsp0 = CalloutFrame->InitialStack;
|
|
Pcr->Prcb.RspBase = CalloutFrame->InitialStack;
|
|
|
|
/* Get the initial stack and restore it */
|
|
CurrentThread->InitialStack = (PVOID)CalloutFrame->InitialStack;
|
|
|
|
/* Restore the trap frame and the previous callback stack */
|
|
CurrentThread->TrapFrame = TrapFrame;
|
|
CurrentThread->CallbackStack = (PVOID)CalloutFrame->CallbackStack;
|
|
|
|
/* Bring interrupts back */
|
|
_enable();
|
|
|
|
/* Now switch back to the old stack */
|
|
KiCallbackReturn(CalloutFrame, CallbackStatus);
|
|
}
|
|
|