Changeset - r27813:6b3a5970aa1b
[Not reviewed]
master
0 13 0
Patric Stout - 16 months ago 2023-08-20 15:16:08
truebrain@openttd.org
Add: use breakpad to create crash.dmp on MacOS / Linux too (#11202)

Normally only the Windows platform could create a crash.dmp, making
analysing crash-reports from MacOS / Linux rather tricky.
13 files changed with 107 insertions and 59 deletions:
0 comments (0 inline, 0 general)
.github/workflows/ci-build.yml
Show inline comments
 
@@ -118,12 +118,16 @@ jobs:
 
          libicu-dev \
 
          liblzma-dev \
 
          liblzo2-dev \
 
          ${{ matrix.libraries }} \
 
          zlib1g-dev \
 
          # EOF
 

	
 
        sudo vcpkg install \
 
          breakpad \
 
          # EOF
 
        echo "::endgroup::"
 
      env:
 
        DEBIAN_FRONTEND: noninteractive
 

	
 
    - name: Get OpenGFX
 
      run: |
 
@@ -146,13 +150,13 @@ jobs:
 
    - name: Build
 
      run: |
 
        mkdir build
 
        cd build
 

	
 
        echo "::group::CMake"
 
        cmake .. ${{ matrix.extra-cmake-parameters }}
 
        cmake .. -DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake ${{ matrix.extra-cmake-parameters }}
 
        echo "::endgroup::"
 

	
 
        echo "::group::Build"
 
        echo "Running on $(nproc) cores"
 
        cmake --build . -j $(nproc)
 
        echo "::endgroup::"
 
@@ -202,12 +206,13 @@ jobs:
 
        restore-keys: |
 
          ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}
 

	
 
    - name: Prepare vcpkg
 
      run: |
 
        vcpkg install --triplet=${{ matrix.arch }}-osx \
 
          breakpad \
 
          curl \
 
          liblzma \
 
          libpng \
 
          lzo \
 
          nlohmann-json \
 
          zlib \
 
@@ -287,12 +292,13 @@ jobs:
 
          ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}
 

	
 
    - name: Prepare vcpkg
 
      shell: bash
 
      run: |
 
        vcpkg install --triplet=${{ matrix.arch }}-windows-static \
 
          breakpad \
 
          liblzma \
 
          libpng \
 
          lzo \
 
          nlohmann-json \
 
          zlib \
 
          # EOF
.github/workflows/release-linux.yml
Show inline comments
 
@@ -105,12 +105,13 @@ jobs:
 

	
 
          # Make Python3 available for other packages.
 
          ./vcpkg install python3
 
          ln -sf $(pwd)/installed/x64-linux/tools/python3/python3.[0-9][0-9] /usr/bin/python3
 

	
 
          ./vcpkg install \
 
            breakpad \
 
            curl[http2] \
 
            fontconfig \
 
            freetype \
 
            harfbuzz \
 
            icu \
 
            liblzma \
.github/workflows/release-macos.yml
Show inline comments
 
@@ -50,12 +50,14 @@ jobs:
 
          ${{ steps.key.outputs.image }}-vcpkg-release
 
          ${{ steps.key.outputs.image }}-vcpkg-x64
 

	
 
    - name: Prepare vcpkg
 
      run: |
 
        vcpkg install \
 
          breakpad:x64-osx \
 
          breakpad:arm64-osx \
 
          curl:x64-osx \
 
          curl:arm64-osx \
 
          liblzma:x64-osx \
 
          liblzma:arm64-osx \
 
          libpng:x64-osx \
 
          libpng:arm64-osx \
.github/workflows/release-windows.yml
Show inline comments
 
@@ -62,12 +62,13 @@ jobs:
 
          ${{ steps.key.outputs.image }}-vcpkg-${{ matrix.arch }}
 

	
 
    - name: Prepare vcpkg
 
      shell: bash
 
      run: |
 
        vcpkg install --triplet=${{ matrix.arch }}-windows-static \
 
          breakpad \
 
          liblzma \
 
          libpng \
 
          lzo \
 
          nlohmann-json \
 
          zlib \
 
          # EOF
CMakeLists.txt
Show inline comments
 
@@ -131,12 +131,17 @@ if(WIN32 OR EMSCRIPTEN)
 
    # Emscripten uses Javascript for HTTP requests.
 
else()
 
    # All other targets use libcurl.
 
    find_package(CURL)
 
endif()
 

	
 
# Breakpad doesn't support emscripten.
 
if(NOT EMSCRIPTEN)
 
    find_package(unofficial-breakpad)
 
endif()
 

	
 
