Windows下dump文件的生成



Windows下Dump文件分为两大类,内核模式Dump和用户模式Dump。内核模式Dump是操作系统创建的崩溃转储,最经典的就是系统蓝屏,这时候会自动创建内核模式的Dump。用户模式Dump进一步可以分为Full Dump和Minidump。Full Dump包含了某个进程完整的地址空间数据,以及许多用于调试的信息,而Minidump则有许多类型,根据需要可以包含不同的信息,有的可能只包含某个线程和部分模块的信息。在程序开发过程中出现的应用崩溃属于用户模式Dump。

Dump文件是进程的内存镜像,与Linux 中的 core 文件类似;往往程序在异常(cpu占用高、死锁、崩溃等)时我们都可以借助dump文件来进行分析,也就是通过查看当时各个线程的内存、堆栈情况,还原“案发现场“。那我们一般怎么能在程序异常的时候得到我们想要的dump文件呢? 本文接下来将介绍四种比较常见的方式;

1、手动生成

这个一般在程序异常但没有退出的情况下我们可以采用手动生成,例如,生成Chrome进程的dump 文件;

注: 使用任务管理器生成转储文件需要遵循一个原则:用32位任务管理器给32位进程(无论该进程是运行在32位还是64位系统上面)生成转储文件,用64位任务管理器给64位进程生成转储文件。在64位系统上,32位的任务管理器位于C:\Windows\SysWOW64\taskmgr.exe

1)打开任务管理器

​      64位默认打开的任务管理器是64位的,如下所示:Windows下dump文件的生成_第1张图片
      如果需要给32位进程生成dump文件,则需要进入C:\Windows\SysWOW64 目录,手动执行taskmgr.exe 程序,如下,任务管理器后面会多出个 32 位:
Windows下dump文件的生成_第2张图片
2)右键选中异常进程,选择创建转储文件,如下即可:
Windows下dump文件的生成_第3张图片
默认路径: C:\Users\用户\AppData\Local\Temp,进入该目录我们就能看到刚才生成的chrome.DMP文件。

注: 这里也不是说必须这样做,如果我们拿到一个64位任务管理器给32位程序转储的dump 文件怎么办呢?这个将会在下一篇《Windows下dump 文件的分析》中作介绍。

2、修改注册表

1) Win + R 输入regedit打开注册表

2)找到注册表路径:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\WindowsError Reporting\LocalDumps

3)在右边窗口新建->字符串值 DumpCount,DumpFolder,DumpType 并修改其值,如下:
Windows下dump文件的生成_第4张图片
这样系统在程序崩溃时就会在C:\CrashDump 文件夹下生成对应程序的dump文件。在实际工作中,我们可以将这些操作写成一个脚本文件,一键完成。

对上面DumpType解释:

0 = Create a custom dump
1 = Mini dump
2 = Full dump


3、Windbg

生成方法:File菜单–>Attach to Process–>输入命令.dump /ma /u d:\test.dmp ,这里我选择的还是chrome.exe 某个进程,如下所示。
Windows下dump文件的生成_第5张图片
提示成功之后,可以在D盘看到生成dmp文件到test_2924_2019-12-24_23-30-09-652_2ab8.dmp文件。
2924_2019-12_24-23-30-09-652_2ab8是/u参数附加上去的,意思是2019年12月24日 23时30分09秒652毫秒,进程PID为2ab8。
.dump命令参数比较多,常用的组合就是/ma,/m表示生成minidump,/a表示dmp包含所有信息,/u参数就是上面说的附加时间和PID信息到文件名。


4、代码生成

一般我们在写程序对能够预期的异常都会作异常捕获或保护处理;但是如果发生了未预期的异常,一般则转由Windows提供的默认异常处理器来进行处理,这个特殊的异常处理函数为UnhandledExceptionFilter(这就是为什么一般情况下我们拿到程序崩溃的文件时,我们需要通过命令去查看哪个线程有UnhandledExceptionFilter异常函数被调用)。该函数会显示一个消息框,提示发生了未处理的异常,同时,让用户选择结束或调试该进程。在了解了这个关系之后,我们就可以通过覆盖默认的异常处理操作,即通过调用函数SetUnhandledExceptionFilter 来完成覆盖,该函数原型如下:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
        _In_ LPTOP_LEVEL_EXCEPTION_FILTER  lpTopLevelExceptionFilter)

