概述

Windows 下调用 dbghelp 自动生成程序 Dump

[toc]

说明

本文主要说明windows操作系统下,程序如何自动生成dump文件

使用 SysInternalsSuite 工具

相关参考

进程异常崩溃

抓取异常的程序 dump

procdump -ma -w test.exe D:\testdump

通过 AeDebug 抓取

procdump -ma -i D:\testdump

抓取程序启动时异常 dump

procdump -ma -e -w explorer.exe D:\testdump

高CPU抓取异常

procdump 20548 -ma -c 70 -s 5 -n 2 -o D:\testdump

命令行说明:

当进程 CPU>70% 时连续间隔超过 5s ,自动抓两个dump 。

20548 进程ID -ma FullDump,为了减少痛苦,必须用这个 -c 70 CPU >70% 时开始抓取,你可以设置 大一点。 -s 5 连续超过 5s 时 -n 2 总计生成2个dump -o xx dump保存地 最后就是截图,我改成 cpu>15% 的阈值,可以看到目录已成功生成 2 个dump。 高CPU截图

Windows 生成 dump

如果生成dump

生成dump主要依赖于windowsapi函数 SetUnhandledExceptionFilter,参数为一个回调函数,主要依赖于该函数以及回调函数中的相关逻辑生成dump。

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
        _In_ LPTOP_LEVEL_EXCEPTION_FILTER  lpTopLevelExceptionFilter)

注意点

  1. 最好是在程序开始处设置回调
  2. 如果回调不生效,也可使用 AddVectoredExceptionHandler 函数设置回调,其用法与 SetUnhandledExceptionFilter 基本一致,但是需要注意的是,使用 AddVectoredExceptionHandler 后,outpuydebugstring 也会触发回调,所以这个函数慎用。

dump回调函数

dump回调函数可根据自己的需求去编写,以下为一个示例:

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 ,入参为回调函数。

SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

到此,设置并捕获异常的接口完成,以下补充几点个人经验。

二、手写崩溃

void CreteCrash()
{
    int *ptr = nullptr;
	*ptr = 1;
}

三、回调不生效问题

SetUnhandledExceptionFilter 是无法捕获 printf, scan, strlen 等CRT函数的异常的,如printf(NULL) 异常;除此之外,在有的环境当中,因为软件被Hook的问题,导致 函数不生效,设置的回调往往不起作用,此时可以修改设置 的函数地址来屏蔽其他软件的Hook

代码

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);
    }
}

使用说明

在程序启动并设置dump回调之后调用 DisableSetUnhandledExceptionFilter即可。

注意事项

经本人测试,在部分机器上,使用 WriteProcessMemory[[【winapi】VirtualProtect|VirtualProtect]] 可能会导致程序崩溃,并且不会生成dump。可尝试修改 [[【winapi】VirtualProtect|VirtualProtect]]WriteProcessMemory 函数的参数测试。

方案一

修改 VirtualProtect 的 flag.设置为 PAGE_EXECUTE_READWRITE

VirtualProtect(addr, size, PAGE_EXECUTE_READWRITE, &dwOldFlag);

方案二

WriteProcessMemory 可直接使用 memcpy 替代。

memcpy(addr, code, size);

代码总结

DWORD dwOldFlag = 0;
VirtualProtect(addr, size, PAGE_EXECUTE_READWRITE, &dwOldFlag);
#if 0
HANDLE hProcess = GetCurrentProcess();
if (!(hProcess == NULL || hProcess == INVALID_HANDLE_VALUE))
{
    WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
}
else
{
    memcpy(addr, code, size);
}
#endif
memcpy(addr, code, size);
VirtualProtect(addr, size, dwOldFlag, &dwOldFlag);