mirror of
https://github.com/reactos/reactos.git
synced 2024-11-03 13:25:57 +00:00
489 lines
15 KiB
C
489 lines
15 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ke/i386/usercall.c
|
|
* PURPOSE: User-mode Callout Mechanisms (APC and Win32K Callbacks)
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Timo Kreuzer (timo.kreuzer@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
extern PGDI_BATCHFLUSH_ROUTINE KeGdiFlushUserBatch;
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
/*++
|
|
* @name KiInitializeUserApc
|
|
*
|
|
* Prepares the Context for a User-Mode APC called through NTDLL.DLL
|
|
*
|
|
* @param Reserved
|
|
* Pointer to the Exception Frame on non-i386 builds.
|
|
*
|
|
* @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.
|
|
*
|
|
* @return None.
|
|
*
|
|
* @remarks None.
|
|
*
|
|
*--*/
|
|
VOID
|
|
NTAPI
|
|
KiInitializeUserApc(IN PKEXCEPTION_FRAME ExceptionFrame,
|
|
IN PKTRAP_FRAME TrapFrame,
|
|
IN PKNORMAL_ROUTINE NormalRoutine,
|
|
IN PVOID NormalContext,
|
|
IN PVOID SystemArgument1,
|
|
IN PVOID SystemArgument2)
|
|
{
|
|
CONTEXT Context;
|
|
ULONG_PTR Stack, AlignedEsp;
|
|
ULONG ContextLength;
|
|
EXCEPTION_RECORD SehExceptRecord;
|
|
|
|
/* Don't deliver APCs in V86 mode */
|
|
if (TrapFrame->EFlags & EFLAGS_V86_MASK) return;
|
|
|
|
/* Save the full context */
|
|
Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
|
|
KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
|
|
|
|
/* Protect with SEH */
|
|
_SEH2_TRY
|
|
{
|
|
/* Sanity check */
|
|
ASSERT(KiUserTrap(TrapFrame));
|
|
|
|
/* Get the aligned size */
|
|
AlignedEsp = Context.Esp & ~3;
|
|
ContextLength = CONTEXT_ALIGNED_SIZE + (4 * sizeof(ULONG_PTR));
|
|
Stack = ((AlignedEsp - 8) & ~3) - ContextLength;
|
|
|
|
/* Probe the stack */
|
|
ProbeForWrite((PVOID)Stack, AlignedEsp - Stack, 1);
|
|
ASSERT(!(Stack & 3));
|
|
|
|
/* Copy data into it */
|
|
RtlCopyMemory((PVOID)(Stack + (4 * sizeof(ULONG_PTR))),
|
|
&Context,
|
|
sizeof(CONTEXT));
|
|
|
|
/* Run at APC dispatcher */
|
|
TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
|
|
TrapFrame->HardwareEsp = Stack;
|
|
|
|
/* Setup Ring 3 state */
|
|
TrapFrame->SegCs = Ke386SanitizeSeg(KGDT_R3_CODE, UserMode);
|
|
TrapFrame->HardwareSegSs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
|
|
TrapFrame->SegDs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
|
|
TrapFrame->SegEs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
|
|
TrapFrame->SegFs = Ke386SanitizeSeg(KGDT_R3_TEB, UserMode);
|
|
TrapFrame->SegGs = 0;
|
|
TrapFrame->ErrCode = 0;
|
|
|
|
/* Sanitize EFLAGS */
|
|
TrapFrame->EFlags = Ke386SanitizeFlags(Context.EFlags, UserMode);
|
|
|
|
/* Check if thread has IOPL and force it enabled if so */
|
|
if (KeGetCurrentThread()->Iopl) TrapFrame->EFlags |= EFLAGS_IOPL;
|
|
|
|
/* Setup the stack */
|
|
*(PULONG_PTR)(Stack + 0 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalRoutine;
|
|
*(PULONG_PTR)(Stack + 1 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalContext;
|
|
*(PULONG_PTR)(Stack + 2 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument1;
|
|
*(PULONG_PTR)(Stack + 3 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument2;
|
|
}
|
|
_SEH2_EXCEPT((RtlCopyMemory(&SehExceptRecord, _SEH2_GetExceptionInformation()->ExceptionRecord, sizeof(EXCEPTION_RECORD)), EXCEPTION_EXECUTE_HANDLER))
|
|
{
|
|
/* Dispatch the exception */
|
|
SehExceptRecord.ExceptionAddress = (PVOID)TrapFrame->Eip;
|
|
KiDispatchException(&SehExceptRecord,
|
|
ExceptionFrame,
|
|
TrapFrame,
|
|
UserMode,
|
|
TRUE);
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
KeUserModeCallback(IN ULONG RoutineIndex,
|
|
IN PVOID Argument,
|
|
IN ULONG ArgumentLength,
|
|
OUT PVOID *Result,
|
|
OUT PULONG ResultLength)
|
|
{
|
|
ULONG_PTR NewStack, OldStack;
|
|
PULONG UserEsp;
|
|
NTSTATUS CallbackStatus;
|
|
PEXCEPTION_REGISTRATION_RECORD ExceptionList;
|
|
PTEB Teb;
|
|
ULONG GdiBatchCount = 0;
|
|
ASSERT(KeGetCurrentThread()->ApcState.KernelApcInProgress == FALSE);
|
|
ASSERT(KeGetPreviousMode() == UserMode);
|
|
|
|
/* Get the current user-mode stack */
|
|
UserEsp = KiGetUserModeStackAddress();
|
|
OldStack = *UserEsp;
|
|
|
|
/* Enter a SEH Block */
|
|
_SEH2_TRY
|
|
{
|
|
/* Calculate and align the stack size */
|
|
NewStack = (OldStack - ArgumentLength) & ~3;
|
|
|
|
/* Make sure it's writable */
|
|
ProbeForWrite((PVOID)(NewStack - 6 * sizeof(ULONG_PTR)),
|
|
ArgumentLength + 6 * sizeof(ULONG_PTR),
|
|
sizeof(CHAR));
|
|
|
|
/* Copy the buffer into the stack */
|
|
RtlCopyMemory((PVOID)NewStack, Argument, ArgumentLength);
|
|
|
|
/* Write the arguments */
|
|
NewStack -= 24;
|
|
*(PULONG)NewStack = 0;
|
|
*(PULONG)(NewStack + 4) = RoutineIndex;
|
|
*(PULONG)(NewStack + 8) = (NewStack + 24);
|
|
*(PULONG)(NewStack + 12) = ArgumentLength;
|
|
|
|
/* Save the exception list */
|
|
Teb = KeGetCurrentThread()->Teb;
|
|
ExceptionList = Teb->NtTib.ExceptionList;
|
|
|
|
/* Jump to user mode */
|
|
*UserEsp = NewStack;
|
|
CallbackStatus = KiCallUserMode(Result, ResultLength);
|
|
if (CallbackStatus != STATUS_CALLBACK_POP_STACK)
|
|
{
|
|
/* Only restore the exception list if we didn't crash in ring 3 */
|
|
Teb->NtTib.ExceptionList = ExceptionList;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise, pop the stack */
|
|
OldStack = *UserEsp;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
*UserEsp -= 256;
|
|
KeGdiFlushUserBatch();
|
|
}
|
|
|
|
/* Restore stack and return */
|
|
*UserEsp = OldStack;
|
|
return CallbackStatus;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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(PKCALLOUT_FRAME CalloutFrame)
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PKTRAP_FRAME TrapFrame, CallbackTrapFrame;
|
|
PFX_SAVE_AREA FxSaveArea, OldFxSaveArea;
|
|
PKPCR Pcr;
|
|
PKTSS Tss;
|
|
ULONG_PTR InitialStack;
|
|
NTSTATUS Status;
|
|
|
|
/* Get the current thread */
|
|
CurrentThread = KeGetCurrentThread();
|
|
|
|
#if DBG
|
|
/* Check if we are at pasive level */
|
|
if (KeGetCurrentIrql() != PASSIVE_LEVEL)
|
|
{
|
|
/* We're not, bugcheck */
|
|
KeBugCheckEx(IRQL_GT_ZERO_AT_SYSTEM_SERVICE,
|
|
0,
|
|
KeGetCurrentIrql(),
|
|
0,
|
|
0);
|
|
}
|
|
|
|
/* Check if we are attached or APCs are disabled */
|
|
if ((CurrentThread->ApcStateIndex != OriginalApcEnvironment) ||
|
|
(CurrentThread->CombinedApcDisable > 0))
|
|
{
|
|
KeBugCheckEx(APC_INDEX_MISMATCH,
|
|
0,
|
|
CurrentThread->ApcStateIndex,
|
|
CurrentThread->CombinedApcDisable,
|
|
0);
|
|
}
|
|
#endif
|
|
|
|
/* Align stack on a 16-byte boundary */
|
|
InitialStack = ALIGN_DOWN_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;
|
|
|
|
/* Set destination and origin NPX Areas */
|
|
OldFxSaveArea = (PVOID)(CalloutFrame->InitialStack - sizeof(FX_SAVE_AREA));
|
|
FxSaveArea = (PVOID)(InitialStack - sizeof(FX_SAVE_AREA));
|
|
|
|
/* Disable interrupts so we can fill the NPX State */
|
|
_disable();
|
|
|
|
/* Now copy the NPX State */
|
|
FxSaveArea->U.FnArea.ControlWord = OldFxSaveArea->U.FnArea.ControlWord;
|
|
FxSaveArea->U.FnArea.StatusWord = OldFxSaveArea->U.FnArea.StatusWord;
|
|
FxSaveArea->U.FnArea.TagWord = OldFxSaveArea->U.FnArea.TagWord;
|
|
FxSaveArea->U.FnArea.DataSelector = OldFxSaveArea->U.FnArea.DataSelector;
|
|
FxSaveArea->Cr0NpxState = OldFxSaveArea->Cr0NpxState;
|
|
|
|
/* Set the stack address */
|
|
CurrentThread->InitialStack = (PVOID)InitialStack;
|
|
|
|
/* Locate the trap frame on the callback stack */
|
|
CallbackTrapFrame = (PVOID)((ULONG_PTR)FxSaveArea - sizeof(KTRAP_FRAME));
|
|
|
|
/* Copy the trap frame to the new location */
|
|
*CallbackTrapFrame = *TrapFrame;
|
|
|
|
/* Get PCR */
|
|
Pcr = KeGetPcr();
|
|
|
|
/* Update the exception list */
|
|
CallbackTrapFrame->ExceptionList = Pcr->NtTib.ExceptionList;
|
|
|
|
/* Get TSS */
|
|
Tss = Pcr->TSS;
|
|
|
|
/* Check for V86 mode */
|
|
if (CallbackTrapFrame->EFlags & EFLAGS_V86_MASK)
|
|
{
|
|
/* Set new stack address in TSS (full trap frame) */
|
|
Tss->Esp0 = (ULONG_PTR)(CallbackTrapFrame + 1);
|
|
}
|
|
else
|
|
{
|
|
/* Set new stack address in TSS (non-V86 trap frame) */
|
|
Tss->Esp0 = (ULONG_PTR)&CallbackTrapFrame->V86Es;
|
|
}
|
|
|
|
/* Set user-mode dispatcher address as EIP */
|
|
CallbackTrapFrame->Eip = (ULONG_PTR)KeUserCallbackDispatcher;
|
|
|
|
/* Bring interrupts back */
|
|
_enable();
|
|
|
|
/* Exit to user-mode */
|
|
KiServiceExit(CallbackTrapFrame, 0);
|
|
}
|
|
|
|
/*++
|
|
* @name NtCallbackReturn
|
|
*
|
|
* The NtCallbackReturn routine returns to kernel mode after a user-mode
|
|
* callback was done through KeUserModeCallback. It uses the callback frame
|
|
* which was setup in order to return the information, restores the stack,
|
|
* and resumes execution where it was left off.
|
|
*
|
|
* @param Result
|
|
* Pointer to a caller-allocated buffer where the return data
|
|
* from the user-mode function is located.
|
|
*
|
|
* @param ResultLength
|
|
* Size of the Output Buffer described above.
|
|
*
|
|
* @param CallbackStatus
|
|
* Status code of the callback operation.
|
|
*
|
|
* @return Status code of the callback operation.
|
|
*
|
|
* @remark This call MUST be paired with KeUserModeCallback.
|
|
*
|
|
*--*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtCallbackReturn(
|
|
_In_ PVOID Result,
|
|
_In_ ULONG ResultLength,
|
|
_In_ NTSTATUS CallbackStatus)
|
|
{
|
|
PKTHREAD CurrentThread;
|
|
PKCALLOUT_FRAME CalloutFrame;
|
|
PKTRAP_FRAME CallbackTrapFrame, TrapFrame;
|
|
PFX_SAVE_AREA FxSaveArea, CbFxSaveArea;
|
|
ULONG Size;
|
|
PKPCR Pcr;
|
|
PKTSS Tss;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Get the trap frame */
|
|
CallbackTrapFrame = CurrentThread->TrapFrame;
|
|
|
|
/* Restore the exception list */
|
|
Pcr = KeGetPcr();
|
|
Pcr->NtTib.ExceptionList = CallbackTrapFrame->ExceptionList;
|
|
|
|
/* Store the results in the callback stack */
|
|
*((PVOID*)CalloutFrame->Result) = Result;
|
|
*((ULONG*)CalloutFrame->ResultLength) = ResultLength;
|
|
|
|
/* Disable interrupts for NPX save and stack switch */
|
|
_disable();
|
|
|
|
/* Set desination and origin NPX Frames */
|
|
CbFxSaveArea = (PVOID)((ULONG)CurrentThread->InitialStack - sizeof(FX_SAVE_AREA));
|
|
FxSaveArea = (PVOID)(CalloutFrame->InitialStack - sizeof(FX_SAVE_AREA));
|
|
|
|
/* Now copy back NPX State */
|
|
FxSaveArea->U.FnArea.ControlWord = CbFxSaveArea->U.FnArea.ControlWord;
|
|
FxSaveArea->U.FnArea.StatusWord = CbFxSaveArea->U.FnArea.StatusWord;
|
|
FxSaveArea->U.FnArea.TagWord = CbFxSaveArea->U.FnArea.TagWord;
|
|
FxSaveArea->U.FnArea.DataSelector = CbFxSaveArea->U.FnArea.DataSelector;
|
|
FxSaveArea->Cr0NpxState = CbFxSaveArea->Cr0NpxState;
|
|
|
|
/* Get the previous trap frame */
|
|
TrapFrame = (PKTRAP_FRAME)CalloutFrame->TrapFrame;
|
|
|
|
/* Check if we failed in user mode */
|
|
if (CallbackStatus == STATUS_CALLBACK_POP_STACK)
|
|
{
|
|
/* Check if we came from v86 mode */
|
|
if (CallbackTrapFrame->EFlags & EFLAGS_V86_MASK)
|
|
{
|
|
Size = sizeof(KTRAP_FRAME) - FIELD_OFFSET(KTRAP_FRAME, SegFs);
|
|
}
|
|
else
|
|
{
|
|
Size = FIELD_OFFSET(KTRAP_FRAME, V86Es) - FIELD_OFFSET(KTRAP_FRAME, SegFs);
|
|
}
|
|
|
|
/* Copy back part of the trap frame */
|
|
RtlCopyMemory(&TrapFrame->SegFs, &CallbackTrapFrame->SegFs, Size);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* Get TSS */
|
|
Tss = Pcr->TSS;
|
|
|
|
/* Check for V86 mode */
|
|
if (TrapFrame->EFlags & EFLAGS_V86_MASK)
|
|
{
|
|
/* Set new stack address in TSS (full trap frame) */
|
|
Tss->Esp0 = (ULONG_PTR)(TrapFrame + 1);
|
|
}
|
|
else
|
|
{
|
|
/* Set new stack address in TSS (non-V86 trap frame) */
|
|
Tss->Esp0 = (ULONG_PTR)&TrapFrame->V86Es;
|
|
}
|
|
|
|
/* 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->Edi, CallbackStatus);
|
|
}
|
|
|
|
|
|
/* EOF */
|