Add minidump support

This commit is contained in:
momo5502 2022-09-17 12:24:04 +02:00
parent 65187f401e
commit 92208520d0
4 changed files with 318 additions and 0 deletions

View File

@ -0,0 +1,213 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/thread.hpp>
#include <utils/compression.hpp>
#include <exception/minidump.hpp>
#include <version.hpp>
namespace exception
{
namespace
{
DWORD main_thread_id{};
thread_local struct
{
DWORD code = 0;
PVOID address = nullptr;
} exception_data{};
struct
{
std::chrono::time_point<std::chrono::high_resolution_clock> last_recovery{};
std::atomic<int> recovery_counts = {0};
} recovery_data{};
bool is_game_thread()
{
return main_thread_id == GetCurrentThreadId();
}
bool is_exception_interval_too_short()
{
const auto delta = std::chrono::high_resolution_clock::now() - recovery_data.last_recovery;
return delta < 1min;
}
bool too_many_exceptions_occured()
{
return recovery_data.recovery_counts >= 3;
}
volatile bool& is_initialized()
{
static volatile bool initialized = false;
return initialized;
}
bool is_recoverable()
{
return is_initialized()
&& is_game_thread()
&& !is_exception_interval_too_short()
&& !too_many_exceptions_occured();
}
void show_mouse_cursor()
{
while (ShowCursor(TRUE) < 0);
}
void display_error_dialog()
{
const std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
"A minidump has been written.\n\n",
exception_data.code, exception_data.address);
utils::thread::suspend_other_threads();
show_mouse_cursor();
MessageBoxA(nullptr, error_str.data(), "S1x ERROR", MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), exception_data.code);
}
void reset_state()
{
if (is_recoverable())
{
recovery_data.last_recovery = std::chrono::high_resolution_clock::now();
++recovery_data.recovery_counts;
game::Com_Error(game::ERR_DROP, "Fatal error (0x%08X) at 0x%p.\nA minidump has been written.\n\n"
"BOIII has tried to recover your game, but it might not run stable anymore.\n\n"
"Make sure to update your graphics card drivers and install operating system updates!\n"
"Closing or restarting Steam might also help.",
exception_data.code, exception_data.address);
}
else
{
display_error_dialog();
}
}
size_t get_reset_state_stub()
{
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
{
a.sub(rsp, 0x10);
a.or_(rsp, 0x8);
a.jmp(reset_state);
});
return reinterpret_cast<size_t>(stub);
}
std::string get_timestamp()
{
tm ltime{};
char timestamp[MAX_PATH] = {0};
const auto time = _time64(nullptr);
_localtime64_s(&ltime, &time);
strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", &ltime);
return timestamp;
}
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
{
std::string info{};
const auto line = [&info](const std::string& text)
{
info.append(text);
info.append("\r\n");
};
line("BOIII Crash Dump");
line("");
line("Version: "s + VERSION);
line("Timestamp: "s + get_timestamp());
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
line(utils::string::va("Address: 0x%llX", exceptioninfo->ExceptionRecord->ExceptionAddress));
#pragma warning(push)
#pragma warning(disable: 4996)
OSVERSIONINFOEXA version_info;
ZeroMemory(&version_info, sizeof(version_info));
version_info.dwOSVersionInfoSize = sizeof(version_info);
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
#pragma warning(pop)
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
return info;
}
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
{
const std::string crash_name = utils::string::va("minidumps/boiii-crash-%s.zip",
get_timestamp().data());
utils::compression::zip::archive zip_file{};
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
zip_file.write(crash_name, "BOIII Crash Dump");
}
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
{
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP;
}
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
{
if (is_harmless_error(exceptioninfo))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
write_minidump(exceptioninfo);
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
exceptioninfo->ContextRecord->Rip = get_reset_state_stub();
return EXCEPTION_CONTINUE_EXECUTION;
}
void WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
// Don't register anything here...
}
}
class component final : public component_interface
{
public:
component()
{
main_thread_id = GetCurrentThreadId();
SetUnhandledExceptionFilter(exception_filter);
}
void pre_start() override
{
const utils::nt::library ntdll("ntdll.dll");
auto* set_filter = ntdll.get_proc<void(*)(LPTOP_LEVEL_EXCEPTION_FILTER)>("RtlSetUnhandledExceptionFilter");
set_filter(exception_filter);
utils::hook::jump(set_filter, set_unhandled_exception_filter_stub);
}
};
}
REGISTER_COMPONENT(exception::component)

View File

@ -416,4 +416,16 @@ namespace game
ITEM_TEXTSTYLE_MONOSPACE = 128,
ITEM_TEXTSTYLE_MONOSPACESHADOWED = 132,
};
enum errorParm
{
ERR_FATAL = 0,
ERR_DROP = 1,
ERR_SERVERDISCONNECT = 2,
ERR_DISCONNECT = 3,
ERR_SCRIPT = 4,
ERR_SCRIPT_DROP = 5,
ERR_LOCALIZATION = 6,
ERR_MAPLOADERRORSUMMARY = 7,
};
}

View File

@ -0,0 +1,85 @@
#include "minidump.hpp"
#include <DbgHelp.h>
#pragma comment(lib, "dbghelp.lib")
namespace exception
{
namespace
{
constexpr MINIDUMP_TYPE get_minidump_type()
{
constexpr auto type = MiniDumpIgnoreInaccessibleMemory //
| MiniDumpWithHandleData //
| MiniDumpScanMemory //
| MiniDumpWithProcessThreadData //
| MiniDumpWithFullMemoryInfo //
| MiniDumpWithThreadInfo //
| MiniDumpWithUnloadedModules;
return static_cast<MINIDUMP_TYPE>(type);
}
std::string get_temp_filename()
{
char filename[MAX_PATH] = {0};
char pathname[MAX_PATH] = {0};
GetTempPathA(sizeof(pathname), pathname);
GetTempFileNameA(pathname, "boiii-", 0, filename);
return filename;
}
HANDLE write_dump_to_temp_file(const LPEXCEPTION_POINTERS exceptioninfo)
{
MINIDUMP_EXCEPTION_INFORMATION minidump_exception_info = {GetCurrentThreadId(), exceptioninfo, FALSE};
auto* const file_handle = CreateFileA(get_temp_filename().data(), GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
nullptr);
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file_handle, get_minidump_type(),
&minidump_exception_info,
nullptr,
nullptr))
{
MessageBoxA(nullptr, "There was an error creating the minidump! Hit OK to close the program.",
"Minidump Error", MB_OK | MB_ICONERROR);
TerminateProcess(GetCurrentProcess(), 123);
}
return file_handle;
}
std::string read_file(const HANDLE file_handle)
{
FlushFileBuffers(file_handle);
SetFilePointer(file_handle, 0, nullptr, FILE_BEGIN);
std::string buffer{};
DWORD bytes_read = 0;
char temp_bytes[0x2000];
do
{
if (!ReadFile(file_handle, temp_bytes, sizeof(temp_bytes), &bytes_read, nullptr))
{
return {};
}
buffer.append(temp_bytes, bytes_read);
}
while (bytes_read == sizeof(temp_bytes));
return buffer;
}
}
std::string create_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
{
const utils::nt::handle file_handle = write_dump_to_temp_file(exceptioninfo);
return read_file(file_handle);
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "../utils/nt.hpp"
namespace exception
{
std::string create_minidump(LPEXCEPTION_POINTERS exceptioninfo);
}