Skip to content

Commit

Permalink
feat: introduce the new crash handler based on crashpad and sentry-na…
Browse files Browse the repository at this point in the history
…tive
  • Loading branch information
wu-vincent committed Jan 3, 2025
1 parent 097cf19 commit 3769725
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 81 deletions.
3 changes: 0 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ endif ()
# =======
option(CODE_COVERAGE "Enable code coverage reporting" OFF)
option(ENDSTONE_ENABLE_DEVTOOLS "Build Endstone with DevTools enabled." OFF)
option(ENDSTONE_ENABLE_SENTRY "Enable Sentry integration for error reporting." OFF)
set(ENDSTONE_SENTRY_DSN "" CACHE STRING "Set the Sentry DSN (Data Source Name) for error tracking.")
set(ENDSTONE_SENTRY_ENVIRONMENT "development" CACHE STRING "Set the environment for Sentry (e.g., development, production).")

if (NOT BUILD_TESTING STREQUAL OFF)
enable_testing()
Expand Down
3 changes: 1 addition & 2 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class EndstoneRecipe(ConanFile):
"capstone/*:m680x": False,
"capstone/*:evm": False,
"date/*:header_only": True,
"sentry-native/*:backend": "breakpad",
"sentry-native/*:backend": "crashpad",
}

exports_sources = "CMakeLists.txt", "src/*", "include/*", "tests/*"
Expand Down Expand Up @@ -164,7 +164,6 @@ def generate(self):
tc = CMakeToolchain(self)
tc.variables["ENDSTONE_VERSION"] = self.version
tc.variables["ENDSTONE_ENABLE_DEVTOOLS"] = self._should_enable_devtools
tc.variables["ENDSTONE_SENTRY_ENVIRONMENT"] = "development" if self._is_dev_build else "production"
tc.generate()

