Click here to Skip to main content
15,886,518 members
Articles / Mobile Apps / Windows Mobile

Post mortem C++ exception analysis in embedded applications

Rate me:
Please Sign up or sign in to vote.
4.96/5 (16 votes)
21 Aug 2009CPOL12 min read 54.2K   309   28  
An article on analysing a program exception or software crash.
#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)


Written By
Team Leader
Belgium Belgium
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions