最近在研究eMule代码,eMule是一款开源的软件,采用VS2003,MFC开发,微软的各个东西,都用到极致了。
本节主要是节选一个小功能,Crash机制。
首先我先复习一下Windows异常机制,异常处理机制流程图如下:
详细内容可以参考《软件调试》张银奎编
由图可以看出,软件在发生异常的时候,Windows有两轮异常分发过程,在eMule中,是如何实现呢?
eMule的异常处理建立过程是调用了debughelp.dll(此接口windbg有提供,详细的接口用法可以参考windbg的sdk的help)的相关接口
主要流程图如下:
|| 检测冲突软件 ||
|| 注册异常处理函数||
||发送错误报告||
检测冲突软件主要代码如下:
BOOL CCheckConflict::CheckModules(struct _EXCEPTION_POINTERS* pExceptionInfo)
{
int i, j, jCount;
HMODULE hMod;
CONTEXT context;
STACKFRAME64 sf;
for (i = 0; i < MODULE_LIST_COUNT; i++)
{
hMod = GetModuleHandle(s_conflictModuleList[i].szModuleName);
if (NULL == hMod)
continue;
memcpy(&context, pExceptionInfo->ContextRecord, sizeof(context));
ZeroMemory(&sf, sizeof(sf));
sf.AddrPC.Offset = context.Eip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Ebp;
sf.AddrFrame.Mode = AddrModeFlat;
jCount = (s_conflictModuleList[i].iStackSearchLevel == 0) ? 100/*最多查找100层*/ : s_conflictModuleList[i].iStackSearchLevel;
for (j = 0; j < jCount; j++)
{
if (! m_pfnStackWalk64(IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(),
&sf, NULL, NULL, m_pfnSymFunctionTableAccess64, m_pfnSymGetModuleBase64, NULL))
{
break;
}
if (IsAddressInModule((PVOID)sf.AddrPC.Offset, hMod))
{
return s_conflictModuleList[i].conflictProc(i, hMod);
}
}
}
return FALSE;
}
其中s_conflictModuleList在初始化的时候已增加了几项被检测到的与eMule冲突的模块或者软件。
代码中我们看出eMule在检测的时候历遍了堆栈里边的模块,然后调用IsAddressInModule函数,判断模块的地址是否处于eMule内存地址范围内,若是,则执行conflictProc函数,此函数在s_conflictModuleList初始化的时候,给了一个指定的错误函数地址。
注册异常函数是通过调用
SetUnhandledExceptionFilter
实现的,其中有一个参数为异常处理函数地址,代码如下:
LONG CMiniDumper::TopLevelFilter(struct _EXCEPTION_POINTERS* pExceptionInfo)
{
try
{
//ADDED by fengwen on 2006/12/25 <begin> : 检测第三方软件冲突。
CCheckConflict cc;
if (cc.CheckConflict(pExceptionInfo))
{
ExitProcess(1);
}
//ADDED by fengwen on 2006/12/25 <end> : 检测第三方软件冲突。
}
catch(...)
{
}
LONG lRetValue = EXCEPTION_CONTINUE_SEARCH;
TCHAR szResult[MAX_PATH + 1024] = {0};
MINIDUMPWRITEDUMP pfnMiniDumpWriteDump = NULL;
HMODULE hDll = GetDebugHelperDll((FARPROC*)&pfnMiniDumpWriteDump, true);
HINSTANCE hInstCrashReporter = NULL; //ADDED by fengwen on 2006/11/15 : 使用新的发送错误报告机制。
if (hDll)
{
if (pfnMiniDumpWriteDump)
{
// Ask user if they want to save a dump file
// Do *NOT* localize that string (in fact, do not use MFC to load it)!
//COMMENTED by fengwen on 2006/11/15 <begin> : 使用新的发送错误报告机制。
//if (MessageBox(NULL, _T("eMule crashed :-(\r\n\r\nA diagnostic file can be created which will help the author to resolve this problem. This file will be saved on your Disk (and not sent).\r\n\r\nDo you want to create this file now?"), m_szAppName, MB_ICONSTOP | MB_YESNO) == IDYES)
//COMMENTED by fengwen on 2006/11/15 <end> : 使用新的发送错误报告机制。
{
// Create full path for DUMP file
TCHAR szDumpPath[MAX_PATH];
_tcsncpy(szDumpPath, m_szDumpDir, _countof(szDumpPath) - 1);
szDumpPath[_countof(szDumpPath) - 1] = _T('\0');
size_t uDumpPathLen = _tcslen(szDumpPath);
TCHAR szBaseName[MAX_PATH] = {0};
_tcsncpy(szBaseName, m_szAppName, _countof(szBaseName) - 1);
/*szBaseName[_countof(szBaseName) - 1] = _T('\0');
size_t uBaseNameLen = _tcslen(szBaseName);
time_t tNow = time(NULL);
_tcsftime(szBaseName + uBaseNameLen, _countof(szBaseName) - uBaseNameLen, _T("_%Y%m%d-%H%M%S"), localtime(&tNow));
szBaseName[_countof(szBaseName) - 1] = _T('\0');
*/
// Replace spaces and dots in file name.
LPTSTR psz = szBaseName;
while (*psz != _T('\0')) {
if (*psz == _T('.'))
*psz = _T('-');
else if (*psz == _T(' '))
*psz = _T('_');
psz++;
}
if (uDumpPathLen < _countof(szDumpPath) - 1) {
_tcsncat(szDumpPath, szBaseName, _countof(szDumpPath) - uDumpPathLen - 1);
szDumpPath[_countof(szDumpPath) - 1] = _T('\0');
uDumpPathLen = _tcslen(szDumpPath);
if (uDumpPathLen < _countof(szDumpPath) - 1) {
_tcsncat(szDumpPath, _T(".dmp"), _countof(szDumpPath) - uDumpPathLen - 1);
szDumpPath[_countof(szDumpPath) - 1] = _T('\0');
}
}
HANDLE hFile = CreateFile(szDumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
_MINIDUMP_EXCEPTION_INFORMATION ExInfo = {0};
ExInfo.ThreadId = GetCurrentThreadId();
ExInfo.ExceptionPointers = pExceptionInfo;
ExInfo.ClientPointers = NULL;
BOOL bOK = (*pfnMiniDumpWriteDump)(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL);
if (bOK)
{
// Do *NOT* localize that string (in fact, do not use MFC to load it)!
_sntprintf(szResult, _countof(szResult) - 1, _T("Saved dump file to \"%s\".\r\n\r\nPlease send this file together with a detailed bug report to dumps@emule-project.net !\r\n\r\nThank you for helping to improve eMule."), szDumpPath);
szResult[_countof(szResult) - 1] = _T('\0');
lRetValue = EXCEPTION_EXECUTE_HANDLER;
//ADDED by fengwen on 2006/11/15 <begin> : 使用新的发送错误报告机制。
hInstCrashReporter = ShellExecute(NULL, _T("open"), _T("CrashReporter.exe"), szDumpPath, NULL, SW_SHOW);
if (hInstCrashReporter <= (HINSTANCE)32)
lRetValue = EXCEPTION_CONTINUE_SEARCH;
//ADDED by fengwen on 2006/11/15 <end> : 使用新的发送错误报告机制。
}
else
{
// Do *NOT* localize that string (in fact, do not use MFC to load it)!
_sntprintf(szResult, _countof(szResult) - 1, _T("Failed to save dump file to \"%s\".\r\n\r\nError: %u"), szDumpPath, GetLastError());
szResult[_countof(szResult) - 1] = _T('\0');
}
CloseHandle(hFile);
}
else
{
// Do *NOT* localize that string (in fact, do not use MFC to load it)!
_sntprintf(szResult, _countof(szResult) - 1, _T("Failed to create dump file \"%s\".\r\n\r\nError: %u"), szDumpPath, GetLastError());
szResult[_countof(szResult) - 1] = _T('\0');
}
}
}
FreeLibrary(hDll);
hDll = NULL;
pfnMiniDumpWriteDump = NULL;
}
//COMMENTED by fengwen on 2006/11/15 <begin> : 使用新的发送错误报告机制。
//if (szResult[0] != _T('\0'))
// MessageBox(NULL, szResult, m_szAppName, MB_ICONINFORMATION | MB_OK);
//COMMENTED by fengwen on 2006/11/15 <end> : 使用新的发送错误报告机制。
#ifndef _DEBUG
if (EXCEPTION_EXECUTE_HANDLER == lRetValue) //ADDED by fengwen on 2006/11/15 : 由此filter处理了异常,才去中止进程。
{
// Exit the process only in release builds, so that in debug builds the exceptio is passed to a possible
// installed debugger
ExitProcess(0);
}
else
return lRetValue;
#else
return lRetValue;
#endif
}
先是检测,然后是写dmp文件,然后是采用CrashReport函数发送错误。
--------end------