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:
parent
f01bfdcdcb
commit
0232d99726
@ -116,6 +116,7 @@ model {
|
||||
include "ObjectList.cpp"
|
||||
include "TokenLine.cpp"
|
||||
include "textconsole.cpp"
|
||||
include "minidump.cpp"
|
||||
if (GradleCppUtils.windows) {
|
||||
include "TextConsoleWin32.cpp"
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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)
|
||||
|
@ -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
203
rehlds/common/minidump.cpp
Normal 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
66
rehlds/common/minidump.h
Normal 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
|
@ -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"
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user