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