/*
 * Process Hacker -
 *   symbol provider
 *
 * Copyright (C) 2010-2015 wj32
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker 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 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker 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 Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <ph.h>
#include <workqueue.h>
#include <kphuser.h>
#include <fastlock.h>

#pragma warning(push)
#pragma warning(disable: 4091) // Ignore 'no variable declared on typedef'
#include <dbghelp.h>
#pragma warning(pop)

#include <symprv.h>
#include <symprvp.h>

typedef struct _PH_SYMBOL_MODULE
{
    LIST_ENTRY ListEntry;
    PH_AVL_LINKS Links;
    ULONG64 BaseAddress;
    ULONG Size;
    PPH_STRING FileName;
    ULONG BaseNameIndex;
} PH_SYMBOL_MODULE, *PPH_SYMBOL_MODULE;

VOID NTAPI PhpSymbolProviderDeleteProcedure(
    _In_ PVOID Object,
    _In_ ULONG Flags
    );

VOID PhpRegisterSymbolProvider(
    _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider
    );

VOID PhpFreeSymbolModule(
    _In_ PPH_SYMBOL_MODULE SymbolModule
    );

LONG NTAPI PhpSymbolModuleCompareFunction(
    _In_ PPH_AVL_LINKS Links1,
    _In_ PPH_AVL_LINKS Links2
    );

PPH_OBJECT_TYPE PhSymbolProviderType;

static PH_INITONCE PhSymInitOnce = PH_INITONCE_INIT;
DECLSPEC_SELECTANY PH_CALLBACK_DECLARE(PhSymInitCallback);

static HANDLE PhNextFakeHandle = (HANDLE)0;
static PH_FAST_LOCK PhSymMutex = PH_FAST_LOCK_INIT;

#define PH_LOCK_SYMBOLS() PhAcquireFastLockExclusive(&PhSymMutex)
#define PH_UNLOCK_SYMBOLS() PhReleaseFastLockExclusive(&PhSymMutex)

_SymInitialize SymInitialize_I;
_SymCleanup SymCleanup_I;
_SymEnumSymbols SymEnumSymbols_I;
_SymEnumSymbolsW SymEnumSymbolsW_I;
_SymFromAddr SymFromAddr_I;
_SymFromAddrW SymFromAddrW_I;
_SymFromName SymFromName_I;
_SymFromNameW SymFromNameW_I;
_SymGetLineFromAddr64 SymGetLineFromAddr64_I;
_SymGetLineFromAddrW64 SymGetLineFromAddrW64_I;
_SymLoadModule64 SymLoadModule64_I;
_SymLoadModuleExW SymLoadModuleExW_I;
_SymGetOptions SymGetOptions_I;
_SymSetOptions SymSetOptions_I;
_SymGetSearchPath SymGetSearchPath_I;
_SymGetSearchPathW SymGetSearchPathW_I;
_SymSetSearchPath SymSetSearchPath_I;
_SymSetSearchPathW SymSetSearchPathW_I;
_SymUnloadModule64 SymUnloadModule64_I;
_SymFunctionTableAccess64 SymFunctionTableAccess64_I;
_SymGetModuleBase64 SymGetModuleBase64_I;
_SymRegisterCallbackW64 SymRegisterCallbackW64_I;
_StackWalk64 StackWalk64_I;
_MiniDumpWriteDump MiniDumpWriteDump_I;
_SymbolServerGetOptions SymbolServerGetOptions;
_SymbolServerSetOptions SymbolServerSetOptions;

BOOLEAN PhSymbolProviderInitialization(
    VOID
    )
{
    PhSymbolProviderType = PhCreateObjectType(L"SymbolProvider", 0, PhpSymbolProviderDeleteProcedure);

    return TRUE;
}

VOID PhSymbolProviderCompleteInitialization(
    _In_opt_ PVOID DbgHelpBase
    )
{
    HMODULE dbghelpHandle;
    HMODULE symsrvHandle;

    // The user should have loaded dbghelp.dll and symsrv.dll already. If not, it's not our problem.

    // The Unicode versions aren't available in dbghelp.dll 5.1, so we fallback on the ANSI versions.

    if (DbgHelpBase)
        dbghelpHandle = DbgHelpBase;
    else
        dbghelpHandle = GetModuleHandle(L"dbghelp.dll");

    symsrvHandle = GetModuleHandle(L"symsrv.dll");

    SymInitialize_I = (PVOID)GetProcAddress(dbghelpHandle, "SymInitialize");
    SymCleanup_I = (PVOID)GetProcAddress(dbghelpHandle, "SymCleanup");
    if (!(SymEnumSymbolsW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbolsW")))
        SymEnumSymbols_I = (PVOID)GetProcAddress(dbghelpHandle, "SymEnumSymbols");
    if (!(SymFromAddrW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddrW")))
        SymFromAddr_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromAddr");
    if (!(SymFromNameW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromNameW")))
        SymFromName_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFromName");
    if (!(SymGetLineFromAddrW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddrW64")))
        SymGetLineFromAddr64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetLineFromAddr64");
    if (!(SymLoadModuleExW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModuleExW")))
        SymLoadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymLoadModule64");
    SymGetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetOptions");
    SymSetOptions_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetOptions");
    if (!(SymGetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPathW")))
        SymGetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetSearchPath");
    if (!(SymSetSearchPathW_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPathW")))
        SymSetSearchPath_I = (PVOID)GetProcAddress(dbghelpHandle, "SymSetSearchPath");
    SymUnloadModule64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymUnloadModule64");
    SymFunctionTableAccess64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymFunctionTableAccess64");
    SymGetModuleBase64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymGetModuleBase64");
    SymRegisterCallbackW64_I = (PVOID)GetProcAddress(dbghelpHandle, "SymRegisterCallbackW64");
    StackWalk64_I = (PVOID)GetProcAddress(dbghelpHandle, "StackWalk64");
    MiniDumpWriteDump_I = (PVOID)GetProcAddress(dbghelpHandle, "MiniDumpWriteDump");
    SymbolServerGetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerGetOptions");
    SymbolServerSetOptions = (PVOID)GetProcAddress(symsrvHandle, "SymbolServerSetOptions");

    if (SymGetOptions_I && SymSetOptions_I)
        SymSetOptions_I(SymGetOptions_I() | SYMOPT_DEFERRED_LOADS | SYMOPT_FAVOR_COMPRESSED);
}

PPH_SYMBOL_PROVIDER PhCreateSymbolProvider(
    _In_opt_ HANDLE ProcessId
    )
{
    PPH_SYMBOL_PROVIDER symbolProvider;

    symbolProvider = PhCreateObject(sizeof(PH_SYMBOL_PROVIDER), PhSymbolProviderType);
    memset(symbolProvider, 0, sizeof(PH_SYMBOL_PROVIDER));
    InitializeListHead(&symbolProvider->ModulesListHead);
    PhInitializeQueuedLock(&symbolProvider->ModulesListLock);
    PhInitializeAvlTree(&symbolProvider->ModulesSet, PhpSymbolModuleCompareFunction);
    PhInitializeCallback(&symbolProvider->EventCallback);
    PhInitializeInitOnce(&symbolProvider->InitOnce);

    if (ProcessId)
    {
        static ACCESS_MASK accesses[] =
        {
            STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xfff, // pre-Vista full access
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_DUP_HANDLE,
            PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
            MAXIMUM_ALLOWED
        };

        ULONG i;

        // Try to open the process with many different accesses.
        // This handle will be re-used when walking stacks, and doing various other things.
        for (i = 0; i < sizeof(accesses) / sizeof(ACCESS_MASK); i++)
        {
            if (NT_SUCCESS(PhOpenProcess(&symbolProvider->ProcessHandle, accesses[i], ProcessId)))
            {
                symbolProvider->IsRealHandle = TRUE;
                break;
            }
        }
    }

    if (!symbolProvider->IsRealHandle)
    {
        HANDLE fakeHandle;

        // Just generate a fake handle.
        fakeHandle = (HANDLE)_InterlockedExchangeAddPointer((PLONG_PTR)&PhNextFakeHandle, 4);
        // Add one to make sure it isn't divisible by 4 (so it can't be mistaken for a real handle).
        symbolProvider->ProcessHandle = (HANDLE)((ULONG_PTR)fakeHandle + 1);
    }

    return symbolProvider;
}

VOID NTAPI PhpSymbolProviderDeleteProcedure(
    _In_ PVOID Object,
    _In_ ULONG Flags
    )
{
    PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)Object;
    PLIST_ENTRY listEntry;

    PhDeleteCallback(&symbolProvider->EventCallback);

    if (SymCleanup_I)
    {
        PH_LOCK_SYMBOLS();

        if (symbolProvider->IsRegistered)
            SymCleanup_I(symbolProvider->ProcessHandle);

        PH_UNLOCK_SYMBOLS();
    }

    listEntry = symbolProvider->ModulesListHead.Flink;

    while (listEntry != &symbolProvider->ModulesListHead)
    {
        PPH_SYMBOL_MODULE module;

        module = CONTAINING_RECORD(listEntry, PH_SYMBOL_MODULE, ListEntry);
        listEntry = listEntry->Flink;

        PhpFreeSymbolModule(module);
    }

    if (symbolProvider->IsRealHandle) NtClose(symbolProvider->ProcessHandle);
}

NTSTATUS PhpSymbolCallbackWorker(
    _In_ PVOID Parameter
    )
{
    PPH_SYMBOL_EVENT_DATA data = Parameter;

    dprintf("symbol event %d: %S\n", data->Type, data->FileName->Buffer);
    PhInvokeCallback(&data->SymbolProvider->EventCallback, data);
    PhClearReference(&data->FileName);
    PhDereferenceObject(data);

    return STATUS_SUCCESS;
}

BOOL CALLBACK PhpSymbolCallbackFunction(
    _In_ HANDLE hProcess,
    _In_ ULONG ActionCode,
    _In_opt_ ULONG64 CallbackData,
    _In_opt_ ULONG64 UserContext
    )
{
    PPH_SYMBOL_PROVIDER symbolProvider = (PPH_SYMBOL_PROVIDER)UserContext;
    PPH_SYMBOL_EVENT_DATA data;
    PIMAGEHLP_DEFERRED_SYMBOL_LOADW64 callbackData;

    if (!IsListEmpty(&symbolProvider->EventCallback.ListHead))
    {
        switch (ActionCode)
        {
        case SymbolDeferredSymbolLoadStart:
        case SymbolDeferredSymbolLoadComplete:
        case SymbolDeferredSymbolLoadFailure:
        case SymbolSymbolsUnloaded:
        case SymbolDeferredSymbolLoadCancel:
            data = PhCreateAlloc(sizeof(PH_SYMBOL_EVENT_DATA));
            memset(data, 0, sizeof(PH_SYMBOL_EVENT_DATA));
            data->SymbolProvider = symbolProvider;
            data->Type = ActionCode;

            if (ActionCode != SymbolSymbolsUnloaded)
            {
                callbackData = (PIMAGEHLP_DEFERRED_SYMBOL_LOADW64)CallbackData;
                data->BaseAddress = callbackData->BaseOfImage;
                data->CheckSum = callbackData->CheckSum;
                data->TimeStamp = callbackData->TimeDateStamp;
                data->FileName = PhCreateString(callbackData->FileName);
            }

            PhQueueItemWorkQueue(PhGetGlobalWorkQueue(), PhpSymbolCallbackWorker, data);

            break;
        }
    }

    return FALSE;
}

VOID PhpRegisterSymbolProvider(
    _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider
    )
{
    if (PhBeginInitOnce(&PhSymInitOnce))
    {
        PhInvokeCallback(&PhSymInitCallback, NULL);
        PhEndInitOnce(&PhSymInitOnce);
    }

    if (!SymbolProvider)
        return;

    if (PhBeginInitOnce(&SymbolProvider->InitOnce))
    {
        if (SymInitialize_I)
        {
            PH_LOCK_SYMBOLS();

            SymInitialize_I(SymbolProvider->ProcessHandle, NULL, FALSE);

            if (SymRegisterCallbackW64_I)
                SymRegisterCallbackW64_I(SymbolProvider->ProcessHandle, PhpSymbolCallbackFunction, (ULONG64)SymbolProvider);

            PH_UNLOCK_SYMBOLS();

            SymbolProvider->IsRegistered = TRUE;
        }

        PhEndInitOnce(&SymbolProvider->InitOnce);
    }
}

VOID PhpFreeSymbolModule(
    _In_ PPH_SYMBOL_MODULE SymbolModule
    )
{
    if (SymbolModule->FileName) PhDereferenceObject(SymbolModule->FileName);

    PhFree(SymbolModule);
}

static LONG NTAPI PhpSymbolModuleCompareFunction(
    _In_ PPH_AVL_LINKS Links1,
    _In_ PPH_AVL_LINKS Links2
    )
{
    PPH_SYMBOL_MODULE symbolModule1 = CONTAINING_RECORD(Links1, PH_SYMBOL_MODULE, Links);
    PPH_SYMBOL_MODULE symbolModule2 = CONTAINING_RECORD(Links2, PH_SYMBOL_MODULE, Links);

    return uint64cmp(symbolModule1->BaseAddress, symbolModule2->BaseAddress);
}

BOOLEAN PhGetLineFromAddress(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ ULONG64 Address,
    _Out_ PPH_STRING *FileName,
    _Out_opt_ PULONG Displacement,
    _Out_opt_ PPH_SYMBOL_LINE_INFORMATION Information
    )
{
    IMAGEHLP_LINEW64 line;
    BOOL result;
    ULONG displacement;
    PPH_STRING fileName;

    PhpRegisterSymbolProvider(SymbolProvider);

    if (!SymGetLineFromAddrW64_I && !SymGetLineFromAddr64_I)
        return FALSE;

    line.SizeOfStruct = sizeof(IMAGEHLP_LINEW64);

    PH_LOCK_SYMBOLS();

    if (SymGetLineFromAddrW64_I)
    {
        result = SymGetLineFromAddrW64_I(
            SymbolProvider->ProcessHandle,
            Address,
            &displacement,
            &line
            );

        if (result)
            fileName = PhCreateString(line.FileName);
    }
    else
    {
        IMAGEHLP_LINE64 lineA;

        lineA.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

        result = SymGetLineFromAddr64_I(
            SymbolProvider->ProcessHandle,
            Address,
            &displacement,
            &lineA
            );

        if (result)
        {
            fileName = PhConvertMultiByteToUtf16(lineA.FileName);
            line.LineNumber = lineA.LineNumber;
            line.Address = lineA.Address;
        }
    }

    PH_UNLOCK_SYMBOLS();

    if (!result)
        return FALSE;

    *FileName = fileName;

    if (Displacement)
        *Displacement = displacement;

    if (Information)
    {
        Information->LineNumber = line.LineNumber;
        Information->Address = line.Address;
    }

    return TRUE;
}

ULONG64 PhGetModuleFromAddress(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ ULONG64 Address,
    _Out_opt_ PPH_STRING *FileName
    )
{
    PH_SYMBOL_MODULE lookupModule;
    PPH_AVL_LINKS links;
    PPH_SYMBOL_MODULE module;
    PPH_STRING foundFileName;
    ULONG64 foundBaseAddress;

    foundFileName = NULL;
    foundBaseAddress = 0;

    PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock);

    // Do an approximate search on the modules set to locate the module with the largest
    // base address that is still smaller than the given address.
    lookupModule.BaseAddress = Address;
    links = PhUpperDualBoundElementAvlTree(&SymbolProvider->ModulesSet, &lookupModule.Links);

    if (links)
    {
        module = CONTAINING_RECORD(links, PH_SYMBOL_MODULE, Links);

        if (Address < module->BaseAddress + module->Size)
        {
            PhSetReference(&foundFileName, module->FileName);
            foundBaseAddress = module->BaseAddress;
        }
    }

    PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock);

    if (foundFileName)
    {
        if (FileName)
        {
            *FileName = foundFileName;
        }
        else
        {
            PhDereferenceObject(foundFileName);
        }
    }

    return foundBaseAddress;
}

VOID PhpSymbolInfoAnsiToUnicode(
    _Out_ PSYMBOL_INFOW SymbolInfoW,
    _In_ PSYMBOL_INFO SymbolInfoA
    )
{
    SymbolInfoW->TypeIndex = SymbolInfoA->TypeIndex;
    SymbolInfoW->Index = SymbolInfoA->Index;
    SymbolInfoW->Size = SymbolInfoA->Size;
    SymbolInfoW->ModBase = SymbolInfoA->ModBase;
    SymbolInfoW->Flags = SymbolInfoA->Flags;
    SymbolInfoW->Value = SymbolInfoA->Value;
    SymbolInfoW->Address = SymbolInfoA->Address;
    SymbolInfoW->Register = SymbolInfoA->Register;
    SymbolInfoW->Scope = SymbolInfoA->Scope;
    SymbolInfoW->Tag = SymbolInfoA->Tag;
    SymbolInfoW->NameLen = 0;

    if (SymbolInfoA->NameLen != 0 && SymbolInfoW->MaxNameLen != 0)
    {
        ULONG copyCount;

        copyCount = min(SymbolInfoA->NameLen, SymbolInfoW->MaxNameLen - 1);

        if (PhCopyStringZFromMultiByte(
            SymbolInfoA->Name,
            copyCount,
            SymbolInfoW->Name,
            SymbolInfoW->MaxNameLen,
            NULL
            ))
        {
            SymbolInfoW->NameLen = copyCount;
        }
    }
}

PPH_STRING PhGetSymbolFromAddress(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ ULONG64 Address,
    _Out_opt_ PPH_SYMBOL_RESOLVE_LEVEL ResolveLevel,
    _Out_opt_ PPH_STRING *FileName,
    _Out_opt_ PPH_STRING *SymbolName,
    _Out_opt_ PULONG64 Displacement
    )
{
    PSYMBOL_INFOW symbolInfo;
    ULONG nameLength;
    PPH_STRING symbol = NULL;
    PH_SYMBOL_RESOLVE_LEVEL resolveLevel;
    ULONG64 displacement;
    PPH_STRING modFileName = NULL;
    PPH_STRING modBaseName = NULL;
    ULONG64 modBase;
    PPH_STRING symbolName = NULL;

    if (Address == 0)
    {
        if (ResolveLevel) *ResolveLevel = PhsrlInvalid;
        if (FileName) *FileName = NULL;
        if (SymbolName) *SymbolName = NULL;
        if (Displacement) *Displacement = 0;

        return NULL;
    }

    PhpRegisterSymbolProvider(SymbolProvider);

    if (!SymFromAddrW_I && !SymFromAddr_I)
        return NULL;

    symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2);
    memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
    symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
    symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;

    // Get the symbol name.

    PH_LOCK_SYMBOLS();

    // Note that we don't care whether this call succeeds or not, based on the assumption that it
    // will not write to the symbolInfo structure if it fails. We've already zeroed the structure,
    // so we can deal with it.

    if (SymFromAddrW_I)
    {
        SymFromAddrW_I(
            SymbolProvider->ProcessHandle,
            Address,
            &displacement,
            symbolInfo
            );
        nameLength = symbolInfo->NameLen;

        if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN)
        {
            PhFree(symbolInfo);
            symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2);
            memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
            symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
            symbolInfo->MaxNameLen = nameLength + 1;

            SymFromAddrW_I(
                SymbolProvider->ProcessHandle,
                Address,
                &displacement,
                symbolInfo
                );
        }
    }
    else if (SymFromAddr_I)
    {
        PSYMBOL_INFO symbolInfoA;

        symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN);
        memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
        symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
        symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;

        SymFromAddr_I(
            SymbolProvider->ProcessHandle,
            Address,
            &displacement,
            symbolInfoA
            );
        nameLength = symbolInfoA->NameLen;

        if (nameLength + 1 > PH_MAX_SYMBOL_NAME_LEN)
        {
            PhFree(symbolInfoA);
            symbolInfoA = PhAllocate(FIELD_OFFSET(SYMBOL_INFO, Name) + nameLength + 1);
            memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
            symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
            symbolInfoA->MaxNameLen = nameLength + 1;

            SymFromAddr_I(
                SymbolProvider->ProcessHandle,
                Address,
                &displacement,
                symbolInfoA
                );

            // Also reallocate the Unicode-based buffer.
            PhFree(symbolInfo);
            symbolInfo = PhAllocate(FIELD_OFFSET(SYMBOL_INFOW, Name) + nameLength * 2 + 2);
            memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
            symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
            symbolInfo->MaxNameLen = nameLength + 1;
        }

        PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA);
        PhFree(symbolInfoA);
    }

    PH_UNLOCK_SYMBOLS();

    // Find the module name.

    if (symbolInfo->ModBase == 0)
    {
        modBase = PhGetModuleFromAddress(
            SymbolProvider,
            Address,
            &modFileName
            );
    }
    else
    {
        PH_SYMBOL_MODULE lookupSymbolModule;
        PPH_AVL_LINKS existingLinks;
        PPH_SYMBOL_MODULE symbolModule;

        lookupSymbolModule.BaseAddress = symbolInfo->ModBase;

        PhAcquireQueuedLockShared(&SymbolProvider->ModulesListLock);

        existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);

        if (existingLinks)
        {
            symbolModule = CONTAINING_RECORD(existingLinks, PH_SYMBOL_MODULE, Links);
            PhSetReference(&modFileName, symbolModule->FileName);
        }

        PhReleaseQueuedLockShared(&SymbolProvider->ModulesListLock);
    }

    // If we don't have a module name, return an address.
    if (!modFileName)
    {
        resolveLevel = PhsrlAddress;
        symbol = PhCreateStringEx(NULL, PH_PTR_STR_LEN * 2);
        PhPrintPointer(symbol->Buffer, (PVOID)Address);
        PhTrimToNullTerminatorString(symbol);

        goto CleanupExit;
    }

    modBaseName = PhGetBaseName(modFileName);

    // If we have a module name but not a symbol name, return the module plus an offset:
    // module+offset.

    if (symbolInfo->NameLen == 0)
    {
        PH_FORMAT format[3];

        resolveLevel = PhsrlModule;

        PhInitFormatSR(&format[0], modBaseName->sr);
        PhInitFormatS(&format[1], L"+0x");
        PhInitFormatIX(&format[2], (ULONG_PTR)(Address - modBase));
        symbol = PhFormat(format, 3, modBaseName->Length + 6 + 32);

        goto CleanupExit;
    }

    // If we have everything, return the full symbol name: module!symbol+offset.

    symbolName = PhCreateStringEx(symbolInfo->Name, symbolInfo->NameLen * 2);
    resolveLevel = PhsrlFunction;

    if (displacement == 0)
    {
        PH_FORMAT format[3];

        PhInitFormatSR(&format[0], modBaseName->sr);
        PhInitFormatC(&format[1], '!');
        PhInitFormatSR(&format[2], symbolName->sr);

        symbol = PhFormat(format, 3, modBaseName->Length + 2 + symbolName->Length);
    }
    else
    {
        PH_FORMAT format[5];

        PhInitFormatSR(&format[0], modBaseName->sr);
        PhInitFormatC(&format[1], '!');
        PhInitFormatSR(&format[2], symbolName->sr);
        PhInitFormatS(&format[3], L"+0x");
        PhInitFormatIX(&format[4], (ULONG_PTR)displacement);

        symbol = PhFormat(format, 5, modBaseName->Length + 2 + symbolName->Length + 6 + 32);
    }

CleanupExit:

    if (ResolveLevel)
        *ResolveLevel = resolveLevel;
    if (FileName)
        PhSetReference(FileName, modFileName);
    if (SymbolName)
        PhSetReference(SymbolName, symbolName);
    if (Displacement)
        *Displacement = displacement;

    PhClearReference(&modFileName);
    PhClearReference(&modBaseName);
    PhClearReference(&symbolName);
    PhFree(symbolInfo);

    return symbol;
}

BOOLEAN PhGetSymbolFromName(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ PWSTR Name,
    _Out_ PPH_SYMBOL_INFORMATION Information
    )
{
    PSYMBOL_INFOW symbolInfo;
    UCHAR symbolInfoBuffer[FIELD_OFFSET(SYMBOL_INFOW, Name) + PH_MAX_SYMBOL_NAME_LEN * 2];
    BOOL result;

    PhpRegisterSymbolProvider(SymbolProvider);

    if (!SymFromNameW_I && !SymFromName_I)
        return FALSE;

    symbolInfo = (PSYMBOL_INFOW)symbolInfoBuffer;
    memset(symbolInfo, 0, sizeof(SYMBOL_INFOW));
    symbolInfo->SizeOfStruct = sizeof(SYMBOL_INFOW);
    symbolInfo->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;

    // Get the symbol information.

    PH_LOCK_SYMBOLS();

    if (SymFromNameW_I)
    {
        result = SymFromNameW_I(
            SymbolProvider->ProcessHandle,
            Name,
            symbolInfo
            );
    }
    else if (SymFromName_I)
    {
        UCHAR buffer[FIELD_OFFSET(SYMBOL_INFO, Name) + PH_MAX_SYMBOL_NAME_LEN];
        PSYMBOL_INFO symbolInfoA;
        PPH_BYTES name;

        symbolInfoA = (PSYMBOL_INFO)buffer;
        memset(symbolInfoA, 0, sizeof(SYMBOL_INFO));
        symbolInfoA->SizeOfStruct = sizeof(SYMBOL_INFO);
        symbolInfoA->MaxNameLen = PH_MAX_SYMBOL_NAME_LEN;

        name = PhConvertUtf16ToMultiByte(Name);

        if (result = SymFromName_I(
            SymbolProvider->ProcessHandle,
            name->Buffer,
            symbolInfoA
            ))
        {
            PhpSymbolInfoAnsiToUnicode(symbolInfo, symbolInfoA);
        }

        PhDereferenceObject(name);
    }
    else
    {
        result = FALSE;
    }

    PH_UNLOCK_SYMBOLS();

    if (!result)
        return FALSE;

    Information->Address = symbolInfo->Address;
    Information->ModuleBase = symbolInfo->ModBase;
    Information->Index = symbolInfo->Index;
    Information->Size = symbolInfo->Size;

    return TRUE;
}

BOOLEAN PhLoadModuleSymbolProvider(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ PWSTR FileName,
    _In_ ULONG64 BaseAddress,
    _In_ ULONG Size
    )
{
    ULONG64 baseAddress;
    PPH_SYMBOL_MODULE symbolModule = NULL;
    PPH_AVL_LINKS existingLinks;
    PH_SYMBOL_MODULE lookupSymbolModule;

    PhpRegisterSymbolProvider(SymbolProvider);

    if (!SymLoadModuleExW_I && !SymLoadModule64_I)
        return FALSE;

    // Check for duplicates. It is better to do this before calling SymLoadModuleExW, because it
    // seems to force symbol loading when it is called twice on the same module even if deferred
    // loading is enabled.
    PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock);
    lookupSymbolModule.BaseAddress = BaseAddress;
    existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);
    PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock);

    if (existingLinks)
        return TRUE;

    PH_LOCK_SYMBOLS();

    if (SymLoadModuleExW_I)
    {
        baseAddress = SymLoadModuleExW_I(
            SymbolProvider->ProcessHandle,
            NULL,
            FileName,
            NULL,
            BaseAddress,
            Size,
            NULL,
            0
            );
    }
    else
    {
        PPH_BYTES fileName;

        fileName = PhConvertUtf16ToMultiByte(FileName);
        baseAddress = SymLoadModule64_I(
            SymbolProvider->ProcessHandle,
            NULL,
            fileName->Buffer,
            NULL,
            BaseAddress,
            Size
            );
        PhDereferenceObject(fileName);
    }

    PH_UNLOCK_SYMBOLS();

    // Add the module to the list, even if we couldn't load symbols for the module.

    PhAcquireQueuedLockExclusive(&SymbolProvider->ModulesListLock);

    // Check for duplicates again.
    lookupSymbolModule.BaseAddress = BaseAddress;
    existingLinks = PhFindElementAvlTree(&SymbolProvider->ModulesSet, &lookupSymbolModule.Links);

    if (!existingLinks)
    {
        symbolModule = PhAllocate(sizeof(PH_SYMBOL_MODULE));
        symbolModule->BaseAddress = BaseAddress;
        symbolModule->Size = Size;
        symbolModule->FileName = PhGetFullPath(FileName, &symbolModule->BaseNameIndex);

        existingLinks = PhAddElementAvlTree(&SymbolProvider->ModulesSet, &symbolModule->Links);
        assert(!existingLinks);
        InsertTailList(&SymbolProvider->ModulesListHead, &symbolModule->ListEntry);
    }

    PhReleaseQueuedLockExclusive(&SymbolProvider->ModulesListLock);

    if (!baseAddress)
    {
        if (GetLastError() != ERROR_SUCCESS)
            return FALSE;
        else
            return TRUE;
    }

    return TRUE;
}

VOID PhSetOptionsSymbolProvider(
    _In_ ULONG Mask,
    _In_ ULONG Value
    )
{
    ULONG options;

    PhpRegisterSymbolProvider(NULL);

    if (!SymGetOptions_I || !SymSetOptions_I)
        return;

    PH_LOCK_SYMBOLS();

    options = SymGetOptions_I();
    options &= ~Mask;
    options |= Value;
    SymSetOptions_I(options);

    PH_UNLOCK_SYMBOLS();
}

VOID PhSetSearchPathSymbolProvider(
    _In_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ PWSTR Path
    )
{
    PhpRegisterSymbolProvider(SymbolProvider);

    if (!SymSetSearchPathW_I && !SymSetSearchPath_I)
        return;

    PH_LOCK_SYMBOLS();

    if (SymSetSearchPathW_I)
    {
        SymSetSearchPathW_I(SymbolProvider->ProcessHandle, Path);
    }
    else if (SymSetSearchPath_I)
    {
        PPH_BYTES path;

        path = PhConvertUtf16ToMultiByte(Path);
        SymSetSearchPath_I(SymbolProvider->ProcessHandle, path->Buffer);
        PhDereferenceObject(path);
    }

    PH_UNLOCK_SYMBOLS();
}

#ifdef _WIN64

NTSTATUS PhpLookupDynamicFunctionTable(
    _In_ HANDLE ProcessHandle,
    _In_ ULONG64 Address,
    _Out_opt_ PDYNAMIC_FUNCTION_TABLE *FunctionTableAddress,
    _Out_opt_ PDYNAMIC_FUNCTION_TABLE FunctionTable,
    _Out_writes_bytes_opt_(OutOfProcessCallbackDllBufferSize) PWCHAR OutOfProcessCallbackDllBuffer,
    _In_ ULONG OutOfProcessCallbackDllBufferSize,
    _Out_opt_ PUNICODE_STRING OutOfProcessCallbackDllString
    )
{
    NTSTATUS status;
    PLIST_ENTRY (NTAPI *rtlGetFunctionTableListHead)(VOID);
    PLIST_ENTRY tableListHead;
    LIST_ENTRY tableListHeadEntry;
    PLIST_ENTRY tableListEntry;
    PDYNAMIC_FUNCTION_TABLE functionTableAddress;
    DYNAMIC_FUNCTION_TABLE functionTable;
    ULONG count;
    SIZE_T numberOfBytesRead;
    ULONG i;
    BOOLEAN foundNull;

    rtlGetFunctionTableListHead = PhGetModuleProcAddress(L"ntdll.dll", "RtlGetFunctionTableListHead");

    if (!rtlGetFunctionTableListHead)
        return STATUS_PROCEDURE_NOT_FOUND;

    tableListHead = rtlGetFunctionTableListHead();

    // Find the function table entry for this address.

    if (!NT_SUCCESS(status = NtReadVirtualMemory(
        ProcessHandle,
        tableListHead,
        &tableListHeadEntry,
        sizeof(LIST_ENTRY),
        NULL
        )))
        return status;

    tableListEntry = tableListHeadEntry.Flink;
    count = 0; // make sure we can't be forced into an infinite loop by crafted data

    while (tableListEntry != tableListHead && count < PH_ENUM_PROCESS_MODULES_LIMIT)
    {
        functionTableAddress = CONTAINING_RECORD(tableListEntry, DYNAMIC_FUNCTION_TABLE, ListEntry);

        if (!NT_SUCCESS(status = NtReadVirtualMemory(
            ProcessHandle,
            functionTableAddress,
            &functionTable,
            sizeof(DYNAMIC_FUNCTION_TABLE),
            NULL
            )))
            return status;

        if (Address >= functionTable.MinimumAddress && Address < functionTable.MaximumAddress)
        {
            if (OutOfProcessCallbackDllBuffer)
            {
                if (functionTable.OutOfProcessCallbackDll)
                {
                    // Read the out-of-process callback DLL path. We don't have a length, so we'll
                    // just have to read as much as possible.

                    memset(OutOfProcessCallbackDllBuffer, 0xff, OutOfProcessCallbackDllBufferSize);
                    status = NtReadVirtualMemory(
                        ProcessHandle,
                        functionTable.OutOfProcessCallbackDll,
                        OutOfProcessCallbackDllBuffer,
                        OutOfProcessCallbackDllBufferSize,
                        &numberOfBytesRead
                        );

                    if (status != STATUS_PARTIAL_COPY && !NT_SUCCESS(status))
                        return status;

                    foundNull = FALSE;

                    for (i = 0; i < OutOfProcessCallbackDllBufferSize / sizeof(WCHAR); i++)
                    {
                        if (OutOfProcessCallbackDllBuffer[i] == 0)
                        {
                            foundNull = TRUE;

                            if (OutOfProcessCallbackDllString)
                            {
                                OutOfProcessCallbackDllString->Buffer = OutOfProcessCallbackDllBuffer;
                                OutOfProcessCallbackDllString->Length = (USHORT)(i * sizeof(WCHAR));
                                OutOfProcessCallbackDllString->MaximumLength = OutOfProcessCallbackDllString->Length;
                            }

                            break;
                        }
                    }

                    // If there was no null terminator, then we didn't read the whole string in.
                    // Fail the operation.
                    if (!foundNull)
                        return STATUS_BUFFER_OVERFLOW;
                }
                else
                {
                    OutOfProcessCallbackDllBuffer[0] = 0;

                    if (OutOfProcessCallbackDllString)
                    {
                        OutOfProcessCallbackDllString->Buffer = NULL;
                        OutOfProcessCallbackDllString->Length = 0;
                        OutOfProcessCallbackDllString->MaximumLength = 0;
                    }
                }
            }

            if (FunctionTableAddress)
                *FunctionTableAddress = functionTableAddress;

            if (FunctionTable)
                *FunctionTable = functionTable;

            return STATUS_SUCCESS;
        }

        tableListEntry = functionTable.ListEntry.Flink;
        count++;
    }

    return STATUS_NOT_FOUND;
}

PRUNTIME_FUNCTION PhpLookupFunctionEntry(
    _In_ PRUNTIME_FUNCTION Functions,
    _In_ ULONG NumberOfFunctions,
    _In_ BOOLEAN Sorted,
    _In_ ULONG64 RelativeControlPc
    )
{
    LONG low;
    LONG high;
    ULONG i;

    if (Sorted)
    {
        if (NumberOfFunctions == 0)
            return NULL;

        low = 0;
        high = NumberOfFunctions - 1;

        do
        {
            i = (low + high) / 2;

            if (RelativeControlPc < Functions[i].BeginAddress)
                high = i - 1;
            else if (RelativeControlPc >= Functions[i].EndAddress)
                low = i + 1;
            else
                return &Functions[i];
        } while (low <= high);
    }
    else
    {
        for (i = 0; i < NumberOfFunctions; i++)
        {
            if (RelativeControlPc >= Functions[i].BeginAddress && RelativeControlPc < Functions[i].EndAddress)
                return &Functions[i];
        }
    }

    return NULL;
}

NTSTATUS PhpAccessCallbackFunctionTable(
    _In_ HANDLE ProcessHandle,
    _In_ PVOID FunctionTableAddress,
    _In_ PUNICODE_STRING OutOfProcessCallbackDllString,
    _Out_ PRUNTIME_FUNCTION *Functions,
    _Out_ PULONG NumberOfFunctions
    )
{
    static PH_STRINGREF knownFunctionTableDllsKeyName = PH_STRINGREF_INIT(L"Software\\Microsoft\\Windows NT\\CurrentVersion\\KnownFunctionTableDlls");

    NTSTATUS status;
    HANDLE keyHandle;
    ULONG returnLength;
    PVOID dllHandle;
    ANSI_STRING outOfProcessFunctionTableCallbackName;
    POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK outOfProcessFunctionTableCallback;

    if (!OutOfProcessCallbackDllString->Buffer)
        return STATUS_INVALID_PARAMETER;

    // Don't load DLLs from arbitrary locations. Check if this is a known function table DLL.

    if (!NT_SUCCESS(status = PhOpenKey(
        &keyHandle,
        KEY_READ,
        PH_KEY_LOCAL_MACHINE,
        &knownFunctionTableDllsKeyName,
        0
        )))
        return status;

    status = NtQueryValueKey(keyHandle, OutOfProcessCallbackDllString, KeyValuePartialInformation, NULL, 0, &returnLength);
    NtClose(keyHandle);

    if (status == STATUS_OBJECT_NAME_NOT_FOUND)
        return STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT;

    status = LdrLoadDll(NULL, NULL, OutOfProcessCallbackDllString, &dllHandle);

    if (!NT_SUCCESS(status))
        return status;

    RtlInitAnsiString(&outOfProcessFunctionTableCallbackName, OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME);
    status = LdrGetProcedureAddress(dllHandle, &outOfProcessFunctionTableCallbackName, 0, (PVOID *)&outOfProcessFunctionTableCallback);

    if (NT_SUCCESS(status))
    {
        status = outOfProcessFunctionTableCallback(
            ProcessHandle,
            FunctionTableAddress,
            NumberOfFunctions,
            Functions
            );
    }

    LdrUnloadDll(dllHandle);

    return status;
}

NTSTATUS PhpAccessNormalFunctionTable(
    _In_ HANDLE ProcessHandle,
    _In_ PDYNAMIC_FUNCTION_TABLE FunctionTable,
    _Out_ PRUNTIME_FUNCTION *Functions,
    _Out_ PULONG NumberOfFunctions
    )
{
    NTSTATUS status;
    SIZE_T bufferSize;
    PRUNTIME_FUNCTION functions;

    // Put a reasonable limit on the number of entries we read.
    if (FunctionTable->EntryCount > 0x100000)
        return STATUS_BUFFER_OVERFLOW;

    bufferSize = FunctionTable->EntryCount * sizeof(RUNTIME_FUNCTION);
    functions = PhAllocatePage(bufferSize, NULL);

    if (!functions)
        return STATUS_NO_MEMORY;

    status = NtReadVirtualMemory(ProcessHandle, FunctionTable->FunctionTable, functions, bufferSize, NULL);

    if (NT_SUCCESS(status))
    {
        *Functions = functions;
        *NumberOfFunctions = FunctionTable->EntryCount;
    }
    else
    {
        PhFreePage(functions);
    }

    return status;
}

NTSTATUS PhAccessOutOfProcessFunctionEntry(
    _In_ HANDLE ProcessHandle,
    _In_ ULONG64 ControlPc,
    _Out_ PRUNTIME_FUNCTION Function
    )
{
    NTSTATUS status;
    PDYNAMIC_FUNCTION_TABLE functionTableAddress;
    DYNAMIC_FUNCTION_TABLE functionTable;
    WCHAR outOfProcessCallbackDll[512];
    UNICODE_STRING outOfProcessCallbackDllString;
    PRUNTIME_FUNCTION functions;
    ULONG numberOfFunctions;
    PRUNTIME_FUNCTION function;

    if (!NT_SUCCESS(status = PhpLookupDynamicFunctionTable(
        ProcessHandle,
        ControlPc,
        &functionTableAddress,
        &functionTable,
        outOfProcessCallbackDll,
        sizeof(outOfProcessCallbackDll),
        &outOfProcessCallbackDllString
        )))
    {
        return status;
    }

    if (functionTable.Type == RF_CALLBACK)
    {
        if (!NT_SUCCESS(status = PhpAccessCallbackFunctionTable(
            ProcessHandle,
            functionTableAddress,
            &outOfProcessCallbackDllString,
            &functions,
            &numberOfFunctions
            )))
        {
            return status;
        }

        function = PhpLookupFunctionEntry(functions, numberOfFunctions, FALSE, ControlPc - functionTable.BaseAddress);

        if (function)
            *Function = *function;
        else
            status = STATUS_NOT_FOUND;

        RtlFreeHeap(RtlProcessHeap(), 0, functions);
    }
    else
    {
        if (!NT_SUCCESS(status = PhpAccessNormalFunctionTable(
            ProcessHandle,
            &functionTable,
            &functions,
            &numberOfFunctions
            )))
        {
            return status;
        }

        function = PhpLookupFunctionEntry(functions, numberOfFunctions, functionTable.Type == RF_SORTED, ControlPc - functionTable.BaseAddress);

        if (function)
            *Function = *function;
        else
            status = STATUS_NOT_FOUND;

        PhFreePage(functions);
    }

    return status;
}

#endif

ULONG64 __stdcall PhGetModuleBase64(
    _In_ HANDLE hProcess,
    _In_ DWORD64 dwAddr
    )
{
    ULONG64 base;
#ifdef _WIN64
    DYNAMIC_FUNCTION_TABLE functionTable;
#endif

#ifdef _WIN64
    if (NT_SUCCESS(PhpLookupDynamicFunctionTable(
        hProcess,
        dwAddr,
        NULL,
        &functionTable,
        NULL,
        0,
        NULL
        )))
    {
        base = functionTable.BaseAddress;
    }
    else
    {
        base = 0;
    }
#else
    base = 0;
#endif

    if (base == 0 && SymGetModuleBase64_I)
        base = SymGetModuleBase64_I(hProcess, dwAddr);

    return base;
}

PVOID __stdcall PhFunctionTableAccess64(
    _In_ HANDLE hProcess,
    _In_ DWORD64 AddrBase
    )
{
#ifdef _WIN64
    static RUNTIME_FUNCTION lastRuntimeFunction;
#endif

    PVOID entry;

#ifdef _WIN64
    if (NT_SUCCESS(PhAccessOutOfProcessFunctionEntry(hProcess, AddrBase, &lastRuntimeFunction)))
        entry = &lastRuntimeFunction;
    else
        entry = NULL;
#else
    entry = NULL;
#endif

    if (!entry && SymFunctionTableAccess64_I)
        entry = SymFunctionTableAccess64_I(hProcess, AddrBase);

    return entry;
}

BOOLEAN PhStackWalk(
    _In_ ULONG MachineType,
    _In_ HANDLE ProcessHandle,
    _In_ HANDLE ThreadHandle,
    _Inout_ LPSTACKFRAME64 StackFrame,
    _Inout_ PVOID ContextRecord,
    _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_opt_ PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
    _In_opt_ PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
    _In_opt_ PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
    _In_opt_ PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
    )
{
    BOOLEAN result;

    PhpRegisterSymbolProvider(SymbolProvider);

    if (!StackWalk64_I)
        return FALSE;

    if (!FunctionTableAccessRoutine)
    {
        if (MachineType == IMAGE_FILE_MACHINE_AMD64)
            FunctionTableAccessRoutine = PhFunctionTableAccess64;
        else
            FunctionTableAccessRoutine = SymFunctionTableAccess64_I;
    }

    if (!GetModuleBaseRoutine)
    {
        if (MachineType == IMAGE_FILE_MACHINE_AMD64)
            GetModuleBaseRoutine = PhGetModuleBase64;
        else
            GetModuleBaseRoutine = SymGetModuleBase64_I;
    }

    PH_LOCK_SYMBOLS();
    result = StackWalk64_I(
        MachineType,
        ProcessHandle,
        ThreadHandle,
        StackFrame,
        ContextRecord,
        ReadMemoryRoutine,
        FunctionTableAccessRoutine,
        GetModuleBaseRoutine,
        TranslateAddress
        );
    PH_UNLOCK_SYMBOLS();

    return result;
}

BOOLEAN PhWriteMiniDumpProcess(
    _In_ HANDLE ProcessHandle,
    _In_ HANDLE ProcessId,
    _In_ HANDLE FileHandle,
    _In_ MINIDUMP_TYPE DumpType,
    _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
    _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
    _In_opt_ PMINIDUMP_CALLBACK_INFORMATION CallbackParam
    )
{
    PhpRegisterSymbolProvider(NULL);

    if (!MiniDumpWriteDump_I)
    {
        SetLastError(ERROR_PROC_NOT_FOUND);
        return FALSE;
    }

    return MiniDumpWriteDump_I(
        ProcessHandle,
        HandleToUlong(ProcessId),
        FileHandle,
        DumpType,
        ExceptionParam,
        UserStreamParam,
        CallbackParam
        );
}

/**
 * Converts a STACKFRAME64 structure to a PH_THREAD_STACK_FRAME structure.
 *
 * \param StackFrame64 A pointer to the STACKFRAME64 structure to convert.
 * \param Flags Flags to set in the resulting structure.
 * \param ThreadStackFrame A pointer to the resulting PH_THREAD_STACK_FRAME structure.
 */
