Reading call stack from Windows crash dump in C++
Asked Answered
C

0

0

I am trying to read a dump to get the call stack where a crash happened.

1st Scenario - When reading and writing the dump file in the same application, I am getting the right call stack from the dump file.

2nd Scenario - When the crash dump is created in one application and the dump file is read in another application, my code is not working in this scenario.

Below code snippet works in the first scenario, but does not work in the second scenario.

// Define a callback function to process the stack frames.
HANDLE hThread ;
BOOL CALLBACK MyStackWalkProc(
    _In_ LPVOID pContext,
    _In_ ULONG_PTR dwFrameAddr,
    _In_ ULONG dwFrameNum
)
{
    // Cast the context pointer to a STACKFRAME pointer.
    LPSTACKFRAME64 pStackFrame = reinterpret_cast<LPSTACKFRAME64>(pContext);

    // Advance to the next stack frame.
    return StackWalk64(
        IMAGE_FILE_MACHINE_I386,     // machine type
        hProcess,         // process handle
        hThread ,          // thread handle
        pStackFrame,                 // current stack frame
        NULL,                        // context pointer (unused)
        NULL,                        // read memory callback (unused)
        SymFunctionTableAccess64,    // function table access callback
        SymGetModuleBase64,          // module base address callback
        NULL                         // address translation callback (unused)
    );
}

// Calls ProcessChunk with each chunk of the file.
void ReadInChunks(const WCHAR* pszFileName) {
    // Offsets must be a multiple of the system's allocation granularity.  We
    // guarantee this by making our view size equal to the allocation granularity.
    SYSTEM_INFO sysinfo = { 0 };
    ::GetSystemInfo(&sysinfo);
    DWORD cbView = sysinfo.dwAllocationGranularity;

    HANDLE hfile = ::CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ,
        NULL, OPEN_EXISTING, 0, NULL);
    if (hfile != INVALID_HANDLE_VALUE) {
        LARGE_INTEGER file_size = { 0 };
        ::GetFileSizeEx(hfile, &file_size);
        const unsigned long long cbFile =
            static_cast<unsigned long long>(file_size.QuadPart);

        HANDLE hmap = ::CreateFileMappingW(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
        if (hmap != NULL)
        {
            const char* pView = static_cast<const char*>(
                ::MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0));
            PVOID strptr = NULL;
            ULONG ssiz = 0;
            PMINIDUMP_DIRECTORY dudir;
            bool res = MiniDumpReadDumpStream((PVOID)pView, ThreadListStream, &dudir, &strptr, &ssiz);
            if (res && strptr != NULL)
            {
                PMINIDUMP_THREAD_LIST tlist = (PMINIDUMP_THREAD_LIST)strptr;
                for (ULONG32 i = 0; i < tlist->NumberOfThreads; i++)
                {
                    ULONG32 ThreadId = tlist->Threads[i].ThreadId;
                    ULONG32 SuspendCount = tlist->Threads[i].SuspendCount;
                    ULONG32 PriorityClass = tlist->Threads[i].PriorityClass;
                    ULONG64 dsiz = tlist->Threads[i].ThreadContext.DataSize;
                    ULONG64 rva = tlist->Threads[i].ThreadContext.Rva;
                    ULONG64 memsta = tlist->Threads[i].Stack.StartOfMemoryRange;
                    ULONG64 memsiz = tlist->Threads[i].Stack.Memory.DataSize;
                    ULONG64 memrva = tlist->Threads[i].Stack.Memory.Rva;

                    std::cout << "ThreadID : " << ThreadId << "\t";
                    std::cout << "SuspendCount : " << SuspendCount << "\t";
                    std::cout << "PriorityClass : " << PriorityClass << "\t";
                    printf("look in debugger %I64x\t%I64x\t%I64x\t%I64x\t%I64x\n",
                        dsiz, rva, memsta, memsiz, memrva);
                }
                _tagSTACKFRAME64 tsf = { 0 };
                printf("%zx\n", sizeof(tsf));
            }
            PVOID excepPtr = NULL;
            bool res1 = MiniDumpReadDumpStream((PVOID)pView, ExceptionStream, &dudir, &excepPtr, &ssiz);
            if (res1 && excepPtr != NULL)
            {
                PMINIDUMP_EXCEPTION_STREAM streamData = (PMINIDUMP_EXCEPTION_STREAM)excepPtr;
                std::cout << "Thread ID: " << streamData->ThreadId << std::endl;
                std::cout << "Reason of exception: " << std::hex << streamData->ExceptionRecord.ExceptionCode << std::endl;

                DWORD  error;
                HANDLE hProcess;

                SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);

                hProcess = GetCurrentProcess();
                std::cout << "Process HANDLE: " << hProcess << std::endl;
                //DWORD processId = GetCurrentProcessId();

                if (!SymInitialize(hProcess, "C:\\Workspace\\Code\\Test\\WriteCrashDump\\Debug", TRUE))
                {
                    // SymInitialize failed
                    error = GetLastError();
                    printf("SymInitialize returned error : %d\n", error);
                    return;
                }
                CHAR path[MAX_PATH];
                if (SymGetSearchPath(hProcess, path, MAX_PATH))
                {
                    std::cout << path << std::endl;
                }

                // Walk the call stack of the specified thread.
                hThread =  OpenThread(THREAD_ALL_ACCESS, FALSE, streamData->ThreadId);//GetCurrentThread();
                std::cout << "Thread HANDLE: " << hProcess << std::endl;
                CONTEXT context;
                memset(&context, 0, sizeof(CONTEXT));
                memcpy(&context, (char*)pView + streamData->ThreadContext.Rva, min(sizeof(context), streamData->ThreadContext.DataSize));
                context.ContextFlags = CONTEXT_FULL;
                //GetThreadContext(hThread, &context);

                STACKFRAME64 stackFrame;
                memset(&stackFrame, 0, sizeof(stackFrame));
                stackFrame.AddrPC.Mode = AddrModeFlat;
                stackFrame.AddrFrame.Mode = AddrModeFlat;
                stackFrame.AddrStack.Mode = AddrModeFlat;
                stackFrame.AddrPC.Offset = context.Eip;
                stackFrame.AddrFrame.Offset = context.Ebp;
                stackFrame.AddrStack.Offset = context.Esp;
                while (true) 
                {
                    if (!MyStackWalkProc(&stackFrame, 0, 0)) {
                        break;
                    }
                    else
                    {
                        IMAGEHLP_LINE64 line;
                        ZeroMemory(&line, sizeof(line));
                        line.SizeOfStruct = sizeof(line);
                        DWORD displacement;
                        SymGetLineFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset, &displacement, &line);

                        DWORD64 functionOffset = stackFrame.AddrPC.Offset;
                        char symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + 256];
                        PIMAGEHLP_SYMBOL64 symbol = (PIMAGEHLP_SYMBOL64)symbolBuffer;
                        symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
                        symbol->MaxNameLength = 256;

                        if (SymGetSymFromAddr64(GetCurrentProcess(), functionOffset, NULL, symbol))
                        {
                            std::cout<<"Function: "<< symbol->Name<< "\t Line no: "<< std::dec<<line.LineNumber << "\t File: "<<line.FileName<<std::endl;
                        }
                    }
                }
                SymCleanup(hProcess);
                CloseHandle(hProcess);
            }

            UnmapViewOfFile(pView);
            ::CloseHandle(hmap);
        }
        ::CloseHandle(hfile);
    }
    else
    {
    std::cout << "File open Error = " << GetLastError() << std::endl;
    }
}