def build(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
// limitations under the License.

#pragma once
#ifdef ENDSTONE_WITH_SENTRY

namespace endstone::detail {

class SentryHandler {
class CrashHandler {
public:
SentryHandler();
~SentryHandler();
CrashHandler();
~CrashHandler();
};

} // namespace endstone::detail
#endif
5 changes: 3 additions & 2 deletions include/endstone/detail/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "endstone/detail/ban/ip_ban_list.h"
#include "endstone/detail/ban/player_ban_list.h"
#include "endstone/detail/command/command_map.h"
#include "endstone/detail/crash_handler.h"
#include "endstone/detail/lang/language.h"
#include "endstone/detail/level/level.h"
#include "endstone/detail/packs/endstone_pack_source.h"
Expand Down Expand Up @@ -121,6 +122,7 @@ class EndstoneServer : public Server {
friend class EndstonePlayer;

void enablePlugin(Plugin &plugin);

ServerInstance *server_instance_;
Logger &logger_;
std::unique_ptr<EndstonePlayerBanList> player_ban_list_;
Expand All @@ -136,10 +138,9 @@ class EndstoneServer : public Server {
std::vector<std::weak_ptr<EndstoneScoreboard>> scoreboards_;
std::unordered_map<const EndstonePlayer *, std::shared_ptr<EndstoneScoreboard>> player_boards_;
std::chrono::system_clock::time_point start_time_;

Bedrock::NonOwnerPointer<IResourcePackRepository> resource_pack_repository_;
std::unique_ptr<EndstonePackSource> resource_pack_source_;

std::unique_ptr<CrashHandler> crash_handler_;
int tick_counter_ = 0;
float current_mspt_ = TargetMillisecondsPerTick * 1.0F;
float average_mspt_[TargetTicksPerSecond] = {TargetMillisecondsPerTick};
Expand Down
3 changes: 1 addition & 2 deletions src/endstone_core/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ namespace endstone::detail {

EndstoneServer::EndstoneServer() : logger_(LoggerFactory::getLogger("Server"))
{
register_signal_handler();

player_ban_list_ = std::make_unique<EndstonePlayerBanList>("banned-players.json");
ip_ban_list_ = std::make_unique<EndstoneIpBanList>("banned-ips.json");
language_ = std::make_unique<EndstoneLanguage>();
plugin_manager_ = std::make_unique<EndstonePluginManager>(*this);
scheduler_ = std::make_unique<EndstoneScheduler>(*this);
crash_handler_ = std::make_unique<CrashHandler>();
start_time_ = std::chrono::system_clock::now();
}

Expand Down
21 changes: 8 additions & 13 deletions src/endstone_runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.15)
project(endstone_runtime LANGUAGES CXX)

find_package(sentry REQUIRED)
include(FetchContent)
FetchContent_Declare(
funchook
Expand Down Expand Up @@ -113,23 +114,15 @@ add_library(endstone_runtime SHARED
windows/hook.cpp
windows/os.cpp
windows/signal_handler.cpp
crash_handler.cpp
hook.cpp
loader.cpp
main.cpp
sentry_handler.cpp
)
add_library(endstone::runtime ALIAS endstone_runtime)
target_link_libraries(endstone_runtime PRIVATE endstone::core funchook::funchook)
target_link_libraries(endstone_runtime PRIVATE endstone::core funchook::funchook sentry::sentry)
if (ENDSTONE_ENABLE_DEVTOOLS)
target_link_libraries(endstone_runtime PRIVATE endstone::devtools)
endif ()
if (ENDSTONE_ENABLE_SENTRY)
find_package(sentry REQUIRED)
target_link_libraries(endstone_runtime PRIVATE sentry::sentry)
target_compile_definitions(endstone_runtime PRIVATE ENDSTONE_WITH_SENTRY)
target_compile_definitions(endstone_runtime PRIVATE ENDSTONE_SENTRY_DSN="${ENDSTONE_SENTRY_DSN}")
target_compile_definitions(endstone_runtime PRIVATE ENDSTONE_SENTRY_ENVIRONMENT="${ENDSTONE_SENTRY_ENVIRONMENT}")
endif ()
if (WIN32)
target_link_libraries(endstone_runtime PRIVATE dbghelp.lib ws2_32.lib)
target_link_options(endstone_runtime PRIVATE "/INCREMENTAL:NO")
Expand All @@ -147,12 +140,14 @@ install(TARGETS endstone_runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS endstone_runtime DESTINATION "endstone/_internal/" COMPONENT endstone_wheel OPTIONAL)
install(DIRECTORY ${sentry_INCLUDE_DIRS}/../bin/ DESTINATION "endstone/_internal/" COMPONENT endstone_wheel OPTIONAL)

if (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND MSVC)
install(FILES $<TARGET_PDB_FILE:endstone_runtime> DESTINATION "endstone/_internal/" COMPONENT endstone_wheel OPTIONAL)
endif ()

if (WIN32)
add_library(endstone_runtime_loader SHARED "loader.cpp")
target_compile_definitions(endstone_runtime_loader PRIVATE _CRT_SECURE_NO_WARNINGS ENDSTONE_RUNTIME_LOADER)
add_library(endstone_runtime_loader SHARED "runtime_loader.cpp")
target_compile_definitions(endstone_runtime_loader PRIVATE _CRT_SECURE_NO_WARNINGS)
install(TARGETS endstone_runtime_loader DESTINATION "endstone/_internal/" COMPONENT endstone_wheel OPTIONAL)
endif ()
endif ()
137 changes: 137 additions & 0 deletions src/endstone_runtime/crash_handler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) 2024, The Endstone Project. (https://endstone.dev) All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "endstone/detail/crash_handler.h"

#include <sentry.h>

#include <filesystem>
#include <iostream>
#include <string>

#include <cpptrace/cpptrace.hpp>
#include <fmt/format.h>

#include "endstone/detail/os.h"
#include "endstone/endstone.h"

namespace fs = std::filesystem;

namespace endstone::detail {

namespace {

void print_frame(std::ostream &stream, bool color, unsigned frame_number_width, std::size_t counter,
const cpptrace::stacktrace_frame &frame)
{
const auto *reset = color ? "\033[0m" : "";
const auto *green = color ? "\033[32m" : "";
const auto *yellow = color ? "\033[33m" : "";
const auto *blue = color ? "\033[34m" : "";
std::string line = fmt::format("[{:<{}}] ", counter, frame_number_width);
if (frame.is_inline) {
line += fmt::format("{:<{}}", "(inlined)", 2 * sizeof(cpptrace::frame_ptr) + 2);
}
else {
line += fmt::format("{}0x{:<{}x}{}", blue, frame.raw_address, 2 * sizeof(cpptrace::frame_ptr), reset);
line += fmt::format(" {}(0x{:09x}){}", green, frame.object_address, reset);
}
if (!frame.symbol.empty()) {
line += fmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if (!frame.filename.empty()) {
line += fmt::format(" at {}{}{}", green, frame.filename, reset);
if (frame.line.has_value()) {
line += fmt::format(":{}{}{}", blue, frame.line.value(), reset);
if (frame.column.has_value()) {
line += fmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
stream << line;
}

void print_crash_dump(const std::string &message, std::size_t skip = 0)
{
printf("=== ENDSTONE CRASHED! - PLEASE REPORT THIS AS AN ISSUE ON GITHUB ===\n");
printf("Operation system: %s\n", os::get_name().c_str());
printf("Endstone version: %s\n", ENDSTONE_VERSION);
printf("Api version : %s\n", ENDSTONE_API_VERSION);
printf("Description : %s\n", message.c_str());

auto stacktrace = cpptrace::generate_trace(skip + 1);
auto &stream = std::cerr;
auto color = cpptrace::isatty(cpptrace::stderr_fileno);
stream << "Stack trace (most recent call first):" << '\n';

auto &frames = stacktrace.frames;
std::size_t counter = 0;
if (frames.empty()) {
stream << "<empty trace>" << '\n';
return;
}
const auto frame_number_width = std::to_string(frames.size()).length();
for (const auto &frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
stream << '\n';
if (frame.line.has_value() && !frame.filename.empty()) {
stream << cpptrace::get_snippet(frame.filename, frame.line.value(), 2, color);
}
counter++;
}
}

sentry_value_t on_crash(const sentry_ucontext_t *ctx, sentry_value_t event, void *closure)
{
#ifdef _WIN32
print_crash_dump("Exception unhandled: " +
fmt::format("{0:#x}", ctx->exception_ptrs.ExceptionRecord->ExceptionCode));
#else
print_crash_dump("Signal received: " + std::to_string(ctx.signum));
#endif

// TODO(crash_handler): filter out crashes originates from BDS / plugins
return event;
}
} // namespace

CrashHandler::CrashHandler()
{
constexpr auto dsn =
"https://69c28eeaef4651abcf0bbeace6a1175c@o4508553519431680.ingest.de.sentry.io/4508569040519248";
#ifdef _WIN32
fs::path handler_path = (fs::path{os::get_module_pathname()}.parent_path()) / "crashpad_handler.exe";
#else
fs::path handler_path = (fs::path{os::get_module_pathname()}.parent_path()) / "crashpad_handler";
#endif
constexpr std::string_view release = "endstone@" ENDSTONE_VERSION;
constexpr bool is_dev = release.find("dev") != std::string_view::npos;

sentry_options_t *options = sentry_options_new();
sentry_options_set_dsn(options, dsn);
sentry_options_set_database_path(options, ".sentry-native");
sentry_options_set_handler_path(options, handler_path.string().c_str());
sentry_options_set_release(options, std::string(release).c_str());
sentry_options_set_debug(options, 1);
sentry_options_set_on_crash(options, on_crash, nullptr);
sentry_options_set_environment(options, is_dev ? "development" : "production");
sentry_init(options);
}

CrashHandler::~CrashHandler()
{
sentry_close();
}

} // namespace endstone::detail
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#ifdef ENDSTONE_RUNTIME_LOADER

#include <windows.h>

#include <cstdio>
Expand Down Expand Up @@ -58,5 +56,3 @@ BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID /*reserved*/)
}
return TRUE;
}

#endif
50 changes: 0 additions & 50 deletions src/endstone_runtime/sentry_handler.cpp

This file was deleted.

0 comments on commit 3769725

Please sign in to comment.