VOID PhpConvertStackFrame(
    _In_ STACKFRAME64 *StackFrame64,
    _In_ ULONG Flags,
    _Out_ PPH_THREAD_STACK_FRAME ThreadStackFrame
    )
{
    ULONG i;

    ThreadStackFrame->PcAddress = (PVOID)StackFrame64->AddrPC.Offset;
    ThreadStackFrame->ReturnAddress = (PVOID)StackFrame64->AddrReturn.Offset;
    ThreadStackFrame->FrameAddress = (PVOID)StackFrame64->AddrFrame.Offset;
    ThreadStackFrame->StackAddress = (PVOID)StackFrame64->AddrStack.Offset;
    ThreadStackFrame->BStoreAddress = (PVOID)StackFrame64->AddrBStore.Offset;

    for (i = 0; i < 4; i++)
        ThreadStackFrame->Params[i] = (PVOID)StackFrame64->Params[i];

    ThreadStackFrame->Flags = Flags;

    if (StackFrame64->FuncTableEntry)
        ThreadStackFrame->Flags |= PH_THREAD_STACK_FRAME_FPO_DATA_PRESENT;
}

/**
 * Walks a thread's stack.
 *
 * \param ThreadHandle A handle to a thread. The handle must have THREAD_QUERY_LIMITED_INFORMATION,
 * THREAD_GET_CONTEXT and THREAD_SUSPEND_RESUME access. The handle can have any access for kernel
 * stack walking.
 * \param ProcessHandle A handle to the thread's parent process. The handle must have
 * PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access. If a symbol provider is being used, pass
 * its process handle and specify the symbol provider in \a SymbolProvider.
 * \param ClientId The client ID identifying the thread.
 * \param SymbolProvider The associated symbol provider.
 * \param Flags A combination of flags.
 * \li \c PH_WALK_I386_STACK Walks the x86 stack. On AMD64 systems this flag walks the WOW64 stack.
 * \li \c PH_WALK_AMD64_STACK Walks the AMD64 stack. On x86 systems this flag is ignored.
 * \li \c PH_WALK_KERNEL_STACK Walks the kernel stack. This flag is ignored if there is no active
 * KProcessHacker connection.
 * \param Callback A callback function which is executed for each stack frame.
 * \param Context A user-defined value to pass to the callback function.
 */