int main()
{
    ReadInChunks((WCHAR*)L"C:\\Path\\To\\v1.0-20230508-122121-12184-12324.dmp");
    std::cout << "Hello World!\n";
}

Please help me to find what I am doing wrong.

Chilcote answered 11/5, 2023 at 5:26 Comment(14)
Is the crashed process still running when the 2nd app reads the dump file? Your code is trying to access a thread described in the dump file, but if the crashed app is not running anymore then that thread no longer exists. Why are you opening the thread at all? You are not using the opened thread handle for anything. Also, the docs for SymInitialize() specifically say DO NOT pass it the process handle from GetCurrentProcess()Barela
Crashed process is not running. I will get crash dump file from client when application will crash in their side, this is 2nd scenario.Chilcote
You are right according to docs for SymInitialize(), I should not use GetCurrentProcess(). It should be HANDLE of the process being debugged. But how can I get this HANDLE?Chilcote
Thread HANDLE will be used in StackWalk64(), I have updated the code please check.Chilcote
Your 2nd process is not acting as a debugger for the 1st process. And AFAICS, the dump file doesn't give you the ID of the crashed process. So, you can't get the process handle. Or the thread handle, for that matter, since the crashed thread no longer exists. Actually, neither is possible, because you are getting the dump file from another machine, so you can't open those handles on your machine anyway. Which means, you can't use StackWalk64() (unless you use a custom ReadMemoryRoutine with a copy of the crashed process' memory dump at the time of the crash)Barela
Thanks for clarifying my doubt, I was also thinking is It would be possible to get HANDLE of crashed process of crashed thread or not. Can you please tell me how can I use ReadMemoryRoutine.Chilcote
Unless you have a time machine, there's not way to read a process' memory for a process that is gone. If you need to analyze a crash dump, why don't you use a tool that knows how to, such as WinDbg?Ketchan
@Ketchan Thanks for reply. Is this possible to get HANDLE from crash dump itself and do analysis? or there is any way to write HANDLE info in crash dump so that we can read it while analysis.Chilcote
The process is history. Even if that process HANDLE were to be stored in the mini dump, it is meaningless now. The mini dump is all you have, but even the tiniest of dumps contains enough information to reconstruct the call stack at the point of failure. If you need more information than is available you will have to change how much information is included. Calling MiniDumpWriteDump, either from custom code or through the system, allows you to include the entire memory of that process, so it's unusual that you want to read the memory after the fact.Ketchan
@Ketchan My purpose is to get crashed call-stack from the mini dump in 2nd scenario. Is there any other way to get it. If you have any sample that would be very helpful.Chilcote
@IInspectable: Handle information in a crash dump is very useful. Why do you say it is unusual to read memory after the fact. Isn't that the whole purpose of a crash dump?Organelle
@ThomasWeller A handle is useful for as long as the referenced kernel object is live. A dead process object isn't live, and the process handle value is no longer of any use. Now the purpose of a crash dump is to capture the (entire) state of a process at the time of failure. It contains all memory (if requested) so that you don't run into a situation where you'd like to read a process' memory when that process no longer exists.Ketchan
@ThomasWeller I think you are right because the same way windbg works, it only takes dump file & symbol file and show the call stack. Do you have any idea how I can read call stack from crash dump?Chilcote
@MayankPrabhakar: I can't tell you how to do it in C++, but it's certainly possible. I use Python with PyKd for such tasks or just WinDbgOrganelle

© 2022 - 2024 — McMap. All rights reserved.