For old releases only upload mini dumps w/o memory
[TortoiseGit.git] / src / Utils / CrashReport.h
blob274457f861148bef4ded2da2e6a78db5297ed789
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013 - TortoiseGit
4 // Copyright (C) 2012 - 2013 - 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 CrashServerSDK
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)
54 public:
55 static CCrashReport& Instance()
57 static CCrashReport instance;
58 return instance;
61 int Uninstall(void) { return FALSE; }
62 int AddFile2(LPCTSTR pszFile,LPCTSTR pszDestFile,LPCTSTR /*pszDesc*/,DWORD /*dwFlags*/)
64 return AddFileToReport(pszFile, pszDestFile) ? 1 : 0;
68 //! Initializes crash handler.
69 //! \note You may call this function multiple times if some data has changed.
70 //! \return Return \b true if crash handling was enabled.
71 bool InitCrashHandler(
72 ApplicationInfo* applicationInfo, //!< [in] Pointer to the ApplicationInfo structure that identifies your application.
73 HandlerSettings* handlerSettings, //!< [in] Pointer to the HandlerSettings structure that customizes crash handling behavior. This paramenter can be \b NULL.
74 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
75 //!< a plugin to some external application) set this option to \b FALSE. In that case you need to explicitly
76 //!< catch exceptions. See \ref SendReport for more information.
77 ) throw()
79 if (!m_InitCrashHandler)
80 return false;
82 return m_InitCrashHandler(applicationInfo, handlerSettings, ownProcess) != FALSE;
85 //! You may add any key/value pair to crash report.
86 //! \return If the function succeeds, the return value is \b true.
87 bool AddUserInfoToReport(
88 LPCWSTR key, //!< [in] key string that will be added to the report.
89 LPCWSTR value //!< [in] value for the key.
90 ) throw()
92 if (!m_AddUserInfoToReport)
93 return false;
94 m_AddUserInfoToReport(key, value);
95 return true;
98 //! You may add any file to crash report. This file will be read when crash appears and will be sent within the report.
99 //! Multiple files may be added. Filename of the file in the report may be changed to any name.
100 //! \return If the function succeeds, the return value is \b true.
101 bool AddFileToReport(
102 LPCWSTR path, //!< [in] Path to the file, that will be added to the report.
103 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.
104 ) throw()
106 if (!m_AddFileToReport)
107 return false;
108 m_AddFileToReport(path, reportFileName);
109 return true;
112 //! Remove from report the file that was registered earlier to be sent within report.
113 //! \return If the function succeeds, the return value is \b true.
114 bool RemoveFileFromReport(
115 LPCWSTR path //!< [in] Path to the file, that will be removed from the report.
116 ) throw()
118 if (!m_RemoveFileFromReport)
119 return false;
120 m_RemoveFileFromReport(path);
121 return true;
124 //! Fills version field (V) of ApplicationInfo with product version
125 //! found in the executable file of the current process.
126 //! \return If the function succeeds, the return value is \b true.
127 bool GetVersionFromApp(
128 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
129 ) throw()
131 if (!m_GetVersionFromApp)
132 return false;
133 return m_GetVersionFromApp(appInfo) != FALSE;
136 //! Fill version field (V) of ApplicationInfo with product version found in the file specified.
137 //! \return If the function succeeds, the return value is \b true.
138 bool GetVersionFromFile(
139 LPCWSTR path, //!< [in] Path to the file product version will be extracted from.
140 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
141 ) throw()
143 if (!m_GetVersionFromFile)
144 return false;
145 return m_GetVersionFromFile(path, appInfo) != FALSE;
148 //! If you do not own the process your code running in (for example you write a plugin to some
149 //! external application) you need to properly initialize CrashHandler using \b ownProcess option.
150 //! Also you need to explicitly catch all exceptions in all entry points to your code and in all
151 //! threads you create. To do so use this construction:
152 //! \code
153 //! bool SomeEntryPoint(PARAM p)
154 //! {
155 //! __try
156 //! {
157 //! return YouCode(p);
158 //! }
159 //! __except (CrashHandler::SendReport(GetExceptionInformation()))
160 //! {
161 //! ::ExitProcess(0); // It is better to stop the process here or else corrupted data may incomprehensibly crash it later.
162 //! return false;
163 //! }
164 //! }
165 //! \endcode
166 LONG SendReport(
167 EXCEPTION_POINTERS* exceptionPointers //!< [in] Pointer to EXCEPTION_POINTERS structure. You should get it using GetExceptionInformation()
168 //!< function inside __except keyword.
171 if (!m_SendReport)
172 return EXCEPTION_CONTINUE_SEARCH;
173 // There is no crash handler but asserts should continue anyway
174 if (exceptionPointers->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
175 return EXCEPTION_CONTINUE_EXECUTION;
176 return m_SendReport(exceptionPointers);
179 //! To send a report about violated assertion you can throw exception with this exception code
180 //! using: \code RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, NULL); \endcode
181 //! Execution will continue after report will be sent (EXCEPTION_CONTINUE_EXECUTION would be used).
182 //! \note If you called CrashHandler constructor and crshhdnl.dll was missing you still may using this exception.
183 //! It will be catched, ignored and execution will continue. \ref SendReport function also works safely
184 //! when crshhdnl.dll was missing.
185 static const DWORD ExceptionAssertionViolated = ((DWORD)0xCCE17000);
187 //! Sends assertion violation report from this point and continue execution.
188 //! \sa ExceptionAssertionViolated
189 //! \note Functions prefixed with "SkipDoctorDump_" will be ignored in stack parsing.
190 void SkipDoctorDump_SendAssertionViolated() const
192 if (!m_InitCrashHandler)
193 return;
194 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, NULL);
197 private:
198 bool LoadDll() throw()
200 bool result = false;
202 // hCrshhndlDll should not be unloaded, crash may appear even after return from main().
203 // So hCrshhndlDll is not saved after construction.
204 BOOL bIsWow = FALSE;
205 IsWow64Process(GetCurrentProcess(), &bIsWow);
206 HMODULE hCrshhndlDll = nullptr;
207 if (bIsWow == FALSE)
208 hCrshhndlDll = ::LoadLibraryW(L"crshhndl.dll");
209 if (hCrshhndlDll != NULL)
211 m_InitCrashHandler = (pfnInitCrashHandler) GetProcAddress(hCrshhndlDll, "InitCrashHandler");
212 m_SendReport = (pfnSendReport) GetProcAddress(hCrshhndlDll, "SendReport");
213 m_IsReadyToExit = (pfnIsReadyToExit) GetProcAddress(hCrshhndlDll, "IsReadyToExit");
214 m_AddUserInfoToReport = (pfnAddUserInfoToReport) GetProcAddress(hCrshhndlDll, "AddUserInfoToReport");
215 m_AddFileToReport = (pfnAddFileToReport) GetProcAddress(hCrshhndlDll, "AddFileToReport");
216 m_RemoveFileFromReport = (pfnRemoveFileFromReport) GetProcAddress(hCrshhndlDll, "RemoveFileFromReport");
217 m_GetVersionFromApp = (pfnGetVersionFromApp) GetProcAddress(hCrshhndlDll, "GetVersionFromApp");
218 m_GetVersionFromFile = (pfnGetVersionFromFile) GetProcAddress(hCrshhndlDll, "GetVersionFromFile");
220 result = m_InitCrashHandler
221 && m_SendReport
222 && m_IsReadyToExit
223 && m_AddUserInfoToReport
224 && m_AddFileToReport
225 && m_RemoveFileFromReport
226 && m_GetVersionFromApp
227 && m_GetVersionFromFile;
230 return result;
233 static LONG CALLBACK SkipAsserts(EXCEPTION_POINTERS* pExceptionInfo)
235 if (pExceptionInfo->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
236 return EXCEPTION_CONTINUE_EXECUTION;
237 return EXCEPTION_CONTINUE_SEARCH;
240 typedef BOOL (*pfnInitCrashHandler)(ApplicationInfo* applicationInfo, HandlerSettings* handlerSettings, BOOL ownProcess);
241 typedef LONG (*pfnSendReport)(EXCEPTION_POINTERS* exceptionPointers);
242 typedef BOOL (*pfnIsReadyToExit)();
243 typedef void (*pfnAddUserInfoToReport)(LPCWSTR key, LPCWSTR value);
244 typedef void (*pfnAddFileToReport)(LPCWSTR path, LPCWSTR reportFileName /* = NULL */);
245 typedef void (*pfnRemoveFileFromReport)(LPCWSTR path);
246 typedef BOOL (*pfnGetVersionFromApp)(ApplicationInfo* appInfo);
247 typedef BOOL (*pfnGetVersionFromFile)(LPCWSTR path, ApplicationInfo* appInfo);
249 pfnInitCrashHandler m_InitCrashHandler;
250 pfnSendReport m_SendReport;
251 pfnIsReadyToExit m_IsReadyToExit;
252 pfnAddUserInfoToReport m_AddUserInfoToReport;
253 pfnAddFileToReport m_AddFileToReport;
254 pfnRemoveFileFromReport m_RemoveFileFromReport;
255 pfnGetVersionFromApp m_GetVersionFromApp;
256 pfnGetVersionFromFile m_GetVersionFromFile;
260 class CCrashReportTGit
262 public:
264 //! Installs exception handlers to the caller process
265 CCrashReportTGit(LPCTSTR appname, USHORT versionMajor, USHORT versionMinor, USHORT versionMicro, USHORT versionBuild, const char * buildDate, bool bOwnProcess = true)
266 : m_nInstallStatus(0)
268 char s_month[6] = {0};
269 int month, day, year;
270 struct tm t = {0};
271 static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
272 sscanf_s(buildDate, "%s %d %d", s_month, (unsigned int)_countof(s_month) - 1, &day, &year);
273 month = (int)((strstr(month_names, s_month)-month_names))/3;
275 t.tm_mon = month;
276 t.tm_mday = day;
277 t.tm_year = year - 1900;
278 t.tm_isdst = -1;
279 __time64_t compiletime = _mktime64(&t);
281 __time64_t now;
282 _time64(&now);
284 ApplicationInfo appInfo = { 0 };
285 appInfo.ApplicationInfoSize = sizeof(ApplicationInfo);
286 appInfo.ApplicationGUID = "7fbde3fc-94e9-408b-b5c8-62bd4e203570";
287 appInfo.Prefix = "tgit";
288 appInfo.AppName = appname;
289 appInfo.Company = L"TortoiseGit";
291 appInfo.Hotfix = 0;
292 appInfo.V[0] = versionMajor;
293 appInfo.V[1] = versionMinor;
294 appInfo.V[2] = versionMicro;
295 appInfo.V[3] = versionBuild;
297 HandlerSettings handlerSettings = { 0 };
298 handlerSettings.HandlerSettingsSize = sizeof(handlerSettings);
299 handlerSettings.LeaveDumpFilesInTempFolder = FALSE;
300 handlerSettings.UseWER = FALSE;
301 handlerSettings.OpenProblemInBrowser = TRUE;
302 #if PREVIEW
303 if ((now - compiletime) > (60 * 60 * 24 * 7 * 5))
304 #else
305 if ((now - compiletime) > (60 * 60 * 24 * 7 * 10))
306 #endif
308 handlerSettings.OverrideDefaultFullDumpType = TRUE;
309 handlerSettings.FullDumpType = MiniDumpFilterMemory;
312 CCrashReport::Instance().InitCrashHandler(&appInfo, &handlerSettings, bOwnProcess);
316 //! Deinstalls exception handlers from the caller process
317 ~CCrashReportTGit()
319 CCrashReport::Instance().Uninstall();
322 //! Install status
323 int m_nInstallStatus;