diff --git a/CMakeLists.txt b/CMakeLists.txt index a775eea..73685df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ if (WIN32) endif() include_directories("include/sentry-native/include") + set(SENTRY_BUILD_SHARED_LIBS OFF) if (MSVC) set(SENTRY_BUILD_RUNTIMESTATIC ON) @@ -25,6 +26,7 @@ add_subdirectory("include/sentry-native") message(STATUS "Setting compiler flags") if (WIN32) + add_subdirectory("include/watchdog") #-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static set(VcpkgRoot ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}) include_directories(${VcpkgRoot}/include) @@ -65,8 +67,7 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") message(STATUS "Looking for Boost") find_package(Boost REQUIRED COMPONENTS system thread) -add_executable(BeamMP-Server - src/main.cpp +file(GLOB source_files src/main.cpp include/TConsole.h src/TConsole.cpp include/TServer.h src/TServer.cpp include/Compat.h src/Compat.cpp @@ -84,6 +85,8 @@ add_executable(BeamMP-Server include/TNetwork.h src/TNetwork.cpp include/SignalHandling.h src/SignalHandling.cpp) +add_executable(BeamMP-Server ${source_files}) + target_compile_definitions(BeamMP-Server PRIVATE SECRET_SENTRY_URL="${BEAMMP_SECRET_SENTRY_URL}") target_include_directories(BeamMP-Server PUBLIC @@ -118,6 +121,11 @@ if (UNIX) sioclient_tls sentry) elseif (WIN32) + set_source_files_properties(${source_files} PROPERTIES COMPILE_FLAGS "/Gh /GH") + add_compile_options("$<$>:/Zi>") + add_link_options("$<$>:/DEBUG>") + add_link_options("$<$>:/OPT:REF>") + add_link_options("$<$>:/OPT:ICF>") include(FindLua) message(STATUS "Looking for libz") find_package(ZLIB REQUIRED) @@ -131,5 +139,7 @@ elseif (WIN32) ${OPENSSL_LIBRARIES} commandline sioclient_tls - sentry) + sentry + watchdog + Dbghelp) endif () diff --git a/include/watchdog/CMakeLists.txt b/include/watchdog/CMakeLists.txt new file mode 100644 index 0000000..2855d48 --- /dev/null +++ b/include/watchdog/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) +project(windows_dbg CXX ASM_MASM) + +set(CMAKE_CXX_STANDARD 17) +add_library(windows_dbg STATIC watchdog.cpp watchdog.h x64Def.asm) +set_source_files_properties(watchdog.cpp PROPERTIES COMPILE_FLAGS "/O2 /Ob2 /DNDEBUG") +STRING(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) +target_link_libraries(windows_dbg Dbghelp) \ No newline at end of file diff --git a/include/watchdog/stack_string.h b/include/watchdog/stack_string.h new file mode 100644 index 0000000..41509c3 --- /dev/null +++ b/include/watchdog/stack_string.h @@ -0,0 +1,80 @@ +// +// Created by Anonymous275 on 9/5/2021. +// + +#pragma once +#include +#include + +namespace fst { + template + class stack_string { + public: + stack_string() noexcept { + memset(Data, 0, Cap); + } + explicit stack_string(const char* Ptr) { + size_t len = strlen(Ptr); + Copy(Ptr, len); + memset(Data + len, 0, Cap - len); + } + stack_string(const char* Ptr, size_t PSize) { + Copy(Ptr, PSize); + memset(Data + PSize, 0, Cap - PSize); + } + inline size_t capacity() noexcept { + return Cap; + } + inline size_t size() noexcept { + return Size; + } + inline size_t length() noexcept { + return Size; + } + inline char* get() { + return Data; + } + [[nodiscard]] inline const char* c_str() const noexcept { + return Data; + } + char& operator[](size_t idx) { + if (idx >= Size) { + throw std::exception("stack_string out of boundaries operator[]"); + } + return Data[idx]; + } + inline void resize(size_t newSize) noexcept { + Size = newSize; + } + inline void push_back(const char* Ptr) { + Copy(Ptr, strlen(Ptr)); + } + inline void push_back(const char* Ptr, size_t Count) { + Copy(Ptr, Count); + } + inline void push_back(char Ptr) { + Copy(&Ptr, 1); + } + friend std::ostream& operator<<(std::ostream& os, const stack_string& obj) { + os << obj.Data; + return os; + } + inline stack_string& operator+=(const char* Ptr) { + push_back(Ptr); + return *this; + } + inline stack_string& operator+=(char Ptr) { + push_back(Ptr); + return *this; + } + private: + inline void Copy(const char* Ptr, size_t PSize) { + if((PSize + Size) <= Cap) { + memcpy(&Data[Size], Ptr, PSize); + Size += PSize; + } else throw std::exception("stack_string out of boundaries copy"); + } + char Data[Cap]{}; + size_t Size{0}; + }; +} diff --git a/include/watchdog/watchdog.cpp b/include/watchdog/watchdog.cpp new file mode 100644 index 0000000..3cb418b --- /dev/null +++ b/include/watchdog/watchdog.cpp @@ -0,0 +1,281 @@ +// +// Created by Anonymous275 on 9/9/2021. +// + +#include +#include +#include +#include +#include "stack_string.h" + +struct function_info { + void* func_address; + uint32_t thread_id; + bool enter; +}; + +fst::stack_string<1024> crash_file; + +template +fst::stack_string<(sizeof(I)<<1)+1> HexString(I w) { + static const char* digits = "0123456789ABCDEF"; + const size_t hex_len = sizeof(I)<<1; + fst::stack_string rc; + rc.resize(hex_len+1); + memset(rc.get(), '0', hex_len); + memset(rc.get() + hex_len, 0, 1); + for (size_t i=0, j=(hex_len-1)*4 ; i>j) & 0x0f]; + return rc; +} + +template +class heap_array { +public: + heap_array() noexcept { + Data = (T_*)(GlobalAlloc(GPTR, Cap * sizeof(T_))); + init = true; + } + explicit heap_array(size_t Cap_) noexcept { + Cap = Cap_; + Data = (T_*)(GlobalAlloc(GPTR, Cap * sizeof(T_))); + init = true; + } + ~heap_array() { + free(Data); + } + inline T_* get() noexcept { + return Data; + } + inline const T_* cget() noexcept { + return Data; + } + inline void insert(const T_& T) { + if(!init)return; + if(Size >= Cap) { + Grow(); + } + Data[Size++] = T; + } + inline void string_insert(const T_* T, size_t len = 0) { + if(len == 0)len = strlen(T); + if(Size+len >= Cap) { + Grow(len); + } + memcpy(&Data[Size], T, len); + Size += len; + } + inline T_ at(size_t idx) { + return Data[idx]; + } + inline size_t size() const noexcept { + return Size; + } + const T_& operator[](size_t idx) { + if (idx >= Size) { + throw std::exception("out of boundaries operator[]"); + } + return Data[idx]; + } +private: + inline void Grow(size_t add = 0) { + Cap = (Cap*2) + add; + auto* NewData = (T_*)(GlobalAlloc(GPTR, Cap * sizeof(T_))); + for(size_t C = 0; C < Size; C++) { + NewData[C] = Data[C]; + } + GlobalFree(Data); + Data = NewData; + } + size_t Size{0}, Cap{5}; + bool init{false}; + T_* Data; +}; + +heap_array* watch_data; + +struct watchdog_mutex { + static void Create() noexcept { + hMutex = CreateMutex(nullptr, FALSE, nullptr); + } + static void Lock() { + WaitForSingleObject(hMutex, INFINITE); + } + static void Unlock() { + ReleaseMutex(hMutex); + } + struct [[nodiscard]] ScopedLock { + ScopedLock() { + if(hMutex) + watchdog_mutex::Lock(); + } + ~ScopedLock() { + if(hMutex) + watchdog_mutex::Unlock(); + } + }; +private: + static HANDLE hMutex; +}; +HANDLE watchdog_mutex::hMutex{nullptr}; +std::atomic Init{false}, Sym; +std::atomic Offset{0}; + +void watchdog_setOffset(int64_t Off) { + Offset.store(Off); +} + +void notify(const char* msg) { + HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (stdOut != nullptr && stdOut != INVALID_HANDLE_VALUE) { + DWORD written = 0; + WriteConsoleA(stdOut, "[WATCHDOG] ", 11, &written, nullptr); + WriteConsoleA(stdOut, msg, DWORD(strlen(msg)), &written, nullptr); + WriteConsoleA(stdOut, "\n", 1, &written, nullptr); + } +} + +fst::stack_string FindFunction(void* Address) { + if(!Sym.load()) { + fst::stack_string undName; + return undName; + } + static HANDLE process = GetCurrentProcess(); + DWORD64 symDisplacement = 0; + fst::stack_string undName; + TCHAR buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + memset(&buffer,0, sizeof(buffer)); + auto pSymbolInfo = (PSYMBOL_INFO)buffer; + pSymbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbolInfo->MaxNameLen = MAX_SYM_NAME; + if (SymFromAddr(process, DWORD64(Address) + Offset, &symDisplacement, pSymbolInfo)) { + undName.push_back(pSymbolInfo->Name); + } + return undName; +} + + +fst::stack_string<512> getCrashInfo(void* Address){ + if(!Sym.load()){ + fst::stack_string<512> Value; + Value.push_back("unknown", 7); + return Value; + } + DWORD pdwDisplacement = 0; + IMAGEHLP_LINE64 line{sizeof(IMAGEHLP_LINE64)}; + SymGetLineFromAddr64(GetCurrentProcess(), DWORD64(Address) + Offset, &pdwDisplacement, &line); + char* Name = nullptr; + if(line.FileName) { + Name = strrchr(line.FileName, '\\'); + } + fst::stack_string<512> Value; + if(Name)Value.push_back(Name+1); + else Value.push_back("unknown", 7); + char buffer[20]; + auto n = sprintf(buffer, ":%lu", line.LineNumber); + Value.push_back(buffer, n); + return Value; +} +const char* getFunctionDetails(void* Address) { + return FindFunction(Address).c_str(); +} +const char* getCrashLocation(void* Address) { + return getCrashInfo(Address).c_str(); +} + +void InitSym(const char* PDBLocation) { + SymInitialize(GetCurrentProcess(), PDBLocation, TRUE); + Sym.store(true); +} + +void write_report(const char* report, size_t size) { + HANDLE hFile = CreateFile(crash_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + notify("Failed to open crash file for writing!"); + return; + } + DWORD dwBytesWritten = 0; + auto Flag = WriteFile(hFile, report, DWORD(size), &dwBytesWritten, nullptr); + if (Flag == FALSE) { + notify("Failed to write to crash file!"); + } + CloseHandle(hFile); +} + +void generate_crash_report(uint32_t Code, void* Address) { + watchdog_mutex::ScopedLock guard; + notify("generating crash report, please wait"); + Init.store(false); + heap_array Report(watch_data->size() * sizeof(function_info)); + Report.string_insert("crash code "); + Report.string_insert(HexString(Code).c_str()); + Report.string_insert(" at "); + Report.string_insert(HexString(size_t(Address) + Offset).c_str()); + Report.string_insert("\n"); + if(Address) { + Report.string_insert("origin and line number -> "); + Report.string_insert(getCrashInfo(Address).c_str()); + Report.string_insert("\n"); + } + Report.string_insert("Call history: \n"); + char buff[20]; + for(size_t C = 0; C < watch_data->size(); C++){ + auto entry = watch_data->at(C); + auto Name = FindFunction(entry.func_address); + if(entry.enter){ + Report.string_insert("[Entry] "); + } + else { + Report.string_insert("[Exit ] "); + } + auto n = sprintf(buff, "(%d) ", entry.thread_id); + Report.string_insert(buff, n); + if(Name.size() > 0){ + Report.string_insert(Name.c_str(), Name.size()); + Report.string_insert(" | "); + auto location = getCrashInfo(entry.func_address); + Report.string_insert(location.c_str(), location.size()); + } + else { + Report.string_insert(HexString(size_t(entry.func_address) + Offset).c_str()); + } + Report.string_insert("\n"); + } + write_report(Report.cget(), Report.size()); + notify("crash report generated"); + Init.store(true); +} + +LONG WINAPI CrashHandler(EXCEPTION_POINTERS* p) { + Init.store(false); + notify("CAUGHT EXCEPTION!"); + generate_crash_report(p->ExceptionRecord->ExceptionCode, p->ExceptionRecord->ExceptionAddress); + return EXCEPTION_EXECUTE_HANDLER; +} + +void watchdog_init(const char* crashFile, const char* SpecificPDBLocation, bool Symbols) { + if(Symbols)SymInitialize(GetCurrentProcess(), SpecificPDBLocation, TRUE); + Sym.store(Symbols); + SetUnhandledExceptionFilter(CrashHandler); + watch_data = new heap_array(); + watchdog_mutex::Create(); + crash_file.push_back(crashFile); + notify("initialized!"); + Init.store(true); +} + +inline void AddEntry(void* func_address, uint32_t thread_id, bool entry) { + watchdog_mutex::ScopedLock guard; + if(Init.load()) { + watch_data->insert({func_address, thread_id, entry}); + } +} + +extern "C" { + void FuncEntry(void* func) { + AddEntry(func, GetCurrentThreadId(), true); + } + void FuncExit(void* func) { + AddEntry(func, GetCurrentThreadId(), false); + } +} \ No newline at end of file diff --git a/include/watchdog/watchdog.h b/include/watchdog/watchdog.h new file mode 100644 index 0000000..6a0c4f2 --- /dev/null +++ b/include/watchdog/watchdog.h @@ -0,0 +1,12 @@ +// +// Created by Anonymous275 on 9/9/2021. +// + +#pragma once +#include +extern void watchdog_init(const char* crashFile, const char* SpecificPDBLocation, bool Symbols = true); +extern void generate_crash_report(uint32_t Code, void* Address); +const char* getFunctionDetails(void* Address); +extern void watchdog_setOffset(int64_t Off); +const char* getCrashLocation(void* Address); +void InitSym(const char* PDBLocation); \ No newline at end of file diff --git a/include/watchdog/x64Def.asm b/include/watchdog/x64Def.asm new file mode 100644 index 0000000..aad4af8 --- /dev/null +++ b/include/watchdog/x64Def.asm @@ -0,0 +1,85 @@ +;// +;// Created by Anonymous275 on 9/9/2021. +;// +;External C functions used by _penter and _pexit +extern FuncEntry:Proc +extern FuncExit:Proc + +.code + +_penter proc + + ; Store the volatile registers + push r11 + push r10 + push r9 + push r8 + push rax + push rdx + push rcx + + ; reserve space for 4 registers [ rcx,rdx,r8 and r9 ] 32 bytes + sub rsp,20h + + ; Get the return address of the function + mov rcx,rsp + mov rcx,qword ptr[rcx+58h] + sub rcx,5 + + ;call the function to get the name of the callee and caller + call FuncEntry + + ;Release the space reserved for the registersk by adding 32 bytes + add rsp,20h + + ;Restore the registers back by poping out + pop rcx + pop rdx + pop rax + pop r8 + pop r9 + pop r10 + pop r11 + + ;return + ret + +_penter endp + +_pexit proc + + ; Store the volatile registers + push r11 + push r10 + push r9 + push r8 + push rax + push rdx + push rcx + + ; reserve space for 4 registers [ rcx,rdx,r8 and r9 ] 32 bytes + sub rsp,20h + + ; Get the return address of the function + mov rcx,rsp + mov rcx,qword ptr[rcx+58h] + + call FuncExit + + ;Release the space reserved for the registersk by adding 32 bytes + add rsp,20h + + ;Restore the registers back by poping out + pop rcx + pop rdx + pop rax + pop r8 + pop r9 + pop r10 + pop r11 + + ;return + ret + +_pexit endp +end \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 27cafee..fec5911 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,7 +11,7 @@ #include "TPPSMonitor.h" #include "TResourceManager.h" #include "TServer.h" - +#include "../include/watchdog/watchdog.h" #include #include @@ -19,47 +19,51 @@ // global, yes, this is ugly, no, it cant be done another way TSentry Sentry {}; -int main(int argc, char** argv) try { - setlocale(LC_ALL, "C"); +int main(int argc, char** argv) { + watchdog_init("watchdog_crash.log", "C:\\Users\\Anonymous\\Documents\\GitHub\\BeamMP-Server\\RelWithDebInfo"); + try { + setlocale(LC_ALL, "C"); - SetupSignalHandlers(); + SetupSignalHandlers(); - bool Shutdown = false; - Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; }); + bool Shutdown = false; + Application::RegisterShutdownHandler([&Shutdown] { Shutdown = true; }); - TServer Server(argc, argv); - TConfig Config; + TServer Server(argc, argv); + TConfig Config; - if (Config.Failed()) { - info("Closing in 10 seconds"); - // loop to make it possible to ctrl+c instead - for (size_t i = 0; i < 20; ++i) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (Config.Failed()) { + info("Closing in 10 seconds"); + // loop to make it possible to ctrl+c instead + for (size_t i = 0; i < 20; ++i) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + return 1; } - return 1; + + RegisterThread("Main"); + + trace("Running in debug mode on a debug build"); + + Sentry.SetupUser(); + Sentry.PrintWelcome(); + TResourceManager ResourceManager; + TPPSMonitor PPSMonitor(Server); + THeartbeatThread Heartbeat(ResourceManager, Server); + TNetwork Network(Server, PPSMonitor, ResourceManager); + TLuaEngine LuaEngine(Server, Network); + PPSMonitor.SetNetwork(Network); + Application::Console().InitializeLuaConsole(LuaEngine); + Application::CheckForUpdates(); + + // TODO: replace + while (!Shutdown) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + info("Shutdown."); + + } catch (const std::exception& e) { + error(e.what()); + Sentry.LogException(e, _file_basename, _line); } - - RegisterThread("Main"); - - trace("Running in debug mode on a debug build"); - - Sentry.SetupUser(); - Sentry.PrintWelcome(); - TResourceManager ResourceManager; - TPPSMonitor PPSMonitor(Server); - THeartbeatThread Heartbeat(ResourceManager, Server); - TNetwork Network(Server, PPSMonitor, ResourceManager); - TLuaEngine LuaEngine(Server, Network); - PPSMonitor.SetNetwork(Network); - Application::Console().InitializeLuaConsole(LuaEngine); - Application::CheckForUpdates(); - - // TODO: replace - while (!Shutdown) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - info("Shutdown."); -} catch (const std::exception& e) { - error(e.what()); - Sentry.LogException(e, _file_basename, _line); }