Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Post mortem C++ exception analysis in embedded applications

, 21 Aug 2009
An article on analysing a program exception or software crash.
CallStackTest_src.zip
CallStackTest
CallStackTest
Ensor6 (x86)
Debug
Win32
Debug
Release
DiaTool
DiaTool
DiaTool.csproj.user
Properties
#include "stdafx.h"
#include <algorithm>
#include "ex.h"

extern std::wstring theRootPath;


unsigned long findDeepestFrame(const unsigned long ebp, const unsigned long esp)
{
    // ebp is the top of the searchable area
    // esp is the bottom of the searchable area

    if (!ebp || !esp || esp > ebp)
    {
        RaiseException(EXCEPTION_BREAKPOINT, 0, 0, 0);
    }

    unsigned long deepest = ebp;

    std::list<unsigned long> potential_frames;
    potential_frames.push_back(ebp);
    unsigned long search = ebp - 4;
    while (search >= esp)
    {
        unsigned long candidate = *reinterpret_cast<unsigned long*>(search);
        std::list<unsigned long>::iterator iter = std::find(potential_frames.begin(),potential_frames.end(), candidate);
        if (iter != potential_frames.end())
        {
            potential_frames.push_back(search);
            deepest = search;
            search -= 4;
        } else
        {
            --search;
        }
    }

    return deepest;
}


std::list<unsigned long> findFrames(const unsigned long ebp, const unsigned long esp)
{
    // ebp is the top of the searchable area
    // esp is the bottom of the searchable area

    if (!ebp || !esp || esp > ebp)
    {
        RaiseException(EXCEPTION_BREAKPOINT, 0, 0, 0);
    }

    std::list<unsigned long> frames;
    frames.push_back(ebp);
    unsigned long searchData = ebp - 4;
    while (searchData >= esp)
    {
        unsigned long candidate = *reinterpret_cast<unsigned long*>(searchData);
        std::list<unsigned long>::iterator iter = std::find(frames.begin(), frames.end(), candidate);
        if (iter != frames.end())
        {
            frames.push_back(searchData);
            searchData -= 4;
        } else
        {
            --searchData;
        }
    }

    return frames;
}


std::list<unsigned long> findCodeAddress(std::list<unsigned long> frames)
{
    const unsigned char CALL = 0xE8;
    const unsigned char JMP = 0xE9;

    unsigned long callReturnAddress = 0;
    unsigned char firstInstruction  = 0;
             long firstOffset       = 0;
    unsigned long secondAddress     = 0;
    unsigned char secondInstruction = 0;
             long secondOffset      = 0;
    unsigned long codeAddress       = 0;
    unsigned long n                 = 0;
    unsigned long data              = 0;
    HANDLE process = GetCurrentProcess();

    std::list<unsigned long> code_frames;

    std::list<unsigned long>::iterator iter;
    for (iter = frames.begin(); iter != frames.end(); ++iter)
    {
        // Note ReadProcessMemory is more safe. In case address points to unaccessable memory, we won't crash on it
        //callReturnAddress = *(unsigned long*)(*iter + 4);
        if (ReadProcessMemory(process, (unsigned long*)(*iter + 4), &callReturnAddress, sizeof(unsigned long), &n) == FALSE) continue;

        if (callReturnAddress < 0x00010000) continue;     // 0x00400000 desktop Windows
        if (callReturnAddress > 0x7FFFFFFF) continue;

        //firstInstruction  = *(unsigned char*)(callReturnAddress - 4 - 1); // returnAddress=4, instruction=1
        //firstOffset       = *(         long*)(callReturnAddress - 4 - 0); // returnAddress=4
        if (ReadProcessMemory(process, (unsigned char*)(callReturnAddress - 4 - 1), &firstInstruction, sizeof(unsigned char), &n) == FALSE) continue;
        if (ReadProcessMemory(process, (         long*)(callReturnAddress - 4 - 0), &firstOffset,      sizeof(         long), &n) == FALSE) continue;

        secondAddress = callReturnAddress + firstOffset;
        if (firstInstruction == CALL)
        {
            secondInstruction   = *(unsigned char*)(secondAddress + 0);
            secondOffset        = *(         long*)(secondAddress + 1);

            if (secondInstruction == JMP)
            {
                codeAddress = secondAddress + secondOffset + 1 + 4;  // JMP to '4byte address'
            } else // real CALL?
            {
                codeAddress = secondAddress;
            }
        } else if (firstInstruction == JMP)  // No CALL, but JMP? (but what is callReturnAddress doing then on stack?)
        {
            // We come here at the first frame. It points to where program should resume after the catch()
            codeAddress = secondAddress;
        } else
        {
            codeAddress = -1;
        }

        if (codeAddress != -1)
        {
            code_frames.push_back(codeAddress);
        }
    }

    return code_frames;
}


