Fixed issue #4126: Capitalize the first letter in the Push dialog
[TortoiseGit.git] / src / Utils / CrashReport.h
blobe19651a122ea541fd8f1a71239dc498101ab19ab
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2013, 2016, 2019, 2023 - 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 <DbgHelp.h>
26 // dummy define, needed only when we use crashrpt instead of this.
27 #define CR_AF_MAKE_FILE_COPY 0
29 __forceinline HMODULE get_my_module_handle()
31 static int s_module_marker = 0;
32 MEMORY_BASIC_INFORMATION memory_basic_information;
33 if (!VirtualQuery(&s_module_marker, &memory_basic_information, sizeof(memory_basic_information)))
35 return nullptr;
37 return static_cast<HMODULE>(memory_basic_information.AllocationBase);
40 /**
41 * \ingroup Utils
42 * helper class for the DoctorDumpSDK
44 class CCrashReport
46 private:
47 CCrashReport()
49 LoadDll();
52 ~CCrashReport()
54 if (!m_IsReadyToExit)
55 return;
57 // If crash has happen not in main thread we should wait here until report will be sent
58 // or else program will be terminated after return from main() and report sending will be halted.
59 while (!m_IsReadyToExit())
60 ::Sleep(100);
62 if (m_bSkipAssertsAdded)
63 RemoveVectoredExceptionHandler(SkipAsserts);
66 public:
67 static CCrashReport& Instance()
69 static CCrashReport instance;
70 return instance;
73 int Uninstall() { return FALSE; }
74 int AddFile2(LPCWSTR pszFile, LPCWSTR pszDestFile, LPCWSTR /*pszDesc*/, DWORD /*dwFlags*/)
76 return AddFileToReport(pszFile, pszDestFile) ? 1 : 0;
79 //! Checks that crash handling was enabled.
80 //! \return Return \b true if crash handling was enabled.
81 bool IsCrashHandlingEnabled() const
83 return m_bWorking;
86 //! Initializes crash handler.
87 //! \note You may call this function multiple times if some data has changed.
88 //! \return Return \b true if crash handling was enabled.
89 bool InitCrashHandler(
90 ApplicationInfo* applicationInfo, //!< [in] Pointer to the ApplicationInfo structure that identifies your application.
91 HandlerSettings* handlerSettings, //!< [in] Pointer to the HandlerSettings structure that customizes crash handling behavior. This parameter can be \b nullptr.
92 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
93 //!< a plugin to some external application) set this option to \b FALSE. In that case you need to explicitly
94 //!< catch exceptions. See \ref SendReport for more information.
95 ) throw()
97 if (!m_InitCrashHandler)
98 return false;
100 m_bWorking = m_InitCrashHandler(applicationInfo, handlerSettings, ownProcess) != FALSE;
102 return m_bWorking;
105 //! \note This function is experimental and may not be available and may not be support by Doctor Dump in the future.
106 //! You may set custom information for your possible report.
107 //! This text will be available on Doctor Dump dumps page.
108 //! The text should not contain private information.
109 //! \return If the function succeeds, the return value is \b true.
110 bool SetCustomInfo(
111 LPCWSTR text //!< [in] custom info for the report. The text will be cut to 100 characters.
114 if (!m_SetCustomInfo)
115 return false;
116 m_SetCustomInfo(text);
117 return true;
120 //! You may add any key/value pair to crash report.
121 //! \return If the function succeeds, the return value is \b true.
122 //! \note This function is thread safe.
123 bool AddUserInfoToReport(
124 LPCWSTR key, //!< [in] key string that will be added to the report.
125 LPCWSTR value //!< [in] value for the key.
126 ) throw()
128 if (!m_AddUserInfoToReport)
129 return false;
130 m_AddUserInfoToReport(key, value);
131 return true;
134 //! You may remove any key that was added previously to crash report by \a AddUserInfoToReport.
135 //! \return If the function succeeds, the return value is \b true.
136 //! \note This function is thread safe.
137 bool RemoveUserInfoFromReport(
138 LPCWSTR key //!< [in] key string that will be removed from the report.
141 if (!m_RemoveUserInfoFromReport)
142 return false;
143 m_RemoveUserInfoFromReport(key);
144 return true;
147 //! You may add any file to crash report. This file will be read when crash appears and will be sent within the report.
148 //! Multiple files may be added. Filename of the file in the report may be changed to any name.
149 //! \return If the function succeeds, the return value is \b true.
150 //! \note This function is thread safe.
151 bool AddFileToReport(
152 LPCWSTR path, //!< [in] Path to the file, that will be added to the report.
153 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.
154 ) throw()
156 if (!m_AddFileToReport)
157 return false;
158 m_AddFileToReport(path, reportFileName);
159 return true;
162 //! Remove from report the file that was registered earlier to be sent within report.
163 //! \return If the function succeeds, the return value is \b true.
164 //! \note This function is thread safe.
165 bool RemoveFileFromReport(
166 LPCWSTR path //!< [in] Path to the file, that will be removed from the report.
167 ) throw()
169 if (!m_RemoveFileFromReport)
170 return false;
171 m_RemoveFileFromReport(path);
172 return true;
175 //! Fills version field (V) of ApplicationInfo with product version
176 //! found in the executable file of the current process.
177 //! \return If the function succeeds, the return value is \b true.
178 bool GetVersionFromApp(
179 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
180 ) throw()
182 if (!m_GetVersionFromApp)
183 return false;
184 return m_GetVersionFromApp(appInfo) != FALSE;
187 //! Fill version field (V) of ApplicationInfo with product version found in the file specified.
188 //! \return If the function succeeds, the return value is \b true.
189 bool GetVersionFromFile(
190 LPCWSTR path, //!< [in] Path to the file product version will be extracted from.
191 ApplicationInfo* appInfo //!< [out] Pointer to ApplicationInfo structure. Its version field (V) will be set to product version.
192 ) throw()
194 if (!m_GetVersionFromFile)
195 return false;
196 return m_GetVersionFromFile(path, appInfo) != FALSE;
199 //! If you do not own the process your code running in (for example you write a plugin to some
200 //! external application) you need to properly initialize CrashHandler using \b ownProcess option.
201 //! Also you need to explicitly catch all exceptions in all entry points to your code and in all
202 //! threads you create. To do so use this construction:
203 //! \code
204 //! bool SomeEntryPoint(PARAM p)
205 //! {
206 //! __try
207 //! {
208 //! return YouCode(p);
209 //! }
210 //! __except (CrashHandler::SendReport(GetExceptionInformation()))
211 //! {
212 //! ::ExitProcess(0); // It is better to stop the process here or else corrupted data may incomprehensibly crash it later.
213 //! return false;
214 //! }
215 //! }
216 //! \endcode
217 LONG SendReport(
218 EXCEPTION_POINTERS* exceptionPointers //!< [in] Pointer to EXCEPTION_POINTERS structure. You should get it using GetExceptionInformation()
219 //!< function inside __except keyword.
222 if (!m_SendReport)
223 return EXCEPTION_CONTINUE_SEARCH;
224 // There is no crash handler but asserts should continue anyway
225 if (exceptionPointers->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
226 return EXCEPTION_CONTINUE_EXECUTION;
227 return m_SendReport(exceptionPointers);
230 //! To send a report about violated assertion you can throw exception with this exception code
231 //! using: \code RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, nullptr); \endcode
232 //! Execution will continue after report will be sent (EXCEPTION_CONTINUE_EXECUTION would be used).
233 //! You may pass grouping string as first parameter (see \a SkipDoctorDump_SendAssertionViolated).
234 //! \note If you called CrashHandler constructor and crshhndl.dll was missing you still may using this exception.
235 //! It will be caught, ignored and execution will continue. \ref SendReport function also works safely
236 //! when crshhndl.dll was missing.
237 static const DWORD ExceptionAssertionViolated = 0xCCE17000;
239 //! Sends assertion violation report from this point and continue execution.
240 //! \sa ExceptionAssertionViolated
241 //! \note Functions containing "SkipDoctorDump" will be ignored in stack parsing.
242 void SkipDoctorDump_SendAssertionViolated(
243 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.
244 ) const
246 if (!m_bWorking)
247 return;
248 if (dumpGroup)
249 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 1, reinterpret_cast<ULONG_PTR*>(&dumpGroup));
250 else
251 ::RaiseException(CrashHandler::ExceptionAssertionViolated, 0, 0, nullptr);
254 private:
255 bool LoadDll(LPCWSTR crashHandlerPath = nullptr) throw()
257 m_bLoaded = false;
258 m_bWorking = false;
259 m_bSkipAssertsAdded = false;
260 m_InitCrashHandler = nullptr;
261 m_SendReport = nullptr;
262 m_IsReadyToExit = nullptr;
263 m_SetCustomInfo = nullptr;
264 m_AddUserInfoToReport = nullptr;
265 m_RemoveUserInfoFromReport = nullptr;
266 m_AddFileToReport = nullptr;
267 m_RemoveFileFromReport = nullptr;
268 m_GetVersionFromApp = nullptr;
269 m_GetVersionFromFile = nullptr;
271 // hCrashHandlerDll should not be unloaded, crash may appear even after return from main().
272 // So hCrashHandlerDll is not saved after construction.
273 BOOL bIsWow = FALSE;
274 IsWow64Process(GetCurrentProcess(), &bIsWow);
275 HMODULE hCrashHandlerDll = nullptr;
276 if (bIsWow == FALSE)
278 if (crashHandlerPath == nullptr)
280 HMODULE hDll = get_my_module_handle();
281 wchar_t modpath[1024] = { 0 };
282 if (GetModuleFileName(hDll, modpath, _countof(modpath)))
284 wchar_t * dirpoint = wcsrchr(modpath, '\\');
285 if (dirpoint)
287 *dirpoint = L'\0';
288 wcscat_s(modpath, L"\\crshhndl.dll");
289 hCrashHandlerDll = ::LoadLibraryW(modpath);
291 else
292 hCrashHandlerDll = ::LoadLibraryW(L"crshhndl.dll");
294 else
295 hCrashHandlerDll = ::LoadLibraryW(L"crshhndl.dll");
297 else
298 hCrashHandlerDll = ::LoadLibraryW(crashHandlerPath);
300 if (hCrashHandlerDll)
302 m_InitCrashHandler = reinterpret_cast<PfnInitCrashHandler>(GetProcAddress(hCrashHandlerDll, "InitCrashHandler"));
303 m_SendReport = reinterpret_cast<PfnSendReport>(GetProcAddress(hCrashHandlerDll, "SendReport"));
304 m_IsReadyToExit = reinterpret_cast<PfnIsReadyToExit>(GetProcAddress(hCrashHandlerDll, "IsReadyToExit"));
305 m_SetCustomInfo = reinterpret_cast<PfnSetCustomInfo>(GetProcAddress(hCrashHandlerDll, "SetCustomInfo"));
306 m_AddUserInfoToReport = reinterpret_cast<PfnAddUserInfoToReport>(GetProcAddress(hCrashHandlerDll, "AddUserInfoToReport"));
307 m_RemoveUserInfoFromReport = reinterpret_cast<PfnRemoveUserInfoFromReport>(GetProcAddress(hCrashHandlerDll, "RemoveUserInfoFromReport"));
308 m_AddFileToReport = reinterpret_cast<PfnAddFileToReport>(GetProcAddress(hCrashHandlerDll, "AddFileToReport"));
309 m_RemoveFileFromReport = reinterpret_cast<PfnRemoveFileFromReport>(GetProcAddress(hCrashHandlerDll, "RemoveFileFromReport"));
310 m_GetVersionFromApp = reinterpret_cast<PfnGetVersionFromApp>(GetProcAddress(hCrashHandlerDll, "GetVersionFromApp"));
311 m_GetVersionFromFile = reinterpret_cast<PfnGetVersionFromFile>(GetProcAddress(hCrashHandlerDll, "GetVersionFromFile"));
313 m_bLoaded = m_InitCrashHandler
314 && m_SendReport
315 && m_IsReadyToExit
316 && m_SetCustomInfo
317 && m_AddUserInfoToReport
318 && m_RemoveUserInfoFromReport
319 && m_AddFileToReport
320 && m_RemoveFileFromReport
321 && m_GetVersionFromApp
322 && m_GetVersionFromFile;
325 #if 0 // TGit don't use it
326 #if _WIN32_WINNT >= 0x0501 /*_WIN32_WINNT_WINXP*/
327 // if no crash processing was started, we need to ignore ExceptionAssertionViolated exceptions.
328 if (!m_bLoaded)
330 ::AddVectoredExceptionHandler(TRUE, SkipAsserts);
331 m_bSkipAssertsAdded = true;
333 #endif
334 #endif
336 return m_bLoaded;
339 static LONG CALLBACK SkipAsserts(EXCEPTION_POINTERS* pExceptionInfo)
341 if (pExceptionInfo->ExceptionRecord->ExceptionCode == ExceptionAssertionViolated)
342 return EXCEPTION_CONTINUE_EXECUTION;
343 return EXCEPTION_CONTINUE_SEARCH;
346 bool m_bLoaded = false;
347 bool m_bWorking = false;
348 bool m_bSkipAssertsAdded = false;
350 using PfnInitCrashHandler = BOOL(*)(ApplicationInfo* applicationInfo, HandlerSettings* handlerSettings, BOOL ownProcess);
351 using PfnSendReport = LONG(*)(EXCEPTION_POINTERS* exceptionPointers);
352 using PfnIsReadyToExit = BOOL(*)();
353 using PfnSetCustomInfo = void(*)(LPCWSTR text);
354 using PfnAddUserInfoToReport = void(*)(LPCWSTR key, LPCWSTR value);
355 using PfnRemoveUserInfoFromReport = void(*)(LPCWSTR key);
356 using PfnAddFileToReport = void(*)(LPCWSTR path, LPCWSTR reportFileName /* = nullptr */);
357 using PfnRemoveFileFromReport = void(*)(LPCWSTR path);
358 using PfnGetVersionFromApp = BOOL(*)(ApplicationInfo* appInfo);
359 using PfnGetVersionFromFile = BOOL(*)(LPCWSTR path, ApplicationInfo* appInfo);
361 PfnInitCrashHandler m_InitCrashHandler = nullptr;
362 PfnSendReport m_SendReport = nullptr;
363 PfnIsReadyToExit m_IsReadyToExit = nullptr;
364 PfnSetCustomInfo m_SetCustomInfo = nullptr;
365 PfnAddUserInfoToReport m_AddUserInfoToReport = nullptr;
366 PfnRemoveUserInfoFromReport m_RemoveUserInfoFromReport = nullptr;
367 PfnAddFileToReport m_AddFileToReport = nullptr;
368 PfnRemoveFileFromReport m_RemoveFileFromReport = nullptr;
369 PfnGetVersionFromApp m_GetVersionFromApp = nullptr;
370 PfnGetVersionFromFile m_GetVersionFromFile = nullptr;
373 class CCrashReportTGit
375 public:
377 //! Installs exception handlers to the caller process
378 CCrashReportTGit(LPCWSTR appname, USHORT versionMajor, USHORT versionMinor, USHORT versionMicro, USHORT versionBuild, const char* /*buildDate*/, bool bOwnProcess = true)
380 ApplicationInfo appInfo = { 0 };
381 appInfo.ApplicationInfoSize = sizeof(ApplicationInfo);
382 appInfo.ApplicationGUID = "7fbde3fc-94e9-408b-b5c8-62bd4e203570";
383 appInfo.Prefix = "tgit";
384 appInfo.AppName = appname;
385 appInfo.Company = L"TortoiseGit";
387 appInfo.Hotfix = 0;
388 appInfo.V[0] = versionMajor;
389 appInfo.V[1] = versionMinor;
390 appInfo.V[2] = versionMicro;
391 appInfo.V[3] = versionBuild;
393 HandlerSettings handlerSettings = { 0 };
394 handlerSettings.HandlerSettingsSize = sizeof(handlerSettings);
395 handlerSettings.LeaveDumpFilesInTempFolder = FALSE;
396 handlerSettings.UseWER = FALSE;
397 handlerSettings.OpenProblemInBrowser = TRUE;
399 CCrashReport::Instance().InitCrashHandler(&appInfo, &handlerSettings, bOwnProcess);
403 //! Deinstalls exception handlers from the caller process
404 ~CCrashReportTGit()
406 CCrashReport::Instance().Uninstall();
409 //! Install status
410 int m_nInstallStatus = 0;