/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2024 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

/**********************************************************************
 * LICENSE (http://www.opensource.org/licenses/bsd-license.php)
 *
 *   Copyright (c) 2005-2011, Jochen Kalmbach
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without modification,
 *   are permitted provided that the following conditions are met:
 *
 *   Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *   Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *   Neither the name of Jochen Kalmbach nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 **********************************************************************/

#include "StackWalker.h"
#if defined(Q_OS_WIN)

#    pragma comment(lib, "version.lib")  // for "VerQueryValue"

#    include <dbghelp.h>
#    include <fstream>
#    include <stdio.h>
#    include <tchar.h>

#    include <QFile>
#    include <QStringList>

#    define USED_CONTEXT_FLAGS CONTEXT_ALL

namespace U2 {

class StackWalkerInternal {
public:
    StackWalkerInternal(StackWalker* parent, HANDLE hProcess) {
        m_parent = parent;
        m_hDbhHelp = nullptr;
        pSC = nullptr;
        m_hProcess = hProcess;
        m_szSymPath = nullptr;
        pSFTA = nullptr;
        pSGLFA = nullptr;
        pSGMB = nullptr;
        pSGMI = nullptr;
        pSGO = nullptr;
        pSGSFA = nullptr;
        pSI = nullptr;
        pSLM = nullptr;
        pSSO = nullptr;
        pSW = nullptr;
        pUDSN = nullptr;
        pSGSP = nullptr;
    }
    ~StackWalkerInternal() {
        if (pSC != nullptr) {
            pSC(m_hProcess);  // SymCleanup
        }
        if (m_hDbhHelp != nullptr) {
            FreeLibrary(m_hDbhHelp);
        }
        m_hDbhHelp = nullptr;
        m_parent = nullptr;
        if (m_szSymPath != nullptr) {
            free(m_szSymPath);
        }
        m_szSymPath = nullptr;
    }
    BOOL Init(LPCSTR szSymPath) {
        if (m_parent == nullptr) {
            return FALSE;
        }
        // Dynamically load the Entry-Points for dbghelp.dll:
        // First try to load the newest one from
        wchar_t szTemp[4096];
        // But before we do this, we first check if the ".local" file exists
        if (GetModuleFileName(nullptr, szTemp, 4096) > 0) {
            wcscat_s(szTemp, TEXT(".local"));
            if (GetFileAttributes(szTemp) == INVALID_FILE_ATTRIBUTES) {
                // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows"
                if (GetEnvironmentVariable(TEXT("ProgramFiles"), szTemp, 4096) > 0) {
                    wcscat_s(szTemp, TEXT("\\Debugging Tools for Windows\\dbghelp.dll"));
                    // now check if the file exists:
                    if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) {
                        m_hDbhHelp = LoadLibrary(szTemp);
                    }
                }
                // Still not found? Then try to load the 64-Bit version:
                if ((m_hDbhHelp == nullptr) && (GetEnvironmentVariable(TEXT("ProgramFiles"), szTemp, 4096) > 0)) {
                    wcscat_s(szTemp, TEXT("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll"));
                    if (GetFileAttributes(szTemp) != INVALID_FILE_ATTRIBUTES) {
                        m_hDbhHelp = LoadLibrary(szTemp);
                    }
                }
            }
        }
        if (m_hDbhHelp == nullptr) {  // if not already loaded, try to load a default-one
            m_hDbhHelp = LoadLibrary(TEXT("dbghelp.dll"));
        }
        if (m_hDbhHelp == nullptr) {
            return FALSE;
        }
        pSI = (tSI)GetProcAddress(m_hDbhHelp, "SymInitialize");
        pSC = (tSC)GetProcAddress(m_hDbhHelp, "SymCleanup");

        pSW = (tSW)GetProcAddress(m_hDbhHelp, "StackWalk64");
        pSGO = (tSGO)GetProcAddress(m_hDbhHelp, "SymGetOptions");
        pSSO = (tSSO)GetProcAddress(m_hDbhHelp, "SymSetOptions");