std::list<unsigned long> findCodeAddress(const unsigned long ebp, const unsigned long esp)
{
    const unsigned char CALL = 0xE8;
    const unsigned char JMP = 0xE9;

    unsigned long callReturnAddress = 0;
    unsigned char firstInstruction  = 0;
             long firstOffset       = 0;
    unsigned long secondAddress     = 0;
    unsigned char secondInstruction = 0;
             long secondOffset      = 0;
    unsigned long codeAddress       = 0;

    std::list<MODULEENTRY32> modulesList = GetModulesList();

    std::list<unsigned long> potential_code;

    unsigned long search = ebp;
    while (search >= esp)
    {
        --search;
        callReturnAddress = *(unsigned long*)(search);
        if (callReturnAddress < 0x00010000) continue;  // 0x00400000 desktop Windows
        if (callReturnAddress > 0x7FFFFFFF) continue;

        if (IsValidCodeMemory(callReturnAddress - 4 - 1, modulesList) == false) continue;
        if (IsValidCodeMemory(callReturnAddress - 4 - 0, modulesList) == false) continue;
        
        firstInstruction  = *(unsigned char*)(callReturnAddress - 4 - 1); // returnAddress=4, instruction=1
        firstOffset       = *(         long*)(callReturnAddress - 4 - 0); // returnAddress=4

        secondAddress = callReturnAddress + firstOffset;
        if (firstInstruction == CALL)
        {
            secondInstruction   = *(unsigned char*)(secondAddress + 0);
            secondOffset        = *(         long*)(secondAddress + 1);

            if (secondInstruction == JMP)
            {
                codeAddress = secondAddress + secondOffset + 1 + 4;  // JMP to '4byte address'
            } else // real CALL?
            {
                codeAddress = secondAddress;
            }
        } else if (firstInstruction == JMP)  // No CALL, but JMP? (but what is callReturnAddress doing then on stack?)
        {
            // We come here at the first frame. It points to where program should resume after the catch()
            codeAddress = secondAddress;
        } else
        {
            codeAddress = -1;
        }

        if (codeAddress != -1)
        {
            potential_code.push_back(codeAddress);
        }
    }

    return potential_code;
}


