【调试技术】程序自动生成dump文件

文章目录
  1. 1. 使用 SysInternalsSuite 工具
  2. 2. Windows 生成 dump
    1. 2.1. 如果生成dump
    2. 2.2. dump回调函数
    3. 2.3. 设置回调
  3. 3. 二、手写崩溃
  4. 4. 三、回调不生效问题
    1. 4.1. 代码
    2. 4.2. 使用说明
    3. 4.3. 注意事项
      1. 4.3.1. 方案一
      2. 4.3.2. 方案二
      3. 4.3.3. 代码总结
概述

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

[toc]

说明

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

使用 SysInternalsSuite 工具

相关参考

进程异常崩溃

抓取异常的程序 dump

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

通过 AeDebug 抓取

1
procdump -ma -i D:\testdump

抓取程序启动时异常 dump

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

高CPU抓取异常

1
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。

Windows 生成 dump

如果生成dump

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

1
2
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
_In_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)

注意点

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

dump回调函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 ,入参为回调函数。

1
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

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

二、手写崩溃

1
2
3
4
5
void CreteCrash()
{
int *ptr = nullptr;
*ptr = 1;
}

三、回调不生效问题

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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即可。

注意事项

经本人测试,在部分机器上,使用 WriteProcessMemoryVirtualProtect 可能会导致程序崩溃,并且不会生成dump。可尝试修改 VirtualProtectWriteProcessMemory 函数的参数测试。

方案一

修改 VirtualProtect 的 flag.设置为 PAGE_EXECUTE_READWRITE

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

方案二

WriteProcessMemory 可直接使用 memcpy 替代。

1
memcpy(addr, code, size);

代码总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);