        pSFTA = (tSFTA)GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64");
        pSGLFA = (tSGLFA)GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64");
        pSGMB = (tSGMB)GetProcAddress(m_hDbhHelp, "SymGetModuleBase64");
        pSGMI = (tSGMI)GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64");
        // pSGMI_V3 = (tSGMI_V3) GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64" );
        pSGSFA = (tSGSFA)GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64");
        pUDSN = (tUDSN)GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName");
        pSLM = (tSLM)GetProcAddress(m_hDbhHelp, "SymLoadModule64");
        pSGSP = (tSGSP)GetProcAddress(m_hDbhHelp, "SymGetSearchPath");

        if (pSC == nullptr || pSFTA == nullptr || pSGMB == nullptr || pSGMI == nullptr ||
            pSGO == nullptr || pSGSFA == nullptr || pSI == nullptr || pSSO == nullptr ||
            pSW == nullptr || pUDSN == nullptr || pSLM == nullptr) {
            FreeLibrary(m_hDbhHelp);
            m_hDbhHelp = nullptr;
            pSC = nullptr;
            return FALSE;
        }

        // SymInitialize
        if (szSymPath != nullptr) {
            m_szSymPath = _strdup(szSymPath);
        }
        if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) {
            this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0);
        }

        DWORD symOptions = this->pSGO();  // SymGetOptions
        symOptions |= SYMOPT_LOAD_LINES;
        symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
        // SymSetOptions
        symOptions = this->pSSO(symOptions);

        char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0};
        if (this->pSGSP != nullptr) {
            if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE) {
                this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0);
            }
        }
        char szUserName[1024] = {0};
        DWORD dwSize = 1024;
        GetUserNameA(szUserName, &dwSize);
        this->m_parent->OnSymInit(buf, symOptions, szUserName);

        return TRUE;
    }

    StackWalker* m_parent;

    HMODULE m_hDbhHelp;
    HANDLE m_hProcess;
    LPSTR m_szSymPath;

    typedef struct _IMAGEHLP_MODULE64_V2 {
        DWORD SizeOfStruct;  // set to sizeof(IMAGEHLP_MODULE64)
        DWORD64 BaseOfImage;  // base load address of module
        DWORD ImageSize;  // virtual size of the loaded module
        DWORD TimeDateStamp;  // date/time stamp from pe header
        DWORD CheckSum;  // checksum from the pe header
        DWORD NumSyms;  // number of symbols in the symbol table
        SYM_TYPE SymType;  // type of symbols loaded
        CHAR ModuleName[32];  // module name
        CHAR ImageName[256];  // image name
        CHAR LoadedImageName[256];  // symbol file name
    } IMAGEHLP_MODULE64_V2;

    // SymCleanup()
    typedef BOOL(__stdcall* tSC)(IN HANDLE hProcess);
    tSC pSC;

    // SymFunctionTableAccess64()
    typedef PVOID(__stdcall* tSFTA)(HANDLE hProcess, DWORD64 AddrBase);
    tSFTA pSFTA;

    // SymGetLineFromAddr64()
    typedef BOOL(__stdcall* tSGLFA)(IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line);
    tSGLFA pSGLFA;

    // SymGetModuleBase64()
    typedef DWORD64(__stdcall* tSGMB)(IN HANDLE hProcess, IN DWORD64 dwAddr);
    tSGMB pSGMB;

    // SymGetModuleInfo64()
    typedef BOOL(__stdcall* tSGMI)(IN HANDLE hProcess, IN DWORD64 dwAddr, OUT IMAGEHLP_MODULE64_V2* ModuleInfo);
    tSGMI pSGMI;

    // SymGetOptions()
    typedef DWORD(__stdcall* tSGO)(VOID);
    tSGO pSGO;

    // SymGetSymFromAddr64()
    typedef BOOL(__stdcall* tSGSFA)(IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol);
    tSGSFA pSGSFA;

    // SymInitialize()
    typedef BOOL(__stdcall* tSI)(IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess);
    tSI pSI;

    // SymLoadModule64()
    typedef DWORD64(__stdcall* tSLM)(IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll);
    tSLM pSLM;

    // SymSetOptions()
    typedef DWORD(__stdcall* tSSO)(IN DWORD SymOptions);
    tSSO pSSO;

    // StackWalk64()
    typedef BOOL(__stdcall* tSW)(
        DWORD MachineType,
        HANDLE hProcess,
        HANDLE hThread,
        LPSTACKFRAME64 StackFrame,
        PVOID ContextRecord,
        PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
        PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
        PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
        PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
    tSW pSW;

    // UnDecorateSymbolName()
    typedef DWORD(__stdcall WINAPI* tUDSN)(PCSTR DecoratedName, PSTR UnDecoratedName, DWORD UndecoratedLength, DWORD Flags);
    tUDSN pUDSN;

    typedef BOOL(__stdcall WINAPI* tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength);
    tSGSP pSGSP;

private:
    // **************************************** ToolHelp32 ************************
#    define MAX_MODULE_NAME32 255
#    define TH32CS_SNAPMODULE 0x00000008
#    pragma pack(push, 8)
    typedef struct tagMODULEENTRY32 {
        DWORD dwSize;
        DWORD th32ModuleID;  // This module
        DWORD th32ProcessID;  // owning process
        DWORD GlblcntUsage;  // Global usage count on the module
        DWORD ProccntUsage;  // Module usage count in th32ProcessID's context
        BYTE* modBaseAddr;  // Base address of module in th32ProcessID's context
        DWORD modBaseSize;  // Size in bytes of module starting at modBaseAddr
        HMODULE hModule;  // The hModule of this module in th32ProcessID's context
        char szModule[MAX_MODULE_NAME32 + 1];
        char szExePath[MAX_PATH];
    } MODULEENTRY32;
    typedef MODULEENTRY32* PMODULEENTRY32;
    typedef MODULEENTRY32* LPMODULEENTRY32;
#    pragma pack(pop)

    BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) {
        // CreateToolhelp32Snapshot()
        typedef HANDLE(__stdcall * tCT32S)(DWORD dwFlags, DWORD th32ProcessID);
        // Module32First()
        typedef BOOL(__stdcall * tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
        // Module32Next()
        typedef BOOL(__stdcall * tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);

        // try both dlls...
        const wchar_t* dllname[] = {TEXT("kernel32.dll"), TEXT("tlhelp32.dll")};
        HINSTANCE hToolhelp = nullptr;
        tCT32S pCT32S = nullptr;
        tM32F pM32F = nullptr;
        tM32N pM32N = nullptr;

        HANDLE hSnap;
        MODULEENTRY32 me;
        me.dwSize = sizeof(me);
        BOOL keepGoing;
        size_t i;

        for (i = 0; i < (sizeof(dllname) / sizeof(dllname[0])); i++) {
            hToolhelp = LoadLibrary(dllname[i]);
            if (hToolhelp == nullptr) {
                continue;
            }
            pCT32S = (tCT32S)GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot");
            pM32F = (tM32F)GetProcAddress(hToolhelp, "Module32First");
            pM32N = (tM32N)GetProcAddress(hToolhelp, "Module32Next");
            if ((pCT32S != nullptr) && (pM32F != nullptr) && (pM32N != nullptr)) {
                break;  // found the functions!
            }
            FreeLibrary(hToolhelp);
            hToolhelp = nullptr;
        }

        if (hToolhelp == nullptr) {
            return FALSE;
        }

        hSnap = pCT32S(TH32CS_SNAPMODULE, pid);
        if (hSnap == (HANDLE)-1) {
            return FALSE;
        }

        keepGoing = !!pM32F(hSnap, &me);
        int cnt = 0;
        while (keepGoing) {
            this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64)me.modBaseAddr, me.modBaseSize);
            cnt++;
            keepGoing = !!pM32N(hSnap, &me);
        }
        CloseHandle(hSnap);
        FreeLibrary(hToolhelp);
        if (cnt <= 0) {
            return FALSE;
        }
        return TRUE;
    }  // GetModuleListTH32

    // **************************************** PSAPI ************************
    typedef struct _MODULEINFO {
        LPVOID lpBaseOfDll;
        DWORD SizeOfImage;
        LPVOID EntryPoint;
    } MODULEINFO, *LPMODULEINFO;

    BOOL GetModuleListPSAPI(HANDLE hProcess) {
        // EnumProcessModules()
        typedef BOOL(__stdcall * tEPM)(HANDLE hProcess, HMODULE * lphModule, DWORD cb, LPDWORD lpcbNeeded);
        // GetModuleFileNameEx()
        typedef DWORD(__stdcall * tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);
        // GetModuleBaseName()
        typedef DWORD(__stdcall * tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);
        // GetModuleInformation()
        typedef BOOL(__stdcall * tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize);

        HINSTANCE hPsapi;
        tEPM pEPM;
        tGMFNE pGMFNE;
        tGMBN pGMBN;
        tGMI pGMI;

        DWORD i;
        // ModuleEntry e;
        DWORD cbNeeded;
        MODULEINFO mi;
        HMODULE* hMods = 0;
        char* tt = nullptr;
        char* tt2 = nullptr;
        const SIZE_T TTBUFLEN = 8096;
        int cnt = 0;

        hPsapi = LoadLibrary(TEXT("psapi.dll"));
        if (hPsapi == nullptr) {
            return FALSE;
        }

        pEPM = (tEPM)GetProcAddress(hPsapi, "EnumProcessModules");
        pGMFNE = (tGMFNE)GetProcAddress(hPsapi, "GetModuleFileNameExA");
        pGMBN = (tGMFNE)GetProcAddress(hPsapi, "GetModuleBaseNameA");
        pGMI = (tGMI)GetProcAddress(hPsapi, "GetModuleInformation");
        if ((pEPM == nullptr) || (pGMFNE == nullptr) || (pGMBN == nullptr) || (pGMI == nullptr)) {
            // we couldn't find all functions
            FreeLibrary(hPsapi);
            return FALSE;
        }

        hMods = (HMODULE*)malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof HMODULE));
        tt = (char*)malloc(sizeof(char) * TTBUFLEN);
        tt2 = (char*)malloc(sizeof(char) * TTBUFLEN);
        if (!(((hMods == nullptr) || (tt == nullptr) || (tt2 == nullptr)) || !pEPM(hProcess, hMods, TTBUFLEN, &cbNeeded) || cbNeeded > TTBUFLEN)) {
            for (i = 0; i < cbNeeded / sizeof hMods[0]; i++) {
                // base address, size
                pGMI(hProcess, hMods[i], &mi, sizeof mi);
                // image file name
                tt[0] = 0;
                pGMFNE(hProcess, hMods[i], tt, TTBUFLEN);
                // module name
                tt2[0] = 0;
                pGMBN(hProcess, hMods[i], tt2, TTBUFLEN);

                DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64)mi.lpBaseOfDll, mi.SizeOfImage);
                if (dwRes != ERROR_SUCCESS)
                    this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0);
                cnt++;
            }
        }

        if (hPsapi != nullptr)
            FreeLibrary(hPsapi);
        if (tt2 != nullptr)
            free(tt2);
        if (tt != nullptr)
            free(tt);
        if (hMods != nullptr)
            free(hMods);

        return cnt != 0;
    }  // GetModuleListPSAPI

    DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size) {
        CHAR* szImg = _strdup(img);
        CHAR* szMod = _strdup(mod);
        DWORD result = ERROR_SUCCESS;
        if ((szImg == nullptr) || (szMod == nullptr))
            result = ERROR_NOT_ENOUGH_MEMORY;
        else {
            if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0)
                result = GetLastError();
        }
        ULONGLONG fileVersion = 0;
        if ((m_parent != nullptr) && (szImg != nullptr)) {
            // try to retrive the file-version:
            if ((this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0) {
                VS_FIXEDFILEINFO* fInfo = nullptr;
                DWORD dwHandle;
                DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle);
                if (dwSize > 0) {
                    LPVOID vData = malloc(dwSize);
                    if (vData != nullptr) {
                        if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0) {
                            UINT len;
                            TCHAR szSubBlock[] = TEXT("\\");
                            if (VerQueryValue(vData, szSubBlock, (LPVOID*)&fInfo, &len) == 0)
                                fInfo = nullptr;
                            else {
                                fileVersion = ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32);
                            }
                        }
                        free(vData);
                    }
                }
            }

            // Retrive some additional-infos about the module
            IMAGEHLP_MODULE64_V2 Module;
            const char* szSymType = "-unknown-";
            if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE) {
                switch (Module.SymType) {
                    case SymNone:
                        szSymType = "-nosymbols-";
                        break;
                    case SymCoff:
                        szSymType = "COFF";
                        break;
                    case SymCv:
                        szSymType = "CV";
                        break;
                    case SymPdb:
                        szSymType = "PDB";
                        break;
                    case SymExport:
                        szSymType = "-exported-";
                        break;
                    case SymDeferred:
                        szSymType = "-deferred-";
                        break;
                    case SymSym:
                        szSymType = "SYM";
                        break;
                    case 8:  // SymVirtual:
                        szSymType = "Virtual";
                        break;
                    case 9:  // SymDia:
                        szSymType = "DIA";
                        break;
                }
            }
            this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, Module.LoadedImageName, fileVersion);
        }
        if (szImg != nullptr)
            free(szImg);
        if (szMod != nullptr)
            free(szMod);
        return result;
    }

