mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 23:16:22 +01:00
Add small crash reports with crash details, CPU preflight check and eu07_starter
This commit is contained in:
@@ -86,6 +86,7 @@ option(GENERATE_PDB "Generate executable with program debugging symbols" ON)
|
||||
option(ENABLE_MCC "Enable multicore compilation" ON)
|
||||
option(WITHDUMPGEN "Enable generating DMP files on crash" ON)
|
||||
option(ENABLE_AVX2 "Enable AVX2 instruction set (requires AVX2-capable CPU, Haswell/2013 or newer)" ON)
|
||||
option(BUNDLE_VC_RUNTIME "Copy the Visual C++ runtime libraries next to the executable (MSVC), so it does not depend on the installed redistributable" ON)
|
||||
option(ADD_VERSION_SUFFIX "Add date and commit hash to the executable name" ON)
|
||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -152,6 +153,8 @@ set(SOURCES
|
||||
"vehicle/Gauge.cpp"
|
||||
"utilities/Globals.cpp"
|
||||
"utilities/Logs.cpp"
|
||||
"utilities/crashdump.cpp"
|
||||
"utilities/crashdump_writer.cpp"
|
||||
"model/MdlMngr.cpp"
|
||||
"world/MemCell.cpp"
|
||||
"model/Model3d.cpp"
|
||||
@@ -912,6 +915,20 @@ if(WIN32 AND WITH_PYTHON)
|
||||
)
|
||||
endif()
|
||||
|
||||
# the Visual C++ runtime the simulator is built with, next to it: older runtimes installed in the system crash the programs of newer
|
||||
# compilers (std::mutex of Visual Studio 2022 17.10 and newer with a runtime older than 14.40), missing ones keep them from starting
|
||||
if (MSVC AND BUNDLE_VC_RUNTIME)
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
|
||||
set(CMAKE_INSTALL_UCRT_LIBRARIES FALSE)
|
||||
include(InstallRequiredSystemLibraries)
|
||||
if (CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
|
||||
else()
|
||||
message(WARNING "BUNDLE_VC_RUNTIME: the Visual C++ runtime libraries were not found, they are not copied")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# yaml-cpp, for the descriptions of scenery templates edited in the scenery editor and for the heightmap terrain
|
||||
add_subdirectory("ref/yaml-cpp" "${CMAKE_CURRENT_BINARY_DIR}/yaml-cpp" EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(eu07_settings INTERFACE yaml-cpp)
|
||||
@@ -942,13 +959,64 @@ if(ENABLE_MCC AND MSVC)
|
||||
add_compile_options(/MP)
|
||||
endif()
|
||||
|
||||
set(EU07_AVX2_TARGET OFF)
|
||||
if(ENABLE_AVX2)
|
||||
# AVX2 belongs to the x86-64 processors: the builds for ARM (Apple silicon, Linux aarch64, Windows on ARM) and the universal macOS ones go without it
|
||||
if (MSVC)
|
||||
if ("${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "x64")
|
||||
set(EU07_AVX2_TARGET ON)
|
||||
endif()
|
||||
elseif (APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||
if ("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64")
|
||||
set(EU07_AVX2_TARGET ON)
|
||||
endif()
|
||||
elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "^(x86_64|AMD64|amd64|x64)$")
|
||||
set(EU07_AVX2_TARGET ON)
|
||||
endif()
|
||||
if (NOT EU07_AVX2_TARGET)
|
||||
message(STATUS "AVX2 instruction set not available for the target processor, disabled.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EU07_AVX2_TARGET)
|
||||
message(STATUS "Enabling AVX2 instruction set.")
|
||||
if (MSVC)
|
||||
target_compile_options(eu07_settings INTERFACE /arch:AVX2)
|
||||
else()
|
||||
target_compile_options(eu07_settings INTERFACE -mavx2)
|
||||
endif()
|
||||
|
||||
# the check of the processor before any code built with AVX2 runs, built without it (it does not use eu07_settings)
|
||||
add_library(eu07_preflight OBJECT "utilities/cpu_preflight.cpp")
|
||||
target_include_directories(eu07_preflight PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set_target_properties(eu07_preflight PROPERTIES FOLDER "libraries" INTERPROCEDURAL_OPTIMIZATION OFF)
|
||||
if (MSVC)
|
||||
# it runs before the C runtime starts, the buffer security checks need the runtime
|
||||
target_compile_options(eu07_preflight PRIVATE /GS-)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} eu07_preflight)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE EU07_CPU_PREFLIGHT)
|
||||
endif()
|
||||
|
||||
# eu07_starter, the starter of the simulator: checks the processor and the libraries before the start, explains the failures
|
||||
# of the start the system reports with a bare error code, and writes the crash dumps of the simulator from outside of it
|
||||
if (MSVC)
|
||||
file(CONFIGURE OUTPUT "${CMAKE_BINARY_DIR}/eu07_starter.rc" CONTENT "IDI_ICON1 ICON \"${CMAKE_CURRENT_SOURCE_DIR}/eu07.ico\"\n" @ONLY)
|
||||
add_executable(eu07_starter WIN32 "starter/eu07_starter.cpp" "utilities/crashdump_writer.cpp" "${CMAKE_BINARY_DIR}/eu07_starter.rc")
|
||||
target_include_directories(eu07_starter PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_compile_definitions(eu07_starter PRIVATE UNICODE _UNICODE "EU07_SIMULATOR_EXE=\"$<TARGET_FILE_NAME:${PROJECT_NAME}>\"")
|
||||
if (EU07_AVX2_TARGET)
|
||||
target_compile_definitions(eu07_starter PRIVATE EU07_REQUIRES_AVX2)
|
||||
endif()
|
||||
# the static C runtime: the starter runs where the Visual C++ runtime is missing, to say so
|
||||
set_target_properties(eu07_starter PROPERTIES
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/pdb"
|
||||
FOLDER "tools")
|
||||
target_compile_options(eu07_starter PRIVATE $<IF:$<CONFIG:Debug>,/MTd,/MT>)
|
||||
target_link_libraries(eu07_starter PRIVATE dbghelp version shell32 user32)
|
||||
add_dependencies(${PROJECT_NAME} eu07_starter)
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
|
||||
60
EU07.cpp
60
EU07.cpp
@@ -20,67 +20,25 @@ Stele, firleju, szociu, hunter, ZiomalCl, OLI_EU and others
|
||||
|
||||
#include "application/application.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/crashdump.h"
|
||||
#include <cstdlib>
|
||||
#ifdef WITHDUMPGEN
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef EU07_CPU_PREFLIGHT
|
||||
// checks the processor before the C runtime starts, see utilities/cpu_preflight.cpp
|
||||
#pragma comment(linker, "/subsystem:windows /ENTRY:eu07_preflight_entry")
|
||||
#else
|
||||
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void export_e3d_standalone(std::string in, std::string out, int flags, bool dynamic);
|
||||
int export_btf_standalone(std::string in, std::string out);
|
||||
int generate_nodebank_previews(int Argc, char *Argv[]);
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <utilities/Globals.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
LONG WINAPI CrashHandler(EXCEPTION_POINTERS *ExceptionInfo)
|
||||
{
|
||||
// Get current local time
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
|
||||
// Format: crash_YYYY-MM-DD_HH-MM-SS.dmp
|
||||
std::ostringstream oss;
|
||||
oss << "crash_" << std::setw(4) << std::setfill('0') << st.wYear << "-" << std::setw(2) << std::setfill('0') << st.wMonth << "-" << std::setw(2) << std::setfill('0') << st.wDay << "_"
|
||||
<< std::setw(2) << std::setfill('0') << st.wHour << "-" << std::setw(2) << std::setfill('0') << st.wMinute << "-" << std::setw(2) << std::setfill('0') << st.wSecond << ".dmp";
|
||||
|
||||
std::string filename = oss.str();
|
||||
|
||||
HANDLE hFile = CreateFileA(filename.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFile != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
|
||||
dumpInfo.ThreadId = GetCurrentThreadId();
|
||||
dumpInfo.ExceptionPointers = ExceptionInfo;
|
||||
dumpInfo.ClientPointers = FALSE;
|
||||
|
||||
// Wybrana kombinacja flag
|
||||
MINIDUMP_TYPE dumpType = MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory |
|
||||
MiniDumpWithFullMemoryInfo | MiniDumpWithTokenInformation);
|
||||
|
||||
MessageBoxA(nullptr, "Simulator crash occured :(\n", "Simulator crashed :(", MB_ICONERROR);
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, dumpType, &dumpInfo, nullptr, nullptr);
|
||||
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// lets the log service write what is still queued and stops it; does nothing once Application.exit() has done that
|
||||
static void stop_log_service()
|
||||
{
|
||||
@@ -92,11 +50,7 @@ static void stop_log_service()
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef WITHDUMPGEN
|
||||
#ifdef _WIN32
|
||||
SetUnhandledExceptionFilter(CrashHandler);
|
||||
#endif
|
||||
#endif
|
||||
crashdump_install();
|
||||
// init start timestamp
|
||||
Global.startTimestamp = std::chrono::steady_clock::now();
|
||||
|
||||
|
||||
536
starter/eu07_starter.cpp
Normal file
536
starter/eu07_starter.cpp
Normal file
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// eu07_starter: starts the simulator next to it, after it checks what the system would otherwise report with a bare error code
|
||||
// or not at all: the processor (AVX2), the libraries the simulator links (the Visual C++ runtime above all) and their versions.
|
||||
// it explains the failures of the start, and writes the minidump of a crashed simulator from outside of it (utilities/crashdump.cpp).
|
||||
// built with the static C runtime and without AVX2, so it runs where the simulator cannot
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <cwctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/cpu_features.h"
|
||||
#include "utilities/crashdump_channel.h"
|
||||
|
||||
#ifndef EU07_SIMULATOR_EXE
|
||||
#error "EU07_SIMULATOR_EXE, the file name of the simulator, is set by CMakeLists.txt"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
wchar_t const *const title{L"MaSzyna"};
|
||||
wchar_t const *const redistributable_url{L"https://aka.ms/vs/17/release/vc_redist.x64.exe"};
|
||||
|
||||
std::wstring widen(char const *Text)
|
||||
{
|
||||
auto const length{::MultiByteToWideChar(CP_UTF8, 0, Text, -1, nullptr, 0)};
|
||||
std::wstring result(length > 0 ? length - 1 : 0, L'\0');
|
||||
if (length > 1)
|
||||
{
|
||||
::MultiByteToWideChar(CP_UTF8, 0, Text, -1, result.data(), length);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring lowercase(std::wstring Text)
|
||||
{
|
||||
std::transform(Text.begin(), Text.end(), Text.begin(), [](wchar_t const Character) { return static_cast<wchar_t>(std::towlower(Character)); });
|
||||
return Text;
|
||||
}
|
||||
|
||||
bool starts_with(std::wstring const &Text, wchar_t const *Prefix)
|
||||
{
|
||||
return Text.compare(0, std::wcslen(Prefix), Prefix) == 0;
|
||||
}
|
||||
|
||||
bool file_exists(std::wstring const &Path)
|
||||
{
|
||||
auto const attributes{::GetFileAttributesW(Path.c_str())};
|
||||
return (attributes != INVALID_FILE_ATTRIBUTES) && ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
|
||||
}
|
||||
|
||||
std::wstring system_directory()
|
||||
{
|
||||
wchar_t path[MAX_PATH];
|
||||
auto const length{::GetSystemDirectoryW(path, MAX_PATH)};
|
||||
return (length > 0) && (length < MAX_PATH) ? std::wstring(path, length) : std::wstring();
|
||||
}
|
||||
|
||||
std::wstring windows_directory()
|
||||
{
|
||||
wchar_t path[MAX_PATH];
|
||||
auto const length{::GetWindowsDirectoryW(path, MAX_PATH)};
|
||||
return (length > 0) && (length < MAX_PATH) ? std::wstring(path, length) : std::wstring();
|
||||
}
|
||||
|
||||
int show(std::wstring const &Text, UINT const Type)
|
||||
{
|
||||
return ::MessageBoxW(nullptr, Text.c_str(), title, Type | MB_SETFOREGROUND);
|
||||
}
|
||||
|
||||
// asks to open the download page of the Visual C++ runtime, true when the user wants it
|
||||
bool offer_redistributable(std::wstring const &Text, bool const CanContinue)
|
||||
{
|
||||
auto const answer{show(Text + L"\n\nOpen the download page of the Microsoft Visual C++ Redistributable (x64) now?" + (CanContinue ? L"\n(No starts the simulator anyway.)" : L""),
|
||||
MB_YESNO | MB_ICONWARNING)};
|
||||
if (answer == IDYES)
|
||||
{
|
||||
::ShellExecuteW(nullptr, L"open", redistributable_url, nullptr, nullptr, SW_SHOWNORMAL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// the libraries an image links, from its import directory, read from the image mapped as the loader maps it, without running it
|
||||
struct image_imports
|
||||
{
|
||||
bool valid{false};
|
||||
bool same_machine{false};
|
||||
std::vector<std::wstring> libraries;
|
||||
};
|
||||
|
||||
image_imports read_imports(std::wstring const &Path)
|
||||
{
|
||||
image_imports result;
|
||||
auto const file{::CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
auto const mapping{::CreateFileMappingW(file, nullptr, PAGE_READONLY | SEC_IMAGE, 0, 0, nullptr)};
|
||||
::CloseHandle(file);
|
||||
if (mapping == nullptr)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
auto const *view{static_cast<unsigned char const *>(::MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0))};
|
||||
::CloseHandle(mapping);
|
||||
if (view == nullptr)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
MEMORY_BASIC_INFORMATION region{};
|
||||
::VirtualQuery(view, ®ion, sizeof(region));
|
||||
auto const *dos{reinterpret_cast<IMAGE_DOS_HEADER const *>(view)};
|
||||
if (dos->e_magic == IMAGE_DOS_SIGNATURE)
|
||||
{
|
||||
auto const *nt{reinterpret_cast<IMAGE_NT_HEADERS const *>(view + dos->e_lfanew)};
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
result.valid = true;
|
||||
#if defined(_M_ARM64)
|
||||
result.same_machine = (nt->FileHeader.Machine == IMAGE_FILE_MACHINE_ARM64);
|
||||
#else
|
||||
result.same_machine = (nt->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64);
|
||||
#endif
|
||||
if (result.same_machine && (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC))
|
||||
{
|
||||
auto const size{nt->OptionalHeader.SizeOfImage};
|
||||
auto const &directory{nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]};
|
||||
if ((directory.VirtualAddress != 0) && (directory.VirtualAddress < size))
|
||||
{
|
||||
for (auto const *descriptor{reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR const *>(view + directory.VirtualAddress)};
|
||||
(reinterpret_cast<unsigned char const *>(descriptor + 1) <= view + size) && (descriptor->Name != 0); ++descriptor)
|
||||
{
|
||||
if (descriptor->Name >= size)
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto const *name{reinterpret_cast<char const *>(view + descriptor->Name)};
|
||||
auto const length{strnlen(name, std::min<std::size_t>(size - descriptor->Name, MAX_PATH))};
|
||||
result.libraries.push_back(widen(std::string(name, length).c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
::UnmapViewOfFile(view);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct library_check
|
||||
{
|
||||
std::wstring application_directory;
|
||||
std::vector<std::wstring> visited;
|
||||
std::vector<std::wstring> missing;
|
||||
std::vector<std::wstring> foreign; // built for another processor, a 32 bit library mostly
|
||||
bool needs_universal_runtime{false};
|
||||
};
|
||||
|
||||
bool is_runtime_library(std::wstring const &Name)
|
||||
{
|
||||
return starts_with(Name, L"msvcp140") || starts_with(Name, L"vcruntime140") || starts_with(Name, L"concrt140") || (Name == L"ucrtbase.dll");
|
||||
}
|
||||
|
||||
// where the loader finds a library of the simulator: next to it first, then in the system. KnownDLLs are system libraries anyway
|
||||
std::wstring locate(library_check const &Check, std::wstring const &Name, bool &Local)
|
||||
{
|
||||
Local = false;
|
||||
auto path{Check.application_directory + L"\\" + Name};
|
||||
if (file_exists(path))
|
||||
{
|
||||
Local = true;
|
||||
return path;
|
||||
}
|
||||
for (auto const &directory : {system_directory(), windows_directory()})
|
||||
{
|
||||
if (directory.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
path = directory + L"\\" + Name;
|
||||
if (file_exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
wchar_t found[MAX_PATH];
|
||||
auto const length{::SearchPathW(nullptr, Name.c_str(), nullptr, MAX_PATH, found, nullptr)};
|
||||
return (length > 0) && (length < MAX_PATH) ? std::wstring(found, length) : std::wstring();
|
||||
}
|
||||
|
||||
// the libraries of the image and of the libraries next to the simulator, which the installation brings. the system ones are taken as they are
|
||||
void check_libraries(library_check &Check, std::wstring const &Image, int const Depth)
|
||||
{
|
||||
auto const imports{read_imports(Image)};
|
||||
for (auto const &library : imports.libraries)
|
||||
{
|
||||
auto const name{lowercase(library)};
|
||||
if (std::find(Check.visited.begin(), Check.visited.end(), name) != Check.visited.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Check.visited.push_back(name);
|
||||
// the api sets the system resolves on its own; the C runtime ones lead to the universal C runtime
|
||||
if (starts_with(name, L"api-ms-") || starts_with(name, L"ext-ms-"))
|
||||
{
|
||||
if (starts_with(name, L"api-ms-win-crt-"))
|
||||
{
|
||||
Check.needs_universal_runtime = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
bool local{false};
|
||||
auto const path{locate(Check, name, local)};
|
||||
if (path.empty())
|
||||
{
|
||||
Check.missing.push_back(library);
|
||||
continue;
|
||||
}
|
||||
if (local)
|
||||
{
|
||||
auto const nested{read_imports(path)};
|
||||
if (nested.valid && (false == nested.same_machine))
|
||||
{
|
||||
Check.foreign.push_back(library);
|
||||
continue;
|
||||
}
|
||||
if (Depth < 8)
|
||||
{
|
||||
check_libraries(Check, path, Depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the version of a library as major, minor and build, zeros when it has none
|
||||
void file_version(std::wstring const &Path, unsigned int &Major, unsigned int &Minor, unsigned int &Build)
|
||||
{
|
||||
Major = Minor = Build = 0;
|
||||
DWORD handle{0};
|
||||
auto const size{::GetFileVersionInfoSizeW(Path.c_str(), &handle)};
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<unsigned char> data(size);
|
||||
if (::GetFileVersionInfoW(Path.c_str(), 0, size, data.data()) == FALSE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
VS_FIXEDFILEINFO *info{nullptr};
|
||||
UINT length{0};
|
||||
if ((::VerQueryValueW(data.data(), L"\\", reinterpret_cast<void **>(&info), &length) == FALSE) || (info == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Major = HIWORD(info->dwFileVersionMS);
|
||||
Minor = LOWORD(info->dwFileVersionMS);
|
||||
Build = HIWORD(info->dwFileVersionLS);
|
||||
}
|
||||
|
||||
std::wstring join(std::vector<std::wstring> const &Items)
|
||||
{
|
||||
std::wstring result;
|
||||
for (auto const &item : Items)
|
||||
{
|
||||
result += L"\n " + item;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// false when the simulator should not be started
|
||||
bool check_system(std::wstring const &Simulator, std::wstring const &Directory)
|
||||
{
|
||||
#ifdef EU07_REQUIRES_AVX2
|
||||
auto const missingfeatures{cpu_features::missing(cpu_features::required_for_avx2)};
|
||||
if (missingfeatures != 0)
|
||||
{
|
||||
std::wstring features;
|
||||
for (unsigned int feature = 1; feature <= cpu_features::os_avx; feature <<= 1)
|
||||
{
|
||||
if ((missingfeatures & feature) != 0)
|
||||
{
|
||||
features += (features.empty() ? L"" : L", ") + widen(cpu_features::name(feature));
|
||||
}
|
||||
}
|
||||
char brand[49];
|
||||
cpu_features::brand(brand);
|
||||
show(L"This processor does not support the AVX2 instruction set the simulator is built for. The simulator won't run.\n\nProcessor: " + widen(brand) + L"\nMissing: " + features +
|
||||
L"\n\nIt needs a processor with AVX2: Intel Core of the 4th generation (Haswell, 2013), AMD of the Excavator generation (2015) or newer.",
|
||||
MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto const imports{read_imports(Simulator)};
|
||||
if (imports.valid && (false == imports.same_machine))
|
||||
{
|
||||
show(L"The simulator " + Simulator + L" is built for another processor than this starter.", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
library_check check;
|
||||
check.application_directory = Directory;
|
||||
check_libraries(check, Simulator, 0);
|
||||
if (check.needs_universal_runtime)
|
||||
{
|
||||
bool local{false};
|
||||
if (locate(check, L"ucrtbase.dll", local).empty())
|
||||
{
|
||||
check.missing.push_back(L"ucrtbase.dll (Universal C Runtime, update KB2999226 on Windows 7 and 8.1)");
|
||||
}
|
||||
}
|
||||
|
||||
if (false == check.foreign.empty())
|
||||
{
|
||||
show(L"These libraries next to the simulator are built for another processor (32 bit?):" + join(check.foreign) +
|
||||
L"\n\nReplace them with the 64 bit ones of the installation: on Steam, verify the integrity of the game files.",
|
||||
MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
if (false == check.missing.empty())
|
||||
{
|
||||
bool runtime{false};
|
||||
for (auto const &library : check.missing)
|
||||
{
|
||||
runtime = runtime || is_runtime_library(lowercase(library));
|
||||
}
|
||||
std::wstring const text{L"The simulator can't start, these libraries are missing:" + join(check.missing)};
|
||||
if (runtime)
|
||||
{
|
||||
offer_redistributable(text + L"\n\nThey belong to the Microsoft Visual C++ Redistributable.", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
show(text + L"\n\nThe installation is incomplete: on Steam, verify the integrity of the game files, otherwise install the simulator again.", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1940)
|
||||
// the programs of Visual Studio 2022 17.10 and newer crash in std::mutex with the runtime older than 14.40 it replaces
|
||||
bool local{false};
|
||||
auto const runtime{locate(check, L"msvcp140.dll", local)};
|
||||
unsigned int major, minor, build;
|
||||
file_version(runtime, major, minor, build);
|
||||
if ((major == 14) && (minor < 40))
|
||||
{
|
||||
if (offer_redistributable(L"The Microsoft Visual C++ runtime (" + runtime + L", version 14." + std::to_wstring(minor) + L"." + std::to_wstring(build) +
|
||||
L") is older than the simulator needs (14.40 or newer). The simulator will likely crash at start.",
|
||||
true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// the explanation of an exit code of a simulator which did not report a crash itself, empty for an ordinary exit
|
||||
std::wstring explain_exit(DWORD const Code)
|
||||
{
|
||||
switch (Code)
|
||||
{
|
||||
case 0xC0000135: // STATUS_DLL_NOT_FOUND
|
||||
return L"A library the simulator needs is missing (0xC0000135). Install the Microsoft Visual C++ Redistributable (x64); if it does not help, verify the integrity of the game files.";
|
||||
case 0xC0000139: // STATUS_ENTRYPOINT_NOT_FOUND
|
||||
return L"A library the simulator needs is too old or of another version (0xC0000139). Install the latest Microsoft Visual C++ Redistributable (x64), and verify the integrity of the game files.";
|
||||
case 0xC000007B: // STATUS_INVALID_IMAGE_FORMAT
|
||||
return L"A library of the simulator is damaged or built for another processor (0xC000007B). Verify the integrity of the game files.";
|
||||
case 0xC0000142: // STATUS_DLL_INIT_FAILED
|
||||
return L"A library of the simulator failed to start (0xC0000142).";
|
||||
case 0xC000001D: // STATUS_ILLEGAL_INSTRUCTION
|
||||
return L"The simulator used an instruction this processor does not have (0xC000001D). It needs a processor with AVX2.";
|
||||
case 0xC0000409: // STATUS_STACK_BUFFER_OVERRUN, also a fast fail of the runtime
|
||||
return L"The simulator was stopped by a fatal error check (0xC0000409), no crash report could be written.";
|
||||
case 0xC000013A: // STATUS_CONTROL_C_EXIT
|
||||
return {};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if ((Code & 0xF0000000) == 0xC0000000)
|
||||
{
|
||||
wchar_t text[160];
|
||||
std::swprintf(text, 160, L"The simulator ended with the error 0x%08lX before its crash report could be written.", static_cast<unsigned long>(Code));
|
||||
return text;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// the arguments of the starter, passed on to the simulator
|
||||
wchar_t const *arguments()
|
||||
{
|
||||
auto const *line{::GetCommandLineW()};
|
||||
if (*line == L'"')
|
||||
{
|
||||
++line;
|
||||
while ((*line != L'\0') && (*line != L'"'))
|
||||
{
|
||||
++line;
|
||||
}
|
||||
if (*line == L'"')
|
||||
{
|
||||
++line;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((*line != L'\0') && (*line != L' ') && (*line != L'\t'))
|
||||
{
|
||||
++line;
|
||||
}
|
||||
}
|
||||
while ((*line == L' ') || (*line == L'\t'))
|
||||
{
|
||||
++line;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
||||
{
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
auto const length{::GetModuleFileNameW(nullptr, path, MAX_PATH * 2)};
|
||||
if ((length == 0) || (length >= MAX_PATH * 2))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
std::wstring directory(path, length);
|
||||
directory.erase(directory.find_last_of(L"\\/"));
|
||||
auto const simulator{directory + L"\\" + widen(EU07_SIMULATOR_EXE)};
|
||||
|
||||
if (false == file_exists(simulator))
|
||||
{
|
||||
show(L"The simulator was not found:\n" + simulator, MB_OK | MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
if (false == check_system(simulator, directory))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// the shared block of the crash report, its handles are inherited by the simulator
|
||||
SECURITY_ATTRIBUTES inheritable{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
|
||||
auto const mapping{::CreateFileMappingW(INVALID_HANDLE_VALUE, &inheritable, PAGE_READWRITE, 0, sizeof(crashdump::channel_data), nullptr)};
|
||||
auto *channel{mapping != nullptr ? static_cast<crashdump::channel_data *>(::MapViewOfFile(mapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(crashdump::channel_data))) : nullptr};
|
||||
auto const request{::CreateEventW(&inheritable, FALSE, FALSE, nullptr)};
|
||||
auto const done{::CreateEventW(&inheritable, FALSE, FALSE, nullptr)};
|
||||
bool const reporting{(channel != nullptr) && (request != nullptr) && (done != nullptr)};
|
||||
if (reporting)
|
||||
{
|
||||
channel->magic = crashdump::channel_magic;
|
||||
channel->version = crashdump::channel_version;
|
||||
wchar_t value[128];
|
||||
std::swprintf(value, 128, L"%llx:%llx:%llx", static_cast<unsigned long long>(reinterpret_cast<ULONG_PTR>(mapping)), static_cast<unsigned long long>(reinterpret_cast<ULONG_PTR>(request)),
|
||||
static_cast<unsigned long long>(reinterpret_cast<ULONG_PTR>(done)));
|
||||
::SetEnvironmentVariableW(crashdump::channel_variable, value);
|
||||
}
|
||||
|
||||
std::wstring commandline{L"\"" + simulator + L"\""};
|
||||
auto const *passed{arguments()};
|
||||
if (*passed != L'\0')
|
||||
{
|
||||
commandline += L" ";
|
||||
commandline += passed;
|
||||
}
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
if (::CreateProcessW(simulator.c_str(), commandline.data(), nullptr, nullptr, reporting ? TRUE : FALSE, 0, nullptr, nullptr, &startup, &process) == FALSE)
|
||||
{
|
||||
auto const error{::GetLastError()};
|
||||
wchar_t text[160];
|
||||
std::swprintf(text, 160, L"\n\nThe system refused to start it (error %lu)", static_cast<unsigned long>(error));
|
||||
show(L"The simulator could not be started:\n" + simulator + text +
|
||||
(error == ERROR_BAD_EXE_FORMAT ? L": it is not a program for this version of Windows (it needs 64 bit Windows)." : L"."),
|
||||
MB_OK | MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
::CloseHandle(process.hThread);
|
||||
|
||||
bool reported{false};
|
||||
HANDLE const waited[]{process.hProcess, request};
|
||||
while (true)
|
||||
{
|
||||
auto const result{::WaitForMultipleObjects(reporting ? 2 : 1, waited, FALSE, INFINITE)};
|
||||
if (result == WAIT_OBJECT_0 + 1)
|
||||
{
|
||||
// the simulator crashed and waits: its minidump, written from here as the crashed process may be unable to
|
||||
if (channel->process_id == process.dwProcessId)
|
||||
{
|
||||
crashdump_write_minidump(process.hProcess, *channel, true);
|
||||
reported = true;
|
||||
}
|
||||
::SetEvent(done);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD code{0};
|
||||
::GetExitCodeProcess(process.hProcess, &code);
|
||||
::CloseHandle(process.hProcess);
|
||||
if ((false == reported) && (code != cpu_features::exit_unsupported))
|
||||
{
|
||||
auto const explanation{explain_exit(code)};
|
||||
if (false == explanation.empty())
|
||||
{
|
||||
show(explanation, MB_OK | MB_ICONERROR);
|
||||
}
|
||||
}
|
||||
return static_cast<int>(code);
|
||||
}
|
||||
@@ -1220,6 +1220,12 @@ bool global_settings::ConfigParseDebug(cParser& Parser, const std::string& token
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "crashdump.full")
|
||||
{
|
||||
ParseOne(Parser, crashdump_full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token == "showsystemconsole")
|
||||
{
|
||||
ParseOne(Parser, ShowSystemConsole);
|
||||
@@ -2158,6 +2164,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "debug.memorystats", debug_memorystats );
|
||||
export_as_text( Output, "debuglog", iWriteLogEnabled );
|
||||
export_as_text( Output, "multiplelogs", MultipleLogs );
|
||||
export_as_text( Output, "crashdump.full", crashdump_full );
|
||||
export_as_text( Output, "showsystemconsole", ShowSystemConsole );
|
||||
export_as_text( Output, "logs.filter", DisabledLogTypes );
|
||||
Output
|
||||
|
||||
@@ -98,6 +98,7 @@ struct global_settings {
|
||||
bool priorityLoadText3D{false}; // ladowanie T3D priorytetowo
|
||||
int iWriteLogEnabled{ 3 }; // maska bitowa: 1-zapis do pliku, 2-okienko, 4-nazwy torów
|
||||
bool MultipleLogs{ false };
|
||||
bool crashdump_full{ false }; // crash dump with the whole memory of the process instead of the small one
|
||||
bool ShowSystemConsole{ false }; // toggle visibility of the OS console window with log output
|
||||
unsigned int DisabledLogTypes{ 0 };
|
||||
bool ParserLogIncludes{ true };
|
||||
|
||||
@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "winheaders.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "application/uilayer.h"
|
||||
#include "utilities/crashdump.h"
|
||||
#include <deque>
|
||||
|
||||
std::ofstream output; // standardowy "log.txt", można go wyłączyć
|
||||
@@ -102,6 +103,7 @@ void LogService()
|
||||
{
|
||||
std::string filename = Global.MultipleLogs ? "logs/log (" + filename_scenery() + ") " + filename_date() + ".txt" : "log.txt";
|
||||
output.open(filename, std::ios::trunc);
|
||||
crashdump_set_log_file(crashdump_log_file::log, filename);
|
||||
}
|
||||
output << msg << "\n";
|
||||
output.flush();
|
||||
@@ -139,6 +141,7 @@ void LogService()
|
||||
{
|
||||
std::string filename = Global.MultipleLogs ? "logs/errors (" + filename_scenery() + ") " + filename_date() + ".txt" : "errors.txt";
|
||||
errors.open(filename, std::ios::trunc);
|
||||
crashdump_set_log_file(crashdump_log_file::errors, filename);
|
||||
errors << "EU07.EXE " + Global.asVersion << "\n";
|
||||
}
|
||||
|
||||
@@ -176,6 +179,8 @@ void WriteLog(std::string_view str, logtype type, bool isError)
|
||||
const auto message = FormatLogMessage(str);
|
||||
|
||||
std::scoped_lock lock(logMutex);
|
||||
// at once rather than by the log service, which may not get to it before a crash
|
||||
crashdump_note_log(message.data(), message.size());
|
||||
InfoStack.emplace_back(message, isError);
|
||||
}
|
||||
|
||||
|
||||
180
utilities/cpu_features.h
Normal file
180
utilities/cpu_features.h
Normal file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
|
||||
// the processor features the simulator is built to use (ENABLE_AVX2), checked before any code built with them runs.
|
||||
// header only and free of the C runtime: the simulator checks them from its entry point, before the runtime starts
|
||||
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
#define EU07_CPU_FEATURES_X64 1
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace cpu_features
|
||||
{
|
||||
|
||||
enum feature : unsigned int
|
||||
{
|
||||
avx2 = 1 << 0,
|
||||
fma3 = 1 << 1,
|
||||
bmi1 = 1 << 2,
|
||||
bmi2 = 1 << 3,
|
||||
lzcnt = 1 << 4,
|
||||
// the processor has AVX, but the system does not save its registers, so it is turned off (old systems, some virtual machines)
|
||||
os_avx = 1 << 5,
|
||||
};
|
||||
|
||||
// the exit code of the simulator stopped by the check of the processor, after it told the user (eu07_starter stays silent then)
|
||||
constexpr unsigned int exit_unsupported{0xE0450010};
|
||||
|
||||
// msvc builds with /arch:AVX2 use the bit manipulation instructions of the same processor generation as well,
|
||||
// gcc and clang with -mavx2 only AVX2 itself. every processor with AVX2 (Intel Haswell, AMD Excavator and newer) has them all
|
||||
#ifdef _MSC_VER
|
||||
constexpr unsigned int required_for_avx2{avx2 | fma3 | bmi1 | bmi2 | lzcnt | os_avx};
|
||||
#else
|
||||
constexpr unsigned int required_for_avx2{avx2 | os_avx};
|
||||
#endif
|
||||
|
||||
#ifdef EU07_CPU_FEATURES_X64
|
||||
|
||||
inline void cpuid(int const Leaf, int const Subleaf, int Registers[4])
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
__cpuidex(Registers, Leaf, Subleaf);
|
||||
#else
|
||||
unsigned int a, b, c, d;
|
||||
__cpuid_count(Leaf, Subleaf, a, b, c, d);
|
||||
Registers[0] = static_cast<int>(a);
|
||||
Registers[1] = static_cast<int>(b);
|
||||
Registers[2] = static_cast<int>(c);
|
||||
Registers[3] = static_cast<int>(d);
|
||||
#endif
|
||||
}
|
||||
|
||||
// the register state the system saves on a thread switch; xgetbv may be used only when cpuid reports OSXSAVE
|
||||
inline unsigned long long xcr0()
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return _xgetbv(0);
|
||||
#else
|
||||
unsigned int low, high;
|
||||
__asm__ volatile("xgetbv" : "=a"(low), "=d"(high) : "c"(0));
|
||||
return (static_cast<unsigned long long>(high) << 32) | low;
|
||||
#endif
|
||||
}
|
||||
|
||||
// the features of the processor, a mask of the feature values
|
||||
inline unsigned int present()
|
||||
{
|
||||
int registers[4];
|
||||
cpuid(0, 0, registers);
|
||||
int const maxleaf{registers[0]};
|
||||
cpuid(0x80000000, 0, registers);
|
||||
unsigned int const maxextendedleaf{static_cast<unsigned int>(registers[0])};
|
||||
|
||||
unsigned int result{0};
|
||||
cpuid(1, 0, registers);
|
||||
bool const hasavx{(registers[2] & (1 << 28)) != 0};
|
||||
bool const hasosxsave{(registers[2] & (1 << 27)) != 0};
|
||||
if ((registers[2] & (1 << 12)) != 0)
|
||||
{
|
||||
result |= fma3;
|
||||
}
|
||||
// bits 1 and 2: the SSE and AVX registers
|
||||
if (hasavx && hasosxsave && ((xcr0() & 0x6) == 0x6))
|
||||
{
|
||||
result |= os_avx;
|
||||
}
|
||||
if (maxleaf >= 7)
|
||||
{
|
||||
cpuid(7, 0, registers);
|
||||
if ((registers[1] & (1 << 5)) != 0)
|
||||
{
|
||||
result |= avx2;
|
||||
}
|
||||
if ((registers[1] & (1 << 3)) != 0)
|
||||
{
|
||||
result |= bmi1;
|
||||
}
|
||||
if ((registers[1] & (1 << 8)) != 0)
|
||||
{
|
||||
result |= bmi2;
|
||||
}
|
||||
}
|
||||
if (maxextendedleaf >= 0x80000001)
|
||||
{
|
||||
cpuid(0x80000001, 0, registers);
|
||||
if ((registers[2] & (1 << 5)) != 0)
|
||||
{
|
||||
result |= lzcnt;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the name of the processor, up to 48 characters
|
||||
inline void brand(char Output[49])
|
||||
{
|
||||
int registers[4];
|
||||
cpuid(0x80000000, 0, registers);
|
||||
if (static_cast<unsigned int>(registers[0]) < 0x80000004)
|
||||
{
|
||||
Output[0] = '\0';
|
||||
return;
|
||||
}
|
||||
for (int leaf = 0; leaf < 3; ++leaf)
|
||||
{
|
||||
cpuid(0x80000002 + leaf, 0, registers);
|
||||
for (int index = 0; index < 16; ++index)
|
||||
{
|
||||
Output[leaf * 16 + index] = reinterpret_cast<char const *>(registers)[index];
|
||||
}
|
||||
}
|
||||
Output[48] = '\0';
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline unsigned int present()
|
||||
{
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
inline void brand(char Output[49])
|
||||
{
|
||||
Output[0] = '\0';
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// the required features the processor does not have
|
||||
inline unsigned int missing(unsigned int const Required)
|
||||
{
|
||||
return Required & ~present();
|
||||
}
|
||||
|
||||
// the name of a single feature
|
||||
inline char const *name(unsigned int const Feature)
|
||||
{
|
||||
switch (Feature)
|
||||
{
|
||||
case avx2:
|
||||
return "AVX2";
|
||||
case fma3:
|
||||
return "FMA3";
|
||||
case bmi1:
|
||||
return "BMI1";
|
||||
case bmi2:
|
||||
return "BMI2";
|
||||
case lzcnt:
|
||||
return "LZCNT";
|
||||
case os_avx:
|
||||
return "AVX support of the operating system";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cpu_features
|
||||
155
utilities/cpu_preflight.cpp
Normal file
155
utilities/cpu_preflight.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the check of the processor of a build with ENABLE_AVX2, before any code built with AVX2 runs. the start of the C runtime runs
|
||||
// the constructors of the global objects, already built with it: on a processor without AVX2 the simulator would end there
|
||||
// without a word. this file is built without AVX2 and without the precompiled header (eu07_preflight in CMakeLists.txt)
|
||||
|
||||
#include "utilities/cpu_features.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
// the entry point of the C runtime, started by the one below
|
||||
extern "C" int mainCRTStartup(void *);
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// the runtime checks need the C runtime, which is not started yet
|
||||
#pragma runtime_checks("", off)
|
||||
#endif
|
||||
|
||||
void append(wchar_t *Output, int const Capacity, int &Length, char const *Text)
|
||||
{
|
||||
while ((*Text != '\0') && (Length < Capacity - 1))
|
||||
{
|
||||
Output[Length++] = static_cast<wchar_t>(*Text++);
|
||||
}
|
||||
Output[Length] = L'\0';
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__declspec(noinline) __declspec(safebuffers)
|
||||
#endif
|
||||
void report_missing(unsigned int const Missing)
|
||||
{
|
||||
wchar_t text[512];
|
||||
int length{0};
|
||||
append(text, 512, length, "This processor does not support the AVX2 instruction set the simulator is built for. The simulator won't run.\n\nMissing: ");
|
||||
bool first{true};
|
||||
for (unsigned int feature = 1; feature <= cpu_features::os_avx; feature <<= 1)
|
||||
{
|
||||
if ((Missing & feature) != 0)
|
||||
{
|
||||
append(text, 512, length, first ? "" : ", ");
|
||||
append(text, 512, length, cpu_features::name(feature));
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
append(text, 512, length, "\n\nIt needs a processor with AVX2: Intel Core of the 4th generation (Haswell, 2013), AMD of the Excavator generation (2015) or newer.");
|
||||
::MessageBoxW(nullptr, text, L"MaSzyna", MB_ICONERROR | MB_OK);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// the entry point of the simulator (/ENTRY), runs before the C runtime starts. without a buffer security check, the security cookie
|
||||
// is set up by the runtime started from here
|
||||
extern "C"
|
||||
#ifdef _MSC_VER
|
||||
__declspec(safebuffers)
|
||||
#endif
|
||||
int eu07_preflight_entry(void *Peb)
|
||||
{
|
||||
auto const missing{cpu_features::missing(cpu_features::required_for_avx2)};
|
||||
if (missing != 0)
|
||||
{
|
||||
report_missing(missing);
|
||||
::ExitProcess(cpu_features::exit_unsupported);
|
||||
}
|
||||
return mainCRTStartup(Peb);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma runtime_checks("", restore)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool show_dialog(char const *Program, char const *const *Arguments)
|
||||
{
|
||||
pid_t process;
|
||||
if (posix_spawnp(&process, Program, nullptr, nullptr, const_cast<char *const *>(Arguments), environ) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int status{0};
|
||||
return (waitpid(process, &status, 0) == process) && WIFEXITED(status) && (WEXITSTATUS(status) != 127);
|
||||
}
|
||||
|
||||
// before the constructors of the other global objects of the simulator
|
||||
__attribute__((constructor(101))) void eu07_preflight()
|
||||
{
|
||||
auto const missing{cpu_features::missing(cpu_features::required_for_avx2)};
|
||||
if (missing == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char text[512];
|
||||
int length{std::snprintf(text, sizeof(text), "This processor does not support the AVX2 instruction set the simulator is built for. The simulator won't run.\nMissing:")};
|
||||
for (unsigned int feature = 1; feature <= cpu_features::os_avx; feature <<= 1)
|
||||
{
|
||||
if (((missing & feature) != 0) && (length > 0) && (length < static_cast<int>(sizeof(text))))
|
||||
{
|
||||
length += std::snprintf(text + length, sizeof(text) - length, " %s", cpu_features::name(feature));
|
||||
}
|
||||
}
|
||||
std::fprintf(stderr, "%s\n", text);
|
||||
// started from the desktop or steam, the console is not seen
|
||||
#ifdef __APPLE__
|
||||
char script[640];
|
||||
std::snprintf(script, sizeof(script), "display alert \"MaSzyna\" message \"%s\" as critical", text);
|
||||
char const *const osascript[]{"osascript", "-e", script, nullptr};
|
||||
show_dialog("osascript", osascript);
|
||||
#else
|
||||
if ((std::getenv("DISPLAY") != nullptr) || (std::getenv("WAYLAND_DISPLAY") != nullptr))
|
||||
{
|
||||
char const *const zenity[]{"zenity", "--error", "--title=MaSzyna", "--text", text, nullptr};
|
||||
char const *const kdialog[]{"kdialog", "--title", "MaSzyna", "--error", text, nullptr};
|
||||
if (false == show_dialog("zenity", zenity))
|
||||
{
|
||||
show_dialog("kdialog", kdialog);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::_Exit(1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
679
utilities/crashdump.cpp
Normal file
679
utilities/crashdump.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "utilities/crashdump.h"
|
||||
|
||||
#if defined(_WIN32) && defined(WITHDUMPGEN)
|
||||
|
||||
#include <DbgHelp.h>
|
||||
#include <psapi.h>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/cpu_features.h"
|
||||
#include "utilities/crashdump_channel.h"
|
||||
#include "version_info.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
#pragma comment(lib, "psapi.lib")
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the crashes reported without an exception of the system
|
||||
constexpr DWORD code_abort{0xE0450001};
|
||||
constexpr DWORD code_purecall{0xE0450002};
|
||||
constexpr DWORD code_invalid_parameter{0xE0450003};
|
||||
// the reports kept in crashdumps/, the older ones are removed at the start
|
||||
constexpr std::size_t reports_kept{10};
|
||||
// "crash_YYYY-MM-DD_HH-MM-SS", the start of the names of the files of a report
|
||||
constexpr std::size_t report_stamp_length{25};
|
||||
|
||||
// the named values, written by the simulator as it goes and read without a lock when it crashes
|
||||
struct info_entry
|
||||
{
|
||||
char name[48];
|
||||
char value[464];
|
||||
};
|
||||
info_entry info_entries[48];
|
||||
std::atomic<std::size_t> info_used{0};
|
||||
std::mutex info_mutex;
|
||||
|
||||
// the recent lines of the log, written under the lock of the log
|
||||
char log_ring[24 * 1024];
|
||||
std::size_t log_written{0};
|
||||
wchar_t log_paths[2][MAX_PATH * 2]{};
|
||||
|
||||
// taken at the installation, when it is still safe to
|
||||
wchar_t report_directory[MAX_PATH * 2]{};
|
||||
wchar_t executable_stem[MAX_PATH]{};
|
||||
char executable_path[MAX_PATH * 3]{};
|
||||
char processor_name[49]{};
|
||||
char system_version[96]{};
|
||||
SYSTEMTIME start_time{};
|
||||
DWORD main_thread_id{0};
|
||||
|
||||
// the report. with eu07_starter the block is shared with it and the starter writes the minidump
|
||||
crashdump::channel_data local_channel{};
|
||||
crashdump::channel_data *channel{&local_channel};
|
||||
HANDLE starter_request{nullptr};
|
||||
HANDLE starter_done{nullptr};
|
||||
|
||||
// the report is written by a thread started beforehand: the crashed one may have no stack left, and a thread started
|
||||
// after a crash may wait forever for the lock of the loader the crashed one holds
|
||||
HANDLE report_request{nullptr};
|
||||
HANDLE report_written{nullptr};
|
||||
HANDLE report_done{nullptr};
|
||||
HANDLE reporter_thread{nullptr};
|
||||
DWORD reporter_thread_id{0};
|
||||
volatile LONG crash_started{0};
|
||||
EXCEPTION_POINTERS *crash_exception{nullptr};
|
||||
DWORD crash_thread_id{0};
|
||||
|
||||
struct text_buffer
|
||||
{
|
||||
char *data;
|
||||
std::size_t capacity;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
void append(text_buffer &Buffer, char const *Format, ...)
|
||||
{
|
||||
if (Buffer.length + 1 >= Buffer.capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
va_list arguments;
|
||||
va_start(arguments, Format);
|
||||
auto const written{std::vsnprintf(Buffer.data + Buffer.length, Buffer.capacity - Buffer.length, Format, arguments)};
|
||||
va_end(arguments);
|
||||
if (written > 0)
|
||||
{
|
||||
Buffer.length = std::min(Buffer.length + static_cast<std::size_t>(written), Buffer.capacity - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void append_wide(text_buffer &Buffer, wchar_t const *Text)
|
||||
{
|
||||
if (Buffer.length + 1 >= Buffer.capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const written{::WideCharToMultiByte(CP_UTF8, 0, Text, -1, Buffer.data + Buffer.length, static_cast<int>(Buffer.capacity - Buffer.length), nullptr, nullptr)};
|
||||
if (written > 0)
|
||||
{
|
||||
// the count includes the terminating zero
|
||||
Buffer.length += static_cast<std::size_t>(written) - 1;
|
||||
}
|
||||
Buffer.data[Buffer.length] = '\0';
|
||||
}
|
||||
|
||||
char const *exception_name(DWORD const Code)
|
||||
{
|
||||
switch (Code)
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return "access violation";
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return "stack overflow";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return "illegal instruction";
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return "privileged instruction";
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return "in page error";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return "integer division by zero";
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return "integer overflow";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return "floating point exception";
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return "datatype misalignment";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return "array bounds exceeded";
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return "breakpoint";
|
||||
case 0xC0000374:
|
||||
return "heap corruption";
|
||||
case 0xC0000409:
|
||||
return "stack buffer overrun";
|
||||
case 0xE06D7363:
|
||||
return "unhandled C++ exception";
|
||||
case code_abort:
|
||||
return "abort() called";
|
||||
case code_purecall:
|
||||
return "pure virtual function call";
|
||||
case code_invalid_parameter:
|
||||
return "invalid parameter passed to a C runtime function";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER) && defined(_WIN64)
|
||||
// the type and the message of an unhandled C++ exception, read from the structures msvc throws it with (image relative on x64).
|
||||
// guarded, the exception may come from anywhere
|
||||
void describe_cpp_exception(EXCEPTION_RECORD const &Record, text_buffer &Buffer)
|
||||
{
|
||||
if ((Record.ExceptionCode != 0xE06D7363) || (Record.NumberParameters < 4) || (Record.ExceptionInformation[0] != 0x19930520))
|
||||
{
|
||||
return;
|
||||
}
|
||||
__try
|
||||
{
|
||||
auto const object{Record.ExceptionInformation[1]};
|
||||
auto const throwinfo{Record.ExceptionInformation[2]};
|
||||
auto const imagebase{Record.ExceptionInformation[3]};
|
||||
if ((throwinfo == 0) || (imagebase == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ThrowInfo: attributes, unwind, forward compat, catchable type array
|
||||
auto const *types{reinterpret_cast<int const *>(imagebase + reinterpret_cast<int const *>(throwinfo)[3])};
|
||||
auto const count{types[0]};
|
||||
for (int index = 0; (index < count) && (index < 32); ++index)
|
||||
{
|
||||
// CatchableType: properties, type descriptor, displacement (mdisp, pdisp, vdisp), size, copy function
|
||||
auto const *type{reinterpret_cast<int const *>(imagebase + types[1 + index])};
|
||||
// TypeDescriptor: vftable, spare, decorated name
|
||||
auto const *name{reinterpret_cast<char const *>(imagebase + type[1]) + 2 * sizeof(void *)};
|
||||
if (index == 0)
|
||||
{
|
||||
append(Buffer, " C++ exception type: %.200s\n", name);
|
||||
}
|
||||
if ((std::strcmp(name, ".?AVexception@std@@") == 0) && (type[3] == -1))
|
||||
{
|
||||
auto const *exception{reinterpret_cast<std::exception const *>(object + type[2])};
|
||||
append(Buffer, " C++ exception message: %.1000s\n", exception->what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
append(Buffer, " (the C++ exception could not be read)\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// the module and the offset in it. through the memory map rather than the module list of the loader, whose lock the crashed thread may hold
|
||||
void describe_address(void const *Address, text_buffer &Buffer)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION region{};
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
if ((::VirtualQuery(Address, ®ion, sizeof(region)) == 0) || (region.Type != MEM_IMAGE) || (::GetMappedFileNameW(::GetCurrentProcess(), region.AllocationBase, path, MAX_PATH * 2) == 0))
|
||||
{
|
||||
append(Buffer, " at 0x%p, outside of the modules\n", Address);
|
||||
return;
|
||||
}
|
||||
path[MAX_PATH * 2 - 1] = L'\0';
|
||||
auto const *name{std::wcsrchr(path, L'\\')};
|
||||
append(Buffer, " at 0x%p, ", Address);
|
||||
append_wide(Buffer, name != nullptr ? name + 1 : path);
|
||||
append(Buffer, "+0x%llx\n", static_cast<unsigned long long>(reinterpret_cast<ULONG_PTR>(Address) - reinterpret_cast<ULONG_PTR>(region.AllocationBase)));
|
||||
}
|
||||
|
||||
void describe_crash(crashdump::channel_data &Data, SYSTEMTIME const &Now)
|
||||
{
|
||||
text_buffer buffer{Data.comment, crashdump::comment_capacity, 0};
|
||||
Data.comment[0] = '\0';
|
||||
|
||||
append(buffer, "MaSzyna crash report\n\n");
|
||||
append(buffer, "version: %s\n", Global.asVersion.c_str());
|
||||
append(buffer, "build: %s, committed at %s\n", GIT_HASH, SRC_DATE);
|
||||
append(buffer, "executable: %s\n", executable_path);
|
||||
append(buffer, "started: %04u-%02u-%02u %02u:%02u:%02u\n", start_time.wYear, start_time.wMonth, start_time.wDay, start_time.wHour, start_time.wMinute, start_time.wSecond);
|
||||
append(buffer, "crashed: %04u-%02u-%02u %02u:%02u:%02u\n", Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond);
|
||||
append(buffer, "processor: %s\n", processor_name);
|
||||
append(buffer, "system: %s\n\n", system_version);
|
||||
|
||||
if ((crash_exception != nullptr) && (crash_exception->ExceptionRecord != nullptr))
|
||||
{
|
||||
auto const &record{*crash_exception->ExceptionRecord};
|
||||
append(buffer, "exception: 0x%08lX, %s\n", static_cast<unsigned long>(record.ExceptionCode), exception_name(record.ExceptionCode));
|
||||
describe_address(record.ExceptionAddress, buffer);
|
||||
if (((record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) || (record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR)) && (record.NumberParameters >= 2))
|
||||
{
|
||||
auto const operation{record.ExceptionInformation[0]};
|
||||
append(buffer, " %s of address 0x%p\n", operation == 0 ? "read" : (operation == 8 ? "execution" : "write"), reinterpret_cast<void *>(record.ExceptionInformation[1]));
|
||||
}
|
||||
#if defined(_MSC_VER) && defined(_WIN64)
|
||||
describe_cpp_exception(record, buffer);
|
||||
#endif
|
||||
}
|
||||
append(buffer, "thread: %lu%s\n\n", static_cast<unsigned long>(crash_thread_id), crash_thread_id == main_thread_id ? " (main)" : "");
|
||||
|
||||
PROCESS_MEMORY_COUNTERS_EX memory{};
|
||||
memory.cb = sizeof(memory);
|
||||
if (::GetProcessMemoryInfo(::GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS *>(&memory), sizeof(memory)) != FALSE)
|
||||
{
|
||||
append(buffer, "process memory: %llu MB private, %llu MB working set, %llu MB peak working set\n", static_cast<unsigned long long>(memory.PrivateUsage >> 20),
|
||||
static_cast<unsigned long long>(memory.WorkingSetSize >> 20), static_cast<unsigned long long>(memory.PeakWorkingSetSize >> 20));
|
||||
}
|
||||
MEMORYSTATUSEX system{};
|
||||
system.dwLength = sizeof(system);
|
||||
if (::GlobalMemoryStatusEx(&system) != FALSE)
|
||||
{
|
||||
append(buffer, "system memory: %llu MB of %llu MB free, %llu MB of %llu MB of the page file free\n", static_cast<unsigned long long>(system.ullAvailPhys >> 20),
|
||||
static_cast<unsigned long long>(system.ullTotalPhys >> 20), static_cast<unsigned long long>(system.ullAvailPageFile >> 20),
|
||||
static_cast<unsigned long long>(system.ullTotalPageFile >> 20));
|
||||
}
|
||||
append(buffer, "dump: %s\n\n", Data.full_memory != 0 ? "full memory" : "small");
|
||||
|
||||
append(buffer, "info:\n");
|
||||
auto const infocount{std::min(info_used.load(), std::size(info_entries))};
|
||||
for (std::size_t index = 0; index < infocount; ++index)
|
||||
{
|
||||
append(buffer, " %.47s: %.463s\n", info_entries[index].name, info_entries[index].value);
|
||||
}
|
||||
|
||||
append(buffer, "\nrecent log:\n");
|
||||
auto const written{log_written};
|
||||
auto const capacity{std::size(log_ring)};
|
||||
if (written <= capacity)
|
||||
{
|
||||
append(buffer, "%.*s", static_cast<int>(written), log_ring);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the ring went around: from the oldest whole line on
|
||||
auto const start{written % capacity};
|
||||
auto skip{start};
|
||||
while ((skip < capacity) && (log_ring[skip] != '\n'))
|
||||
{
|
||||
++skip;
|
||||
}
|
||||
if (skip < capacity)
|
||||
{
|
||||
append(buffer, "%.*s", static_cast<int>(capacity - skip - 1), log_ring + skip + 1);
|
||||
}
|
||||
append(buffer, "%.*s", static_cast<int>(start), log_ring);
|
||||
}
|
||||
|
||||
Data.comment_length = static_cast<std::uint32_t>(buffer.length);
|
||||
}
|
||||
|
||||
void write_file(wchar_t const *Path, char const *Text, std::size_t const Length)
|
||||
{
|
||||
auto const file{::CreateFileW(Path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DWORD written{0};
|
||||
::WriteFile(file, Text, static_cast<DWORD>(Length), &written, nullptr);
|
||||
::CloseHandle(file);
|
||||
}
|
||||
|
||||
void write_report()
|
||||
{
|
||||
auto &data{*channel};
|
||||
SYSTEMTIME now;
|
||||
::GetLocalTime(&now);
|
||||
|
||||
// crashdumps\crash_<date>_<time>_<executable> with .dmp, _info.txt, _log.txt and _errors.txt
|
||||
wchar_t name[MAX_PATH];
|
||||
std::swprintf(name, MAX_PATH, L"crash_%04u-%02u-%02u_%02u-%02u-%02u_%ls", now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, executable_stem);
|
||||
wchar_t base[MAX_PATH * 3];
|
||||
std::swprintf(base, MAX_PATH * 3, L"%ls\\%ls", report_directory, name);
|
||||
::CreateDirectoryW(report_directory, nullptr);
|
||||
|
||||
data.process_id = ::GetCurrentProcessId();
|
||||
data.thread_id = crash_thread_id;
|
||||
data.exception_pointers = reinterpret_cast<ULONG_PTR>(crash_exception);
|
||||
data.main_module_base = reinterpret_cast<ULONG_PTR>(::GetModuleHandleW(nullptr));
|
||||
data.full_memory = Global.crashdump_full ? 1 : 0;
|
||||
data.state = crashdump::dump_pending;
|
||||
data.error = 0;
|
||||
std::swprintf(data.dump_path, crashdump::path_capacity, L"%ls.dmp", base);
|
||||
describe_crash(data, now);
|
||||
|
||||
if (starter_request != nullptr)
|
||||
{
|
||||
::SetEvent(starter_request);
|
||||
auto const timeout{static_cast<DWORD>((data.full_memory != 0 ? 15 : 3) * 60 * 1000)};
|
||||
::WaitForSingleObject(starter_done, timeout);
|
||||
}
|
||||
bool const bystarter{data.state == crashdump::dump_written};
|
||||
if (data.state != crashdump::dump_written)
|
||||
{
|
||||
// without the starter, or it failed: from inside, which works for most of the crashes
|
||||
crashdump_write_minidump(::GetCurrentProcess(), data, false);
|
||||
}
|
||||
|
||||
wchar_t path[MAX_PATH * 3];
|
||||
{
|
||||
char status[160];
|
||||
auto const statuslength{data.state == crashdump::dump_written ? std::snprintf(status, sizeof(status), "\ndump written by the %s\n", bystarter ? "starter" : "simulator")
|
||||
: std::snprintf(status, sizeof(status), "\ndump failed, error %lu\n", static_cast<unsigned long>(data.error))};
|
||||
std::swprintf(path, MAX_PATH * 3, L"%ls_info.txt", base);
|
||||
auto const file{::CreateFileW(path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if (file != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DWORD written{0};
|
||||
::WriteFile(file, data.comment, data.comment_length, &written, nullptr);
|
||||
if (statuslength > 0)
|
||||
{
|
||||
::WriteFile(file, status, static_cast<DWORD>(std::min<int>(statuslength, sizeof(status) - 1)), &written, nullptr);
|
||||
}
|
||||
::CloseHandle(file);
|
||||
}
|
||||
}
|
||||
// the log is written again from the start by the next run, the copies keep it with the report
|
||||
if (log_paths[0][0] != L'\0')
|
||||
{
|
||||
std::swprintf(path, MAX_PATH * 3, L"%ls_log.txt", base);
|
||||
::CopyFileW(log_paths[0], path, FALSE);
|
||||
}
|
||||
if (log_paths[1][0] != L'\0')
|
||||
{
|
||||
std::swprintf(path, MAX_PATH * 3, L"%ls_errors.txt", base);
|
||||
::CopyFileW(log_paths[1], path, FALSE);
|
||||
}
|
||||
::SetEvent(report_written);
|
||||
|
||||
wchar_t message[MAX_PATH * 4 + 512];
|
||||
if (data.state == crashdump::dump_written)
|
||||
{
|
||||
std::swprintf(message, std::size(message),
|
||||
L"The simulator has crashed.\n\nA crash report was saved in:\n%ls\n\nPlease send the files %ls.dmp, %ls_info.txt and %ls_log.txt to the developers.",
|
||||
report_directory, name, name, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::swprintf(message, std::size(message), L"The simulator has crashed.\n\nIts memory dump could not be written (error %lu), the description of the crash is in:\n%ls_info.txt",
|
||||
static_cast<unsigned long>(data.error), base);
|
||||
}
|
||||
::MessageBoxW(nullptr, message, L"MaSzyna", MB_ICONERROR | MB_OK | MB_TOPMOST | MB_SETFOREGROUND);
|
||||
::SetEvent(report_done);
|
||||
}
|
||||
|
||||
DWORD WINAPI reporter_main(LPVOID)
|
||||
{
|
||||
::WaitForSingleObject(report_request, INFINITE);
|
||||
write_report();
|
||||
return 0;
|
||||
}
|
||||
|
||||
LONG WINAPI crash_filter(EXCEPTION_POINTERS *Exception)
|
||||
{
|
||||
if (::GetCurrentThreadId() == reporter_thread_id)
|
||||
{
|
||||
// the report itself crashed
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
if (::InterlockedCompareExchange(&crash_started, 1, 0) != 0)
|
||||
{
|
||||
// another thread crashed first, its report ends the process
|
||||
::WaitForSingleObject(report_done, INFINITE);
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
crash_exception = Exception;
|
||||
crash_thread_id = ::GetCurrentThreadId();
|
||||
if (reporter_thread != nullptr)
|
||||
{
|
||||
::SetEvent(report_request);
|
||||
auto const timeout{static_cast<DWORD>((Global.crashdump_full ? 16 : 4) * 60 * 1000)};
|
||||
if (::WaitForSingleObject(report_written, timeout) == WAIT_OBJECT_0)
|
||||
{
|
||||
// the message the user reads
|
||||
::WaitForSingleObject(report_done, INFINITE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
write_report();
|
||||
}
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
|
||||
// the fatal errors of the C runtime, reported as crashes with the stack where they happened
|
||||
void report_and_terminate(DWORD const Code, void *Address)
|
||||
{
|
||||
CONTEXT context{};
|
||||
::RtlCaptureContext(&context);
|
||||
EXCEPTION_RECORD record{};
|
||||
record.ExceptionCode = Code;
|
||||
record.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
|
||||
record.ExceptionAddress = Address;
|
||||
EXCEPTION_POINTERS pointers{&record, &context};
|
||||
crash_filter(&pointers);
|
||||
::TerminateProcess(::GetCurrentProcess(), Code);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define EU07_RETURN_ADDRESS() _ReturnAddress()
|
||||
#else
|
||||
#define EU07_RETURN_ADDRESS() __builtin_return_address(0)
|
||||
#endif
|
||||
|
||||
void on_abort(int)
|
||||
{
|
||||
report_and_terminate(code_abort, EU07_RETURN_ADDRESS());
|
||||
}
|
||||
|
||||
void __cdecl on_purecall()
|
||||
{
|
||||
report_and_terminate(code_purecall, EU07_RETURN_ADDRESS());
|
||||
}
|
||||
|
||||
void __cdecl on_invalid_parameter(wchar_t const *, wchar_t const *, wchar_t const *, unsigned int, uintptr_t)
|
||||
{
|
||||
report_and_terminate(code_invalid_parameter, EU07_RETURN_ADDRESS());
|
||||
}
|
||||
|
||||
// the starter passes the handles of the shared block and its events in the environment
|
||||
void connect_to_starter()
|
||||
{
|
||||
wchar_t value[128];
|
||||
auto const length{::GetEnvironmentVariableW(crashdump::channel_variable, value, static_cast<DWORD>(std::size(value)))};
|
||||
if ((length == 0) || (length >= std::size(value)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the processes the simulator starts do not get it
|
||||
::SetEnvironmentVariableW(crashdump::channel_variable, nullptr);
|
||||
|
||||
unsigned long long mapping{0}, request{0}, done{0};
|
||||
if (std::swscanf(value, L"%llx:%llx:%llx", &mapping, &request, &done) != 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto *view{static_cast<crashdump::channel_data *>(::MapViewOfFile(reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(mapping)), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(crashdump::channel_data)))};
|
||||
if (view == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ((view->magic != crashdump::channel_magic) || (view->version != crashdump::channel_version))
|
||||
{
|
||||
::UnmapViewOfFile(view);
|
||||
return;
|
||||
}
|
||||
channel = view;
|
||||
starter_request = reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(request));
|
||||
starter_done = reinterpret_cast<HANDLE>(static_cast<ULONG_PTR>(done));
|
||||
}
|
||||
|
||||
void read_system_version()
|
||||
{
|
||||
using rtlgetversion = LONG(WINAPI *)(PRTL_OSVERSIONINFOW);
|
||||
auto *const ntdll{::GetModuleHandleW(L"ntdll.dll")};
|
||||
auto const function{ntdll != nullptr ? reinterpret_cast<rtlgetversion>(reinterpret_cast<void *>(::GetProcAddress(ntdll, "RtlGetVersion"))) : nullptr};
|
||||
RTL_OSVERSIONINFOW version{};
|
||||
version.dwOSVersionInfoSize = sizeof(version);
|
||||
if ((function != nullptr) && (function(&version) == 0))
|
||||
{
|
||||
std::snprintf(system_version, sizeof(system_version), "Windows %lu.%lu build %lu", version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// keeps the newest reports, each the files starting with the same "crash_<date>_<time>"
|
||||
void remove_old_reports()
|
||||
{
|
||||
std::error_code error;
|
||||
std::filesystem::path const directory{report_directory};
|
||||
if (false == std::filesystem::is_directory(directory, error))
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::set<std::wstring, std::greater<>> stamps;
|
||||
std::vector<std::filesystem::path> files;
|
||||
for (auto const &entry : std::filesystem::directory_iterator(directory, error))
|
||||
{
|
||||
auto const filename{entry.path().filename().wstring()};
|
||||
if ((filename.size() >= report_stamp_length) && (filename.compare(0, 6, L"crash_") == 0) && entry.is_regular_file(error))
|
||||
{
|
||||
stamps.insert(filename.substr(0, report_stamp_length));
|
||||
files.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
if (stamps.size() <= reports_kept)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const oldestkept{*std::next(stamps.begin(), reports_kept - 1)};
|
||||
for (auto const &file : files)
|
||||
{
|
||||
if (file.filename().wstring().substr(0, report_stamp_length) < oldestkept)
|
||||
{
|
||||
std::filesystem::remove(file, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void crashdump_install()
|
||||
{
|
||||
main_thread_id = ::GetCurrentThreadId();
|
||||
::GetLocalTime(&start_time);
|
||||
|
||||
wchar_t path[MAX_PATH * 2];
|
||||
auto length{::GetCurrentDirectoryW(MAX_PATH, path)};
|
||||
if ((length > 0) && (length < MAX_PATH))
|
||||
{
|
||||
std::swprintf(report_directory, std::size(report_directory), L"%ls\\crashdumps", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::swprintf(report_directory, std::size(report_directory), L"crashdumps");
|
||||
}
|
||||
length = ::GetModuleFileNameW(nullptr, path, MAX_PATH * 2);
|
||||
path[std::min<DWORD>(length, MAX_PATH * 2 - 1)] = L'\0';
|
||||
::WideCharToMultiByte(CP_UTF8, 0, path, -1, executable_path, sizeof(executable_path), nullptr, nullptr);
|
||||
auto const *filename{std::wcsrchr(path, L'\\')};
|
||||
std::swprintf(executable_stem, std::size(executable_stem), L"%ls", filename != nullptr ? filename + 1 : path);
|
||||
if (auto *extension{std::wcsrchr(executable_stem, L'.')})
|
||||
{
|
||||
*extension = L'\0';
|
||||
}
|
||||
cpu_features::brand(processor_name);
|
||||
read_system_version();
|
||||
connect_to_starter();
|
||||
|
||||
report_request = ::CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
report_written = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
report_done = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if ((report_request != nullptr) && (report_written != nullptr) && (report_done != nullptr))
|
||||
{
|
||||
reporter_thread = ::CreateThread(nullptr, 256 * 1024, reporter_main, nullptr, STACK_SIZE_PARAM_IS_A_RESERVATION, &reporter_thread_id);
|
||||
}
|
||||
|
||||
// a debugger gets the unhandled exceptions before the filter, and the fatal errors of the C runtime keep stopping in it as they did
|
||||
::SetUnhandledExceptionFilter(crash_filter);
|
||||
if (::IsDebuggerPresent() == FALSE)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
// abort() reports to the system directly by default, past the filter
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||
#endif
|
||||
std::signal(SIGABRT, on_abort);
|
||||
_set_purecall_handler(on_purecall);
|
||||
_set_invalid_parameter_handler(on_invalid_parameter);
|
||||
}
|
||||
|
||||
remove_old_reports();
|
||||
}
|
||||
|
||||
void crashdump_set_info(char const *Name, std::string const &Value)
|
||||
{
|
||||
if (Name == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock(info_mutex);
|
||||
auto const used{info_used.load()};
|
||||
std::size_t index{0};
|
||||
while ((index < used) && (std::strncmp(info_entries[index].name, Name, sizeof(info_entries[index].name) - 1) != 0))
|
||||
{
|
||||
++index;
|
||||
}
|
||||
if (index >= std::size(info_entries))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &entry{info_entries[index]};
|
||||
std::snprintf(entry.value, sizeof(entry.value), "%s", Value.c_str());
|
||||
if (index == used)
|
||||
{
|
||||
std::snprintf(entry.name, sizeof(entry.name), "%s", Name);
|
||||
info_used.store(used + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void crashdump_note_log(char const *Text, std::size_t const Length)
|
||||
{
|
||||
auto const capacity{std::size(log_ring)};
|
||||
for (std::size_t index = 0; index <= Length; ++index)
|
||||
{
|
||||
log_ring[log_written % capacity] = index < Length ? Text[index] : '\n';
|
||||
++log_written;
|
||||
}
|
||||
}
|
||||
|
||||
void crashdump_set_log_file(crashdump_log_file const File, std::string const &Path)
|
||||
{
|
||||
wchar_t relative[MAX_PATH * 2];
|
||||
if (::MultiByteToWideChar(CP_ACP, 0, Path.c_str(), -1, relative, MAX_PATH * 2) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &target{log_paths[File == crashdump_log_file::log ? 0 : 1]};
|
||||
auto const length{::GetFullPathNameW(relative, MAX_PATH * 2, target, nullptr)};
|
||||
if ((length == 0) || (length >= MAX_PATH * 2))
|
||||
{
|
||||
target[0] = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void crashdump_install() {}
|
||||
|
||||
void crashdump_set_info(char const *, std::string const &) {}
|
||||
|
||||
void crashdump_note_log(char const *, std::size_t) {}
|
||||
|
||||
void crashdump_set_log_file(crashdump_log_file, std::string const &) {}
|
||||
|
||||
#endif
|
||||
22
utilities/crashdump.h
Normal file
22
utilities/crashdump.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// the crash report of the simulator (WITHDUMPGEN, windows): a small minidump, a text description of the crash with the recent log lines
|
||||
// and copies of the log files, written to crashdumps/. when the simulator runs under eu07_starter, the starter writes the minidump
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
enum class crashdump_log_file
|
||||
{
|
||||
log,
|
||||
errors
|
||||
};
|
||||
|
||||
// sets up the reporting, at the start of main
|
||||
void crashdump_install();
|
||||
// a named value for the description of the crash: the renderer, the scenario, the graphics card...
|
||||
void crashdump_set_info(char const *Name, std::string const &Value);
|
||||
// a line of the log, the last ones go into the description of the crash
|
||||
void crashdump_note_log(char const *Text, std::size_t Length);
|
||||
// a log file, copied next to the dump when the simulator crashes
|
||||
void crashdump_set_log_file(crashdump_log_file File, std::string const &Path);
|
||||
53
utilities/crashdump_channel.h
Normal file
53
utilities/crashdump_channel.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
// the crash report of the simulator as eu07_starter receives it: the starter shares this block with the simulator it starts
|
||||
// and writes the minidump of the crashed simulator from outside of it, see utilities/crashdump.cpp and starter/eu07_starter.cpp
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace crashdump
|
||||
{
|
||||
|
||||
constexpr std::uint32_t channel_magic{0x37305545}; // "EU07"
|
||||
constexpr std::uint32_t channel_version{1};
|
||||
// the environment variable with the handles of the block and its two events the starter passes to the simulator, "mapping:request:done" in hex
|
||||
constexpr wchar_t const *channel_variable{L"EU07_CRASH_CHANNEL"};
|
||||
constexpr std::size_t path_capacity{1024};
|
||||
constexpr std::size_t comment_capacity{64 * 1024};
|
||||
|
||||
enum dump_state : std::uint32_t
|
||||
{
|
||||
dump_pending = 0,
|
||||
dump_written = 1,
|
||||
dump_failed = 2,
|
||||
};
|
||||
|
||||
struct channel_data
|
||||
{
|
||||
std::uint32_t magic;
|
||||
std::uint32_t version;
|
||||
std::uint32_t process_id;
|
||||
std::uint32_t thread_id;
|
||||
// the EXCEPTION_POINTERS of the crash, an address in the crashed process
|
||||
std::uint64_t exception_pointers;
|
||||
// the module whose data sections a small dump keeps, the simulator itself
|
||||
std::uint64_t main_module_base;
|
||||
std::uint32_t full_memory;
|
||||
std::uint32_t comment_length;
|
||||
std::uint32_t state;
|
||||
std::uint32_t error;
|
||||
wchar_t dump_path[path_capacity];
|
||||
// the description of the crash, stored in the dump as its comment stream
|
||||
char comment[comment_capacity];
|
||||
};
|
||||
|
||||
} // namespace crashdump
|
||||
|
||||
// writes the minidump of the process described by the block to its dump_path and sets its state and error.
|
||||
// ClientPointers: the exception pointers are an address in the dumped process, not in the calling one
|
||||
bool crashdump_write_minidump(void *Process, crashdump::channel_data &Data, bool ClientPointers);
|
||||
|
||||
#endif
|
||||
98
utilities/crashdump_writer.cpp
Normal file
98
utilities/crashdump_writer.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
// the minidump writer shared by the simulator and eu07_starter, see utilities/crashdump_channel.h
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
#include "utilities/crashdump_channel.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct dump_context
|
||||
{
|
||||
ULONG64 main_module_base;
|
||||
};
|
||||
|
||||
BOOL CALLBACK dump_callback(PVOID Parameter, PMINIDUMP_CALLBACK_INPUT const Input, PMINIDUMP_CALLBACK_OUTPUT Output)
|
||||
{
|
||||
if ((Input == nullptr) || (Output == nullptr))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
switch (Input->CallbackType)
|
||||
{
|
||||
case IncludeModuleCallback:
|
||||
case IncludeThreadCallback:
|
||||
case ThreadCallback:
|
||||
case ThreadExCallback:
|
||||
return TRUE;
|
||||
case ModuleCallback:
|
||||
{
|
||||
// the data sections of the other modules, the graphics driver above all, take tens of megabytes and tell nothing about a crash of the simulator.
|
||||
// its own globals stay in the dump
|
||||
auto const *context{static_cast<dump_context const *>(Parameter)};
|
||||
if (Input->Module.BaseOfImage != context->main_module_base)
|
||||
{
|
||||
Output->ModuleWriteFlags &= ~ModuleWriteDataSeg;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool crashdump_write_minidump(void *Process, crashdump::channel_data &Data, bool const ClientPointers)
|
||||
{
|
||||
Data.dump_path[crashdump::path_capacity - 1] = L'\0';
|
||||
auto const file{::CreateFileW(Data.dump_path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)};
|
||||
if (file == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Data.error = ::GetLastError();
|
||||
Data.state = crashdump::dump_failed;
|
||||
return false;
|
||||
}
|
||||
|
||||
// the full dump, on request of crashdump.full in eu07.ini, has the whole memory of the process: hundreds of megabytes or more.
|
||||
// the small one the stacks of all threads with the memory their pointers lead to, the globals of the simulator and the map of the memory
|
||||
auto const type{static_cast<MINIDUMP_TYPE>(
|
||||
(Data.full_memory != 0 ? MiniDumpWithFullMemory : (MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory)) | MiniDumpWithHandleData | MiniDumpWithFullMemoryInfo |
|
||||
MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData | MiniDumpIgnoreInaccessibleMemory)};
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception{};
|
||||
exception.ThreadId = Data.thread_id;
|
||||
exception.ExceptionPointers = reinterpret_cast<PEXCEPTION_POINTERS>(static_cast<ULONG_PTR>(Data.exception_pointers));
|
||||
exception.ClientPointers = ClientPointers ? TRUE : FALSE;
|
||||
|
||||
auto const commentlength{Data.comment_length < crashdump::comment_capacity ? Data.comment_length : static_cast<std::uint32_t>(crashdump::comment_capacity)};
|
||||
MINIDUMP_USER_STREAM comment{};
|
||||
comment.Type = CommentStreamA;
|
||||
comment.BufferSize = commentlength;
|
||||
comment.Buffer = Data.comment;
|
||||
MINIDUMP_USER_STREAM_INFORMATION streams{};
|
||||
streams.UserStreamCount = commentlength > 0 ? 1 : 0;
|
||||
streams.UserStreamArray = &comment;
|
||||
|
||||
dump_context context{Data.main_module_base};
|
||||
MINIDUMP_CALLBACK_INFORMATION callback{};
|
||||
callback.CallbackRoutine = dump_callback;
|
||||
callback.CallbackParam = &context;
|
||||
|
||||
auto const result{::MiniDumpWriteDump(Process, Data.process_id, file, type, Data.exception_pointers != 0 ? &exception : nullptr, &streams, Data.full_memory != 0 ? nullptr : &callback)};
|
||||
Data.error = result ? 0 : ::GetLastError();
|
||||
::CloseHandle(file);
|
||||
Data.state = result ? crashdump::dump_written : crashdump::dump_failed;
|
||||
return result != FALSE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include "version_info.h"
|
||||
#include "utilities/crashdump.h"
|
||||
|
||||
#if defined __has_attribute
|
||||
# if __has_attribute (init_priority)
|
||||
@@ -166,6 +167,8 @@ const std::string& crashreport_get_provider()
|
||||
|
||||
void crashreport_add_info(const char *name, const std::string &value)
|
||||
{
|
||||
crashdump_set_info(name, value);
|
||||
|
||||
char *copy = new char[value.size() + 1];
|
||||
strcpy(copy, value.c_str());
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "utilities/crashdump.h"
|
||||
|
||||
#ifdef WITH_CRASHPAD
|
||||
void crashreport_add_info(const char *name, const std::string &value);
|
||||
const std::string& crashreport_get_provider();
|
||||
@@ -6,7 +8,8 @@ bool crashreport_is_pending();
|
||||
void crashreport_upload_reject();
|
||||
void crashreport_upload_accept();
|
||||
#else
|
||||
#define crashreport_add_info(a,b)
|
||||
// the values go into the description of the crash, see utilities/crashdump.h
|
||||
#define crashreport_add_info(a,b) crashdump_set_info(a,b)
|
||||
#define crashreport_get_provider() (std::string(""))
|
||||
#define crashreport_set_autoupload()
|
||||
#define crashreport_is_pending() (false)
|
||||
|
||||
Reference in New Issue
Block a user