void get_thread_stack()

in v2/src/get_thread_stack.c [118:296]


void get_thread_stack(DWORD threadId, char* destination, size_t destinationSize)
{

    /*1) parameter validation*/
    if ((destination == NULL) || (destinationSize == 0))
    {
        /*cannot compute if the output space is not sufficient (invalid args)*/
        return;
    }
    else
    {
        destination[0] = '\0';

        bool firstLine = true; /*only used to insert a \n between stack frames*/

        HANDLE hProcess = GetCurrentProcess();

        /*2) call SymInitialize for the current process*/
        /*lazily call once SymInitialize*/
        LONG state;

        while ((state = InterlockedCompareExchange(&doSymInit.state, SYM_INIT_INITIALIZING, SYM_INIT_NOT_INITIALIZED)) != SYM_INIT_INITIALIZED)
        {
            if (state == SYM_INIT_NOT_INITIALIZED)
            {
                (void)SymInitialize(hProcess, NULL, TRUE); /*this is a process-wide initialization, and will leak because we don't know when to call SymCleanup. It is a good candidate for platform_init*/
                (void)InterlockedExchange(&doSymInit.state, SYM_INIT_INITIALIZED);
            }
        }

        /*3) get hThread's context.*/
        DWORD currentThreadId = GetCurrentThreadId();

bool wasThreadSuspended = false; /*only suspend threads that are not "current" thread to capture their stack frame*/
        bool wasContextAcquired = false;

        CONTEXT context = { 0 };
        context.ContextFlags = CONTEXT_CONTROL;

        HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, threadId);
        if (hThread == NULL)
        {
            /*falling back to console*/
            (void)printf("failure (%" PRIu32 ") in OpenThread(THREAD_GET_CONTEXT, FALSE, threadId=%" PRIx32 ");", GetLastError(), threadId);
        }
        else
        {
            if (currentThreadId == threadId)
            {
                /*3.1) if the current thread is hThread, call RtlCaptureContext*/
                RtlCaptureContext(&context); /*this is GetThreadContext for current thread*/
                wasContextAcquired = true;
            }
            else
            {
                /*3.2) if the current thread is not hThread, call SuspendThread and GetThreadContext*/
                if (SuspendThread(hThread) == (DWORD)-1)
                {
                    snprintf_fallback(&destination, &destinationSize, snprintfFailed, sizeof(snprintfFailed), "%sfailure (GetLastError=%" PRIu32 ") in SuspendThread", firstLine ? (firstLine = false, "") : "\n", GetLastError());
                }
                else
                {
                    if (!GetThreadContext(hThread, &context))
                    {
                        snprintf_fallback(&destination, &destinationSize, snprintfFailed, sizeof(snprintfFailed), "%sfailure (GetLastError=%" PRIu32 ") in GetThreadContext", firstLine ? (firstLine = false, "") : "\n", GetLastError());
                    }
                    else
                    {
                        wasContextAcquired = true;
                    }
                    wasThreadSuspended = true;
                }
            }


            if (!wasContextAcquired)
            {
                /*not much to do without the thread context*/
            }
            else
            {
                /*all following function calls are protected by the same SRW*/
                AcquireSRWLockExclusive(&lockOverSymCalls);
                {
                    STACKFRAME64 stackFrame = { 0 };
                    stackFrame.AddrPC.Offset = context.INSTRUCTION_POINTER_REGISTER;
                    stackFrame.AddrPC.Mode = AddrModeFlat;
                    stackFrame.AddrFrame.Offset = context.FRAME_POINTER_REGISTER;
                    stackFrame.AddrFrame.Mode = AddrModeFlat;
                    stackFrame.AddrStack.Offset = context.STACK_POINTER_REGISTER;
                    stackFrame.AddrStack.Mode = AddrModeFlat;

                    bool thisIsFirstFrame = true; /*used to skip the first frame, which is "us", that is don't display information about "get_thread_stack"*/

                    /*4) once the context has been acquired, call StackWalk64 to get the stack frames. For every frame:*/
                    while (StackWalk64(
                        STACK_WALK_IMAGE_TYPE,
                        hProcess,
                        hThread,
                        &stackFrame,
                        &context,
                        NULL,
                        SymFunctionTableAccess64,
                        SymGetModuleBase64,
                        NULL))
                    {
                        if (thisIsFirstFrame && CAPTURE_TOP_OF_STACK) /*x64 does capture the current frame, x86 apparently does not... ?! really weird frankly. this CAPTURE_TOP_OF_STACK helps with not capturin the top of the stack*/
                        {
                            thisIsFirstFrame = false; /*no printing for the top of the stack, which is "us". us = get_thread_stack*/
                            continue;
                        }

                        DWORD64 displacement = 0;

                        SYMBOL_INFO_EXTENDED buffer;

                        PSYMBOL_INFO pSymbol = &buffer.symbol;

                        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
                        pSymbol->MaxNameLen = MAX_SYM_NAME;

                        const char* function_name;

                        /*4.1) determine the function name*/
                        if (!SymFromAddr(hProcess, stackFrame.AddrPC.Offset, &displacement, pSymbol))
                        {
                            function_name = "failure in SymFromAddr";
                        }
                        else
                        {
                            function_name = pSymbol->Name;
                        }

                        IMAGEHLP_LINE64 line;
                        line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
                        DWORD lineDisplacement;
                        const char* file_name;
                        uint32_t line_number;

                        /*4.2) determine the file name and line number*/
                        if (!SymGetLineFromAddr64(hProcess, stackFrame.AddrPC.Offset, &lineDisplacement, &line))
                        {
                            file_name = "failure in SymGetLineFromAddr64";
                            line_number = 0;
                        }
                        else
                        {
                            file_name = line.FileName;
                            line_number = line.LineNumber;
                        }

                        /*4.3) append the function name, file name and line number to the destination*/
                        snprintf_fallback(&destination, &destinationSize, snprintfFailed, sizeof(snprintfFailed), "%s!%s %s:%" PRIu32 "", firstLine ? (firstLine = false, "") : "\n", function_name, file_name, line_number);
                    }

                    ReleaseSRWLockExclusive(&lockOverSymCalls);
                }
            }

            if (wasThreadSuspended)
            {
                if (ResumeThread(hThread) == (DWORD)-1)
                {
                    /*falling back to console*/
                    (void)printf("failure (%" PRIu32 ") in ResumeThread", GetLastError());
                    return;
                }
            }

            if (!CloseHandle(hThread))
            {
                /*falling back to console*/
                (void)printf("failure (%" PRIu32 ") in CloseHandle(currentThreadHandle=%p)", GetLastError(), hThread);
            }
        }

        destination[destinationSize - 1] = '\0';
    }
}