Sync CrashReport.h from TortoiseSVN
[TortoiseGit.git] / src / Utils / CrashReport.h
blob486eb46dc30e5dce21decef1dda3300e1b36db42
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013 - TortoiseGit
4 // Copyright (C) 2012 - 2014 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #pragma once
21 #include "../../ext/CrashServer/CrashHandler/CrashHandler/CrashHandler.h"
22 #include <time.h>
23 #include <string>
24 #include <tchar.h>
25 #include <DbgHelp.h>
27 // dummy define, needed only when we use crashrpt instead of this.
28 #define CR_AF_MAKE_FILE_COPY 0
30 /**
31 * \ingroup Utils
32 * helper class for the DoctorDumpSDK
34 class CCrashReport
36 private:
37 CCrashReport(void)
38 : m_InitCrashHandler(nullptr)
39 , m_SendReport(nullptr)
40 , m_IsReadyToExit(nullptr)
41 , m_AddUserInfoToReport(nullptr)
42 , m_AddFileToReport(nullptr)
43 , m_RemoveFileFromReport(nullptr)
44 , m_GetVersionFromApp(nullptr)
45 , m_GetVersionFromFile(nullptr)
47 LoadDll();
50 ~CCrashReport(void)
52 if (!m_IsReadyToExit)
53 return;
55 // If crash has happen not in main thread we should wait here until report will be sent
56 // or else program will be terminated after return from main() and report sending will be halted.
57 while (!m_IsReadyToExit())
58 ::Sleep(100);
60 if (m_bSkipAssertsAdded)
61 RemoveVectoredExceptionHandler(SkipAsserts);
64 public:
65 static CCrashReport& Instance()
67 static CCrashReport instance;
68 return instance;
71 int Uninstall(void) { return FALSE; }
72 int AddFile2(LPCTSTR pszFile,LPCTSTR pszDestFile,LPCTSTR /*pszDesc*/,DWORD /*dwFlags*/)
74 return AddFileToReport(pszFile, pszDestFile) ? 1 : 0;
77 //! Checks that crash handling was enabled.
78 //! \return Return \b true if crash handling was enabled.
79 bool IsCrashHandlingEnabled() const
81 return m_bWorking;
84 //! Initializes crash handler.
85 //! \note You may call this function multiple times if some data has changed.
86 //! \return Return \b true if crash handling was enabled.
87 bool InitCrashHandler(
88 ApplicationInfo* applicationInfo, //!< [in] Pointer to the ApplicationInfo structure that identifies your application.
89 HandlerSettings* handlerSettings, //!< [in] Pointer to the HandlerSettings structure that customizes crash handling behavior. This parameter can be \b NULL.
90 BOOL ownProcess = TRUE //!< [in] If you own the process your code running in set this option to \b TRUE. If don't (for example you write
91 //!< a plugin to some external application) set this option to \b FALSE. In that case you need to explicitly
92 //!< catch exceptions. See \ref SendReport for more information.
93 ) throw()
95 if (!m_InitCrashHandler)
96 return false;
98 m_bWorking = m_InitCrashHandler(applicationInfo, handlerSettings, ownProcess) != FALSE;
100 return m_bWorking;
103 //! \note This function is experimental and may not be available and may not be support by Doctor Dump in the future.
104 //! You may set custom information for your possible report.
105 //! This text will be available on Doctor Dump dumps page.
106 //! The text should not contain private information.
107 //! \return If the function succeeds, the return value is \b true.
108 bool SetCustomInfo(
109 LPCWSTR text //!< [in] custom info for the report. The text will be cut to 100 characters.
112 if (!m_SetCustomInfo)
113 return false;
114 m_SetCustomInfo(text);
115 return true;
118 //! You may add any key/value pair to crash report.
119 //! \return If the function succeeds, the return value is \b true.
120 //! \note This function is thread safe.
121 bool AddUserInfoToReport(
122 LPCWSTR key, //!< [in] key string that will be added to the report.
123 LPCWSTR value //!< [in] value for the key.
124 ) throw()
126 if (!m_AddUserInfoToReport)
127 return false;
128 m_AddUserInfoToReport(key, value);
129 return true;
132 //! You may remove any key that was added previously to crash report by \a AddUserInfoToReport.
133 //! \return If the function succeeds, the return value is \b true.
134 //! \note This function is thread safe.
135 bool RemoveUserInfoFromReport(
136 LPCWSTR key //!< [in] key string that will be removed from the report.
139 if (!m_RemoveUserInfoFromReport)
140 return false;
141 m_RemoveUserInfoFromReport(key);
142 return true;
145 //! You may add any file to crash report. This file will be read when crash appears and will be sent within the report.
146 //! Multiple files may be added. Filename of the file in the report may be changed to any name.
147 //! \return If the function succeeds, the return value is \b true.
148 //! \note This function is thread safe.
149 bool AddFileToReport(
150 LPCWSTR path, //!< [in] Path to the file, that will be added to the report.
151 LPCWSTR reportFileName /* = NULL */ //!< [in] Filename that will be used in report for this file. If parameter is \b NULL, original name from path will be used.
152 ) throw()
154 if (!m_AddFileToReport)
155 return false;
156 m_AddFileToReport(path, reportFileName);
157 return true;
160 //! Remove from report the file that was registered earlier to be sent within report.
161 //! \return If the function succeeds, the return value is \b true.
162 //! \note This function is thread safe.
163 bool RemoveFileFromReport(
164 LPCWSTR path //!< [in] Path to the file, that will be removed from the report.
165 ) throw()
167 if (!m_RemoveFileFromReport)
168 return false;
169 m_RemoveFileFromReport(path);
170 return true;
173 //! Fills version field (V) of ApplicationInfo with product version
174 //! found in the executable file of the current process.
175 //! \return If the function succeeds, the return value is \b true.
176 bool GetVersionFromApp(
177 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
178 ) throw()
180 if (!m_GetVersionFromApp)
181 return false;
182 return m_GetVersionFromApp(appInfo) != FALSE;
185 //! Fill version field (V) of ApplicationInfo with product version found in the file specified.
186 //! \return If the function succeeds, the return value is \b true.
187 bool GetVersionFromFile(
188 LPCWSTR path, //!< [in] Path to the file product version will be extracted from.
189 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
190 ) throw()
192 if (!m_GetVersionFromFile)
193 return false;
194 return m_GetVersionFromFile(path, appInfo) != FALSE;
197 //! If you do not own the process your code running in (for example you write a plugin to some
198 //! external application) you need to properly initialize CrashHandler using \b ownProcess option.
199 //! Also you need to explicitly catch all exceptions in all entry points to your code and in all
200 //! threads you create. To do so use this construction:
201 //! \code
202 //! bool SomeEntryPoint(PARAM p)
203 //! {
204 //! __try
205 //! {
206 //! return YouCode(p);
207 //! }
208 //! __except (CrashHandler::SendReport(GetExceptionInformation()))
209 //! {
210 //! ::ExitProcess(0); // It is better to stop the process here or else corrupted data may incomprehensibly crash it later.
211 //! return false;
212 //! }
213 //! }
214 //! \endcode
215 LONG SendReport(
216 EXCEPTION_POINTERS* exceptionPointers //!< [in] Pointer to EXCEPTION_POINTERS structure. You should get it using GetExceptionInformation()
217 //!< function inside __except keyword.
220 if (!m_SendReport)
221 return EXCEPTION_CONTINUE_SEARCH;
222 // There is no crash handler but asserts should continue anyway
223 if (exceptionPointers->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
224 return EXCEPTION_CONTINUE_EXECUTION;
225 return m_SendReport(exceptionPointers);
228 //! To send a report about violated assertion you can throw exception with this exception code
229 //! using: \code RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, NULL); \endcode
230 //! Execution will continue after report will be sent (EXCEPTION_CONTINUE_EXECUTION would be used).
231 //! You may pass grouping string as first parameter (see \a SkipDoctorDump_SendAssertionViolated).
232 //! \note If you called CrashHandler constructor and crshhndl.dll was missing you still may using this exception.
233 //! It will be caught, ignored and execution will continue. \ref SendReport function also works safely
234 //! when crshhndl.dll was missing.
235 static const DWORD ExceptionAssertionViolated = ((DWORD)0xCCE17000);
237 //! Sends assertion violation report from this point and continue execution.
238 //! \sa ExceptionAssertionViolated
239 //! \note Functions containing "SkipDoctorDump" will be ignored in stack parsing.
240 void SkipDoctorDump_SendAssertionViolated(
241 LPCSTR dumpGroup = NULL //!< [in] All dumps with that group will be separated from dumps with same stack but another group. Set parameter to \b NULL if no grouping is required.
242 ) const
244 if (!m_bWorking)
245 return;
246 if (dumpGroup)
247 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 1, reinterpret_cast<ULONG_PTR*>(&dumpGroup));
248 else
249 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, NULL);
252 private:
253 bool LoadDll(LPCWSTR crashHandlerPath = NULL) throw()
255 m_bLoaded = false;
256 m_bWorking = false;
257 m_bSkipAssertsAdded = false;
258 m_InitCrashHandler = NULL;
259 m_SendReport = NULL;
260 m_IsReadyToExit = NULL;
261 m_SetCustomInfo = NULL;
262 m_AddUserInfoToReport = NULL;
263 m_RemoveUserInfoFromReport = NULL;
264 m_AddFileToReport = NULL;
265 m_RemoveFileFromReport = NULL;
266 m_GetVersionFromApp = NULL;
267 m_GetVersionFromFile = NULL;
269 // hCrashHandlerDll should not be unloaded, crash may appear even after return from main().
270 // So hCrashHandlerDll is not saved after construction.
271 BOOL bIsWow = FALSE;
272 IsWow64Process(GetCurrentProcess(), &bIsWow);
273 HMODULE hCrashHandlerDll = NULL;
274 if (bIsWow == FALSE)
275 hCrashHandlerDll = ::LoadLibraryW(crashHandlerPath ? crashHandlerPath : L"crshhndl.dll");
276 if (hCrashHandlerDll != NULL)
278 m_InitCrashHandler = (pfnInitCrashHandler) GetProcAddress(hCrashHandlerDll, "InitCrashHandler");
279 m_SendReport = (pfnSendReport) GetProcAddress(hCrashHandlerDll, "SendReport");
280 m_IsReadyToExit = (pfnIsReadyToExit) GetProcAddress(hCrashHandlerDll, "IsReadyToExit");
281 m_SetCustomInfo = (pfnSetCustomInfo) GetProcAddress(hCrashHandlerDll, "SetCustomInfo");
282 m_AddUserInfoToReport = (pfnAddUserInfoToReport) GetProcAddress(hCrashHandlerDll, "AddUserInfoToReport");
283 m_RemoveUserInfoFromReport = (pfnRemoveUserInfoFromReport) GetProcAddress(hCrashHandlerDll, "RemoveUserInfoFromReport");
284 m_AddFileToReport = (pfnAddFileToReport) GetProcAddress(hCrashHandlerDll, "AddFileToReport");
285 m_RemoveFileFromReport = (pfnRemoveFileFromReport) GetProcAddress(hCrashHandlerDll, "RemoveFileFromReport");
286 m_GetVersionFromApp = (pfnGetVersionFromApp) GetProcAddress(hCrashHandlerDll, "GetVersionFromApp");
287 m_GetVersionFromFile = (pfnGetVersionFromFile) GetProcAddress(hCrashHandlerDll, "GetVersionFromFile");
289 m_bLoaded = m_InitCrashHandler
290 && m_SendReport
291 && m_IsReadyToExit
292 && m_SetCustomInfo
293 && m_AddUserInfoToReport
294 && m_RemoveUserInfoFromReport
295 && m_AddFileToReport
296 && m_RemoveFileFromReport
297 && m_GetVersionFromApp
298 && m_GetVersionFromFile;
301 #if 0 // TGit don't use it
302 #if _WIN32_WINNT >= 0x0501 /*_WIN32_WINNT_WINXP*/
303 // if no crash processing was started, we need to ignore ExceptionAssertionViolated exceptions.
304 if (!m_bLoaded)
306 ::AddVectoredExceptionHandler(TRUE, SkipAsserts);
307 m_bSkipAssertsAdded = true;
309 #endif
310 #endif
312 return m_bLoaded;
315 static LONG CALLBACK SkipAsserts(EXCEPTION_POINTERS* pExceptionInfo)
317 if (pExceptionInfo->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
318 return EXCEPTION_CONTINUE_EXECUTION;
319 return EXCEPTION_CONTINUE_SEARCH;
322 bool m_bLoaded;
323 bool m_bWorking;
324 bool m_bSkipAssertsAdded;
326 typedef BOOL (*pfnInitCrashHandler)(ApplicationInfo* applicationInfo, HandlerSettings* handlerSettings, BOOL ownProcess);
327 typedef LONG (*pfnSendReport)(EXCEPTION_POINTERS* exceptionPointers);
328 typedef BOOL (*pfnIsReadyToExit)();
329 typedef void (*pfnSetCustomInfo)(LPCWSTR text);
330 typedef void (*pfnAddUserInfoToReport)(LPCWSTR key, LPCWSTR value);
331 typedef void (*pfnRemoveUserInfoFromReport)(LPCWSTR key);
332 typedef void (*pfnAddFileToReport)(LPCWSTR path, LPCWSTR reportFileName /* = NULL */);
333 typedef void (*pfnRemoveFileFromReport)(LPCWSTR path);
334 typedef BOOL (*pfnGetVersionFromApp)(ApplicationInfo* appInfo);
335 typedef BOOL (*pfnGetVersionFromFile)(LPCWSTR path, ApplicationInfo* appInfo);
337 pfnInitCrashHandler m_InitCrashHandler;
338 pfnSendReport m_SendReport;
339 pfnIsReadyToExit m_IsReadyToExit;
340 pfnSetCustomInfo m_SetCustomInfo;
341 pfnAddUserInfoToReport m_AddUserInfoToReport;
342 pfnRemoveUserInfoFromReport m_RemoveUserInfoFromReport;
343 pfnAddFileToReport m_AddFileToReport;
344 pfnRemoveFileFromReport m_RemoveFileFromReport;
345 pfnGetVersionFromApp m_GetVersionFromApp;
346 pfnGetVersionFromFile m_GetVersionFromFile;
349 class CCrashReportTGit
351 public:
353 //! Installs exception handlers to the caller process
354 CCrashReportTGit(LPCTSTR appname, USHORT versionMajor, USHORT versionMinor, USHORT versionMicro, USHORT versionBuild, const char * buildDate, bool bOwnProcess = true)
355 : m_nInstallStatus(0)
357 char s_month[6] = {0};
358 int month, day, year;
359 struct tm t = {0};
360 static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
361 sscanf_s(buildDate, "%s %d %d", s_month, (unsigned int)_countof(s_month) - 1, &day, &year);
362 month = (int)((strstr(month_names, s_month)-month_names))/3;
364 t.tm_mon = month;
365 t.tm_mday = day;
366 t.tm_year = year - 1900;
367 t.tm_isdst = -1;
368 __time64_t compiletime = _mktime64(&t);
370 __time64_t now;
371 _time64(&now);
373 ApplicationInfo appInfo = { 0 };
374 appInfo.ApplicationInfoSize = sizeof(ApplicationInfo);
375 appInfo.ApplicationGUID = "7fbde3fc-94e9-408b-b5c8-62bd4e203570";
376 appInfo.Prefix = "tgit";
377 appInfo.AppName = appname;
378 appInfo.Company = L"TortoiseGit";
380 appInfo.Hotfix = 0;
381 appInfo.V[0] = versionMajor;
382 appInfo.V[1] = versionMinor;
383 appInfo.V[2] = versionMicro;
384 appInfo.V[3] = versionBuild;
386 HandlerSettings handlerSettings = { 0 };
387 handlerSettings.HandlerSettingsSize = sizeof(handlerSettings);
388 handlerSettings.LeaveDumpFilesInTempFolder = FALSE;
389 handlerSettings.UseWER = FALSE;
390 handlerSettings.OpenProblemInBrowser = TRUE;
391 #if PREVIEW
392 if ((now - compiletime) > (60 * 60 * 24 * 7 * 5))
393 #else
394 if ((now - compiletime) > (60 * 60 * 24 * 7 * 10))
395 #endif
397 handlerSettings.OverrideDefaultFullDumpType = TRUE;
398 handlerSettings.FullDumpType = MiniDumpFilterMemory;
401 CCrashReport::Instance().InitCrashHandler(&appInfo, &handlerSettings, bOwnProcess);
405 //! Deinstalls exception handlers from the caller process
406 ~CCrashReportTGit()
408 CCrashReport::Instance().Uninstall();
411 //! Install status
412 int m_nInstallStatus;