if(NOT OPTION_DEDICATED)
 
    if(NOT WIN32)
 
        find_package(Allegro)
 
        if(NOT APPLE)
 
            find_package(Freetype)
 
            find_package(SDL2)
 
@@ -307,12 +312,16 @@ link_package(LZO)
 
link_package(nlohmann_json ENCOURAGED)
 

	
 
if(NOT WIN32 AND NOT EMSCRIPTEN)
 
    link_package(CURL ENCOURAGED)
 
endif()
 

	
 
if(NOT EMSCRIPTEN)
 
    link_package(unofficial-breakpad TARGET unofficial::breakpad::libbreakpad_client ENCOURAGED)
 
endif()
 

	
 
if(NOT OPTION_DEDICATED)
 
    link_package(Fluidsynth)
 
    link_package(SDL)
 
    link_package(SDL2 TARGET SDL2::SDL2)
 
    link_package(Allegro)
 
    link_package(FREETYPE TARGET Freetype::Freetype)
COMPILING.md
Show inline comments
 
@@ -2,12 +2,13 @@
 

	
 
## Required/optional libraries
 

	
 
OpenTTD makes use of the following external libraries:
 

	
 
- (encouraged) nlohmann-json: JSON handling
 
- (encouraged) breakpad: creates minidumps on crash
 
- (encouraged) zlib: (de)compressing of old (0.3.0-1.0.5) savegames, content downloads,
 
   heightmaps
 
- (encouraged) liblzma: (de)compressing of savegames (1.1.0 and later)
 
- (encouraged) libpng: making screenshots and loading heightmaps
 
- (optional) liblzo2: (de)compressing of old (pre 0.3.0) savegames
 

	
 
@@ -47,23 +48,24 @@ Dependencies for OpenTTD on Windows are 
 
by following the `Quick Start` instructions of their
 
