可以在Windows中通过SetUnhandledExceptionFilter设置未处理异常的默认处理。很多程序都用它来执行记录程序中的BUG,如QQ崩溃时弹出的框框。
捕获到异常后,需要分析异常新,这个可以通过Windows提供的DbgHelp完成。对于异常的处理,一般都是终止程序。但是也可以在处理函数中调用ExitThread退出当前线程,这样就仅有当前线程被破坏,不影响进程的其他部分。关于异常的代码意义,可以参看GCLsoft的博文,地址是:http://hi.baidu.com/gclsoft/blog/item/96d7eb456934d75e500ffea1.html。下面是一个异常分析代码,参考了http://blog.vckbase.com/zaboli/archive/2010/11/18/52088.html。
void GetCurModulePath(TCHAR* ptcPath,int iLen,LPVOID addr)
{
MEMORY_BASIC_INFORMATION mbi = {0};
if(FALSE == ::VirtualQuery( addr, &mbi, sizeof(mbi)))
return;
UINT_PTR h_module = (UINT_PTR)mbi.AllocationBase;
::GetModuleFileName((HMODULE)h_module, ptcPath, iLen);
return;
}
void EI2Str(TCHAR* ptcInfo,int iInfoLen,ULONG_PTR* Info,int iCount)
{
int i = 0;
TCHAR* p1 = ptcInfo;
for(i = 0; i < iCount; i++)
{
_stprintf(p1,_T("%08X "),Info[i]);
p1 += 9;
}
}
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* pei)
{
STACKFRAME sf;
CONTEXT context;
TCHAR tcPath[2048] = {0};
TCHAR tcInfo[10240] = {0};
TCHAR tcExcep[20480] = {0};
GetCurModulePath(tcPath,2048,pei->ExceptionRecord->ExceptionAddress);
EI2Str(tcInfo,10240,pei->ExceptionRecord->ExceptionInformation,pei->ExceptionRecord->NumberParameters);
_stprintf(tcExcep,_T("程序遇到未知异常,具体信息如下: 代码:0x%08X 标志:0x%08X 地址:0x%08X ")
_T("参数:%d %s 模块:%s 堆栈信息如下: /r/n"),
pei->ExceptionRecord->ExceptionCode,pei->ExceptionRecord->ExceptionFlags,pei->ExceptionRecord->ExceptionAddress,
pei->ExceptionRecord->NumberParameters,tcInfo,tcPath);
memset(tcInfo,0,sizeof(tcInfo));
memset( &sf, 0, sizeof(STACKFRAME));
memcpy(&context,pei->ContextRecord,sizeof(CONTEXT));
sf.AddrPC.Offset = context.Eip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrStack.Offset = context.Esp;
sf.AddrStack.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Ebp;
sf.AddrFrame.Mode = AddrModeFlat;
DWORD machineType = IMAGE_FILE_MACHINE_I386;
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
SymInitialize(hProcess,NULL,TRUE);
for( ; ; )
{
if( !StackWalk(machineType, hProcess, hThread, &sf, &context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
{
break;
}
if( sf.AddrFrame.Offset == 0 )
{
break;
}
BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;
pSymbol->SizeOfStruct = sizeof( symbolBuffer );
pSymbol->MaxNameLen = 1024;
_stprintf(tcPath,_T("sf.AddrPC.Offset:0x%08X "),sf.AddrPC.Offset);
_tcscat(tcExcep,tcPath);
memset(tcPath,0,sizeof(tcPath));
DWORD64 symDisplacement = 0;
if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
{
_stprintf(tcPath,_T("Function : %S "), pSymbol->Name );
}
else
{
}
_tcscat(tcExcep,tcPath);
IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
DWORD dwLineDisplacement;
memset(tcPath,0,sizeof(tcPath));
if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
{
_stprintf(tcPath,_T( "[Source File : %S] [Source Line : %u] "), lineInfo.FileName,lineInfo.LineNumber );
}
else
{
}
_tcscat(tcExcep,tcPath);
_tcscat(tcExcep, L"/r/n");
}
SymCleanup(hProcess);
//AfxMessageBox(tcExcep);
MessageBox(NULL, tcExcep, L"出现错误", MB_OK);
ExitThread(-1);
return EXCEPTION_EXECUTE_HANDLER;//EXCEPTION_CONTINUE_SEARCH;
}
最后,要说的是异常处理并不能捕获到所有异常,主要是安全原因导致CRT将某些异常的处理强行发送到了默认异常处理器(一般为dr watson,程序自动保存系统,http://baike.baidu.com/view/1300613.htm)。可以通过Hook SetUnhandledExceptionFilter调用完成。可用http://www.cppblog.com/woaidongmao/archive/2009/10/21/99129.html中的方法:
#ifndef _M_IX86
#error "The following code only works for x86!"
#endif
void DisableSetUnhandledExceptionFilter()
{
void *addr = (void*)GetProcAddress(LoadLibrary(_T("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_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
也可以用http://blog.kalmbachnet.de/?postid=75中的方法:
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
return NULL;
}
BOOL PreventSetUnhandledExceptionFilter()
{
HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
if (hKernel32 == NULL) return FALSE;
void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
if(pOrgEntry == NULL) return FALSE;
unsigned char newJump[ 100 ];
DWORD dwOrgEntryAddr = (DWORD) pOrgEntry;
dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
DWORD dwNewEntryAddr = (DWORD) pNewFunc;
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
newJump[ 0 ] = 0xE9; // JMP absolute
memcpy(&newJump[ 1 ], &dwRelativeAddr, sizeof(pNewFunc));
SIZE_T bytesWritten;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
return bRet;
}
上述两种方法是类似的,但是都不能彻底解决问题。对于如堆栈溢出这类异常,这两类方法都没办法解决。原因当然还是来自微软,还是安全因素导致的。因此,理论上也就不可能处理所有异常了,否则这点必然会被Cracker利用,安全也就无从谈起了。