std::list<MODULEENTRY32> GetModulesList()
{
    bool ok = false;
    HANDLE snapShot = INVALID_HANDLE_VALUE;
    std::list<MODULEENTRY32> listOfModules;
    std::list<PROCESSENTRY32> listOfProcesses;

    // Need to use __try __except on ToolHelp API.
    // If a process is being destroyed (shutdown), the API crashes (AV on NULL pointer)
    // Can use try catch if /EHa compiler settings is used
//  __try
    try
    {
        // Clear previous list
        listOfModules.clear();

        snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (snapShot != INVALID_HANDLE_VALUE)
        {
            PROCESSENTRY32 processEntry;
            processEntry.dwSize = sizeof(PROCESSENTRY32);
            BOOL ret = Process32First(snapShot, &processEntry);
            while (ret == TRUE)
            {
                listOfProcesses.push_back(processEntry);
#if defined(UNDER_CE)
                // By default the current exe is not in the MODULES list under CE,
                // so we have to find the info elsewhere
                if (processEntry.th32ProcessID == GetCurrentProcessId())
                {
                    MODULEENTRY32 moduleEntry;
                    moduleEntry.dwSize = sizeof(MODULEENTRY32);
                    moduleEntry.modBaseAddr = (BYTE*)processEntry.th32MemoryBase;
                    moduleEntry.modBaseSize = 0x10000000 - (unsigned long)moduleEntry.modBaseAddr;   // Not correct, but will work in Windows CE 6.0
                    moduleEntry.th32ModuleID = processEntry.th32ModuleID;
                    moduleEntry.th32ProcessID = processEntry.th32ProcessID;
                    moduleEntry.hModule = 0;
                    wcscpy_s(moduleEntry.szModule, MAX_PATH, processEntry.szExeFile);
                    listOfModules.push_back(moduleEntry);
                }
#endif

                ret = Process32Next(snapShot, &processEntry);
            }
#if defined(UNDER_CE)
            CloseToolhelp32Snapshot(snapShot);
#endif
        }

        snapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);

        if (snapShot != INVALID_HANDLE_VALUE)
        {
            // Build new list
            MODULEENTRY32 moduleEntry;
            moduleEntry.dwSize = sizeof(MODULEENTRY32);
            BOOL ret = Module32First(snapShot, &moduleEntry);
            while (ret == TRUE)
            {
                listOfModules.push_back(moduleEntry);
                ret = Module32Next(snapShot, &moduleEntry);
            }
#if defined(UNDER_CE)
            CloseToolhelp32Snapshot(snapShot);
#endif
        } else
        {
            DWORD err = GetLastError();
            LPVOID lpMsgBuf;
            FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                            NULL,
                            err,
                            0, // Default language
                            (LPTSTR) &lpMsgBuf,
                            0,
                            NULL);
            lpMsgBuf = lpMsgBuf;
        }
        ok = true;
//  } __except ( Filter(GetExceptionCode(), GetExceptionInformation()) )
    } catch (...)
    {
        ok = false;
        if (snapShot != INVALID_HANDLE_VALUE)
        {
#if defined(UNDER_CE)
            CloseToolhelp32Snapshot(snapShot);
#endif
        }
    }

    return listOfModules;
}


bool IsValidCodeMemory(unsigned long address, std::list<MODULEENTRY32> modulesList)
{
    std::list<MODULEENTRY32>::iterator iter;
    for (iter = modulesList.begin(); iter != modulesList.end(); iter++)
    {
        if (((unsigned long)iter->modBaseAddr <= address) && (address < ((unsigned long)iter->modBaseAddr + (unsigned long)iter->modBaseSize)))
        {
            return true;
        }
    }
    return false;
}


std::map<unsigned long, std::wstring> LoadAsm(const wchar_t* filename)
{
    std::map<unsigned long, std::wstring> table;

    errno_t err;
    FILE* file;
    err = _wfopen_s(&file, filename, L"r");

    if (err == 0)
    {
        wchar_t line[512] = L"";
        // search start
        while (fgetws(line, 512, file) != NULL)
        {
            if (wcsstr(line, L"Publics by Value") != 0)
            {
                break;
            }
        }

        // start reading
        wchar_t address[64];
        wchar_t publicsByValue[512];
        wchar_t libObject[128];
        unsigned long rvaBase;

        while (fgetws(line, 512, file) != NULL)
        {
            int n;
            n = swscanf_s(line, L"%s%s%x%s\r\n", address, 64, publicsByValue, 512, &rvaBase, libObject, 128);
            if (n == 4)
            {
                table[rvaBase] = std::wstring(publicsByValue);
            }
        }

        fclose(file);
    }

    return table;
}


std::map<unsigned long, std::wstring> LoadPdb(const wchar_t* filename)
{
    std::map<unsigned long, std::wstring> table;

    errno_t err;
    FILE* file;
    err = _wfopen_s(&file, filename, L"r");

    if (err == 0)
    {
        wchar_t line[512] = L"";
        // search start
        while (fgetws(line, 512, file) != NULL)
        {
            if (wcsstr(line, L"Publics by Value") != 0)
            {
                break;
            }
        }

        // start reading
        wchar_t address[64];
        wchar_t publicsByValue[512];
        wchar_t libObject[128];
        unsigned long rvaBase;

        while (fgetws(line, 512, file) != NULL)
        {
            int n;
            n = swscanf_s(line, L"%s%s%x%s\r\n", address, 64, publicsByValue, 512, &rvaBase, libObject, 128);
            if (n == 4)
            {
                table[rvaBase] = std::wstring(publicsByValue);
            }
        }

        fclose(file);
    }

    return table;
}