[README](https://github.com/Microsoft/vcpkg/blob/master/README.md).
 

	
 
After this, you can install the dependencies OpenTTD needs. We advise to use
 
the `static` versions, and OpenTTD currently needs the following dependencies:
 

	
 
- breakpad
 
- liblzma
 
- libpng
 
- lzo
 
- nlohmann-json
 
- zlib
 

	
 
To install both the x64 (64bit) and x86 (32bit) variants (though only one is necessary), you can use:
 

	
 
```ps
 
.\vcpkg install liblzma:x64-windows-static libpng:x64-windows-static lzo:x64-windows-static nlohmann-json:x64-windows-static zlib:x64-windows-static
 
.\vcpkg install liblzma:x86-windows-static libpng:x86-windows-static lzo:x86-windows-static nlohmann-json:x86-windows-static zlib:x86-windows-static
 
.\vcpkg install breakpad:x64-windows-static liblzma:x64-windows-static libpng:x64-windows-static lzo:x64-windows-static nlohmann-json:x64-windows-static zlib:x64-windows-static
 
.\vcpkg install breakpad:x86-windows-static liblzma:x86-windows-static libpng:x86-windows-static lzo:x86-windows-static nlohmann-json:x86-windows-static zlib:x86-windows-static
 
```
 

	
 
You can open the folder (as a CMake project). CMake will be detected, and you can compile from there.
 
If libraries are installed but not found, you need to set VCPKG_TARGET_TRIPLET in CMake parameters.
 
For Visual Studio 2017 you also need to set CMAKE_TOOLCHAIN_FILE.
 
(Typical values are shown in the MSVC project file command line example)
cmake/FindLZO.cmake
Show inline comments
 
@@ -52,13 +52,13 @@ find_library(LZO_LIBRARY
 
# with Windows.
 
#
 
# NOTE: this is based on the assumption that the debug file has the same
 
# name as the optimized file. This is not always the case, but so far
 
# experiences has shown that in those case vcpkg CMake files do the right
 
# thing.
 
if(VCPKG_TOOLCHAIN AND LZO_LIBRARY)
 
if(VCPKG_TOOLCHAIN AND LZO_LIBRARY AND LZO_LIBRARY MATCHES "${VCPKG_INSTALLED_DIR}")
 
    if(LZO_LIBRARY MATCHES "/debug/")
 
        set(LZO_LIBRARY_DEBUG ${LZO_LIBRARY})
 
        string(REPLACE "/debug/lib/" "/lib/" LZO_LIBRARY_RELEASE ${LZO_LIBRARY})
 
    else()
 
        set(LZO_LIBRARY_RELEASE ${LZO_LIBRARY})
 
        string(REPLACE "/lib/" "/debug/lib/" LZO_LIBRARY_DEBUG ${LZO_LIBRARY})
cmake/LinkPackage.cmake
Show inline comments
 
function(link_package NAME)
 
    cmake_parse_arguments(LP "ENCOURAGED" "TARGET" "" ${ARGN})
 

	
 
    if(${NAME}_FOUND)
 
        string(TOUPPER "${NAME}" UCNAME)
 
        # Some libraries have a dash, which is not allowed in a preprocessor macro.
 
        string(REPLACE "-" "_" UCNAME "${UCNAME}")
 

	
 
        add_definitions(-DWITH_${UCNAME})
 
        # Some libraries' cmake packages (looking at you, SDL2) leave trailing whitespace in the link commands,
 
        # which (later) cmake considers to be an error. Work around this with by stripping the incoming string.
 
        if(LP_TARGET AND TARGET ${LP_TARGET})
 
            string(STRIP "${LP_TARGET}" LP_TARGET)
 
            target_link_libraries(openttd_lib ${LP_TARGET})
src/crashlog.cpp
Show inline comments
 
@@ -366,15 +366,20 @@ bool CrashLog::WriteCrashLog()
 
	size_t written = fwrite(this->crashlog.data(), 1, len, file);
 

	
 
	FioFCloseFile(file);
 
	return len == written;
 
}
 

	
 
/**
 
 * Write the (crash) dump to a file.
 
 *
 
 * @note Sets \c crashdump_filename when there is a successful return.
 
 * @return 1 iff the crashdump was successfully created, -1 if it failed, 0 if not implemented.
 
 */
 
/* virtual */ int CrashLog::WriteCrashDump()
 
{
 
	/* Stub implementation; not all OSes support this. */
 
	return 0;
 
}
 

	
 
/**
 
 * Write the (crash) savegame to a file.
 
 * @note The filename will be written to \c savegame_filename.
 
@@ -449,19 +454,21 @@ bool CrashLog::MakeCrashLog()
 
		fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
 
	} else {
 
		fmt::print("Writing crash log failed. Please attach the output above to any bug reports.\n\n");
 
		ret = false;
 
	}
 

	
 
	/* Don't mention writing crash dumps because not all platforms support it. */
 
	fmt::print("Writing crash dump to disk...\n");
 
	int dret = this->WriteCrashDump();
 
	if (dret < 0) {
 
		fmt::print("Writing crash dump failed.\n\n");
 
		ret = false;
 
	} else if (dret > 0) {
 
		fmt::print("Crash dump written to {}. Please add this file to any bug reports.\n\n", this->crashdump_filename);
 
	} else {
 
		fmt::print("Skipped; missing dependency to create crash dump.\n");
 
	}
 

	
 
	fmt::print("Writing crash savegame...\n");
 
	bret = this->WriteSavegame();
 
	if (bret) {
 
		fmt::print("Crash savegame written to {}. Please add this file and the last (auto)save to any bug reports.\n\n", this->savegame_filename);
src/crashlog.h
Show inline comments
 
@@ -70,18 +70,12 @@ public:
 
	std::string savegame_filename;
 
	std::string screenshot_filename;
 

	
 
	void FillCrashLog(std::back_insert_iterator<std::string> &output_iterator) const;
 
	bool WriteCrashLog();
 

	
 
	/**
 
	 * Write the (crash) dump to a file.
 
	 * @note Sets \c crashdump_filename when there is a successful return.
 
	 * @return if less than 0, error. If 0 no dump is made, otherwise the dump
 
	 *         was successful (not all OSes support dumping files).
 
	 */
 
	virtual int WriteCrashDump();
 
	bool WriteSavegame();
 
	bool WriteScreenshot();
 

	
 
	void SendSurvey() const;
 

	
src/os/macosx/crashlog_osx.cpp
Show inline comments
 
@@ -6,23 +6,28 @@
 
 */
 

	
 
/** @file crashlog_osx.cpp OS X crash log handler */
 

	
 
#include "../../stdafx.h"
 
#include "../../crashlog.h"
 
#include "../../fileio_func.h"
 
#include "../../string_func.h"
 
#include "../../gamelog.h"
 
#include "../../saveload/saveload.h"
 
#include "../../video/video_driver.hpp"
 
#include "macos.h"
 

	
 
#include <signal.h>
 
#include <mach-o/arch.h>
 
#include <dlfcn.h>
 
#include <cxxabi.h>
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
#	include <client/mac/handler/exception_handler.h>
 
#endif
 

	
 
#include "../../safeguards.h"
 

	
 

	
 
/* Macro testing a stack address for valid alignment. */
 
#if defined(__i386__)
 
#define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 8)
 
@@ -136,12 +141,28 @@ class CrashLogOSX : public CrashLog {
 
			frame = next;
 
		}
 

	
 
		fmt::format_to(output_iterator, "\n");
 
	}
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
	static bool MinidumpCallback(const char* dump_dir, const char* minidump_id, void* context, bool succeeded)
 
	{
 
		CrashLogOSX *crashlog = reinterpret_cast<CrashLogOSX *>(context);
 

	
 
		crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
 
		std::rename(fmt::format("{}/{}.dmp", dump_dir, minidump_id).c_str(), crashlog->crashdump_filename.c_str());
 
		return succeeded;
 
	}
 

	
 
	int WriteCrashDump() override
 
	{
 
		return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this) ? 1 : -1;
 
	}
 
