/*++

Copyright (c) 1991 Microsoft Corporation

Module Name:

    getcalr.c

Abstract:

    This module implements the routine RtlGetCallerAddress. It will
    return the address of the caller, and the callers caller to the
    specified procedure.

Author:

    William K. Cheung (wcheung) 17-Jan-1996

    based on version by Larry Osterman (larryo) 18-Mar-1991

Revision History:

    18-Feb-2001 (silviuc) : added RtlCaptureStackBackTrace.

--*/
#include "ntrtlp.h"

//
// Undefine get callers address since it is defined as a macro.
//

#undef RtlGetCallersAddress

ULONG
RtlpWalkFrameChainExceptionFilter (
    ULONG ExceptionCode,
    PVOID ExceptionRecord
    );


PRUNTIME_FUNCTION
RtlpLookupFunctionEntryForStackWalks (
    IN ULONGLONG ControlPc,
    OUT PULONGLONG ImageBase,
    OUT PULONGLONG TargetGp
    )

/*++

Routine Description:

    This function searches the currently active function tables for an
    entry that corresponds to the specified PC value.

    This code is identical to RtlLookupFunctionEntry, except that it does not
    check the dynamic function table list.

Arguments:

    ControlPc - Supplies the virtual address of an instruction bundle
        within the specified function.

    ImageBase - Returns the base address of the module to which the
                function belongs.

    TargetGp - Returns the global pointer value of the module.

Return Value:

    If there is no entry in the function table for the specified PC, then
    NULL is returned.  Otherwise, the address of the function table entry
    that corresponds to the specified PC is returned.

--*/

{
    PRUNTIME_FUNCTION FunctionEntry;
    PRUNTIME_FUNCTION FunctionTable;
    ULONG SizeOfExceptionTable;
    ULONG Size;
    LONG High;
    LONG Middle;
    LONG Low;
    USHORT i;

    //
    // Search for the image that includes the specified swizzled PC value.
    //

    *ImageBase = (ULONG_PTR)RtlPcToFileHeader((PVOID)ControlPc,
                                              (PVOID *)ImageBase);


    //
    // If an image is found that includes the specified PC, then locate the
    // function table for the image.
    //

    if ((PVOID)*ImageBase != NULL) {

        *TargetGp = (ULONG_PTR)(RtlImageDirectoryEntryToData(
                               (PVOID)*ImageBase,
                               TRUE,
                               IMAGE_DIRECTORY_ENTRY_GLOBALPTR,
                               &Size
                               ));

        FunctionTable = (PRUNTIME_FUNCTION)RtlImageDirectoryEntryToData(
                         (PVOID)*ImageBase,
                         TRUE,
                         IMAGE_DIRECTORY_ENTRY_EXCEPTION,
                         &SizeOfExceptionTable);

        //
        // If a function table is located, then search the table for a
        // function table entry for the specified PC.
        //

        if (FunctionTable != NULL) {

            //
            // Initialize search indices.
            //

            Low = 0;
            High = (SizeOfExceptionTable / sizeof(RUNTIME_FUNCTION)) - 1;
            ControlPc = ControlPc - *ImageBase;

            //
            // Perform binary search on the function table for a function table
            // entry that subsumes the specified PC.
            //

            while (High >= Low) {

                //
                // Compute next probe index and test entry. If the specified PC
                // is greater than of equal to the beginning address and less
                // than the ending address of the function table entry, then
                // return the address of the function table entry. Otherwise,
                // continue the search.
                //

                Middle = (Low + High) >> 1;
                FunctionEntry = &FunctionTable[Middle];

                if (ControlPc < FunctionEntry->BeginAddress) {
                    High = Middle - 1;

                } else if (ControlPc >= FunctionEntry->EndAddress) {
                    Low = Middle + 1;

                } else {
                    return FunctionEntry;

                }
            }
        }
    }

    return NULL;
}



VOID
RtlGetCallersAddress (
    OUT PVOID *CallersPc,
    OUT PVOID *CallersCallersPc
    )

/*++

Routine Description:

    This routine returns the address of the routine that called the routine
    that called this routine, and the routine that called the routine that
    called this routine. For example, if A called B called C which called
    this routine, the return addresses in A and B would be returned.

Arguments:

    CallersPc - Supplies a pointer to a variable that receives the address
        of the caller of the caller of this routine (B).

    CallersCallersPc - Supplies a pointer to a variable that receives the
        address of the caller of the caller of the caller of this routine
        (A).

Return Value:

    None.

Note:

    If either of the calling stack frames exceeds the limits of the stack,
    they are set to NULL.

--*/

