/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    context.c

Abstract:

    This module implements user-mode callable context manipulation routines.
    The interfaces exported from this module are portable, but they must
    be re-implemented for each architecture.

--*/

#include "ntrtlp.h"

#if defined(ALLOC_PRAGMA)
#pragma alloc_text(PAGE,RtlInitializeContext)
#pragma alloc_text(PAGE,RtlRemoteCall)
#endif


VOID
RtlInitializeContext(
    IN HANDLE Process,
    OUT PCONTEXT Context,
    IN PVOID Parameter OPTIONAL,
    IN PVOID InitialPc OPTIONAL,
    IN PVOID InitialSp OPTIONAL
    )

/*++

Routine Description:

    This function initializes a context structure so that it can
    be used in a subsequent call to NtCreateThread.

Arguments:

    Context - Supplies a context buffer to be initialized by this routine.

    InitialPc - Supplies an initial program counter value.

    InitialSp - Supplies an initial stack pointer value.

Return Value:

    Raises STATUS_BAD_INITIAL_STACK if the value of InitialSp is not properly
           aligned.

    Raises STATUS_BAD_INITIAL_PC if the value of InitialPc is not properly
           aligned.

--*/

{
    RTL_PAGED_CODE();

    Context->Eax = 0L;
    Context->Ebx = 1L;
    Context->Ecx = 2L;
    Context->Edx = 3L;
    Context->Esi = 4L;
    Context->Edi = 5L;
    Context->Ebp = 0L;

    Context->SegGs = 0;
    Context->SegFs = KGDT_R3_TEB;
    Context->SegEs = KGDT_R3_DATA;
    Context->SegDs = KGDT_R3_DATA;
    Context->SegSs = KGDT_R3_DATA;
    Context->SegCs = KGDT_R3_CODE;

    Context->EFlags = 0x200L;	    // force interrupts on, clear all else.

    //
    // Even though these are optional, they are used as is, since NULL
    // is what these would have been initialized to anyway
    //

    Context->Esp = (ULONG) InitialSp;
    Context->Eip = (ULONG) InitialPc;

    //
    // add code to check alignment and raise exception...
    //

    Context->ContextFlags = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS;

    //
    // Set the initial context of the thread in a machine specific way.
    // ie, pass the initial parameter to the start address
    //

    Context->Esp -= sizeof(Parameter);
    ZwWriteVirtualMemory(Process,
			 (PVOID)Context->Esp,
			 (PVOID)&Parameter,
			 sizeof(Parameter),
			 NULL);
    Context->Esp -= sizeof(Parameter); // Reserve room for ret address


}



NTSTATUS
RtlRemoteCall(
    HANDLE Process,
    HANDLE Thread,
    PVOID CallSite,
    ULONG ArgumentCount,
    PULONG Arguments,
    BOOLEAN PassContext,
    BOOLEAN AlreadySuspended
    )

/*++

Routine Description:

    This function calls a procedure in another thread/process, using
    NtGetContext and NtSetContext.  Parameters are passed to the
    target procedure via its stack.

Arguments:

    Process - Handle of the target process

    Thread - Handle of the target thread within that process

    CallSite - Address of the procedure to call in the target process.

    ArgumentCount - Number of 32 bit parameters to pass to the target
                    procedure.

    Arguments - Pointer to the array of 32 bit parameters to pass.

    PassContext - TRUE if an additional parameter is to be passed that
        points to a context record.

    AlreadySuspended - TRUE if the target thread is already in a suspended
                       or waiting state.

Return Value:

    Status - Status value

--*/

{
    NTSTATUS Status;
    CONTEXT Context;
    ULONG NewSp;
    ULONG ArgumentsCopy[5];

    RTL_PAGED_CODE();

    if (ArgumentCount > 4)
        return STATUS_INVALID_PARAMETER;

    //
    // If necessary, suspend the guy before with we mess with his stack.
    //
    if (!AlreadySuspended) {
        Status = NtSuspendThread( Thread, NULL );
        if (!NT_SUCCESS( Status )) {
            return( Status );
            }
        }


    //
    // Get the context record for the target thread.
    //

    Context.ContextFlags = CONTEXT_FULL;
    Status = NtGetContextThread( Thread, &Context );
    if (!NT_SUCCESS( Status )) {
        if (!AlreadySuspended) {
            NtResumeThread( Thread, NULL );
            }
        return( Status );
        }


    //
    //	Pass all parameters on the stack, regardless of whether a
    //	a context record is passed.
    //

    //
    //	Put Context Record on stack first, so it is above other args.
    //
    NewSp = Context.Esp;
    if (PassContext) {
	NewSp -= sizeof( CONTEXT );
	Status = NtWriteVirtualMemory( Process,
				       (PVOID)NewSp,
				       &Context,
				       sizeof( CONTEXT ),
				       NULL
				    );
	if (!NT_SUCCESS( Status )) {
            if (!AlreadySuspended) {
                NtResumeThread( Thread, NULL );
                }
	    return( Status );
	    }
        ArgumentsCopy[0] = NewSp;   // pass pointer to context
        RtlCopyMemory(&(ArgumentsCopy[1]),Arguments,ArgumentCount*sizeof( ULONG ));
        ArgumentCount++;
	}
    else {
        RtlCopyMemory(ArgumentsCopy,Arguments,ArgumentCount*sizeof( ULONG ));
        }

    //
    //	Copy the arguments onto the target stack
    //
    if (ArgumentCount) {
        NewSp -= ArgumentCount * sizeof( ULONG );
        Status = NtWriteVirtualMemory( Process,
                                       (PVOID)NewSp,
                                       ArgumentsCopy,
                                       ArgumentCount * sizeof( ULONG ),
                                       NULL
                                     );
        if (!NT_SUCCESS( Status )) {
            if (!AlreadySuspended) {
                NtResumeThread( Thread, NULL );
                }
            return( Status );
            }
        }

    //
    // Set the address of the target code into Eip, the new target stack
    // into Esp, and reload context to make it happen.
    //
    Context.Esp = NewSp;
    Context.Eip = (ULONG)CallSite;
    Status = NtSetContextThread( Thread, &Context );
    if (!AlreadySuspended) {
        NtResumeThread( Thread, NULL );
        }

    return( Status );
}

