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

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

[toc]

一、Windows 生成 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 = n;
}

三、回调不生效问题

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

【调试技术】程序自动生成dump文件
https://hodlyounger.github.io/wiki/调试技术/【调试技术】程序自动生成dump文件/
作者
mingming
发布于
2024年8月31日
许可协议