Applied backgroundcolors.patch
[TortoiseGit.git] / src / Utils / CrashReport.h
blob74ca3828e3d670004246e84aa2d6511301ab2180
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013, 2016 - 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 #pragma warning(push)
26 #pragma warning(disable: 4091)
27 #include <DbgHelp.h>
28 #pragma warning(pop)
30 // dummy define, needed only when we use crashrpt instead of this.
31 #define CR_AF_MAKE_FILE_COPY 0
33 __forceinline HMODULE get_my_module_handle(void)
35 static int s_module_marker = 0;
36 MEMORY_BASIC_INFORMATION memory_basic_information;
37 if (!VirtualQuery(&s_module_marker, &memory_basic_information, sizeof(memory_basic_information)))
39 return nullptr;
41 return (HMODULE)memory_basic_information.AllocationBase;
44 /**
45 * \ingroup Utils
46 * helper class for the DoctorDumpSDK
48 class CCrashReport
50 private:
51 CCrashReport(void)
52 : m_InitCrashHandler(nullptr)
53 , m_SendReport(nullptr)
54 , m_IsReadyToExit(nullptr)
55 , m_AddUserInfoToReport(nullptr)
56 , m_AddFileToReport(nullptr)
57 , m_RemoveFileFromReport(nullptr)
58 , m_GetVersionFromApp(nullptr)
59 , m_GetVersionFromFile(nullptr)
61 LoadDll();
64 ~CCrashReport(void)
66 if (!m_IsReadyToExit)
67 return;
69 // If crash has happen not in main thread we should wait here until report will be sent
70 // or else program will be terminated after return from main() and report sending will be halted.
71 while (!m_IsReadyToExit())
72 ::Sleep(100);
74 if (m_bSkipAssertsAdded)
75 RemoveVectoredExceptionHandler(SkipAsserts);
78 public:
79 static CCrashReport& Instance()
81 static CCrashReport instance;
82 return instance;
85 int Uninstall(void) { return FALSE; }
86 int AddFile2(LPCTSTR pszFile,LPCTSTR pszDestFile,LPCTSTR /*pszDesc*/,DWORD /*dwFlags*/)
88 return AddFileToReport(pszFile, pszDestFile) ? 1 : 0;
91 //! Checks that crash handling was enabled.
92 //! \return Return \b true if crash handling was enabled.
93 bool IsCrashHandlingEnabled() const
95 return m_bWorking;
98 //! Initializes crash handler.
99 //! \note You may call this function multiple times if some data has changed.
100 //! \return Return \b true if crash handling was enabled.
101 bool InitCrashHandler(
102 ApplicationInfo* applicationInfo, //!< [in] Pointer to the ApplicationInfo structure that identifies your application.
103 HandlerSettings* handlerSettings, //!< [in] Pointer to the HandlerSettings structure that customizes crash handling behavior. This parameter can be \b nullptr.
104 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
105 //!< a plugin to some external application) set this option to \b FALSE. In that case you need to explicitly
106 //!< catch exceptions. See \ref SendReport for more information.
107 ) throw()
109 if (!m_InitCrashHandler)
110 return false;
112 m_bWorking = m_InitCrashHandler(applicationInfo, handlerSettings, ownProcess) != FALSE;
114 return m_bWorking;
117 //! \note This function is experimental and may not be available and may not be support by Doctor Dump in the future.
118 //! You may set custom information for your possible report.
119 //! This text will be available on Doctor Dump dumps page.
120 //! The text should not contain private information.
121 //! \return If the function succeeds, the return value is \b true.
122 bool SetCustomInfo(
123 LPCWSTR text //!< [in] custom info for the report. The text will be cut to 100 characters.
126 if (!m_SetCustomInfo)
127 return false;
128 m_SetCustomInfo(text);
129 return true;
132 //! You may add any key/value pair to crash report.
133 //! \return If the function succeeds, the return value is \b true.
134 //! \note This function is thread safe.
135 bool AddUserInfoToReport(
136 LPCWSTR key, //!< [in] key string that will be added to the report.
137 LPCWSTR value //!< [in] value for the key.
138 ) throw()
140 if (!m_AddUserInfoToReport)
141 return false;
142 m_AddUserInfoToReport(key, value);
143 return true;
146 //! You may remove any key that was added previously to crash report by \a AddUserInfoToReport.
147 //! \return If the function succeeds, the return value is \b true.
148 //! \note This function is thread safe.
149 bool RemoveUserInfoFromReport(
150 LPCWSTR key //!< [in] key string that will be removed from the report.
153 if (!m_RemoveUserInfoFromReport)
154 return false;
155 m_RemoveUserInfoFromReport(key);
156 return true;
159 //! You may add any file to crash report. This file will be read when crash appears and will be sent within the report.
160 //! Multiple files may be added. Filename of the file in the report may be changed to any name.
161 //! \return If the function succeeds, the return value is \b true.
162 //! \note This function is thread safe.
163 bool AddFileToReport(
164 LPCWSTR path, //!< [in] Path to the file, that will be added to the report.
165 LPCWSTR reportFileName /* = nullptr */ //!< [in] Filename that will be used in report for this file. If parameter is \b nullptr, original name from path will be used.
166 ) throw()
168 if (!m_AddFileToReport)
169 return false;
170 m_AddFileToReport(path, reportFileName);
171 return true;
174 //! Remove from report the file that was registered earlier to be sent within report.
175 //! \return If the function succeeds, the return value is \b true.
176 //! \note This function is thread safe.
177 bool RemoveFileFromReport(
178 LPCWSTR path //!< [in] Path to the file, that will be removed from the report.
179 ) throw()
181 if (!m_RemoveFileFromReport)
182 return false;
183 m_RemoveFileFromReport(path);
184 return true;
187 //! Fills version field (V) of ApplicationInfo with product version
188 //! found in the executable file of the current process.
189 //! \return If the function succeeds, the return value is \b true.
190 bool GetVersionFromApp(
191 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
192 ) throw()
194 if (!m_GetVersionFromApp)
195 return false;
196 return m_GetVersionFromApp(appInfo) != FALSE;
199 //! Fill version field (V) of ApplicationInfo with product version found in the file specified.
200 //! \return If the function succeeds, the return value is \b true.
201 bool GetVersionFromFile(
202 LPCWSTR path, //!< [in] Path to the file product version will be extracted from.
203 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
204 ) throw()
206 if (!m_GetVersionFromFile)
207 return false;
208 return m_GetVersionFromFile(path, appInfo) != FALSE;
211 //! If you do not own the process your code running in (for example you write a plugin to some
212 //! external application) you need to properly initialize CrashHandler using \b ownProcess option.
213 //! Also you need to explicitly catch all exceptions in all entry points to your code and in all
214 //! threads you create. To do so use this construction:
215 //! \code
216 //! bool SomeEntryPoint(PARAM p)
217 //! {
218 //! __try
219 //! {
220 //! return YouCode(p);
221 //! }
222 //! __except (CrashHandler::SendReport(GetExceptionInformation()))
223 //! {
224 //! ::ExitProcess(0); // It is better to stop the process here or else corrupted data may incomprehensibly crash it later.
225 //! return false;
226 //! }
227 //! }
228 //! \endcode
229 LONG SendReport(
230 EXCEPTION_POINTERS* exceptionPointers //!< [in] Pointer to EXCEPTION_POINTERS structure. You should get it using GetExceptionInformation()
231 //!< function inside __except keyword.
234 if (!m_SendReport)
235 return EXCEPTION_CONTINUE_SEARCH;
236 // There is no crash handler but asserts should continue anyway
237 if (exceptionPointers->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
238 return EXCEPTION_CONTINUE_EXECUTION;
239 return m_SendReport(exceptionPointers);
242 //! To send a report about violated assertion you can throw exception with this exception code
243 //! using: \code RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, nullptr); \endcode
244 //! Execution will continue after report will be sent (EXCEPTION_CONTINUE_EXECUTION would be used).
245 //! You may pass grouping string as first parameter (see \a SkipDoctorDump_SendAssertionViolated).
246 //! \note If you called CrashHandler constructor and crshhndl.dll was missing you still may using this exception.
247 //! It will be caught, ignored and execution will continue. \ref SendReport function also works safely
248 //! when crshhndl.dll was missing.
249 static const DWORD ExceptionAssertionViolated = ((DWORD)0xCCE17000);
251 //! Sends assertion violation report from this point and continue execution.
252 //! \sa ExceptionAssertionViolated
253 //! \note Functions containing "SkipDoctorDump" will be ignored in stack parsing.
254 void SkipDoctorDump_SendAssertionViolated(
255 LPCSTR dumpGroup = nullptr //!< [in] All dumps with that group will be separated from dumps with same stack but another group. Set parameter to \b nullptr if no grouping is required.
256 ) const
258 if (!m_bWorking)
259 return;
260 if (dumpGroup)
261 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 1, reinterpret_cast<ULONG_PTR*>(&dumpGroup));
262 else
263 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, nullptr);
266 private:
267 bool LoadDll(LPCWSTR crashHandlerPath = nullptr) throw()
269 m_bLoaded = false;
270 m_bWorking = false;
271 m_bSkipAssertsAdded = false;
272 m_InitCrashHandler = nullptr;
273 m_SendReport = nullptr;
274 m_IsReadyToExit = nullptr;
275 m_SetCustomInfo = nullptr;
276 m_AddUserInfoToReport = nullptr;
277 m_RemoveUserInfoFromReport = nullptr;
278 m_AddFileToReport = nullptr;
279 m_RemoveFileFromReport = nullptr;
280 m_GetVersionFromApp = nullptr;
281 m_GetVersionFromFile = nullptr;
283 // hCrashHandlerDll should not be unloaded, crash may appear even after return from main().
284 // So hCrashHandlerDll is not saved after construction.
285 BOOL bIsWow = FALSE;
286 IsWow64Process(GetCurrentProcess(), &bIsWow);
287 HMODULE hCrashHandlerDll = nullptr;
288 if (bIsWow == FALSE)
290 if (crashHandlerPath == nullptr)
292 HMODULE hDll = get_my_module_handle();
293 wchar_t modpath[1024] = { 0 };
294 if (GetModuleFileName(hDll, modpath, _countof(modpath)))
296 wchar_t * dirpoint = wcsrchr(modpath, '\\');
297 if (dirpoint)
299 *dirpoint = L'\0';
300 wcscat_s(modpath, L"\\crshhndl.dll");
301 hCrashHandlerDll = ::LoadLibraryW(modpath);
303 else
304 hCrashHandlerDll = ::LoadLibraryW(L"crshhndl.dll");
306 else
307 hCrashHandlerDll = ::LoadLibraryW(L"crshhndl.dll");
309 else
310 hCrashHandlerDll = ::LoadLibraryW(crashHandlerPath);
312 if (hCrashHandlerDll)
314 m_InitCrashHandler = (pfnInitCrashHandler) GetProcAddress(hCrashHandlerDll, "InitCrashHandler");
315 m_SendReport = (pfnSendReport) GetProcAddress(hCrashHandlerDll, "SendReport");
316 m_IsReadyToExit = (pfnIsReadyToExit) GetProcAddress(hCrashHandlerDll, "IsReadyToExit");
317 m_SetCustomInfo = (pfnSetCustomInfo) GetProcAddress(hCrashHandlerDll, "SetCustomInfo");
318 m_AddUserInfoToReport = (pfnAddUserInfoToReport) GetProcAddress(hCrashHandlerDll, "AddUserInfoToReport");
319 m_RemoveUserInfoFromReport = (pfnRemoveUserInfoFromReport) GetProcAddress(hCrashHandlerDll, "RemoveUserInfoFromReport");
320 m_AddFileToReport = (pfnAddFileToReport) GetProcAddress(hCrashHandlerDll, "AddFileToReport");
321 m_RemoveFileFromReport = (pfnRemoveFileFromReport) GetProcAddress(hCrashHandlerDll, "RemoveFileFromReport");
322 m_GetVersionFromApp = (pfnGetVersionFromApp) GetProcAddress(hCrashHandlerDll, "GetVersionFromApp");
323 m_GetVersionFromFile = (pfnGetVersionFromFile) GetProcAddress(hCrashHandlerDll, "GetVersionFromFile");
325 m_bLoaded = m_InitCrashHandler
326 && m_SendReport
327 && m_IsReadyToExit
328 && m_SetCustomInfo
329 && m_AddUserInfoToReport
330 && m_RemoveUserInfoFromReport
331 && m_AddFileToReport
332 && m_RemoveFileFromReport
333 && m_GetVersionFromApp
334 && m_GetVersionFromFile;
337 #if 0 // TGit don't use it
338 #if _WIN32_WINNT >= 0x0501 /*_WIN32_WINNT_WINXP*/
339 // if no crash processing was started, we need to ignore ExceptionAssertionViolated exceptions.
340 if (!m_bLoaded)
342 ::AddVectoredExceptionHandler(TRUE, SkipAsserts);
343 m_bSkipAssertsAdded = true;
345 #endif
346 #endif
348 return m_bLoaded;
351 static LONG CALLBACK SkipAsserts(EXCEPTION_POINTERS* pExceptionInfo)
353 if (pExceptionInfo->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
354 return EXCEPTION_CONTINUE_EXECUTION;
355 return EXCEPTION_CONTINUE_SEARCH;
358 bool m_bLoaded;
359 bool m_bWorking;
360 bool m_bSkipAssertsAdded;
362 typedef BOOL (*pfnInitCrashHandler)(ApplicationInfo* applicationInfo, HandlerSettings* handlerSettings, BOOL ownProcess);
363 typedef LONG (*pfnSendReport)(EXCEPTION_POINTERS* exceptionPointers);
364 typedef BOOL (*pfnIsReadyToExit)();
365 typedef void (*pfnSetCustomInfo)(LPCWSTR text);
366 typedef void (*pfnAddUserInfoToReport)(LPCWSTR key, LPCWSTR value);
367 typedef void (*pfnRemoveUserInfoFromReport)(LPCWSTR key);
368 typedef void (*pfnAddFileToReport)(LPCWSTR path, LPCWSTR reportFileName /* = nullptr */);
369 typedef void (*pfnRemoveFileFromReport)(LPCWSTR path);
370 typedef BOOL (*pfnGetVersionFromApp)(ApplicationInfo* appInfo);
371 typedef BOOL (*pfnGetVersionFromFile)(LPCWSTR path, ApplicationInfo* appInfo);
373 pfnInitCrashHandler m_InitCrashHandler;
374 pfnSendReport m_SendReport;
375 pfnIsReadyToExit m_IsReadyToExit;
376 pfnSetCustomInfo m_SetCustomInfo;
377 pfnAddUserInfoToReport m_AddUserInfoToReport;
378 pfnRemoveUserInfoFromReport m_RemoveUserInfoFromReport;
379 pfnAddFileToReport m_AddFileToReport;
380 pfnRemoveFileFromReport m_RemoveFileFromReport;
381 pfnGetVersionFromApp m_GetVersionFromApp;
382 pfnGetVersionFromFile m_GetVersionFromFile;
385 class CCrashReportTGit
387 public:
389 //! Installs exception handlers to the caller process
390 CCrashReportTGit(LPCTSTR appname, USHORT versionMajor, USHORT versionMinor, USHORT versionMicro, USHORT versionBuild, const char* /*buildDate*/, bool bOwnProcess = true)
391 : m_nInstallStatus(0)
393 ApplicationInfo appInfo = { 0 };
394 appInfo.ApplicationInfoSize = sizeof(ApplicationInfo);
395 appInfo.ApplicationGUID = "7fbde3fc-94e9-408b-b5c8-62bd4e203570";
396 appInfo.Prefix = "tgit";
397 appInfo.AppName = appname;
398 appInfo.Company = L"TortoiseGit";
400 appInfo.Hotfix = 0;
401 appInfo.V[0] = versionMajor;
402 appInfo.V[1] = versionMinor;
403 appInfo.V[2] = versionMicro;
404 appInfo.V[3] = versionBuild;
406 HandlerSettings handlerSettings = { 0 };
407 handlerSettings.HandlerSettingsSize = sizeof(handlerSettings);
408 handlerSettings.LeaveDumpFilesInTempFolder = FALSE;
409 handlerSettings.UseWER = FALSE;
410 handlerSettings.OpenProblemInBrowser = TRUE;
412 CCrashReport::Instance().InitCrashHandler(&appInfo, &handlerSettings, bOwnProcess);
416 //! Deinstalls exception handlers from the caller process
417 ~CCrashReportTGit()
419 CCrashReport::Instance().Uninstall();
422 //! Install status
423 int m_nInstallStatus;