#endif
 

	
 
public:
 
	/**
 
	 * A crash log is always generated by signal.
 
	 * @param signum the signal that was caused by the crash.
 
	 */
 
	CrashLogOSX(int signum) : signum(signum) {}
 
@@ -152,14 +173,14 @@ public:
 
		static const char crash_title[] =
 
			"A serious fault condition occurred in the game. The game will shut down.";
 

	
 
		std::string message = fmt::format(
 
				 "Please send the generated crash information and the last (auto)save to the developers. "
 
				 "This will greatly help debugging. The correct place to do this is https://github.com/OpenTTD/OpenTTD/issues.\n\n"
 
				 "Generated file(s):\n{}\n{}\n{}",
 
				 this->crashlog_filename, this->savegame_filename, this->screenshot_filename);
 
				 "Generated file(s):\n{}\n{}\n{}\n{}",
 
				 this->crashlog_filename, this->crashdump_filename, this->savegame_filename, this->screenshot_filename);
 

	
 
		ShowMacDialog(crash_title, message.c_str(), "Quit");
 
	}
 
};
 

	
 
/** The signals we want our crash handler to handle. */
src/os/unix/crashlog_unix.cpp
Show inline comments
 
@@ -6,12 +6,13 @@
 
 */
 

	
 
/** @file crashlog_unix.cpp Unix crash log handler */
 

	
 
#include "../../stdafx.h"
 
#include "../../crashlog.h"
 
#include "../../fileio_func.h"
 
#include "../../string_func.h"
 
#include "../../gamelog.h"
 
#include "../../saveload/saveload.h"
 

	
 
#include <signal.h>
 
#include <sys/utsname.h>
 
@@ -22,12 +23,16 @@
 
#endif
 

	
 
#if defined(__NetBSD__)
 
#include <unistd.h>
 
#endif
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
#	include <client/linux/handler/exception_handler.h>
 
#endif
 

	
 
#include "../../safeguards.h"
 

	
 
/**
 
 * Unix implementation for the crash logger.
 
 */
 
class CrashLogUnix : public CrashLog {
 
@@ -81,12 +86,29 @@ class CrashLogUnix : public CrashLog {
 
		free(messages);
 
#else
 
		fmt::format_to(output_iterator, " Not supported.\n");
 
#endif
 
		fmt::format_to(output_iterator, "\n");
 
	}
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
	static bool MinidumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
 
	{
 
		CrashLogUnix *crashlog = reinterpret_cast<CrashLogUnix *>(context);
 

	
 
		crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
 
		std::rename(descriptor.path(), crashlog->crashdump_filename.c_str());
 
		return succeeded;
 
	}
 

	
 
	int WriteCrashDump() override
 
	{
 
		return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this) ? 1 : -1;
 
	}
 
#endif
 

	
 
public:
 
	/**
 
	 * A crash log is always generated by signal.
 
	 * @param signum the signal that was caused by the crash.
 
	 */
 
	CrashLogUnix(int signum) :
src/os/windows/crashlog_win.cpp
Show inline comments
 
@@ -20,12 +20,20 @@
 

	
 
#include <windows.h>
 
#include <mmsystem.h>
 
#include <signal.h>
 
#include <psapi.h>
 

	
 
#if defined(_MSC_VER)
 
#	include <dbghelp.h>
 
#endif
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
#	include <client/windows/handler/exception_handler.h>
 
#endif
 

	
 
#include "../../safeguards.h"
 

	
 
/**
 
 * Windows implementation for the crash logger.
 
 */
 
class CrashLogWindows : public CrashLog {
 
@@ -34,14 +42,30 @@ class CrashLogWindows : public CrashLog 
 

	
 
	void LogOSVersion(std::back_insert_iterator<std::string> &output_iterator) const override;
 
