1 ///////////////////////////////////////////////////////////////////////////////
\r
3 // Module: CrashHandler.cpp
\r
5 // Desc: See CrashHandler.h
\r
7 // Copyright (c) 2003 Michael Carruth
\r
9 ///////////////////////////////////////////////////////////////////////////////
\r
12 #include "CrashHandler.h"
\r
13 #include "zlibcpp.h"
\r
14 #include "excprpt.h"
\r
15 #include "maindlg.h"
\r
16 #include "mailmsg.h"
\r
17 #include "WriteRegistry.h"
\r
18 #include "resource.h"
\r
20 #include <windows.h>
\r
21 #include <shlwapi.h>
\r
22 #include <commctrl.h>
\r
23 #pragma comment(lib, "shlwapi")
\r
24 #pragma comment(lib, "comctl32")
\r
26 #include <algorithm>
\r
28 BOOL g_bNoCrashHandler;// don't use the crash handler but let the system handle it
\r
30 // maps crash objects to processes
\r
31 map<DWORD, CCrashHandler*> _crashStateMap;
\r
33 // unhandled exception callback set with SetUnhandledExceptionFilter()
\r
34 LONG WINAPI CustomUnhandledExceptionFilter(PEXCEPTION_POINTERS pExInfo)
\r
36 OutputDebugString("Exception\n");
\r
37 if (EXCEPTION_BREAKPOINT == pExInfo->ExceptionRecord->ExceptionCode)
\r
39 // Breakpoint. Don't treat this as a normal crash.
\r
40 return EXCEPTION_CONTINUE_SEARCH;
\r
43 if (g_bNoCrashHandler)
\r
45 return EXCEPTION_CONTINUE_SEARCH;
\r
48 BOOL result = false;
\r
49 if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())
\r
50 result = _crashStateMap[GetCurrentProcessId()]->GenerateErrorReport(pExInfo, NULL);
\r
52 // If we're in a debugger, return EXCEPTION_CONTINUE_SEARCH to cause the debugger to stop;
\r
53 // or if GenerateErrorReport returned FALSE (i.e. drop into debugger).
\r
54 return (!result || IsDebuggerPresent()) ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER;
\r
57 CCrashHandler * CCrashHandler::GetInstance()
\r
59 CCrashHandler *instance = NULL;
\r
60 if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())
\r
61 instance = _crashStateMap[GetCurrentProcessId()];
\r
62 if (instance == NULL) {
\r
64 instance = new CCrashHandler();
\r
69 CCrashHandler::CCrashHandler():
\r
71 m_lpfnCallback(NULL),
\r
72 m_pid(GetCurrentProcessId()),
\r
80 // wtl initialization stuff...
\r
81 HRESULT hRes = ::CoInitialize(NULL);
\r
85 _crashStateMap[m_pid] = this;
\r
88 void CCrashHandler::Install(LPGETLOGFILE lpfn, LPCTSTR lpcszTo, LPCTSTR lpcszSubject, BOOL bUseUI)
\r
93 OutputDebugString("::Install\n");
\r
98 // save user supplied callback
\r
99 m_lpfnCallback = lpfn;
\r
100 // save optional email info
\r
102 m_sSubject = lpcszSubject;
\r
105 // This is needed for CRT to not show dialog for invalid param
\r
106 // failures and instead let the code handle it.
\r
107 _CrtSetReportMode(_CRT_ASSERT, 0);
\r
108 // add this filter in the exception callback chain
\r
109 m_oldFilter = SetUnhandledExceptionFilter(CustomUnhandledExceptionFilter);
\r
110 /*m_oldErrorMode=*/ SetErrorMode( SEM_FAILCRITICALERRORS );
\r
112 m_installed = true;
\r
115 void CCrashHandler::Uninstall()
\r
118 OutputDebugString("Uninstall\n");
\r
120 // reset exception callback (to previous filter, which can be NULL)
\r
121 SetUnhandledExceptionFilter(m_oldFilter);
\r
122 m_installed = false;
\r
125 void CCrashHandler::EnableUI()
\r
130 void CCrashHandler::DisableUI()
\r
135 void CCrashHandler::DisableHandler()
\r
137 g_bNoCrashHandler = TRUE;
\r
140 void CCrashHandler::EnableHandler()
\r
142 g_bNoCrashHandler = FALSE;
\r
145 CCrashHandler::~CCrashHandler()
\r
150 _crashStateMap.erase(m_pid);
\r
152 ::CoUninitialize();
\r
156 void CCrashHandler::AddFile(LPCTSTR lpFile, LPCTSTR lpDesc)
\r
158 // make sure we don't already have the file
\r
159 RemoveFile(lpFile);
\r
160 // make sure the file exists
\r
161 HANDLE hFile = ::CreateFile(
\r
164 FILE_SHARE_READ | FILE_SHARE_WRITE,
\r
167 FILE_ATTRIBUTE_NORMAL,
\r
169 if (hFile != INVALID_HANDLE_VALUE)
\r
171 // add file to report
\r
172 m_files.push_back(TStrStrPair(lpFile, lpDesc));
\r
173 ::CloseHandle(hFile);
\r
177 void CCrashHandler::RemoveFile(LPCTSTR lpFile)
\r
179 TStrStrVector::iterator iter;
\r
180 for (iter = m_files.begin(); iter != m_files.end(); ++iter) {
\r
181 if ((*iter).first == lpFile) {
\r
182 iter = m_files.erase(iter);
\r
188 void CCrashHandler::AddRegistryHive(LPCTSTR lpRegistryHive, LPCTSTR lpDesc)
\r
190 // make sure we don't already have the RegistryHive
\r
191 RemoveRegistryHive(lpRegistryHive);
\r
192 // Unfortunately, we have no easy way of verifying the existence of
\r
193 // the registry hive.
\r
194 // add RegistryHive to report
\r
195 m_registryHives.push_back(TStrStrPair(lpRegistryHive, lpDesc));
\r
198 void CCrashHandler::RemoveRegistryHive(LPCTSTR lpRegistryHive)
\r
200 TStrStrVector::iterator iter;
\r
201 for (iter = m_registryHives.begin(); iter != m_registryHives.end(); ++iter) {
\r
202 if ((*iter).first == lpRegistryHive) {
\r
203 iter = m_registryHives.erase(iter);
\r
208 void CCrashHandler::AddEventLog(LPCTSTR lpEventLog, LPCTSTR lpDesc)
\r
210 // make sure we don't already have the EventLog
\r
211 RemoveEventLog(lpEventLog);
\r
212 // Unfortunately, we have no easy way of verifying the existence of
\r
214 // add EventLog to report
\r
215 m_eventLogs.push_back(TStrStrPair(lpEventLog, lpDesc));
\r
218 void CCrashHandler::RemoveEventLog(LPCTSTR lpEventLog)
\r
220 TStrStrVector::iterator iter;
\r
221 for (iter = m_eventLogs.begin(); iter != m_eventLogs.end(); ++iter) {
\r
222 if ((*iter).first == lpEventLog) {
\r
223 iter = m_eventLogs.erase(iter);
\r
228 DWORD WINAPI CCrashHandler::DialogThreadExecute(LPVOID pParam)
\r
230 // New thread. This will display the dialog and handle the result.
\r
231 CCrashHandler * self = reinterpret_cast<CCrashHandler *>(pParam);
\r
233 string sTempFileName = CUtility::getTempFileName();
\r
236 // delete existing copy, if any
\r
237 DeleteFile(sTempFileName.c_str());
\r
240 if (!zlib.Open(sTempFileName))
\r
243 // add report files to zip
\r
244 TStrStrVector::iterator cur = self->m_files.begin();
\r
245 for (cur = self->m_files.begin(); cur != self->m_files.end(); cur++)
\r
246 if (PathFileExists((*cur).first.c_str()))
\r
247 zlib.AddFile((*cur).first);
\r
251 mainDlg.m_pUDFiles = &self->m_files;
\r
252 mainDlg.m_sendButton = !self->m_sTo.empty();
\r
254 INITCOMMONCONTROLSEX used = {
\r
255 sizeof(INITCOMMONCONTROLSEX),
\r
256 ICC_LISTVIEW_CLASSES | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_USEREX_CLASSES
\r
258 InitCommonControlsEx(&used);
\r
260 INT_PTR status = mainDlg.DoModal(GetModuleHandle("CrashRpt.dll"), IDD_MAINDLG, GetDesktopWindow());
\r
261 if (IDOK == status || IDC_SAVE == status)
\r
263 if (IDC_SAVE == status || self->m_sTo.empty() ||
\r
264 !self->MailReport(*self->m_rpt, sTempFileName.c_str(), mainDlg.m_sEmail.c_str(), mainDlg.m_sDescription.c_str()))
\r
266 // write user data to file if to be supplied
\r
267 if (!self->m_userDataFile.empty()) {
\r
268 HANDLE hFile = ::CreateFile(
\r
269 self->m_userDataFile.c_str(),
\r
270 GENERIC_READ | GENERIC_WRITE,
\r
271 FILE_SHARE_READ | FILE_SHARE_WRITE,
\r
274 FILE_ATTRIBUTE_NORMAL,
\r
276 if (hFile != INVALID_HANDLE_VALUE)
\r
278 static const char e_mail[] = "E-mail:";
\r
279 static const char newline[] = "\r\n";
\r
280 static const char description[] = "\r\n\r\nDescription:";
\r
281 DWORD writtenBytes;
\r
282 ::WriteFile(hFile, e_mail, sizeof(e_mail) - 1, &writtenBytes, NULL);
\r
283 ::WriteFile(hFile, mainDlg.m_sEmail.c_str(), mainDlg.m_sEmail.size(), &writtenBytes, NULL);
\r
284 ::WriteFile(hFile, description, sizeof(description) - 1, &writtenBytes, NULL);
\r
285 ::WriteFile(hFile, mainDlg.m_sDescription.c_str(), mainDlg.m_sDescription.size(), &writtenBytes, NULL);
\r
286 ::WriteFile(hFile, newline, sizeof(newline) - 1, &writtenBytes, NULL);
\r
287 ::CloseHandle(hFile);
\r
288 // redo zip file to add user data
\r
289 // delete existing copy, if any
\r
290 DeleteFile(sTempFileName.c_str());
\r
293 if (!zlib.Open(sTempFileName))
\r
296 // add report files to zip
\r
297 TStrStrVector::iterator cur = self->m_files.begin();
\r
298 for (cur = self->m_files.begin(); cur != self->m_files.end(); cur++)
\r
299 if (PathFileExists((*cur).first.c_str()))
\r
300 zlib.AddFile((*cur).first);
\r
305 self->SaveReport(*self->m_rpt, sTempFileName.c_str());
\r
309 DeleteFile(sTempFileName.c_str());
\r
311 self->m_wantDebug = IDC_DEBUG == status;
\r
312 // signal main thread to resume
\r
313 ::SetEvent(self->m_ipc_event);
\r
314 // set flag for debugger break
\r
318 // keep compiler happy.
\r
322 BOOL CCrashHandler::GenerateErrorReport(PEXCEPTION_POINTERS pExInfo, BSTR message)
\r
324 CExceptionReport rpt(pExInfo, message);
\r
326 // save state of file list prior to generating report
\r
327 TStrStrVector save_m_files = m_files;
\r
328 char temp[_MAX_PATH];
\r
330 GetTempPath(sizeof temp, temp);
\r
333 // let client add application specific files to report
\r
334 if (m_lpfnCallback && !m_lpfnCallback(this))
\r
339 // if no e-mail address, add file to contain user data
\r
340 m_userDataFile = "";
\r
341 if (m_sTo.empty()) {
\r
342 m_userDataFile = temp + string("\\") + CUtility::getAppName() + "_UserInfo.txt";
\r
343 HANDLE hFile = ::CreateFile(
\r
344 m_userDataFile.c_str(),
\r
345 GENERIC_READ | GENERIC_WRITE,
\r
346 FILE_SHARE_READ | FILE_SHARE_WRITE,
\r
349 FILE_ATTRIBUTE_NORMAL,
\r
351 if (hFile != INVALID_HANDLE_VALUE)
\r
353 static const char description[] = "Your e-mail and description will go here.";
\r
354 DWORD writtenBytes;
\r
355 ::WriteFile(hFile, description, sizeof(description)-1, &writtenBytes, NULL);
\r
356 ::CloseHandle(hFile);
\r
357 m_files.push_back(TStrStrPair(m_userDataFile, LoadResourceString(IDS_USER_DATA)));
\r
359 return m_wantDebug;
\r
365 // add crash files to report
\r
366 string crashFile = rpt.getCrashFile();
\r
367 string crashLog = rpt.getCrashLog();
\r
368 m_files.push_back(TStrStrPair(crashFile, LoadResourceString(IDS_CRASH_DUMP)));
\r
369 m_files.push_back(TStrStrPair(crashLog, LoadResourceString(IDS_CRASH_LOG)));
\r
371 // Export registry hives and add to report
\r
372 std::vector<string> extraFiles;
\r
375 TStrStrVector::iterator iter;
\r
378 for (iter = m_registryHives.begin(); iter != m_registryHives.end(); iter++) {
\r
380 TCHAR buf[MAX_PATH] = {0};
\r
381 _tprintf_s(buf, "%d", n);
\r
383 file = temp + string("\\") + CUtility::getAppName() + "_registry" + number + ".reg";
\r
384 ::DeleteFile(file.c_str());
\r
386 // we want to export in a readable format. Unfortunately, RegSaveKey saves in a binary
\r
387 // form, so let's use our own function.
\r
388 if (WriteRegistryTreeToFile((*iter).first.c_str(), file.c_str())) {
\r
389 extraFiles.push_back(file);
\r
390 m_files.push_back(TStrStrPair(file, (*iter).second));
\r
392 OutputDebugString("Could not write registry hive\n");
\r
396 // Add the specified event log(s). Note that this will not work on Win9x/WinME.
\r
398 for (iter = m_eventLogs.begin(); iter != m_eventLogs.end(); iter++) {
\r
400 h = OpenEventLog( NULL, // use local computer
\r
401 (*iter).first.c_str()); // source name
\r
404 file = temp + string("\\") + CUtility::getAppName() + "_" + (*iter).first + ".evt";
\r
406 DeleteFile(file.c_str());
\r
408 if (BackupEventLog(h, file.c_str())) {
\r
409 m_files.push_back(TStrStrPair(file, (*iter).second));
\r
410 extraFiles.push_back(file);
\r
412 OutputDebugString("could not backup log\n");
\r
416 OutputDebugString("could not open log\n");
\r
421 // add symbol files to report
\r
422 for (i = 0; i < (UINT)rpt.getNumSymbolFiles(); i++)
\r
423 m_files.push_back(TStrStrPair(rpt.getSymbolFile(i).c_str(),
\r
424 string("Symbol File")));
\r
426 //remove the crash handler, just in case the dialog crashes...
\r
430 // Start a new thread to display the dialog, and then wait
\r
431 // until it completes
\r
432 m_ipc_event = ::CreateEvent(NULL, FALSE, FALSE, "ACrashHandlerEvent");
\r
433 if (m_ipc_event == NULL)
\r
434 return m_wantDebug;
\r
436 if (::CreateThread(NULL, 0, DialogThreadExecute,
\r
437 reinterpret_cast<LPVOID>(this), 0, &threadId) == NULL)
\r
438 return m_wantDebug;
\r
439 ::WaitForSingleObject(m_ipc_event, INFINITE);
\r
440 CloseHandle(m_ipc_event);
\r
444 string sTempFileName = CUtility::getTempFileName();
\r
447 sTempFileName += _T(".zip");
\r
448 // delete existing copy, if any
\r
449 DeleteFile(sTempFileName.c_str());
\r
452 if (!zlib.Open(sTempFileName))
\r
455 // add report files to zip
\r
456 TStrStrVector::iterator cur = m_files.begin();
\r
457 for (cur = m_files.begin(); cur != m_files.end(); cur++)
\r
458 if (PathFileExists((*cur).first.c_str()))
\r
459 zlib.AddFile((*cur).first);
\r
461 fprintf(stderr, "a zipped crash report has been saved to\n");
\r
462 _ftprintf(stderr, sTempFileName.c_str());
\r
463 fprintf(stderr, "\n");
\r
464 if (!m_sTo.empty())
\r
466 fprintf(stderr, "please send the report to ");
\r
467 _ftprintf(stderr, m_sTo.c_str());
\r
468 fprintf(stderr, "\n");
\r
471 // clean up - delete files we created
\r
472 ::DeleteFile(crashFile.c_str());
\r
473 ::DeleteFile(crashLog.c_str());
\r
474 if (!m_userDataFile.empty()) {
\r
475 ::DeleteFile(m_userDataFile.c_str());
\r
478 std::vector<string>::iterator file_iter;
\r
479 for (file_iter = extraFiles.begin(); file_iter != extraFiles.end(); file_iter++) {
\r
480 ::DeleteFile(file_iter->c_str());
\r
483 // restore state of file list
\r
484 m_files = save_m_files;
\r
488 return !m_wantDebug;
\r
491 BOOL CCrashHandler::SaveReport(CExceptionReport&, LPCTSTR lpcszFile)
\r
493 // let user more zipped report
\r
494 return (CopyFile(lpcszFile, CUtility::getSaveFileName().c_str(), TRUE));
\r
497 BOOL CCrashHandler::MailReport(CExceptionReport&, LPCTSTR lpcszFile,
\r
498 LPCTSTR lpcszEmail, LPCTSTR lpcszDesc)
\r
503 .SetFrom(lpcszEmail)
\r
504 .SetSubject(m_sSubject.empty()?_T("Incident Report"):m_sSubject)
\r
505 .SetMessage(lpcszDesc)
\r
506 .AddAttachment(lpcszFile, CUtility::getAppName() + _T(".zip"));
\r
508 return (msg.Send());
\r
511 string CCrashHandler::LoadResourceString(UINT id)
\r
513 static int address;
\r
515 if (m_hModule == NULL) {
\r
516 m_hModule = GetModuleHandle("CrashRpt.dll");
\r
520 LoadString(m_hModule, id, buffer, sizeof buffer);
\r