654 lines
17 KiB
C
654 lines
17 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
psctx.c
|
||
|
||
Abstract:
|
||
|
||
This procedure implements Get/Set Context Thread
|
||
|
||
Author:
|
||
|
||
Mark Lucovsky (markl) 25-May-1989
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "psp.h"
|
||
|
||
VOID
|
||
PspQueueApcSpecialApc(
|
||
IN PKAPC Apc,
|
||
IN PKNORMAL_ROUTINE *NormalRoutine,
|
||
IN PVOID *NormalContext,
|
||
IN PVOID *SystemArgument1,
|
||
IN PVOID *SystemArgument2
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text(PAGE, NtGetContextThread)
|
||
#pragma alloc_text(PAGE, NtSetContextThread)
|
||
#pragma alloc_text(PAGE, PsGetContextThread)
|
||
#pragma alloc_text(PAGE, PsSetContextThread)
|
||
#pragma alloc_text(PAGE, NtQueueApcThread)
|
||
#pragma alloc_text(PAGE, PspQueueApcSpecialApc)
|
||
#endif
|
||
|
||
VOID
|
||
PspQueueApcSpecialApc(
|
||
IN PKAPC Apc,
|
||
IN PKNORMAL_ROUTINE *NormalRoutine,
|
||
IN PVOID *NormalContext,
|
||
IN PVOID *SystemArgument1,
|
||
IN PVOID *SystemArgument2
|
||
)
|
||
{
|
||
PAGED_CODE();
|
||
|
||
UNREFERENCED_PARAMETER (NormalRoutine);
|
||
UNREFERENCED_PARAMETER (NormalContext);
|
||
UNREFERENCED_PARAMETER (SystemArgument1);
|
||
UNREFERENCED_PARAMETER (SystemArgument2);
|
||
|
||
ExFreePool(Apc);
|
||
}
|
||
|
||
NTSYSAPI
|
||
NTSTATUS
|
||
NTAPI
|
||
NtQueueApcThread(
|
||
IN HANDLE ThreadHandle,
|
||
IN PPS_APC_ROUTINE ApcRoutine,
|
||
IN PVOID ApcArgument1,
|
||
IN PVOID ApcArgument2,
|
||
IN PVOID ApcArgument3
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function is used to queue a user-mode APC to the specified thread. The APC
|
||
will fire when the specified thread does an alertable wait
|
||
|
||
Arguments:
|
||
|
||
ThreadHandle - Supplies a handle to a thread object. The caller
|
||
must have THREAD_SET_CONTEXT access to the thread.
|
||
|
||
ApcRoutine - Supplies the address of the APC routine to execute when the
|
||
APC fires.
|
||
|
||
ApcArgument1 - Supplies the first PVOID passed to the APC
|
||
|
||
ApcArgument2 - Supplies the second PVOID passed to the APC
|
||
|
||
ApcArgument3 - Supplies the third PVOID passed to the APC
|
||
|
||
Return Value:
|
||
|
||
Returns an NT Status code indicating success or failure of the API
|
||
|
||
--*/
|
||
|
||
{
|
||
PETHREAD Thread;
|
||
NTSTATUS st;
|
||
KPROCESSOR_MODE Mode;
|
||
PKAPC Apc;
|
||
|
||
PAGED_CODE();
|
||
|
||
Mode = KeGetPreviousMode ();
|
||
|
||
st = ObReferenceObjectByHandle (ThreadHandle,
|
||
THREAD_SET_CONTEXT,
|
||
PsThreadType,
|
||
Mode,
|
||
&Thread,
|
||
NULL);
|
||
if (NT_SUCCESS (st)) {
|
||
st = STATUS_SUCCESS;
|
||
if (IS_SYSTEM_THREAD (Thread)) {
|
||
st = STATUS_INVALID_HANDLE;
|
||
} else {
|
||
Apc = ExAllocatePoolWithQuotaTag (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
|
||
sizeof(*Apc),
|
||
'pasP');
|
||
|
||
if (Apc == NULL) {
|
||
st = STATUS_NO_MEMORY;
|
||
} else {
|
||
KeInitializeApc (Apc,
|
||
&Thread->Tcb,
|
||
OriginalApcEnvironment,
|
||
PspQueueApcSpecialApc,
|
||
NULL,
|
||
(PKNORMAL_ROUTINE)ApcRoutine,
|
||
UserMode,
|
||
ApcArgument1);
|
||
|
||
if (!KeInsertQueueApc (Apc, ApcArgument2, ApcArgument3, 0)) {
|
||
ExFreePool (Apc);
|
||
st = STATUS_UNSUCCESSFUL;
|
||
}
|
||
}
|
||
}
|
||
ObDereferenceObject (Thread);
|
||
}
|
||
|
||
return st;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
PsGetContextThread(
|
||
IN PETHREAD Thread,
|
||
IN OUT PCONTEXT ThreadContext,
|
||
IN KPROCESSOR_MODE Mode
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function returns the usermode context of the specified thread. This
|
||
function will fail if the specified thread is a system thread. It will
|
||
return the wrong answer if the thread is a non-system thread that does
|
||
not execute in user-mode.
|
||
|
||
Arguments:
|
||
|
||
Thread - Supplies a pointer to the thread object from
|
||
which to retrieve context information.
|
||
|
||
ThreadContext - Supplies the address of a buffer that will receive
|
||
the context of the specified thread.
|
||
|
||
Mode - Mode to use for validation checks.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
ULONG ContextFlags=0;
|
||
GETSETCONTEXT ContextFrame = {0};
|
||
ULONG ContextLength=0;
|
||
KIRQL Irql;
|
||
NTSTATUS Status;
|
||
PETHREAD CurrentThread;
|
||
|
||
PAGED_CODE();
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Get previous mode and reference specified thread.
|
||
//
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
|
||
//
|
||
// Attempt to get the context of the specified thread.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Set the default alignment, capture the context flags,
|
||
// and set the default size of the context record.
|
||
//
|
||
|
||
if (Mode != KernelMode) {
|
||
ProbeForReadSmallStructure (ThreadContext,
|
||
FIELD_OFFSET (CONTEXT, ContextFlags) + sizeof (ThreadContext->ContextFlags),
|
||
CONTEXT_ALIGN);
|
||
}
|
||
|
||
ContextFlags = ThreadContext->ContextFlags;
|
||
|
||
//
|
||
// We don't need to re-probe here so long as the structure is smaller
|
||
// than the guard region
|
||
//
|
||
ContextLength = sizeof(CONTEXT);
|
||
ASSERT (ContextLength < 0x10000);
|
||
|
||
#if defined(_X86_)
|
||
//
|
||
// CONTEXT_EXTENDED_REGISTERS is SET, then we want sizeof(CONTEXT) set above
|
||
// otherwise (not set) we only want the old part of the context record.
|
||
//
|
||
if ((ContextFlags & CONTEXT_EXTENDED_REGISTERS) != CONTEXT_EXTENDED_REGISTERS) {
|
||
ContextLength = FIELD_OFFSET(CONTEXT, ExtendedRegisters);
|
||
}
|
||
#endif
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
return GetExceptionCode ();
|
||
}
|
||
|
||
KeInitializeEvent (&ContextFrame.OperationComplete,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
ContextFrame.Context.ContextFlags = ContextFlags;
|
||
|
||
ContextFrame.Mode = Mode;
|
||
if (Thread == CurrentThread) {
|
||
ContextFrame.Apc.SystemArgument1 = NULL;
|
||
ContextFrame.Apc.SystemArgument2 = Thread;
|
||
KeRaiseIrql (APC_LEVEL, &Irql);
|
||
PspGetSetContextSpecialApc (&ContextFrame.Apc,
|
||
NULL,
|
||
NULL,
|
||
&ContextFrame.Apc.SystemArgument1,
|
||
&ContextFrame.Apc.SystemArgument2);
|
||
|
||
KeLowerIrql (Irql);
|
||
|
||
//
|
||
// Move context to specfied context record. If an exception
|
||
// occurs, then return the error.
|
||
//
|
||
|
||
try {
|
||
RtlCopyMemory (ThreadContext,
|
||
&ContextFrame.Context,
|
||
ContextLength);
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
Status = GetExceptionCode ();
|
||
}
|
||
|
||
} else {
|
||
KeInitializeApc (&ContextFrame.Apc,
|
||
&Thread->Tcb,
|
||
OriginalApcEnvironment,
|
||
PspGetSetContextSpecialApc,
|
||
NULL,
|
||
NULL,
|
||
KernelMode,
|
||
NULL);
|
||
|
||
if (!KeInsertQueueApc (&ContextFrame.Apc, NULL, Thread, 2)) {
|
||
Status = STATUS_UNSUCCESSFUL;
|
||
|
||
} else {
|
||
KeWaitForSingleObject (&ContextFrame.OperationComplete,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
//
|
||
// Move context to specfied context record. If an
|
||
// exception occurs, then silently handle it and
|
||
// return success.
|
||
//
|
||
|
||
try {
|
||
RtlCopyMemory (ThreadContext,
|
||
&ContextFrame.Context,
|
||
ContextLength);
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
Status = GetExceptionCode ();
|
||
}
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
NtGetContextThread(
|
||
IN HANDLE ThreadHandle,
|
||
IN OUT PCONTEXT ThreadContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function returns the usermode context of the specified thread. This
|
||
function will fail if the specified thread is a system thread. It will
|
||
return the wrong answer if the thread is a non-system thread that does
|
||
not execute in user-mode.
|
||
|
||
Arguments:
|
||
|
||
ThreadHandle - Supplies an open handle to the thread object from
|
||
which to retrieve context information. The handle
|
||
must allow THREAD_GET_CONTEXT access to the thread.
|
||
|
||
ThreadContext - Supplies the address of a buffer that will receive
|
||
the context of the specified thread.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
KPROCESSOR_MODE Mode;
|
||
NTSTATUS Status;
|
||
PETHREAD Thread;
|
||
PETHREAD CurrentThread;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Get previous mode and reference specified thread.
|
||
//
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
Mode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
|
||
|
||
Status = ObReferenceObjectByHandle (ThreadHandle,
|
||
THREAD_GET_CONTEXT,
|
||
PsThreadType,
|
||
Mode,
|
||
&Thread,
|
||
NULL);
|
||
|
||
//
|
||
// If the reference was successful, the check if the specified thread
|
||
// is a system thread.
|
||
//
|
||
|
||
if (NT_SUCCESS (Status)) {
|
||
|
||
//
|
||
// If the thread is not a system thread, then attempt to get the
|
||
// context of the thread.
|
||
//
|
||
|
||
if (IS_SYSTEM_THREAD (Thread) == FALSE) {
|
||
|
||
Status = PsGetContextThread (Thread, ThreadContext, Mode);
|
||
|
||
} else {
|
||
Status = STATUS_INVALID_HANDLE;
|
||
}
|
||
|
||
ObDereferenceObject (Thread);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
PsSetContextThread(
|
||
IN PETHREAD Thread,
|
||
IN PCONTEXT ThreadContext,
|
||
IN KPROCESSOR_MODE Mode
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function sets the usermode context of the specified thread. This
|
||
function will fail if the specified thread is a system thread. It will
|
||
return the wrong answer if the thread is a non-system thread that does
|
||
not execute in user-mode.
|
||
|
||
Arguments:
|
||
|
||
Thread - Supplies the thread object from
|
||
which to retrieve context information.
|
||
|
||
ThreadContext - Supplies the address of a buffer that contains new
|
||
context for the specified thread.
|
||
|
||
Mode - Mode to use for validation checks.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG ContextFlags=0;
|
||
GETSETCONTEXT ContextFrame;
|
||
ULONG ContextLength=0;
|
||
KIRQL Irql;
|
||
NTSTATUS Status;
|
||
PETHREAD CurrentThread;
|
||
|
||
PAGED_CODE();
|
||
|
||
Status = STATUS_SUCCESS;
|
||
|
||
//
|
||
// Get previous mode and reference specified thread.
|
||
//
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
|
||
//
|
||
// Attempt to get the context of the specified thread.
|
||
//
|
||
|
||
try {
|
||
|
||
//
|
||
// Capture the context flags,
|
||
// and set the default size of the context record.
|
||
//
|
||
|
||
if (Mode != KernelMode) {
|
||
ProbeForReadSmallStructure (ThreadContext,
|
||
FIELD_OFFSET (CONTEXT, ContextFlags) + sizeof (ThreadContext->ContextFlags),
|
||
CONTEXT_ALIGN);
|
||
}
|
||
|
||
//
|
||
// We don't need to re-probe here so long as the structure is small
|
||
// enough not to cross the guard region.
|
||
//
|
||
ContextFlags = ThreadContext->ContextFlags;
|
||
ContextLength = sizeof (CONTEXT);
|
||
ASSERT (ContextLength < 0x10000);
|
||
|
||
#if defined(_X86_)
|
||
//
|
||
// CONTEXT_EXTENDED_REGISTERS is SET, then we want sizeof(CONTEXT) set above
|
||
// otherwise (not set) we only want the old part of the context record.
|
||
//
|
||
if ((ContextFlags & CONTEXT_EXTENDED_REGISTERS) != CONTEXT_EXTENDED_REGISTERS) {
|
||
ContextLength = FIELD_OFFSET(CONTEXT, ExtendedRegisters);
|
||
}
|
||
#endif
|
||
|
||
RtlCopyMemory (&ContextFrame.Context, ThreadContext, ContextLength);
|
||
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
return GetExceptionCode ();
|
||
}
|
||
|
||
//
|
||
// set the context of the target thread.
|
||
//
|
||
|
||
|
||
#if defined (_IA64_)
|
||
|
||
//
|
||
// On IA64 we need to fix up the PC if its a PLABEL address.
|
||
//
|
||
|
||
if (ContextFlags & CONTEXT_CONTROL) {
|
||
|
||
PLABEL_DESCRIPTOR Label, *LabelAddress;
|
||
SIZE_T BytesCopied;
|
||
|
||
if (ContextFrame.Context.IntGp == 0) {
|
||
LabelAddress = (PPLABEL_DESCRIPTOR)ContextFrame.Context.StIIP;
|
||
try {
|
||
//
|
||
// We are in the wrong process here but it doesn't matter.
|
||
// We just want to make sure this isn't a kernel address.
|
||
//
|
||
ProbeForReadSmallStructure (LabelAddress,
|
||
sizeof (*LabelAddress),
|
||
sizeof (ULONGLONG));
|
||
} except (EXCEPTION_EXECUTE_HANDLER) {
|
||
return GetExceptionCode ();
|
||
}
|
||
|
||
Status = MmCopyVirtualMemory (THREAD_TO_PROCESS (Thread),
|
||
LabelAddress,
|
||
PsGetCurrentProcessByThread (CurrentThread),
|
||
&Label,
|
||
sizeof (Label),
|
||
KernelMode, // Needed to write to local stack
|
||
&BytesCopied);
|
||
if (NT_SUCCESS (Status)) {
|
||
ContextFrame.Context.IntGp = Label.GlobalPointer;
|
||
ContextFrame.Context.StIIP = Label.EntryPoint;
|
||
ContextFrame.Context.StIPSR &= ~ISR_EI_MASK;
|
||
} else {
|
||
return Status;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|
||
|
||
KeInitializeEvent (&ContextFrame.OperationComplete,
|
||
NotificationEvent,
|
||
FALSE);
|
||
|
||
ContextFrame.Context.ContextFlags = ContextFlags;
|
||
|
||
ContextFrame.Mode = Mode;
|
||
if (Thread == CurrentThread) {
|
||
ContextFrame.Apc.SystemArgument1 = (PVOID)1;
|
||
ContextFrame.Apc.SystemArgument2 = Thread;
|
||
KeRaiseIrql(APC_LEVEL, &Irql);
|
||
PspGetSetContextSpecialApc (&ContextFrame.Apc,
|
||
NULL,
|
||
NULL,
|
||
&ContextFrame.Apc.SystemArgument1,
|
||
&ContextFrame.Apc.SystemArgument2);
|
||
|
||
KeLowerIrql (Irql);
|
||
|
||
} else {
|
||
KeInitializeApc (&ContextFrame.Apc,
|
||
&Thread->Tcb,
|
||
OriginalApcEnvironment,
|
||
PspGetSetContextSpecialApc,
|
||
NULL,
|
||
NULL,
|
||
KernelMode,
|
||
NULL);
|
||
|
||
if (!KeInsertQueueApc (&ContextFrame.Apc, (PVOID)1, Thread, 2)) {
|
||
Status = STATUS_UNSUCCESSFUL;
|
||
|
||
} else {
|
||
KeWaitForSingleObject (&ContextFrame.OperationComplete,
|
||
Executive,
|
||
KernelMode,
|
||
FALSE,
|
||
NULL);
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
NtSetContextThread(
|
||
IN HANDLE ThreadHandle,
|
||
IN PCONTEXT ThreadContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function sets the usermode context of the specified thread. This
|
||
function will fail if the specified thread is a system thread. It will
|
||
return the wrong answer if the thread is a non-system thread that does
|
||
not execute in user-mode.
|
||
|
||
Arguments:
|
||
|
||
ThreadHandle - Supplies an open handle to the thread object from
|
||
which to retrieve context information. The handle
|
||
must allow THREAD_SET_CONTEXT access to the thread.
|
||
|
||
ThreadContext - Supplies the address of a buffer that contains new
|
||
context for the specified thread.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
KPROCESSOR_MODE Mode;
|
||
NTSTATUS Status;
|
||
PETHREAD Thread;
|
||
PETHREAD CurrentThread;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Get previous mode and reference specified thread.
|
||
//
|
||
|
||
CurrentThread = PsGetCurrentThread ();
|
||
Mode = KeGetPreviousModeByThread (&CurrentThread->Tcb);
|
||
|
||
Status = ObReferenceObjectByHandle (ThreadHandle,
|
||
THREAD_SET_CONTEXT,
|
||
PsThreadType,
|
||
Mode,
|
||
&Thread,
|
||
NULL);
|
||
|
||
//
|
||
// If the reference was successful, the check if the specified thread
|
||
// is a system thread.
|
||
//
|
||
|
||
if (NT_SUCCESS (Status)) {
|
||
|
||
//
|
||
// If the thread is not a system thread, then attempt to get the
|
||
// context of the thread.
|
||
//
|
||
|
||
if (IS_SYSTEM_THREAD (Thread) == FALSE) {
|
||
|
||
Status = PsSetContextThread (Thread, ThreadContext, Mode);
|
||
|
||
} else {
|
||
Status = STATUS_INVALID_HANDLE;
|
||
}
|
||
|
||
ObDereferenceObject (Thread);
|
||
}
|
||
|
||
return Status;
|
||
}
|