	void LogError(std::back_insert_iterator<std::string> &output_iterator, const std::string_view message) const override;
 
	void LogStacktrace(std::back_insert_iterator<std::string> &output_iterator) const override;
 
	void LogModules(std::back_insert_iterator<std::string> &output_iterator) const override;
 
public:
 

	
 
#ifdef WITH_UNOFFICIAL_BREAKPAD
 
	static bool MinidumpCallback(const wchar_t *dump_dir, const wchar_t *minidump_id, void *context, EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *assertion, bool succeeded)
 
	{
 
		CrashLogWindows *crashlog = reinterpret_cast<CrashLogWindows *>(context);
 

	
 
		crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
 
		std::rename(fmt::format("{}/{}.dmp", FS2OTTD(dump_dir), FS2OTTD(minidump_id)).c_str(), crashlog->crashdump_filename.c_str());
 
		return succeeded;
 
	}
 

	
 
	int WriteCrashDump() override
 
	{
 
		return google_breakpad::ExceptionHandler::WriteMinidump(OTTD2FS(_personal_dir), MinidumpCallback, this) ? 1 : -1;
 
	}
 
#endif
 

	
 
#if defined(_MSC_VER)
 
	int WriteCrashDump() override;
 
	void AppendDecodedStacktrace(std::back_insert_iterator<std::string> &output_iterator) const;
 
#else
 
	void AppendDecodedStacktrace(std::back_insert_iterator<std::string> &output_iterator) const {}
 
#endif /* _MSC_VER */
 

	
 
	/**
 
@@ -202,16 +226,12 @@ static void PrintModuleInfo(std::back_in
 
}
 

	
 
#if defined(_MSC_VER)
 
static const uint MAX_SYMBOL_LEN = 512;
 
static const uint MAX_FRAMES     = 64;
 

	
 
#pragma warning(disable:4091)
 
#include <dbghelp.h>
 
#pragma warning(default:4091)
 

	
 
void CrashLogWindows::AppendDecodedStacktrace(std::back_insert_iterator<std::string> &output_iterator) const
 
{
 
	DllLoader dbghelp(L"dbghelp.dll");
 
	struct ProcPtrs {
 
		BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
 
		BOOL (WINAPI * pSymSetOptions)(DWORD);
 
@@ -318,52 +338,12 @@ void CrashLogWindows::AppendDecodedStack
 

	
 
		proc.pSymCleanup(hCur);
 
	}
 

	
 
	fmt::format_to(output_iterator, "\n*** End of additional info ***\n");
 
}
 

	
 
/* virtual */ int CrashLogWindows::WriteCrashDump()
 
{
 
	int ret = 0;
 
	DllLoader dbghelp(L"dbghelp.dll");
 
	if (dbghelp.Success()) {
 
		typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
 
				MINIDUMP_TYPE,
 
				CONST PMINIDUMP_EXCEPTION_INFORMATION,
 
				CONST PMINIDUMP_USER_STREAM_INFORMATION,
 
				CONST PMINIDUMP_CALLBACK_INFORMATION);
 
		MiniDumpWriteDump_t funcMiniDumpWriteDump = dbghelp.GetProcAddress("MiniDumpWriteDump");
 
		if (funcMiniDumpWriteDump != nullptr) {
 
			this->crashdump_filename = this->CreateFileName(".dmp");
 
			HANDLE file  = CreateFile(OTTD2FS(this->crashdump_filename).c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
 
			HANDLE proc  = GetCurrentProcess();
 
			DWORD procid = GetCurrentProcessId();
 
			MINIDUMP_EXCEPTION_INFORMATION mdei;
 
			MINIDUMP_USER_STREAM userstream;
 
			MINIDUMP_USER_STREAM_INFORMATION musi;
 

	
 
			userstream.Type        = LastReservedStream + 1;
 
			userstream.Buffer      = (void*)this->crashlog.data();
 
			userstream.BufferSize  = (ULONG)this->crashlog.size() + 1;
 

	
 
			musi.UserStreamCount   = 1;
 
			musi.UserStreamArray   = &userstream;
 

	
 
			mdei.ThreadId = GetCurrentThreadId();
 
			mdei.ExceptionPointers  = ep;
 
			mdei.ClientPointers     = false;
 

	
 
			funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, nullptr);
 
			ret = 1;
 
		} else {
 
			ret = -1;
 
		}
 
	}
 
	return ret;
 
}
 
#endif /* _MSC_VER */
 

	
 
extern bool CloseConsoleLogIfActive();
 
static void ShowCrashlogWindow();
 

	
 
/**
0 comments (0 inline, 0 general)