{
#ifdef REALLY_GET_CALLERS_CALLER
    CONTEXT ContextRecord;
    FRAME_POINTERS EstablisherFrame;
    PRUNTIME_FUNCTION FunctionEntry;
    BOOLEAN InFunction;
    ULONG_PTR NextPc;
    ULONGLONG HighStackLimit, LowStackLimit;
    ULONGLONG HighBStoreLimit, LowBStoreLimit;
    ULONGLONG ImageBase;
    ULONGLONG TargetGp;

    //
    // Assume the function table entries for the various routines cannot be
    // found or there are not four procedure activation records on the stack.
    //

    *CallersPc = NULL;
    *CallersCallersPc = NULL;

    //
    // Capture the current context.
    //

    RtlCaptureContext(&ContextRecord);
    NextPc = (ULONG_PTR)ContextRecord.BrRp;

    //
    // Get the high and low limits of the current thread's stack.
    //

    Rtlp64GetStackLimits(&LowStackLimit, &HighStackLimit);
    Rtlp64GetBStoreLimits(&LowBStoreLimit, &HighBStoreLimit);

    //
    // Attempt to unwind to the caller of this routine (C).
    //

    FunctionEntry = RtlpLookupFunctionEntryForStackWalks(NextPc, &ImageBase, &TargetGp);
    if (FunctionEntry != NULL) {

        //
        // A function entry was found for this routine. Virtually unwind
        // to the caller of this routine (C).
        //

        NextPc = RtlVirtualUnwind(NextPc,
                                  FunctionEntry,
                                  &ContextRecord,
                                  &InFunction,
                                  &EstablisherFrame,
                                  NULL);

        //
        // Attempt to unwind to the caller of the caller of this routine (B).
        //

        FunctionEntry = RtlpLookupFunctionEntryForStackWalks(NextPc);
        if ((FunctionEntry != NULL) && (((ULONG_PTR)ContextRecord.IntSp < HighStackLimit) && ((ULONG_PTR)ContextRecord.RsBSP > LowBStoreLimit))) {

            //
            // A function table entry was found for the caller of the caller
            // of this routine (B). Virtually unwind to the caller of the
            // caller of this routine (B).
            //

            NextPc = RtlVirtualUnwind(NextPc,
                                      FunctionEntry,
                                      &ContextRecord,
                                      &InFunction,
                                      &EstablisherFrame,
                                      NULL);

            *CallersPc = (PVOID)NextPc;

            //
            // Attempt to unwind to the caller of the caller of the caller
            // of the caller of this routine (A).
            //

            FunctionEntry = RtlpLookupFunctionEntryForStackWalks(NextPc);
            if ((FunctionEntry != NULL) && (((ULONG_PTR)ContextRecord.IntSp < HighStackLimit) && ((ULONG_PTR)ContextRecord.RsBSP > LowBStoreLimit))) {

                //
                // A function table entry was found for the caller of the
                // caller of the caller of this routine (A). Virtually unwind
                // to the caller of the caller of the caller of this routine
                // (A).
                //

                NextPc = RtlVirtualUnwind(NextPc,
                                          FunctionEntry,
                                          &ContextRecord,
                                          &InFunction,
                                          &EstablisherFrame,
                                          NULL);

                *CallersCallersPc = (PVOID)NextPc;
            }
        }
    }
#else
    *CallersPc = NULL;
    *CallersCallersPc = NULL;
#endif // REALLY_GET_CALLERS_CALLER
    return;
}

ULONG
RtlWalkFrameChain (
    OUT PVOID *Callers,
    IN ULONG Count,
    IN ULONG Flags
    )
/*++

Routine Description:

    RtlpWalkFrameChain64

Description:

    This function tries to walk the call chain and fill out a vector of
    return addresses. The function works only on IA64. It is possible that
    the function cannot fill the requested number of callers.
    In this case the function will just return with
    a less then requested count.

    In kernel mode the function should not take
    any exceptions (page faults) because it can be called at all sorts of
    irql levels. It needs to be tested if this is the case.

Return value:

    The number of identified return addresses on the stack. This can be less
    then the Count requested if the stack ends or we encounter an error while
    virtually unwinding the stack.

--*/

{
    CONTEXT ContextRecord;
    FRAME_POINTERS EstablisherFrame;
    PRUNTIME_FUNCTION FunctionEntry;
    BOOLEAN InFunction;
    ULONG_PTR NextPc, ControlPc;
    ULONGLONG HighStackLimit, LowStackLimit;
    ULONGLONG HighBStoreLimit, LowBStoreLimit;
    ULONGLONG ImageBase;
    ULONGLONG TargetGp;
    ULONG CallersFound;

    //
    // In kernel mode avoid running at irql levels where we cannot
    // take page faults. The walking code will touch various sections
    // from driver images and this will cause page faults.
    //

#ifdef NTOS_KERNEL_RUNTIME

    if (KeGetCurrentIrql() > PASSIVE_LEVEL) {
        return 0;
    }

#endif

    //
    // Assume the function table entries for the various routines cannot be
    // found or there are not enough procedure activation records on the stack.
    //

    CallersFound = 0;
    RtlZeroMemory (Callers, Count * sizeof(PVOID));

    //
    // Capture the current context.
    //

    RtlCaptureContext (&ContextRecord);
    NextPc = (ULONG_PTR)ContextRecord.BrRp;

    //
    // Get the high and low limits of the current thread's stack.
    //

    Rtlp64GetStackLimits (&LowStackLimit, &HighStackLimit);
    Rtlp64GetBStoreLimits (&LowBStoreLimit, &HighBStoreLimit);

    //
    // Loop to get requested number of callers.
    //

    try {
        while (CallersFound < Count) {

#ifdef NTOS_KERNEL_RUNTIME

            //
            // We need to check the NextPc value that we have got from
            // CaptureContext() or VirtualUnwind(). It can happen that
            // we pick up a bogus value from a session driver but in the
            // current process no session space is mapped. 
            //

            if ((MmIsSessionAddress ((PVOID)NextPc) == TRUE) &&
                (MmGetSessionId (PsGetCurrentProcess()) == 0)) {
                break;
            }
#endif

            FunctionEntry = RtlpLookupFunctionEntryForStackWalks (NextPc,
                                                    &ImageBase,
                                                    &TargetGp);

            //
            // If we cannot find a function table entry or we are not
            // within stack limits or backing store limits anymore
            // we are done.
            //

            if (FunctionEntry == NULL) {
                break;
            }

            if ((ULONG_PTR)(ContextRecord.IntSp) >= HighStackLimit ||
                (ULONG_PTR)(ContextRecord.IntSp) <= LowStackLimit) {

                break;
            }

            if ((ULONG_PTR)(ContextRecord.RsBSP) <= LowBStoreLimit ||
                (ULONG_PTR)(ContextRecord.RsBSP) >= HighBStoreLimit) {

                break;
            }

            //
            // A function table entry was found.
            // Virtually unwind to the caller of this routine.
            //

            NextPc = RtlVirtualUnwind (ImageBase,
                                       NextPc,
                                       FunctionEntry,
                                       &ContextRecord,
                                       &InFunction,
                                       &EstablisherFrame,
                                       NULL);

            Callers[CallersFound] = (PVOID)NextPc;
            CallersFound += 1;
        }

    } except (RtlpWalkFrameChainExceptionFilter (_exception_code(), _exception_info())) {
        
          CallersFound = 0;
    }

    return CallersFound;
}


USHORT
RtlCaptureStackBackTrace(
    IN ULONG FramesToSkip,
    IN ULONG FramesToCapture,
    OUT PVOID *BackTrace,
    OUT PULONG BackTraceHash
    )
/*++

Routine Description:

    This routine walks up the stack frames, capturing the return address from
    each frame requested.

Arguments:

    FramesToSkip - frames detected but not included in the stack trace

    FramesToCapture - frames to be captured in the stack trace buffer.
        One of the frames will be for RtlCaptureStackBackTrace.

    BackTrace - stack trace buffer

    BackTraceHash - very simple hash value that can be used to organize
      hash tables. It is just an arithmetic sum of the pointers in the
      stack trace buffer. If NULL then no hash value is computed.

Return Value:

     Number of return addresses returned in the stack trace buffer.

--*/
{
    PVOID Trace [2 * MAX_STACK_DEPTH];
    ULONG FramesFound;
    ULONG HashValue;
    ULONG Index;

    //
    // One more frame to skip for the "capture" function (RtlWalkFrameChain).
    //

    FramesToSkip += 1;

    //
    // Sanity checks.
    //

    if (FramesToCapture + FramesToSkip >= 2 * MAX_STACK_DEPTH) {
        return 0;
    }

    FramesFound = RtlWalkFrameChain (Trace,
                                     FramesToCapture + FramesToSkip,
                                     0);

    if (FramesFound <= FramesToSkip) {
        return 0;
    }

    for (Index = 0, HashValue = 0; Index < FramesToCapture; Index += 1) {

        if (FramesToSkip + Index >= FramesFound) {
            break;
        }

        BackTrace[Index] = Trace[FramesToSkip + Index];
        HashValue += PtrToUlong(BackTrace[Index]);
    }

    if (BackTraceHash != NULL) {

        *BackTraceHash = HashValue;
    }

    return (USHORT)Index;
}


ULONG
RtlpWalkFrameChainExceptionFilter (
    ULONG ExceptionCode,
    PVOID ExceptionRecord
    )
/*++

Routine Description:

    This routine is the exception filter used by RtlWalkFramechain function.

Arguments:

    ExceptionCode - exception code
    ExceptionRecord - structure with pointers to .exr and .cxr

Return Value:

    Always EXCEPTION_EXECUTE_HANDLER.

--*/
{

#if DBG
        DbgPrint ("Unexpected exception (info %p) in RtlWalkFrameChain ...\n",
                  ExceptionRecord);

        DbgBreakPoint ();
#endif

    return EXCEPTION_EXECUTE_HANDLER;
}