public:
    BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) {
        // first try toolhelp32
        if (GetModuleListTH32(hProcess, dwProcessId)) {
            return true;
        }
        // then try psapi
        return GetModuleListPSAPI(hProcess);
    }

    bool GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V2* pModuleInfo) {
        if (this->pSGMI == nullptr) {
            SetLastError(ERROR_DLL_INIT_FAILED);
            return FALSE;
        }
        pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);
        void* pData = malloc(4096);  // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites...
        if (pData == nullptr) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        }
        memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2));
        if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V2*)pData) != FALSE) {
            // only copy as much memory as is reserved...
            memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2));
            pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2);
            free(pData);
            return TRUE;
        }
        free(pData);
        SetLastError(ERROR_DLL_INIT_FAILED);
        return FALSE;
    }
};

// #############################################################
StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) {
    this->m_options = OptionsAll;
    this->m_modulesLoaded = FALSE;
    this->m_hProcess = hProcess;
    this->m_sw = new StackWalkerInternal(this, this->m_hProcess);
    this->m_dwProcessId = dwProcessId;
    this->m_szSymPath = nullptr;
}
StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) {
    this->m_options = options;
    this->m_modulesLoaded = FALSE;
    this->m_hProcess = hProcess;
    this->m_sw = new StackWalkerInternal(this, this->m_hProcess);
    this->m_dwProcessId = dwProcessId;
    if (szSymPath != nullptr) {
        this->m_szSymPath = _strdup(szSymPath);
        this->m_options |= SymBuildPath;
    } else {
        this->m_szSymPath = nullptr;
    }
}

