通常我们通过SetUnhandledExceptionFilter来捕获程序的异常进而得到相应的dump文件,就像下面:
LONG NTAPI TopLevelExcepFilter(EXCEPTION_POINTERS *pExcepInfo)
{
printf("TopLevelEHandler\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
SetUnhandledExceptionFilter(&TopLevelExcepFilter);
但是对于部分CRT函数比如printf类不定参数类型的函数的异常是捕获不到的,比如:
printf(NULL);
这个动作是会导致程序崩溃的,但是SEH并没有捕获到,但是我们可以通过crt的另一个函数来设置异常检测的回调:_set_invalid_parameter_handler
_invalid_parameter_handler oldHandler;
oldHandler = _set_invalid_parameter_handler(myInvalidParameterHandler);
void myInvalidParameterHandler(const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved)
{
// function、file、line在Release下无效
wprintf(L"Invalid parameter detected in function %s."
L" File: %s Line: %d\n", function, file, line);
wprintf(L"Expression: %s\n", expression);
// 必须抛出异常,否则无法定位错误位置
throw 1;
}
这样再次调用printf(NULL);就会定位到myInvalidParameterHandler函数中来了,但是只有debug下才能看到参数中的function、file、line
我们在回调函数中抛出异常,这样外面的SEH设置的回调函数就可以将dmp输出了。
同样对于程序的纯虚函数调用SEH也是无法捕获到的,我们同样需要通过CRT函数中_set_purecall_handler来设置异常回调:
_purecall_handler old_pure_handle;
old_pure_handle = _set_purecall_handler(myPurecallHandler);
void myPurecallHandler(void)
{
printf("In _purecall_handler.");
// 必须抛出异常,否则无法定位错误位置
throw 1;
}
而至于为什么SEH函数无法捕获他们的异常呢,是因为这些函数内部在有异常发生的时候,会自调用SetUnhandledExceptionFilter这个函数,我们知道这个函数是注册了一个异常调用传递链表,每次调用这个函数,都会把新的回调函数放到链表的头部,系统只会把异常传递给给这个链表的表头,而第一个接收到异常的处理函数可以继续将异常传递给链表后面的异常处理函数去继续处理,也可以直接截断。
CRT调用SEH的代码如下:
/* 代码来源于 gs_report.c */
/* Make sure any filter already in place is deleted. */
SetUnhandledExceptionFilter(NULL);
UnhandledExceptionFilter(&ExceptionPointers);
所以我们可以调用完这个函数后,就把这个hook住,或者disable掉,这样就可以防止其他地方再去调用这个函数了。
// 此函数一旦成功调用,之后对 SetUnhandledExceptionFilter 的调用将无效
void DisableSetUnhandledExceptionFilter()
{
#define X86_NOP 0x90
void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"), "SetUnhandledExceptionFilter");
if (addr)
{
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_EXECUTE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
还有一个CRT函数是terminate(),我们可以通过set_terminate来获得回调:
#include "stdafx.h"
#include
#include
#include
void my_terminate_handler()
{
// Abnormal program termination (terminate() function was called)
// Do something here
std::cout << "terminate.\n";
exit(1);
}
void main()
{
set_terminate(my_terminate_handler);
terminate();
}