unsigned long GetLoadAddress(const wchar_t* filename)
{
    HANDLE process = GetCurrentProcess();
    HMODULE module = GetModuleHandle(filename);
    MODULEINFO moduleInfo;
    BOOL ret = GetModuleInformation(process, module, &moduleInfo, sizeof(MODULEINFO));

    if (ret == 0)
    {
        return 0;
    } else
    {
        return (unsigned long)moduleInfo.lpBaseOfDll;
    }
}


unsigned long GetLoadAddressEx(const wchar_t* filename, std::list<MODULEENTRY32> listOfModules)
{
    std::list<MODULEENTRY32>::iterator iter;
    for (iter = listOfModules.begin(); iter != listOfModules.end(); ++iter)
    {
        if (_wcsicmp(iter->szModule, filename) == 0)
        {
            break;
        }
    }
    if (iter == listOfModules.end())
    {
        return 0;
    } else
    {
        return (unsigned long)iter->modBaseAddr;
    }
}


void Output(std::list<unsigned long> codeAddresses, std::list<MODULEENTRY32> modulesList)
{
    std::wstring path = theRootPath;
    path += std::wstring(L"\\Dump.txt");
    FILE* file = NULL;
    errno_t err = _wfopen_s(&file, path.c_str(), L"w");

    fwprintf(file, L"Start modules dump\n");
    std::list<MODULEENTRY32>::iterator iterModules;
    for (iterModules = modulesList.begin(); iterModules != modulesList.end(); ++iterModules)
    {
        unsigned long modBaseStartAddr = (unsigned long)iterModules->modBaseAddr;
        unsigned long modBaseEndAddr = modBaseStartAddr + (unsigned long)iterModules->modBaseSize;
        const wchar_t* szModule = iterModules->szModule;
        wprintf(L"0x%08X - 0x%08X    %s\n", modBaseStartAddr, modBaseEndAddr, szModule);
        fwprintf(file, L"0x%08X - 0x%08X    %s\n", modBaseStartAddr, modBaseEndAddr, szModule);
    }
    fwprintf(file, L"End modules dump\n");

    fwprintf(file, L"Start stack dump\n");
    std::list<unsigned long>::iterator iterCodeAddresses;
    for (iterCodeAddresses = codeAddresses.begin(); iterCodeAddresses != codeAddresses.end(); ++iterCodeAddresses)
    {
        wprintf(L"0x%08X\n", *iterCodeAddresses);
        fwprintf(file, L"0x%08X\n", *iterCodeAddresses);
    }
    fwprintf(file, L"End stack dump\n");

    fclose(file);
}

void AnalyzeCallStack(const BYTE* pFrame, const BYTE* pStack)
{
    // Adjust pFrame to nearest upper 64K boundary (so we look further back in time)
    BYTE* pStackBase = (BYTE*)(((unsigned long)pFrame & 0xFFFF0000) + 0x0000FFFF);

    // Method 1 using frames
    std::list<unsigned long> frames = findFrames((unsigned long)pFrame, (unsigned long)pStack);
    std::list<unsigned long> codeAddresses = findCodeAddress(frames);
    std::list<MODULEENTRY32> modulesList = GetModulesList();
    Output(codeAddresses, modulesList);

    // Method 2 walking complete stack
//      std::list<unsigned long> codeAddresses2 = findCodeAddress((unsigned long)pFrame, (unsigned long)pStackBase);
//      Output(codeAddresses2, modulesList);

    unsigned long a1 = GetLoadAddress(L"kernel32.dll");
    unsigned long a2 = GetLoadAddressEx(L"kernel32.dll", modulesList);
    unsigned long a3 = GetLoadAddressEx(L"ntdll.dll", modulesList);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Werner Willemsens
Team Leader
Belgium Belgium
No Biography provided

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 21 Aug 2009
Article Copyright 2009 by Werner Willemsens
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid