Windows上使用dump文件调试

dump文件

dump文件记录当前程序运行某一时刻的信息,包括内存,线程,线程栈,变量等等,相当于调试程序时运行到某个断点上,把程序运行的信息记录下来。可以通过Windbg打开dump,查看程序运行的变量等,来调试程序。

在Liunx上也有类似的技术,Coredump,具体可以参考:coredump详解_coredump文件分析_贺二公子的博客-CSDN博客

dump 文件分类

dump可以分为 minidump 和 Full dump

minidump通常只包含了一些关键信息,一般比较小,通常只要几MB,

Full dump包含了程序运行时的所有信息,包括程序的所有内存,一般有几十MB到几GB。

minidump虽然只包含了部分信息,但这些信息大部分情况足够用于调试,所以通常都是使用minidump调试

生成dump文件

通过任务管理器导出

在进程上右击->创建内存转储文件,这样创建的是Full dump

Windows上使用dump文件调试_第1张图片

通过Process Explorer导出

Process Explorer - Sysinternals | Microsoft Learn

选择对应的进程->Process->Create Dump,然后选择要创建minidump 还是 Full Dump

Windows上使用dump文件调试_第2张图片

使用MiniDumpWriteDump函数序生成 

#include 
#include 
#include 
#include 
#pragma comment(lib, "Dbghelp.lib")

