OSDN Git Service

Add Show Whole Project Checkbox at log dialog
[tortoisegit/TortoiseGitJp.git] / src / crashrpt / CrashHandler.cpp
1 ///////////////////////////////////////////////////////////////////////////////\r
2 //\r
3 //  Module: CrashHandler.cpp\r
4 //\r
5 //    Desc: See CrashHandler.h\r
6 //\r
7 // Copyright (c) 2003 Michael Carruth\r
8 //\r
9 ///////////////////////////////////////////////////////////////////////////////\r
10 \r
11 #include "stdafx.h"\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
19 \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
25 \r
26 #include <algorithm>\r
27 \r
28 BOOL g_bNoCrashHandler;// don't use the crash handler but let the system handle it\r
29 \r
30 // maps crash objects to processes\r
31 map<DWORD, CCrashHandler*> _crashStateMap;\r
32 \r
33 // unhandled exception callback set with SetUnhandledExceptionFilter()\r
34 LONG WINAPI CustomUnhandledExceptionFilter(PEXCEPTION_POINTERS pExInfo)\r
35 {\r
36         OutputDebugString("Exception\n");\r
37    if (EXCEPTION_BREAKPOINT == pExInfo->ExceptionRecord->ExceptionCode)\r
38    {\r
39            // Breakpoint. Don't treat this as a normal crash.\r
40            return EXCEPTION_CONTINUE_SEARCH;\r
41    }\r
42 \r
43    if (g_bNoCrashHandler)\r
44    {\r
45            return EXCEPTION_CONTINUE_SEARCH;\r
46    }\r
47 \r
48    BOOL result = false;\r
49    if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())\r
50         result = _crashStateMap[GetCurrentProcessId()]->GenerateErrorReport(pExInfo, NULL);\r
51 \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
55 }\r
56 \r
57 CCrashHandler * CCrashHandler::GetInstance()\r
58 {\r
59         CCrashHandler *instance = NULL;\r
60         if (_crashStateMap.find(GetCurrentProcessId()) != _crashStateMap.end())\r
61                 instance = _crashStateMap[GetCurrentProcessId()];\r
62         if (instance == NULL) {\r
63                 // will register\r
64                 instance = new CCrashHandler();\r
65         }\r
66         return instance;\r
67 }\r
68 \r
69 CCrashHandler::CCrashHandler():\r
70         m_oldFilter(NULL),\r
71         m_lpfnCallback(NULL),\r
72         m_pid(GetCurrentProcessId()),\r
73         m_ipc_event(NULL),\r
74         m_rpt(NULL),\r
75         m_installed(false),\r
76         m_hModule(NULL),\r
77         m_bUseUI(TRUE),\r
78         m_wantDebug(false)\r
79 {\r
80    // wtl initialization stuff...\r
81         HRESULT hRes = ::CoInitialize(NULL);\r
82         if (hRes != S_OK)\r
83                 m_pid = 0;\r
84         else\r
85                 _crashStateMap[m_pid] = this;\r
86 }\r
87 \r
88 void CCrashHandler::Install(LPGETLOGFILE lpfn, LPCTSTR lpcszTo, LPCTSTR lpcszSubject, BOOL bUseUI)\r
89 {\r
90         if (m_pid == 0)\r
91                 return;\r
92 #ifdef _DEBUG\r
93         OutputDebugString("::Install\n");\r
94 #endif\r
95         if (m_installed) {\r
96                 Uninstall();\r
97         }\r
98    // save user supplied callback\r
99    m_lpfnCallback = lpfn;\r
100    // save optional email info\r
101    m_sTo = lpcszTo;\r
102    m_sSubject = lpcszSubject;\r
103    m_bUseUI = bUseUI;\r
104 \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
111 \r
112    m_installed = true;\r
113 }\r
114 \r
115 void CCrashHandler::Uninstall()\r
116 {\r
117 #ifdef _DEBUG\r
118         OutputDebugString("Uninstall\n");\r
119 #endif\r
120    // reset exception callback (to previous filter, which can be NULL)\r
121    SetUnhandledExceptionFilter(m_oldFilter);\r
122    m_installed = false;\r
123 }\r
124 \r
125 void CCrashHandler::EnableUI()\r
126 {\r
127         m_bUseUI = TRUE;\r
128 }\r
129 \r
130 void CCrashHandler::DisableUI()\r
131 {\r
132         m_bUseUI = FALSE;\r
133 }\r
134 \r
135 void CCrashHandler::DisableHandler()\r
136 {\r
137         g_bNoCrashHandler = TRUE;\r
138 }\r
139 \r
140 void CCrashHandler::EnableHandler()\r
141 {\r
142         g_bNoCrashHandler = FALSE;\r
143 }\r
144 \r
145 CCrashHandler::~CCrashHandler()\r
146 {\r
147 \r
148         Uninstall();\r
149 \r
150         _crashStateMap.erase(m_pid);\r
151 \r
152         ::CoUninitialize();\r
153 \r
154 }\r
155 \r
156 void CCrashHandler::AddFile(LPCTSTR lpFile, LPCTSTR lpDesc)\r
157 {\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
162                                          lpFile,\r
163                                          GENERIC_READ,\r
164                                          FILE_SHARE_READ | FILE_SHARE_WRITE,\r
165                                          NULL,\r
166                                          OPEN_EXISTING,\r
167                                          FILE_ATTRIBUTE_NORMAL,\r
168                                          0);\r
169    if (hFile != INVALID_HANDLE_VALUE)\r
170    {\r
171           // add file to report\r
172           m_files.push_back(TStrStrPair(lpFile, lpDesc));\r
173           ::CloseHandle(hFile);\r
174    }\r
175 }\r
176 \r
177 void CCrashHandler::RemoveFile(LPCTSTR lpFile)\r
178 {\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
183                         break;\r
184                 }\r
185         }\r
186 }\r
187 \r
188 void CCrashHandler::AddRegistryHive(LPCTSTR lpRegistryHive, LPCTSTR lpDesc)\r
189 {\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
196 }\r
197 \r
198 void CCrashHandler::RemoveRegistryHive(LPCTSTR lpRegistryHive)\r
199 {\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
204                 }\r
205         }\r
206 }\r
207 \r
208 void CCrashHandler::AddEventLog(LPCTSTR lpEventLog, LPCTSTR lpDesc)\r
209 {\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
213    // the event log..\r
214    // add EventLog to report\r
215    m_eventLogs.push_back(TStrStrPair(lpEventLog, lpDesc));\r
216 }\r
217 \r
218 void CCrashHandler::RemoveEventLog(LPCTSTR lpEventLog)\r
219 {\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
224                 }\r
225         }\r
226 }\r
227 \r
228 DWORD WINAPI CCrashHandler::DialogThreadExecute(LPVOID pParam)\r
229 {\r
230    // New thread. This will display the dialog and handle the result.\r
231    CCrashHandler * self = reinterpret_cast<CCrashHandler *>(pParam);\r
232    CMainDlg          mainDlg;\r
233    string                        sTempFileName = CUtility::getTempFileName();\r
234    CZLib             zlib;\r
235 \r
236    // delete existing copy, if any\r
237    DeleteFile(sTempFileName.c_str());\r
238 \r
239    // zip the report\r
240    if (!zlib.Open(sTempFileName))\r
241       return TRUE;\r
242    \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
248 \r
249    zlib.Close();\r
250 \r
251    mainDlg.m_pUDFiles = &self->m_files;\r
252    mainDlg.m_sendButton = !self->m_sTo.empty();\r
253 \r
254    INITCOMMONCONTROLSEX used = {\r
255            sizeof(INITCOMMONCONTROLSEX),\r
256            ICC_LISTVIEW_CLASSES | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_USEREX_CLASSES\r
257    };\r
258    InitCommonControlsEx(&used);\r
259 \r
260    INT_PTR status = mainDlg.DoModal(GetModuleHandle("CrashRpt.dll"), IDD_MAINDLG, GetDesktopWindow());\r
261    if (IDOK == status || IDC_SAVE == status)\r
262    {\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
265       {\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
272                                                                  NULL,\r
273                                                                  CREATE_ALWAYS,\r
274                                                                  FILE_ATTRIBUTE_NORMAL,\r
275                                                                  0);\r
276                            if (hFile != INVALID_HANDLE_VALUE)\r
277                            {\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
291 \r
292                                    // zip the report\r
293                                    if (!zlib.Open(sTempFileName))\r
294                                           return TRUE;\r
295    \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
301 \r
302                                    zlib.Close();\r
303                            }\r
304                   }\r
305          self->SaveReport(*self->m_rpt, sTempFileName.c_str());\r
306       }\r
307    }\r
308 \r
309    DeleteFile(sTempFileName.c_str());\r
310 \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
315 \r
316    // exit thread\r
317    ::ExitThread(0);\r
318    // keep compiler happy.\r
319    return TRUE;\r
320 }\r
321 \r
322 BOOL CCrashHandler::GenerateErrorReport(PEXCEPTION_POINTERS pExInfo, BSTR message)\r
323 {\r
324    CExceptionReport  rpt(pExInfo, message);\r
325    unsigned int      i;\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
329 \r
330         GetTempPath(sizeof temp, temp);\r
331 \r
332 \r
333    // let client add application specific files to report\r
334    if (m_lpfnCallback && !m_lpfnCallback(this))\r
335       return TRUE;\r
336 \r
337    m_rpt = &rpt;\r
338 \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
347                                                  NULL,\r
348                                                  CREATE_ALWAYS,\r
349                                                  FILE_ATTRIBUTE_NORMAL,\r
350                                                  0);\r
351            if (hFile != INVALID_HANDLE_VALUE)\r
352            {\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
358            } else {\r
359                    return m_wantDebug;\r
360            }\r
361    }\r
362 \r
363 \r
364 \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
370 \r
371    // Export registry hives and add to report\r
372    std::vector<string> extraFiles;\r
373    string file;\r
374    string number;\r
375    TStrStrVector::iterator iter;\r
376    int n = 0;\r
377 \r
378    for (iter = m_registryHives.begin(); iter != m_registryHives.end(); iter++) {\r
379            ++n;\r
380            TCHAR buf[MAX_PATH] = {0};\r
381            _tprintf_s(buf, "%d", n);\r
382            number = buf;\r
383            file = temp + string("\\") + CUtility::getAppName() + "_registry" + number + ".reg";\r
384            ::DeleteFile(file.c_str());\r
385 \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
391            } else {\r
392                    OutputDebugString("Could not write registry hive\n");\r
393            }\r
394    }\r
395    //\r
396    // Add the specified event log(s). Note that this will not work on Win9x/WinME.\r
397    //\r
398    for (iter = m_eventLogs.begin(); iter != m_eventLogs.end(); iter++) {\r
399                 HANDLE h;\r
400                 h = OpenEventLog( NULL,    // use local computer\r
401                                  (*iter).first.c_str());   // source name\r
402                 if (h != NULL) {\r
403 \r
404                         file = temp + string("\\") + CUtility::getAppName() +  "_" + (*iter).first + ".evt";\r
405 \r
406                         DeleteFile(file.c_str());\r
407 \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
411                         } else {\r
412                                 OutputDebugString("could not backup log\n");\r
413                         }\r
414                         CloseEventLog(h);\r
415                 } else {\r
416                         OutputDebugString("could not open log\n");\r
417                 }\r
418    }\r
419  \r
420 \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
425  \r
426    //remove the crash handler, just in case the dialog crashes...\r
427    Uninstall();\r
428    if (m_bUseUI)\r
429    {\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
435            DWORD threadId;\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
441    }\r
442    else\r
443    {\r
444            string sTempFileName = CUtility::getTempFileName();\r
445            CZLib             zlib;\r
446 \r
447            sTempFileName += _T(".zip");\r
448            // delete existing copy, if any\r
449            DeleteFile(sTempFileName.c_str());\r
450 \r
451            // zip the report\r
452            if (!zlib.Open(sTempFileName))\r
453                    return TRUE;\r
454 \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
460            zlib.Close();\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
465            {\r
466                  fprintf(stderr, "please send the report to ");\r
467                  _ftprintf(stderr, m_sTo.c_str());\r
468                  fprintf(stderr, "\n");\r
469            }\r
470    }\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
476    }\r
477 \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
481    }\r
482 \r
483    // restore state of file list\r
484    m_files = save_m_files;\r
485 \r
486    m_rpt = NULL;\r
487 \r
488    return !m_wantDebug;\r
489 }\r
490 \r
491 BOOL CCrashHandler::SaveReport(CExceptionReport&, LPCTSTR lpcszFile)\r
492 {\r
493    // let user more zipped report\r
494    return (CopyFile(lpcszFile, CUtility::getSaveFileName().c_str(), TRUE));\r
495 }\r
496 \r
497 BOOL CCrashHandler::MailReport(CExceptionReport&, LPCTSTR lpcszFile,\r
498                                LPCTSTR lpcszEmail, LPCTSTR lpcszDesc)\r
499 {\r
500    CMailMsg msg;\r
501    msg\r
502       .SetTo(m_sTo)\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
507 \r
508    return (msg.Send());\r
509 }\r
510 \r
511 string CCrashHandler::LoadResourceString(UINT id)\r
512 {\r
513         static int address;\r
514         char buffer[512];\r
515         if (m_hModule == NULL) {\r
516                 m_hModule = GetModuleHandle("CrashRpt.dll");\r
517         }\r
518         buffer[0] = '\0';\r
519         if (m_hModule) {\r
520                 LoadString(m_hModule, id, buffer, sizeof buffer);\r
521         }\r
522         return buffer;\r
523 }\r