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.
21 #include "../../ext/CrashServer/CrashHandler/CrashHandler/CrashHandler.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
)))
37 return static_cast<HMODULE
>(memory_basic_information
.AllocationBase
);
42 * helper class for the DoctorDumpSDK
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())
62 if (m_bSkipAssertsAdded
)
63 RemoveVectoredExceptionHandler(SkipAsserts
);
67 static CCrashReport
& Instance()
69 static CCrashReport 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
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.
97 if (!m_InitCrashHandler
)
100 m_bWorking
= m_InitCrashHandler(applicationInfo
, handlerSettings
, ownProcess
) != FALSE
;
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.
111 LPCWSTR text
//!< [in] custom info for the report. The text will be cut to 100 characters.
114 if (!m_SetCustomInfo
)
116 m_SetCustomInfo(text
);
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.
128 if (!m_AddUserInfoToReport
)
130 m_AddUserInfoToReport(key
, value
);
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
)
143 m_RemoveUserInfoFromReport(key
);
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.
156 if (!m_AddFileToReport
)
158 m_AddFileToReport(path
, reportFileName
);
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.
169 if (!m_RemoveFileFromReport
)
171 m_RemoveFileFromReport(path
);
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.
182 if (!m_GetVersionFromApp
)
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.
194 if (!m_GetVersionFromFile
)
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:
204 //! bool SomeEntryPoint(PARAM p)
208 //! return YouCode(p);
210 //! __except (CrashHandler::SendReport(GetExceptionInformation()))
212 //! ::ExitProcess(0); // It is better to stop the process here or else corrupted data may incomprehensibly crash it later.
218 EXCEPTION_POINTERS
* exceptionPointers
//!< [in] Pointer to EXCEPTION_POINTERS structure. You should get it using GetExceptionInformation()
219 //!< function inside __except keyword.
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.
249 ::RaiseException(CrashHandler::ExceptionAssertionViolated
, 0, 1, reinterpret_cast<ULONG_PTR
*>(&dumpGroup
));
251 ::RaiseException(CrashHandler::ExceptionAssertionViolated
, 0, 0, nullptr);
255 bool LoadDll(LPCWSTR crashHandlerPath
= nullptr) throw()
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.
274 IsWow64Process(GetCurrentProcess(), &bIsWow
);
275 HMODULE hCrashHandlerDll
= nullptr;
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
, '\\');
288 wcscat_s(modpath
, L
"\\crshhndl.dll");
289 hCrashHandlerDll
= ::LoadLibraryW(modpath
);
292 hCrashHandlerDll
= ::LoadLibraryW(L
"crshhndl.dll");
295 hCrashHandlerDll
= ::LoadLibraryW(L
"crshhndl.dll");
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
317 && m_AddUserInfoToReport
318 && m_RemoveUserInfoFromReport
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.
330 ::AddVectoredExceptionHandler(TRUE
, SkipAsserts
);
331 m_bSkipAssertsAdded
= true;
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
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";
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
406 CCrashReport::Instance().Uninstall();
410 int m_nInstallStatus
= 0;