StackWalker::~StackWalker() {
    if (m_szSymPath != nullptr) {
        free(m_szSymPath);
    }
    m_szSymPath = nullptr;
    if (this->m_sw != nullptr) {
        delete this->m_sw;
    }
    this->m_sw = nullptr;
}

BOOL StackWalker::LoadModules() {
    if (this->m_sw == nullptr) {
        SetLastError(ERROR_DLL_INIT_FAILED);
        return FALSE;
    }
    if (m_modulesLoaded != FALSE) {
        return TRUE;
    }

    // Build the sym-path:
    char* szSymPath = nullptr;
    if ((this->m_options & SymBuildPath) != 0) {
        const size_t nSymPathLen = 4096;
        szSymPath = (char*)malloc(nSymPathLen);
        if (szSymPath == nullptr) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        }
        szSymPath[0] = 0;
        // Now first add the (optional) provided sympath:
        if (this->m_szSymPath != nullptr) {
            strcat_s(szSymPath, nSymPathLen, this->m_szSymPath);
            strcat_s(szSymPath, nSymPathLen, ";");
        }

        strcat_s(szSymPath, nSymPathLen, ".;");

        const size_t nTempLen = 1024;
        char szTemp[nTempLen];
        // Now add the current folder:
        if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) {
            szTemp[nTempLen - 1] = 0;
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
        }

        // Now add the path for the main-module:
        if (GetModuleFileNameA(nullptr, szTemp, nTempLen) > 0) {
            szTemp[nTempLen - 1] = 0;
            for (char* p = (szTemp + strlen(szTemp) - 1); p >= szTemp; --p) {
                // locate the rightmost path separator
                if ((*p == '\\') || (*p == '/') || (*p == ':')) {
                    *p = 0;
                    break;
                }
            }  // for (search for path separator...)
            if (strlen(szTemp) > 0) {
                strcat_s(szSymPath, nSymPathLen, szTemp);
                strcat_s(szSymPath, nSymPathLen, ";");
            }
        }
        if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) {
            szTemp[nTempLen - 1] = 0;
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
        }
        if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) {
            szTemp[nTempLen - 1] = 0;
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
        }
        if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) {
            szTemp[nTempLen - 1] = 0;
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
            // also add the "system32"-folder:
            strcat_s(szTemp, nTempLen, "\\system32");
            strcat_s(szSymPath, nSymPathLen, szTemp);
            strcat_s(szSymPath, nSymPathLen, ";");
        }

        if ((this->m_options & SymBuildPath) != 0) {
            if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0) {
                szTemp[nTempLen - 1] = 0;
                strcat_s(szSymPath, nSymPathLen, "SRV*");
                strcat_s(szSymPath, nSymPathLen, szTemp);
                strcat_s(szSymPath, nSymPathLen, "\\websymbols");
                strcat_s(szSymPath, nSymPathLen, "*http://msdl.microsoft.com/download/symbols;");
            } else {
                strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://msdl.microsoft.com/download/symbols;");
            }
        }
    }

    // First Init the whole stuff...
    BOOL bRet = this->m_sw->Init(szSymPath);
    if (szSymPath != nullptr) {
        free(szSymPath);
    }
    szSymPath = nullptr;
    if (bRet == FALSE) {
        this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0);
        SetLastError(ERROR_DLL_INIT_FAILED);
        return FALSE;
    }

    bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId);
    if (bRet != FALSE) {
        m_modulesLoaded = TRUE;
    }
    return bRet;
}

// The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction
// This has to be done due to a problem with the "hProcess"-parameter in x64...
// Because this class is in no case multi-threading-enabled (because of the limitations
// of dbghelp.dll) it is "safe" to use a static-variable
static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = nullptr;
static LPVOID s_readMemoryFunction_UserData = nullptr;

bool StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT* context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) {
    CONTEXT c;
    CallstackEntry csEntry;
    IMAGEHLP_SYMBOL64* pSym = nullptr;
    StackWalkerInternal::IMAGEHLP_MODULE64_V2 Module;
    IMAGEHLP_LINE64 Line;
    int frameNum;

    if (m_modulesLoaded == FALSE)
        this->LoadModules();  // ignore the result...

    if (this->m_sw->m_hDbhHelp == nullptr) {
        SetLastError(ERROR_DLL_INIT_FAILED);
        return FALSE;
    }

    s_readMemoryFunction = readMemoryFunction;
    s_readMemoryFunction_UserData = pUserData;

    c = *context;

    // init STACKFRAME for first call
    STACKFRAME64 s;  // in/out stackframe
    memset(&s, 0, sizeof(s));
    DWORD imageType;
#    ifdef _M_IX86
    // normally, call ImageNtHeader() and use machine info from PE header
    imageType = IMAGE_FILE_MACHINE_I386;
    s.AddrPC.Offset = c.Eip;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.Ebp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.Esp;
    s.AddrStack.Mode = AddrModeFlat;
#    elif _M_X64
    imageType = IMAGE_FILE_MACHINE_AMD64;
    s.AddrPC.Offset = c.Rip;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.Rsp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.Rsp;
    s.AddrStack.Mode = AddrModeFlat;
#    elif _M_IA64
    imageType = IMAGE_FILE_MACHINE_IA64;
    s.AddrPC.Offset = c.StIIP;
    s.AddrPC.Mode = AddrModeFlat;
    s.AddrFrame.Offset = c.IntSp;
    s.AddrFrame.Mode = AddrModeFlat;
    s.AddrBStore.Offset = c.RsBSP;
    s.AddrBStore.Mode = AddrModeFlat;
    s.AddrStack.Offset = c.IntSp;
    s.AddrStack.Mode = AddrModeFlat;
#    else
#        error "Platform not supported!"
#    endif

    pSym = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
    if (!pSym)
        goto cleanup;  // not enough memory...
    memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN);
    pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
    pSym->MaxNameLength = STACKWALK_MAX_NAMELEN;

    memset(&Line, 0, sizeof(Line));
    Line.SizeOfStruct = sizeof(Line);

    memset(&Module, 0, sizeof(Module));
    Module.SizeOfStruct = sizeof(Module);

    for (frameNum = 0;; ++frameNum) {
        // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())
        // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
        // assume that either you are done, or that the stack is so hosed that the next
        // deeper frame could not be found.
        // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!
        if (!this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, this->m_sw->pSFTA, this->m_sw->pSGMB, nullptr)) {
            this->OnDbgHelpErr("StackWalk64", GetLastError(), s.AddrPC.Offset);
            break;
        }

        csEntry.offset = s.AddrPC.Offset;
        csEntry.name[0] = 0;
        csEntry.undName[0] = 0;
        csEntry.undFullName[0] = 0;
        csEntry.offsetFromSmybol = 0;
        csEntry.offsetFromLine = 0;
        csEntry.lineFileName[0] = 0;
        csEntry.lineNumber = 0;
        csEntry.loadedImageName[0] = 0;
        csEntry.moduleName[0] = 0;
        if (s.AddrPC.Offset == s.AddrReturn.Offset) {
            this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset);
            break;
        }
        if (s.AddrPC.Offset != 0) {
            // we seem to have a valid PC
            // show procedure info (SymGetSymFromAddr64())
            if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), pSym) != FALSE) {
                // TODO: Mache dies sicher...!
                strcpy_s(csEntry.name, pSym->Name);
                // UnDecorateSymbolName()
                this->m_sw->pUDSN(pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY);
                this->m_sw->pUDSN(pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE);
            } else {
                this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset);
            }

            // show line number info, NT5.0-method (SymGetLineFromAddr64())
            if (this->m_sw->pSGLFA != nullptr) {  // yes, we have SymGetLineFromAddr64()
                if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), &Line) != FALSE) {
                    csEntry.lineNumber = Line.LineNumber;
                    // TODO: Mache dies sicher...!
                    strcpy_s(csEntry.lineFileName, Line.FileName);
                } else {
                    this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset);
                }
            }  // yes, we have SymGetLineFromAddr64()

            // show module info (SymGetModuleInfo64())
            if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module) != FALSE) {  // got module info OK
                switch (Module.SymType) {
                    case SymNone:
                        csEntry.symTypeString = "-nosymbols-";
                        break;
                    case SymCoff:
                        csEntry.symTypeString = "COFF";
                        break;
                    case SymCv:
                        csEntry.symTypeString = "CV";
                        break;
                    case SymPdb:
                        csEntry.symTypeString = "PDB";
                        break;
                    case SymExport:
                        csEntry.symTypeString = "-exported-";
                        break;
                    case SymDeferred:
                        csEntry.symTypeString = "-deferred-";
                        break;
                    case SymSym:
                        csEntry.symTypeString = "SYM";
                        break;
#    if API_VERSION_NUMBER >= 9
                    case SymDia:
                        csEntry.symTypeString = "DIA";
                        break;
#    endif
                    case 8:  // SymVirtual:
                        csEntry.symTypeString = "Virtual";
                        break;
                    default:
                        //_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
                        csEntry.symTypeString = nullptr;
                        break;
                }

                // TODO: Mache dies sicher...!
                strcpy_s(csEntry.moduleName, Module.ModuleName);
                csEntry.baseOfImage = Module.BaseOfImage;
                strcpy_s(csEntry.loadedImageName, Module.LoadedImageName);
            }  // got module info OK
            else {
                this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset);
            }
        }  // we seem to have a valid PC

        CallstackEntryType et = nextEntry;
        if (frameNum == 0)
            et = firstEntry;
        this->OnCallstackEntry(et, csEntry);

        if (s.AddrReturn.Offset == 0) {
            this->OnCallstackEntry(lastEntry, csEntry);
            SetLastError(ERROR_SUCCESS);
            break;
        }
    }  // for ( frameNum )