lpTopLevelExceptionFilter即异常处理函数指针,如果设置为NULL,则默认使用UnhandledExceptionFilter。因此我们可以对照lpTopLevelExceptionFilter自定义一个异常处理函数;然后在该异常处理函数中再通过调用函数MiniDumpWriteDump来实现程序异常时的内存转储。

LONG WINAPI MyUnhandledExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
{
    SYSTEMTIME st;
    GetLocalTime(&st);
 
    CString time_now = _T("");
    time_now.Format(_T("%04d_%02d_%02d_%02d_%02d_%03d.dmp"), st.wYear, st.wMonth, st.wDay,st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    CString dump_file_path = GetRootPath();            //获取程序所在的文件夹的路径
    dump_file_path += _T("dump/");
    CreateDirectory(dump_file_path, NULL);
    dump_file_path += time_now;
    
    HANDLE hFile = CreateFile(dump_file_path, GENERIC_READ|GENERIC_WRITE,
        FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if( hFile == INVALID_HANDLE_VALUE )
        return EXCEPTION_CONTINUE_EXECUTION; // 往下传,继续交给 windows 默认的处理函数处理

    MINIDUMP_EXCEPTION_INFORMATION mdei;
    mdei.ThreadId = GetCurrentThreadId();
    mdei.ExceptionPointers = ExceptionInfo;
    mdei.ClientPointers = NULL;
    MINIDUMP_CALLBACK_INFORMATION mci;  
    mci.CallbackRoutine     = NULL;  
    mci.CallbackParam       = 0;  

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);  

    CloseHandle(hFile);

    return EXCEPTION_EXECUTE_HANDLER;
}

一般我们在程序入口处设置异常捕获函数,如下:

SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

如果仅仅像上面那样写的话,仍然有可能会发现有时我们写的程序崩溃了,但是没有生成dump文件,或是程序崩溃时还是弹出window的错误报告窗口,也就是说程序崩溃时并没有调用我们重写的异常处理函数,还是调用了Windows默认的异常处理函数,这是怎么回事呢 ? 我们又该如何解决呢?

原因: SetUnhandledExceptionFilter是无法捕获 printf, scan, strlen 等CRT函数的异常的,如printf(NULL)异常;

解决方法: 在SetUnhandledExceptionFilter (…) 之后,利用 Hook 技术对 SetUnhandledExceptionFilter 接口进行处理“屏蔽”,这样就可以防止其他地方再去调用这个函数了;

void DisableSetUnhandledExceptionFilter()
{
    void* addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")), "SetUnhandledExceptionFilter");
 
    if (addr != NULL)
    {
        unsigned char code[16];
        int size = 0;
 
        code[size++] = 0x33;
        code[size++] = 0xC0;
        code[size++] = 0xC2;
        code[size++] = 0x04;
        code[size++] = 0x00;
 
        DWORD dwOldFlag, dwTempFlag;
        VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
        WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
        VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
    }
}

这样我们在函数入口的地方做如下调用即可:

SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
DisableSetUnhandledExceptionFilter();

至于为什么SEH(异常处理结构体)函数无法捕获CRT函数的异常呢,是因为这些函数内部在有异常发生的时候,会自调用SetUnhandledExceptionFilter这个函数,我们知道这个函数是注册了一个异常调用传递链表,每次调用这个函数,都会把新的回调函数放到链表的头部,系统只会把异常传递给这个链表的表头,而第一个接收到异常的处理函数可以继续将异常传递给链表后面的异常处理函数去继续处理,也可以直接截断。

/* 代码来源于 gs_report.c */
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
UnhandledExceptionFilter(&ExceptionPointers);

你可能感兴趣的:(Windows,知识,windows,c++)