NTSTATUS PhWalkThreadStack(
    _In_ HANDLE ThreadHandle,
    _In_opt_ HANDLE ProcessHandle,
    _In_opt_ PCLIENT_ID ClientId,
    _In_opt_ PPH_SYMBOL_PROVIDER SymbolProvider,
    _In_ ULONG Flags,
    _In_ PPH_WALK_THREAD_STACK_CALLBACK Callback,
    _In_opt_ PVOID Context
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN suspended = FALSE;
    BOOLEAN processOpened = FALSE;
    BOOLEAN isCurrentThread = FALSE;
    BOOLEAN isSystemThread = FALSE;
    THREAD_BASIC_INFORMATION basicInfo;

    // Open a handle to the process if we weren't given one.
    if (!ProcessHandle)
    {
        if (KphIsConnected() || !ClientId)
        {
            if (!NT_SUCCESS(status = PhOpenThreadProcess(
                ThreadHandle,
                PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                &ProcessHandle
                )))
                return status;
        }
        else
        {
            if (!NT_SUCCESS(status = PhOpenProcess(
                &ProcessHandle,
                PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
                ClientId->UniqueProcess
                )))
                return status;
        }

        processOpened = TRUE;
    }

    // Determine if the caller specified the current thread.
    if (ClientId)
    {
        if (ClientId->UniqueThread == NtCurrentTeb()->ClientId.UniqueThread)
            isCurrentThread = TRUE;
        if (ClientId->UniqueProcess == SYSTEM_PROCESS_ID)
            isSystemThread = TRUE;
    }
    else
    {
        if (ThreadHandle == NtCurrentThread())
        {
            isCurrentThread = TRUE;
        }
        else if (NT_SUCCESS(PhGetThreadBasicInformation(ThreadHandle, &basicInfo)))
        {
            if (basicInfo.ClientId.UniqueThread == NtCurrentTeb()->ClientId.UniqueThread)
                isCurrentThread = TRUE;
            if (basicInfo.ClientId.UniqueProcess == SYSTEM_PROCESS_ID)
                isSystemThread = TRUE;
        }
    }

    // Make sure this isn't a kernel-mode thread.
    if (!isSystemThread)
    {
        PVOID startAddress;

        if (NT_SUCCESS(NtQueryInformationThread(ThreadHandle, ThreadQuerySetWin32StartAddress,
            &startAddress, sizeof(PVOID), NULL)))
        {
            if ((ULONG_PTR)startAddress > PhSystemBasicInformation.MaximumUserModeAddress)
                isSystemThread = TRUE;
        }
    }

    // Suspend the thread to avoid inaccurate results. Don't suspend if we're walking the stack of
    // the current thread or this is a kernel-mode thread.
    if (!isCurrentThread && !isSystemThread)
    {
        if (NT_SUCCESS(NtSuspendThread(ThreadHandle, NULL)))
            suspended = TRUE;
    }

    // Kernel stack walk.
    if ((Flags & PH_WALK_KERNEL_STACK) && KphIsConnected())
    {
        PVOID stack[256 - 2]; // See MAX_STACK_DEPTH
        ULONG capturedFrames;
        ULONG i;

        if (NT_SUCCESS(KphCaptureStackBackTraceThread(
            ThreadHandle,
            1,
            sizeof(stack) / sizeof(PVOID),
            stack,
            &capturedFrames,
            NULL
            )))
        {
            PH_THREAD_STACK_FRAME threadStackFrame;

            memset(&threadStackFrame, 0, sizeof(PH_THREAD_STACK_FRAME));

            for (i = 0; i < capturedFrames; i++)
            {
                threadStackFrame.PcAddress = stack[i];
                threadStackFrame.Flags = PH_THREAD_STACK_FRAME_KERNEL;

                if (!Callback(&threadStackFrame, Context))
                {
                    goto ResumeExit;
                }
            }
        }
    }

#ifdef _WIN64
    if (Flags & PH_WALK_AMD64_STACK)
    {
        STACKFRAME64 stackFrame;
        PH_THREAD_STACK_FRAME threadStackFrame;
        CONTEXT context;

        context.ContextFlags = CONTEXT_ALL;

        if (!NT_SUCCESS(status = NtGetContextThread(
            ThreadHandle,
            &context
            )))
            goto SkipAmd64Stack;

        memset(&stackFrame, 0, sizeof(STACKFRAME64));
        stackFrame.AddrPC.Mode = AddrModeFlat;
        stackFrame.AddrPC.Offset = context.Rip;
        stackFrame.AddrStack.Mode = AddrModeFlat;
        stackFrame.AddrStack.Offset = context.Rsp;
        stackFrame.AddrFrame.Mode = AddrModeFlat;
        stackFrame.AddrFrame.Offset = context.Rbp;

        while (TRUE)
        {
            if (!PhStackWalk(
                IMAGE_FILE_MACHINE_AMD64,
                ProcessHandle,
                ThreadHandle,
                &stackFrame,
                &context,
                SymbolProvider,
                NULL,
                NULL,
                NULL,
                NULL
                ))
                break;

            // If we have an invalid instruction pointer, break.
            if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1)
                break;

            // Convert the stack frame and execute the callback.

            PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_AMD64, &threadStackFrame);

            if (!Callback(&threadStackFrame, Context))
                goto ResumeExit;
        }
    }