cleanup:
    if (pSym)
        free(pSym);

    if (context == nullptr)
        ResumeThread(hThread);

    return TRUE;
}

BOOL __stdcall StackWalker::myReadProcMem(
    HANDLE hProcess,
    DWORD64 qwBaseAddress,
    PVOID lpBuffer,
    DWORD nSize,
    LPDWORD lpNumberOfBytesRead) {
    if (s_readMemoryFunction == nullptr) {
        SIZE_T st;
        BOOL bRet = ReadProcessMemory(hProcess, (LPVOID)qwBaseAddress, lpBuffer, nSize, &st);
        *lpNumberOfBytesRead = (DWORD)st;
        return bRet;
    } else {
        return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData);
    }
}

void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) {
    libraries[(LPVOID)baseAddr] = QString(img);
}

void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry) {
    QMap<LPVOID, QString>::iterator it = libraries.begin();
    while (entry.offset > (DWORD)it.key() && it != libraries.end()) {
        it++;
    }
    it--;
    QString library = it.value();
    DWORD libAddr = (DWORD)it.key();
    QString binaryName = library.split("\\").last();
    library.replace("dll", "map");
    library.replace("exe", "map");
    QFile file(library);
    if (!file.exists()) {
        QString str;
        buffer += str.sprintf("%p", entry.offset) + ": (" + binaryName + ") " + entry.name + "\n";
        return;
    }
    file.open(QIODevice::ReadOnly);
    DWORD preferredAddress = 0x10000000;

    QString name;
    DWORD offset = 0;
    while (!file.readLine().contains("Rva+Base") && !file.atEnd())
        ;
    file.readLine();
    while (!file.atEnd()) {
        QString line = file.readLine();
        QStringList list = line.split(" ", QString::SkipEmptyParts);
        if (list.size() > 3) {
            DWORD addr = list[2].toInt(nullptr, 16);
            DWORD actualAddress = preferredAddress + entry.offset - libAddr;
            if (actualAddress < addr) {
                break;
            }
            name = list[1];
            offset = actualAddress - addr;
        }
    }
    if (file.atEnd()) {
        name = "Some static function";
    }
    file.close();
    QString str;
    buffer += str.sprintf("%p", entry.offset) + ": (" + binaryName + ") " + name.replace("@", ":") + " + " + str.sprintf("%p", offset) + "\n";
}

void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) {
    // CHAR buffer[STACKWALK_MAX_NAMELEN];
    //_snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr);
    // OnOutput("--");
}

void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) {
    CHAR buffer[STACKWALK_MAX_NAMELEN];
    _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName);
    // Also display the OS-version
    OSVERSIONINFOEXA ver;
    ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA));
    ver.dwOSVersionInfoSize = sizeof(ver);
    if (GetVersionExA((OSVERSIONINFOA*)&ver) != FALSE) {
        _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType);
        OnOutput(buffer);
    }
}

void StackWalker::OnOutput(LPCSTR _buffer) {
    buffer.append(_buffer);
}

}  // namespace U2

#endif