void createMinidump()
{
    wchar_t DumpPath[MAX_PATH] = {0};

    SYSTEMTIME SystemTime;
    GetLocalTime(&SystemTime);

    WCHAR szExeFileName[256] = {0};
    GetModuleFileNameW(nullptr, szExeFileName, 99);

    wsprintfW(DumpPath, L"%s_%d-%d-%d_%d-%d-%d.dmp", szExeFileName, SystemTime.wYear, SystemTime.wMonth,
              SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond);

    HANDLE file = CreateFileW(DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (file != INVALID_HANDLE_VALUE)
    {
        DWORD Flags = MiniDumpWithHandleData |
                      MiniDumpWithUnloadedModules |
                      MiniDumpScanMemory|
                      MiniDumpWithIndirectlyReferencedMemory |
                      MiniDumpWithProcessThreadData |
                      MiniDumpWithThreadInfo;

        if (MiniDumpWriteDump(GetCurrentProcess(), (DWORD) GetCurrentProcessId(), file,
                              (MINIDUMP_TYPE) (Flags),
                              nullptr, nullptr, nullptr) != 0)
        {
            std::cout << "Create Minidump successful!! file:";
            std::wcout << DumpPath << std::endl;
        }
        else
        {
            std::cout << "Create Minidump failed!!" << std::endl;
        }
    }

    CloseHandle(file);
}

void createFullDump()
{
    wchar_t DumpPath[MAX_PATH] = {0};

    SYSTEMTIME SystemTime;
    GetLocalTime(&SystemTime);

    WCHAR szExeFileName[256] = {0};
    GetModuleFileNameW(nullptr, szExeFileName, 99);

    wsprintfW(DumpPath, L"%s_%d-%d-%d_%d-%d-%d_full.dmp", szExeFileName, SystemTime.wYear, SystemTime.wMonth,
              SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond);

    HANDLE file = CreateFileW(DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (file != INVALID_HANDLE_VALUE)
    {

        const DWORD Flags = MiniDumpWithFullMemory |
                            MiniDumpWithFullMemoryInfo |
                            MiniDumpWithHandleData |
                            MiniDumpWithUnloadedModules |
                            MiniDumpWithProcessThreadData |
                            MiniDumpWithThreadInfo;

        if (MiniDumpWriteDump(GetCurrentProcess(), (DWORD) GetCurrentProcessId(), file,
                              (MINIDUMP_TYPE) (Flags),
                              nullptr, nullptr, nullptr) != 0)
        {
            std::cout << "Create Full dump successful!! file:";
            std::wcout << DumpPath << std::endl;
        }
        else
        {
            std::cout << "Create Full dump failed!!" << std::endl;
        }
    }

    CloseHandle(file);
}

程序崩溃时自动导出Dump

我们希望程序运行崩溃时可以自动导出dump,这样可以通过分析dump文件找到崩溃原因。

程序崩溃很多情况都是由异常引起的,Windows提供了SetUnhandledExceptionFilter函数用来设置一个函数指针,用于处理未处理的异常,可以在这个函数中导出Dump文件。

步骤:

1. 准备一个处理异常的函数,并在其中导出dump。异常处理函数有一个参数,这个参数记录了当前异常信息,这个异常信息可以一起随dump文件导出,方便后续查找Bug

LONG WINAPI DumpException(EXCEPTION_POINTERS* info)
{
    std::cout << "DumpException, Thread ID:"<< std::this_thread::get_id() << std::endl;
    std::cout << "Exception: 0x" << std::hex << info->ExceptionRecord->ExceptionCode << std::endl;

    wchar_t DumpPath[MAX_PATH] = { 0 };

    SYSTEMTIME SystemTime;
    GetLocalTime(&SystemTime);

    WCHAR szExeFileName[100] = { 0 };
    GetModuleFileNameW(nullptr, szExeFileName, 99);

    wsprintfW(DumpPath, L"%s_%d-%d-%d_%d-%d-%d_crash.dmp", szExeFileName, SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond);

    HANDLE file = CreateFileW(DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

    if (file != INVALID_HANDLE_VALUE)
    {
        MINIDUMP_EXCEPTION_INFORMATION mdei;
        mdei.ThreadId = (DWORD)GetCurrentThreadId();
        mdei.ExceptionPointers = info;
        mdei.ClientPointers = 0;

        DWORD Flags = MiniDumpWithHandleData |
                      MiniDumpWithUnloadedModules |
                      MiniDumpScanMemory|
                      MiniDumpWithIndirectlyReferencedMemory |
                      MiniDumpWithProcessThreadData |
                      MiniDumpWithThreadInfo;

//        Flags = MiniDumpWithFullMemory |
//                MiniDumpWithFullMemoryInfo |
//                MiniDumpWithHandleData |
//                MiniDumpWithUnloadedModules |
//                MiniDumpWithThreadInfo;

        if (MiniDumpWriteDump(GetCurrentProcess(), (DWORD)GetCurrentProcessId(), file,
                              (MINIDUMP_TYPE)(Flags),
                              &mdei, nullptr, nullptr) != 0)
        {
            std::cout << "Create Crash dump successful!! file:";
            std::wcout << DumpPath << std::endl;
            CloseHandle(file);
            return EXCEPTION_EXECUTE_HANDLER;
        }
    }
    std::cout << "Create Crash dump failed!!" << std::endl;
    CloseHandle(file);

    return EXCEPTION_CONTINUE_SEARCH;
}

2. 在程序启动时设置异常处理函数

int main()
{
    std::cout << "Main Thread ID:" << std::this_thread::get_id() << std::endl;

    LPTOP_LEVEL_EXCEPTION_FILTER oldExceptionFilter = nullptr;
    oldExceptionFilter = SetUnhandledExceptionFilter(&DumpException);

    // ... ...
    // ... ...

}

PS

1. 这里是通过异常捕获生成dump,如果是调用abort,exit,TerminateProcess, TerminateThread函数,这些函数会立即结束进程,所以不会生成dump。

2. SetUnhandledExceptionFilter是全局的,只需设置一次,设置后对所有线程有效。SetUnhandledExceptionFilter有些异常捕获不到。

3. 可以使用第三方库捕获崩溃事件,例如:crashrpt,google breakpad,qBreakpad,Crashpad

UnhandledExceptionFilter未处理的异常

Windows中所有的函数都是从BaseThreadStart函数开始运行

VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread((pfnStartAddr)(pvParam));
}
__except (UnhandledExceptionFilter(GetExceptionInformation())) {
ExitProcess(GetExceptionCode());
}
// NOTE: We never get here
}

这里的函数UnhandledExceptionFilter用来处理线程中捕获的未处理的异常,调用SetUnhandledExceptionFilter就是用来设置这个函数。

这里的__try{}__except{} 是 Windows系统的结构化异常处理(SEH),具体参考 《Windows核心编程第五版》——第24章

使用VS调试Dump文件

调试Dump文件,dump文件以外,还需要pdb符号文件,pdb符号文件是编译时和exe程序同时生成的,默认情况下Debug版本会生成符号文件,Release文件不生成符号文件,Release模式下需要手动打开生成符号文件。

打开Dump文件

文件->打开->文件,选择dump文件

Windows上使用dump文件调试_第3张图片

Windows上使用dump文件调试_第4张图片

设置符号文件

符号文件(pdb)必须保证时和exe同时生成的,且不能改文件名,否则会加载失败

有两种方法

1. 把符号文件和dump文件放在同一个目录下,VS在加载dump时会读取dump目录下的符号文件。

2. 通过 【工具->选项->调试->符号】设置符号文件路径。

Windows上使用dump文件调试_第5张图片

调试 

点击右上角的 【使用 混合 进行调试】,则可以查看dump文件的内容,如果有异常会显示对应异常位置。整体效果和调试模式下运行出现异常是一样的。

Windows上使用dump文件调试_第6张图片

 Windows上使用dump文件调试_第7张图片

使用Windbg调试Dump文件

Windbg 下载 Install WinDbg - Windows drivers | Microsoft Learn

Windbg打开dump后会提示是否有异常

Windows上使用dump文件调试_第8张图片

调试步骤:

1. 设置符号文件 【文件->settings->debuging settings】

2. 输入 .ecxr 命令

3. 输入 kn 命令

查看exe编译时间

lm vm test_win*

Windows上使用dump文件调试_第9张图片

通过这个时间可以去查找exe对应的pdb文件 

完整代码例子:小康6650/StudyProject - Gitee.com

你可能感兴趣的:(Windows,C++实战,windows)