SkipAmd64Stack:
#endif

    // x86/WOW64 stack walk.
    if (Flags & PH_WALK_I386_STACK)
    {
        STACKFRAME64 stackFrame;
        PH_THREAD_STACK_FRAME threadStackFrame;
#ifndef _WIN64
        CONTEXT context;

        context.ContextFlags = CONTEXT_ALL;

        if (!NT_SUCCESS(status = NtGetContextThread(
            ThreadHandle,
            &context
            )))
            goto SkipI386Stack;
#else
        WOW64_CONTEXT context;

        context.ContextFlags = WOW64_CONTEXT_ALL;

        if (!NT_SUCCESS(status = NtQueryInformationThread(
            ThreadHandle,
            ThreadWow64Context,
            &context,
            sizeof(WOW64_CONTEXT),
            NULL
            )))
            goto SkipI386Stack;
#endif

        memset(&stackFrame, 0, sizeof(STACKFRAME64));
        stackFrame.AddrPC.Mode = AddrModeFlat;
        stackFrame.AddrPC.Offset = context.Eip;
        stackFrame.AddrStack.Mode = AddrModeFlat;
        stackFrame.AddrStack.Offset = context.Esp;
        stackFrame.AddrFrame.Mode = AddrModeFlat;
        stackFrame.AddrFrame.Offset = context.Ebp;

        while (TRUE)
        {
            if (!PhStackWalk(
                IMAGE_FILE_MACHINE_I386,
                ProcessHandle,
                ThreadHandle,
                &stackFrame,
                &context,
                SymbolProvider,
                NULL,
                NULL,
                NULL,
                NULL
                ))
                break;

            // If we have an invalid instruction pointer, break.
            if (!stackFrame.AddrPC.Offset || stackFrame.AddrPC.Offset == -1)
                break;

            // Convert the stack frame and execute the callback.

            PhpConvertStackFrame(&stackFrame, PH_THREAD_STACK_FRAME_I386, &threadStackFrame);

            if (!Callback(&threadStackFrame, Context))
                goto ResumeExit;

            // (x86 only) Allow the user to change Eip, Esp and Ebp.
            context.Eip = PtrToUlong(threadStackFrame.PcAddress);
            stackFrame.AddrPC.Offset = PtrToUlong(threadStackFrame.PcAddress);
            context.Ebp = PtrToUlong(threadStackFrame.FrameAddress);
            stackFrame.AddrFrame.Offset = PtrToUlong(threadStackFrame.FrameAddress);
            context.Esp = PtrToUlong(threadStackFrame.StackAddress);
            stackFrame.AddrStack.Offset = PtrToUlong(threadStackFrame.StackAddress);
        }
    }

SkipI386Stack:

ResumeExit:
    if (suspended)
        NtResumeThread(ThreadHandle, NULL);

    if (processOpened)
        NtClose(ProcessHandle);

    return status;
}
