﻿/**********************************************************************
*
* History:
*  2005-07-27   v1    - First public release on http://www.codeproject.com/
*  (for additional changes see History in 'StackWalker.cpp'!
*
**********************************************************************/
// #pragma once is supported starting with _MCS_VER 1000,
// so we need not to check the version (because we only support _MSC_VER >= 1100)!
#pragma once

#if defined(ORION_WINDOWS)
#include <windows.h>

// special defines for VC5/6 (if no actual PSDK is installed):
#if _MSC_VER < 1300
typedef uint64_t DWORD64, *PDWORD64;
#if defined(_WIN64)
typedef uint64_t SIZE_T, *PSIZE_T;
#else
typedef unsigned long SIZE_T, *PSIZE_T;
#endif
#endif // _MSC_VER < 1300

class StackWalkerInternal; // forward
class StackWalker
{
public:
    typedef enum StackWalkOptions
    {
        // No addition info will be retrived
        // (only the address is available)
        RetrieveNone = 0,

        // Try to get the symbol-name
        RetrieveSymbol = 1,

        // Try to get the line for this symbol
        RetrieveLine = 2,

        // Try to retrieve the module-infos
        RetrieveModuleInfo = 4,

        // Also retrieve the version for the DLL/EXE
        RetrieveFileVersion = 8,

        // Contains all the abouve
        RetrieveVerbose = 0xF,

        // Generate a "good" symbol-search-path
        SymBuildPath = 0x10,

        // Also use the public Microsoft-Symbol-Server
        SymUseSymSrv = 0x20,

        // Contains all the abouve "Sym"-options
        SymAll = 0x30,

        // Contains all options (default)
        OptionsAll = 0x3F
    } StackWalkOptions;

    StackWalker(
        int options = OptionsAll, // 'int' is by design, to combine the enum-flags
        LPCSTR szSymPath = nullptr,
        DWORD dwProcessId = GetCurrentProcessId(),
        HANDLE hProcess = GetCurrentProcess());
    StackWalker(DWORD dwProcessId, HANDLE hProcess);
    virtual ~StackWalker();

    typedef BOOL(__stdcall *PReadProcessMemoryRoutine)(
        HANDLE hProcess,
        DWORD64 qwBaseAddress,
        void *lpBuffer,
        DWORD nSize,
        LPDWORD lpNumberOfBytesRead,
        LPVOID pUserData // optional data, which was passed in "ShowCallstack"
    );

    BOOL LoadModules();

    BOOL ShowCallstack(
        HANDLE hThread = GetCurrentThread(),
        const CONTEXT *context = nullptr,
        PReadProcessMemoryRoutine readMemoryFunction = nullptr,
        LPVOID pUserData =
            nullptr // optional to identify some data in the 'readMemoryFunction'-callback
    );

#if _MSC_VER >= 1300
    // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
    // in older compilers in order to use it... starting with VC7 we can declare it as "protected"
protected:
#endif
    enum
    {
        STACKWALK_MAX_NAMELEN = 1024
    }; // max name length for found symbols

protected:
    // Entry for each Callstack-Entry
    typedef struct CallstackEntry
    {
        DWORD64 offset; // if 0, we have no valid entry
        CHAR name[STACKWALK_MAX_NAMELEN];
        CHAR undName[STACKWALK_MAX_NAMELEN];
        CHAR undFullName[STACKWALK_MAX_NAMELEN];
        DWORD64 offsetFromSmybol;
        DWORD offsetFromLine;
        DWORD lineNumber;
        CHAR lineFileName[STACKWALK_MAX_NAMELEN];
        DWORD symType;
        LPCSTR symTypeString;
        CHAR moduleName[STACKWALK_MAX_NAMELEN];
        DWORD64 baseOfImage;
        CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
    } CallstackEntry;

    enum CallstackEntryType
    {
        firstEntry,
        nextEntry,
        lastEntry
    };

    virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
    virtual void OnLoadModule(
        LPCSTR img,
        LPCSTR mod,
        DWORD64 baseAddr,
        DWORD size,
        DWORD result,
        LPCSTR symType,
        LPCSTR pdbName,
        ULONGLONG fileVersion);
    virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
    virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
    virtual void OnOutput(LPCSTR szText);

    StackWalkerInternal *m_sw;
    HANDLE m_hProcess;
    DWORD m_dwProcessId;
    BOOL m_modulesLoaded;
    LPSTR m_szSymPath;

    int m_options;

    static BOOL __stdcall myReadProcMem(
        HANDLE hProcess,
        DWORD64 qwBaseAddress,
        void *lpBuffer,
        DWORD nSize,
        LPDWORD lpNumberOfBytesRead);

    friend StackWalkerInternal;
};

// The "ugly" assembler-implementation is needed for systems before XP
// If you have a new PSDK and you only compile for XP and later, then you can use
// the "RtlCaptureContext"
// Currently there is no define which determines the PSDK-Version...
// So we just use the compiler-version (and assumes that the PSDK is
// the one which was installed by the VS-IDE)

// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
//       But I currently use it in x64/IA64 environments...
//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)

#if defined(_M_IX86)
#ifdef CURRENT_THREAD_VIA_EXCEPTION
// TODO: The following is not a "good" implementation,
// because the callstack is only valid in the "__except" block...
#define GET_CURRENT_CONTEXT(c, contextFlags)                                                       \
    do                                                                                             \
    {                                                                                              \
        memset(&c, 0, sizeof(CONTEXT));                                                            \
        EXCEPTION_POINTERS *pExp = nullptr;                                                        \
        __try                                                                                      \
        {                                                                                          \
            throw 0;                                                                               \
        }
__except (
    ((pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER))
{
}
if (pExp != nullptr)
    memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT));
c.ContextFlags = contextFlags;
}
while (0)
    ;
#else
// The following should be enough for walking the callstack...
#define GET_CURRENT_CONTEXT(c, contextFlags)                                                       \
    do                                                                                             \
    {                                                                                              \
        memset(&c, 0, sizeof(CONTEXT));                                                            \
        c.ContextFlags = contextFlags;                                                             \
        __asm call x __asm x : pop eax __asm mov c.Eip, eax __asm mov c.Ebp, ebp __asm mov c.Esp,  \
            esp                                                                                    \
    } while (0);
#endif

#else

// The following is defined for x86 (XP and higher), x64 and IA64:
#define GET_CURRENT_CONTEXT(c, contextFlags)                                                       \
    do                                                                                             \
    {                                                                                              \
        memset(&c, 0, sizeof(CONTEXT));                                                            \
        c.ContextFlags = contextFlags;                                                             \
        RtlCaptureContext(&c);                                                                     \
    } while (0);
#endif

#else // ORION_WINDOWS

// TODO: Implement backtrace support

struct StackWalker
{
    virtual void OnOutput(const char *) {}
};

#endif // ORION_WINDOWS