2
0
mirror of https://github.com/rehlds/rehlds.git synced 2025-01-28 22:48:05 +03:00

Catch crashes and write minidumps on windows (#703)

* Catch crashes and write minidumps on windows
* Allow to enable/disable writing mini dumps as in other Source games
* Fix gradle build - write minidumps
* Appends application version to mini dump name

Makes easier to reproduce issues via mini dumps.

New mini dump file name format:
<exe name>_<app version>_crash_<date YYYYMMDD>_<time HHMMSS>_<unique counter>.mdmp
Example:
hlds_3.6.0.675-dev+m_crash_20190702_005019_1.mdmp
This commit is contained in:
Dmitry Tsarevich 2019-08-11 15:44:06 +03:00 committed by Dmitry Novikov
parent f01bfdcdcb
commit 0232d99726
12 changed files with 325 additions and 11 deletions

View File

@ -116,6 +116,7 @@ model {
include "ObjectList.cpp"
include "TokenLine.cpp"
include "textconsole.cpp"
include "minidump.cpp"
if (GradleCppUtils.windows) {
include "TextConsoleWin32.cpp"
}

View File

@ -19,6 +19,7 @@
<ClInclude Include="..\..\..\common\ObjectList.h" />
<ClInclude Include="..\..\..\common\textconsole.h" />
<ClInclude Include="..\..\..\common\TokenLine.h" />
<ClInclude Include="..\..\..\common\minidump.h" />
<ClInclude Include="..\..\..\engine\mem.h" />
<ClInclude Include="..\..\..\game_shared\bitvec.h" />
<ClInclude Include="..\..\..\game_shared\perf_counter.h" />
@ -60,6 +61,11 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release Swds|Win32'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\..\..\common\minidump.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release Swds|Win32'">Use</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\..\..\engine\mem.cpp" />
<ClCompile Include="..\..\common\common.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
@ -252,4 +258,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -58,6 +58,9 @@
<ClInclude Include="..\..\..\engine\mem.h">
<Filter>engine</Filter>
</ClInclude>
<ClInclude Include="..\..\..\common\minidump.h">
<Filter>common</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\precompiled.cpp">
@ -96,6 +99,9 @@
<ClCompile Include="..\..\..\engine\mem.cpp">
<Filter>engine</Filter>
</ClCompile>
<ClCompile Include="..\..\..\common\minidump.cpp">
<Filter>common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="icon.ico" />

View File

@ -1092,8 +1092,15 @@ unsigned char *System::LoadFile(const char *name, int *length)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
gSystem.BuildCommandLine(lpCmdLine);
return gSystem.Run();
#ifdef HLTV_FIXES
return CatchAndWriteMiniDump([=]()
{
#endif
gSystem.BuildCommandLine(lpCmdLine);
return gSystem.Run();
#ifdef HLTV_FIXES
}, lpCmdLine);
#endif
}
void System::BuildCommandLine(char *argv)

View File

@ -1,5 +1,7 @@
#pragma once
#include "version/appversion.h"
#include "basetypes.h"
#include "FileSystem.h"
#include "strtools.h"
@ -16,3 +18,7 @@
// Console stuff
#include "System.h"
#include "common/random.h"
#ifdef _WIN32
#include "minidump.h"
#endif

203
rehlds/common/minidump.cpp Normal file
View File

@ -0,0 +1,203 @@
#include "precompiled.h"
#if defined(HLTV_FIXES) || defined(LAUNCHER_FIXES)
#ifdef _WIN32
#if defined(_MSC_VER) && !defined(_IMAGEHLP_)
#pragma warning(push)
#pragma warning(disable:4091) // A microsoft header has warnings. Very nice.
#include <DbgHelp.h>
#pragma warning(pop)
#endif
// Gets last error.
static inline HRESULT GetLastHresult()
{
return HRESULT_FROM_WIN32(GetLastError());
}
#define WIDE_TEXT_HELPER_(quote) L##quote
#define WIDE_TEXT(quote) WIDE_TEXT_HELPER_(quote)
// Gets application version which is safe for use in minidump file name. Can be entirely constexpr with C++17 and std::array.
template<size_t appVersionSize>
static wchar_t* GetAppVersionForMiniDumpName(wchar_t (&appVersion)[appVersionSize]) {
constexpr wchar_t rawAppVersion[]{WIDE_TEXT(APP_VERSION "-" APP_COMMIT_SHA)};
static_assert(appVersionSize >= ARRAYSIZE(rawAppVersion), "App version buffer size should be enough to store app version.");
// See https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-pathcleanupspec#remarks for details.
constexpr wchar_t invalidPathChars[]{L'\\', L'/', L':', L'*', L'?', L'"', L'<', L'>', L'|', L';', L','};
size_t outIt{0};
for (size_t rawIt{0}; rawIt < ARRAYSIZE(rawAppVersion); ++rawIt)
{
const wchar_t currentRawIt{rawAppVersion[rawIt]};
bool isValidRaw{true};
for (size_t invalidIt{0}; invalidIt < ARRAYSIZE(invalidPathChars); ++invalidIt)
{
isValidRaw = invalidPathChars[invalidIt] != currentRawIt;
if (!isValidRaw)
{
break;
}
}
if (isValidRaw) {
appVersion[outIt++] = currentRawIt;
}
}
return appVersion;
}
// Creates a new minidump file and dumps the exception info into it.
static HRESULT WriteMiniDumpUsingExceptionInfo(unsigned int exceptionCode,
struct _EXCEPTION_POINTERS *exceptionInfo, MINIDUMP_TYPE minidumpType)
{
// Counter used to make sure minidump names are unique.
static int minidumpsWrittenCount{0};
HMODULE dbghelpModule{LoadLibraryW(L"DbgHelp.dll")};
HRESULT errorCode{dbghelpModule ? S_OK : GetLastHresult()};
using MiniDumpWriteDumpFn = decltype(&MiniDumpWriteDump);
MiniDumpWriteDumpFn miniDumpWriteDump{nullptr};
if (SUCCEEDED(errorCode))
{
miniDumpWriteDump = reinterpret_cast<MiniDumpWriteDumpFn>(GetProcAddress(
dbghelpModule, "MiniDumpWriteDump"));
errorCode = miniDumpWriteDump ? S_OK : GetLastHresult();
}
// Creates a unique filename for the minidump based on the current time and
// module name.
const time_t timeNow{time(nullptr)};
tm *localTimeNow{localtime(&timeNow)};
if (localTimeNow == nullptr)
{
errorCode = E_INVALIDARG;
}
// Strip off the rest of the path from the .exe name.
wchar_t moduleName[MAX_PATH];
if (SUCCEEDED(errorCode))
{
::SetLastError(NOERROR);
::GetModuleFileNameW(nullptr, moduleName, static_cast<DWORD>(ARRAYSIZE(moduleName)));
errorCode = GetLastHresult();
}
wchar_t fileName[MAX_PATH];
HANDLE minidumpFile{nullptr};
if (SUCCEEDED(errorCode))
{
wchar_t *strippedModuleName{wcsrchr(moduleName, L'.')};
if (strippedModuleName) *strippedModuleName = L'\0';
strippedModuleName = wcsrchr(moduleName, L'\\');
// Move past the last slash.
if (strippedModuleName) ++strippedModuleName;
wchar_t appVersion[ARRAYSIZE(APP_VERSION) + ARRAYSIZE(APP_COMMIT_SHA) + 2];
// <exe name>_<app version-app hash build>_crash_<date YYYYMMDD>_<time HHMMSS>_<unique counter>.mdmp
swprintf(fileName, ARRAYSIZE(fileName), L"%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d_%d.mdmp",
strippedModuleName ? strippedModuleName : L"unknown",
GetAppVersionForMiniDumpName(appVersion),
localTimeNow->tm_year + 1900, /* Year less 2000 */
localTimeNow->tm_mon + 1, /* month (0 - 11 : 0 = January) */
localTimeNow->tm_mday, /* day of month (1 - 31) */
localTimeNow->tm_hour, /* hour (0 - 23) */
localTimeNow->tm_min, /* minutes (0 - 59) */
localTimeNow->tm_sec, /* seconds (0 - 59) */
++minidumpsWrittenCount); // ensures the filename is unique
minidumpFile = CreateFileW(fileName, GENERIC_WRITE, FILE_SHARE_WRITE,
nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
errorCode = minidumpFile != INVALID_HANDLE_VALUE ? S_OK : GetLastHresult();
}
if (SUCCEEDED(errorCode) && miniDumpWriteDump)
{
// Dump the exception information into the file.
MINIDUMP_EXCEPTION_INFORMATION exInfo;
exInfo.ThreadId = GetCurrentThreadId();
exInfo.ExceptionPointers = exceptionInfo;
exInfo.ClientPointers = FALSE;
const BOOL wasWrittenMinidump{miniDumpWriteDump(
GetCurrentProcess(), GetCurrentProcessId(), minidumpFile,
minidumpType, &exInfo, nullptr, nullptr)};
// If the function succeeds, the return value is TRUE; otherwise, the
// return value is FALSE. To retrieve extended error information, call
// GetLastError. Note that the last error will be an HRESULT value. If
// the operation is canceled, the last error code is HRESULT_FROM_WIN32(ERROR_CANCELLED).
// See
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump
errorCode = wasWrittenMinidump ? S_OK : static_cast<HRESULT>(GetLastError());
}
if (minidumpFile && minidumpFile != INVALID_HANDLE_VALUE)
{
CloseHandle(minidumpFile);
}
if (dbghelpModule)
{
FreeLibrary(dbghelpModule);
}
return errorCode;
}
// Writes process mini dump by exception code and info.
void WriteMiniDump(unsigned int exceptionCode,
struct _EXCEPTION_POINTERS *exceptionInfo)
{
// First try to write it with all the indirectly referenced memory (ie: a
// large file). If that doesn't work, then write a smaller one.
auto minidumpType = static_cast<MINIDUMP_TYPE>(
MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo |
MiniDumpWithIndirectlyReferencedMemory);
if (FAILED(WriteMiniDumpUsingExceptionInfo(exceptionCode, exceptionInfo, minidumpType)))
{
minidumpType = MiniDumpWithDataSegs;
WriteMiniDumpUsingExceptionInfo(exceptionCode, exceptionInfo, minidumpType);
}
}
// Determines either debugger attached to a process or not.
bool HasDebuggerPresent()
{
return ::IsDebuggerPresent() != FALSE;
}
// Determines either writing mini dumps is enabled or not (-nominidumps). Same as in Source games.
bool IsWriteMiniDumpsEnabled(const char *commandLine)
{
if (!commandLine)
{
return true;
}
const char *noMiniDumps{strstr(commandLine, "-nominidumps")};
if (!noMiniDumps)
{
return true;
}
const char *nextCharAfterNoMiniDumps{noMiniDumps + ARRAYSIZE("-nominidumps") - 1};
// Command line ends with -nominidumps or has space after -nominidumps -> not mini dump.
return !(nextCharAfterNoMiniDumps[0] == '\0' || isspace(nextCharAfterNoMiniDumps[0]));
}
#endif // _WIN32
#endif // HLTV_FIXES || LAUNCHER_FIXES

66
rehlds/common/minidump.h Normal file
View File

@ -0,0 +1,66 @@
/*
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In addition, as a special exception, the author gives permission to
* link the code of this program with the Half-Life Game Engine ("HL
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
* L.L.C ("Valve"). You must obey the GNU General Public License in all
* respects for all of the code used other than the HL Engine and MODs
* from Valve. If you modify this file, you may extend this exception
* to your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version.
*
*/
#pragma once
#if defined(HLTV_FIXES) || defined(LAUNCHER_FIXES)
#ifdef _WIN32
// Writes process mini dump by exception code and info.
void WriteMiniDump(unsigned int exceptionCode,
struct _EXCEPTION_POINTERS *exceptionInfo);
// Determines either debugger attached to a process or not.
bool HasDebuggerPresent();
// Determines either writing mini dumps is enabled or not. Same as in Source games.
bool IsWriteMiniDumpsEnabled(const char *commandLine);
// Catches and writes out any exception thrown by the specified function.
template <typename F>
int CatchAndWriteMiniDump(F function, const char *commandLine)
{
// Don't mask exceptions when mini dumps disabled (-nominidumps) or running in the debugger.
if (!IsWriteMiniDumpsEnabled(commandLine) || HasDebuggerPresent())
{
return function();
}
__try
{
return function();
} __except (WriteMiniDump(GetExceptionCode(), GetExceptionInformation()),
EXCEPTION_EXECUTE_HANDLER)
{
// Write the minidump from inside the filter.
return GetExceptionCode();
}
}
#endif // _WIN32
#endif // HLTV_FIXES || LAUNCHER_FIXES

View File

@ -24,7 +24,7 @@ void setupToolchain(NativeBinarySpec b) {
boolean useGcc = project.hasProperty("useGcc")
def cfg = rootProject.createToolchainConfig(b);
cfg.projectInclude(project, '/src', '/../engine', '/../common', '/../public', '/../public/rehlds');
cfg.projectInclude(project, '/src', '/../engine', '/../common', '/../public', '/../public/rehlds', '/../');
cfg.singleDefines 'USE_BREAKPAD_HANDLER', 'DEDICATED', 'LAUNCHER_FIXES', '_CONSOLE'
@ -121,6 +121,7 @@ model {
include "SteamAppStartUp.cpp"
include "textconsole.cpp"
include "commandline.cpp"
include "minidump.cpp"
if (GradleCppUtils.windows) {
include "TextConsoleWin32.cpp"
}

View File

@ -201,6 +201,7 @@
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\common\minidump.h" />
<ClInclude Include="..\..\common\ObjectList.h" />
<ClInclude Include="..\..\common\SteamAppStartUp.h" />
<ClInclude Include="..\..\common\textconsole.h" />
@ -222,6 +223,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\common\commandline.cpp" />
<ClCompile Include="..\..\common\minidump.cpp" />
<ClCompile Include="..\..\common\ObjectList.cpp" />
<ClCompile Include="..\..\common\SteamAppStartUp.cpp" />
<ClCompile Include="..\..\common\textconsole.cpp" />
@ -255,4 +257,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -47,6 +47,9 @@
<ClInclude Include="..\..\common\SteamAppStartUp.h">
<Filter>common</Filter>
</ClInclude>
<ClInclude Include="..\..\common\minidump.h">
<Filter>common</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\src\sys_ded.cpp">
@ -94,6 +97,9 @@
<ClCompile Include="..\..\common\commandline.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="..\..\common\minidump.cpp">
<Filter>common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="icon.ico" />

View File

@ -1,5 +1,7 @@
#pragma once
#include "version/appversion.h"
#include "basetypes.h"
#include "FileSystem.h"
#include "strtools.h"
@ -23,6 +25,7 @@
#ifdef _WIN32
#include "conproc.h"
#include "minidump.h"
#include <mmsystem.h> // timeGetTime
#else
#include <signal.h>

View File

@ -239,11 +239,18 @@ void Sys_WriteProcessIdFile()
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
if (ShouldLaunchAppViaSteam(lpCmdLine, STDIO_FILESYSTEM_LIB, STDIO_FILESYSTEM_LIB))
return 0;
#ifdef LAUNCHER_FIXES
return CatchAndWriteMiniDump([=]()
{
#endif
if (ShouldLaunchAppViaSteam(lpCmdLine, STDIO_FILESYSTEM_LIB, STDIO_FILESYSTEM_LIB))
return 0;
//auto command = CommandLineToArgvW(GetCommandLineW(), (int *)&lpCmdLine);
auto ret = StartServer(lpCmdLine);
//LocalFree(command);
return ret;
//auto command = CommandLineToArgvW(GetCommandLineW(), (int *)&lpCmdLine);
auto ret = StartServer(lpCmdLine);
//LocalFree(command);
return ret;
#ifdef LAUNCHER_FIXES
}, lpCmdLine);
#endif
}