From 335e9325b02fb6410b78e066775d952c0ea436aa Mon Sep 17 00:00:00 2001 From: Yimura <24669514+Yimura@users.noreply.github.com> Date: Wed, 8 Feb 2023 23:36:55 +0100 Subject: [PATCH] feat(logger): use AsyncLogger instead of g3log (#882) * feat(exception_handler): skip certain exception codes * feat(zydis): Disable BUILD_DOXYGEN and FEATURE_ENCODER * feat(cmake): link dbghelp * feat(exception_handler): implement stack dumper * feat(logger): differentiate between wine and non-wine * feat(logger): add NO_COLOR env var support * fix(logger): fix wine logging (#960) * feat(exception_handler): added string logging of exception * fix(logger): exception access violation NO_COLOR -------- Co-authored-by: Aure7138 <100095051+Aure7138@users.noreply.github.com> Co-authored-by: tupoy-ya <72797377+tupoy-ya@users.noreply.github.com> --- CMakeLists.txt | 5 +- scripts/async-logger.cmake | 12 + scripts/g3log.cmake | 16 -- scripts/zydis.cmake | 15 ++ src/common.hpp | 2 +- src/detour_hook.cpp | 1 - src/hooking.cpp | 1 - src/hooks/protections/receive_net_message.cpp | 2 +- .../protections/script_event_handler.cpp | 2 +- src/invoker.cpp | 1 - src/logger.hpp | 224 ------------------ src/logger/exception_handler.cpp | 35 +++ src/logger/exception_handler.hpp | 18 ++ src/logger/logger.cpp | 212 +++++++++++++++++ src/logger/logger.hpp | 64 +++++ src/logger/stack_trace.cpp | 222 +++++++++++++++++ src/logger/stack_trace.hpp | 60 +++++ src/main.cpp | 7 +- src/memory/batch.cpp | 3 +- src/memory/module.cpp | 2 +- src/script.cpp | 1 - src/script_function.cpp | 2 +- src/services/gta_data/gta_data_service.cpp | 8 +- src/thread_pool.cpp | 4 +- src/views/debug/view_debug_misc.cpp | 11 + 25 files changed, 669 insertions(+), 261 deletions(-) create mode 100644 scripts/async-logger.cmake delete mode 100644 scripts/g3log.cmake create mode 100644 scripts/zydis.cmake delete mode 100644 src/logger.hpp create mode 100644 src/logger/exception_handler.cpp create mode 100644 src/logger/exception_handler.hpp create mode 100644 src/logger/logger.cpp create mode 100644 src/logger/logger.hpp create mode 100644 src/logger/stack_trace.cpp create mode 100644 src/logger/stack_trace.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 515042e6..3d486b90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,11 @@ include(scripts/git.cmake) # Fetch modules message("\nFetching modules") include(scripts/minhook.cmake) -include(scripts/g3log.cmake) +include(scripts/async-logger.cmake) include(scripts/pugixml.cmake) include(scripts/json.cmake) include(scripts/cpr.cmake) +include(scripts/zydis.cmake) message("\nFetching custom modules") include(scripts/imgui.cmake) @@ -45,7 +46,7 @@ target_include_directories(YimMenu PRIVATE ) target_precompile_headers(YimMenu PRIVATE "${SRC_DIR}/common.hpp") -target_link_libraries(YimMenu PRIVATE pugixml minhook g3log imgui cpr) +target_link_libraries(YimMenu PRIVATE pugixml minhook AsyncLogger dbghelp imgui cpr Zydis) # Warnings as errors set_property(TARGET YimMenu PROPERTY COMPILE_WARNING_AS_ERROR ON) diff --git a/scripts/async-logger.cmake b/scripts/async-logger.cmake new file mode 100644 index 00000000..e0effc2a --- /dev/null +++ b/scripts/async-logger.cmake @@ -0,0 +1,12 @@ +include(FetchContent) + +message("AsyncLogger") +FetchContent_Declare( + AsyncLogger + GIT_REPOSITORY https://github.com/Yimura/AsyncLogger.git + GIT_TAG v0.0.5 + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(AsyncLogger) + +set_property(TARGET AsyncLogger PROPERTY CXX_STANDARD 23) diff --git a/scripts/g3log.cmake b/scripts/g3log.cmake deleted file mode 100644 index c181ba04..00000000 --- a/scripts/g3log.cmake +++ /dev/null @@ -1,16 +0,0 @@ -include(FetchContent) - -set(ADD_FATAL_EXAMPLE OFF CACHE INTERNAL "") -set(INSTALL_G3LOG OFF CACHE INTERNAL "") -set(G3_SHARED_LIB OFF CACHE INTERNAL "") -set(G3_SHARED_RUNTIME OFF CACHE INTERNAL "") - -FetchContent_Declare( - g3log - GIT_REPOSITORY https://github.com/YimMenu/g3log.git - GIT_TAG 6ccf93c2e966e9ae13df75496a88e7b45214f7f8 - GIT_PROGRESS TRUE -) -message("g3log") -FetchContent_MakeAvailable(g3log) -set_property(TARGET g3log PROPERTY CXX_STANDARD 23) diff --git a/scripts/zydis.cmake b/scripts/zydis.cmake new file mode 100644 index 00000000..01150635 --- /dev/null +++ b/scripts/zydis.cmake @@ -0,0 +1,15 @@ +include(FetchContent) + +message("zydis") + +set(ZYDIS_BUILD_DOXYGEN OFF CACHE BOOL "Zydis Build Doxygen") +set(ZYDIS_FEATURE_ENCODER OFF CACHE BOOL "Zydis Feature Encoder") + +FetchContent_Declare( + zydis + GIT_REPOSITORY https://github.com/zyantific/zydis.git + GIT_TAG 8948d9a8f493330d27a0e7bbebf40f0391e45f1b + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(zydis) +set_property(TARGET Zydis PROPERTY CXX_STANDARD 23) diff --git a/src/common.hpp b/src/common.hpp index 68a09766..3d39a8ff 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -49,7 +49,7 @@ #include #include -#include "logger.hpp" +#include "logger/logger.hpp" #include "core/globals.hpp" #include "gta/natives.hpp" diff --git a/src/detour_hook.cpp b/src/detour_hook.cpp index e70f62aa..93101afb 100644 --- a/src/detour_hook.cpp +++ b/src/detour_hook.cpp @@ -1,6 +1,5 @@ #include "common.hpp" #include "detour_hook.hpp" -#include "logger.hpp" #include "memory/handle.hpp" #include diff --git a/src/hooking.cpp b/src/hooking.cpp index fa0d672d..dbc82a4e 100644 --- a/src/hooking.cpp +++ b/src/hooking.cpp @@ -1,6 +1,5 @@ #include "common.hpp" #include "function_types.hpp" -#include "logger.hpp" #include "gta/script_thread.hpp" #include "gui.hpp" #include "hooking.hpp" diff --git a/src/hooks/protections/receive_net_message.cpp b/src/hooks/protections/receive_net_message.cpp index 0cf37d0c..ba350d0f 100644 --- a/src/hooks/protections/receive_net_message.cpp +++ b/src/hooks/protections/receive_net_message.cpp @@ -438,7 +438,7 @@ namespace big } } - LOG(G3LOG_DEBUG) << "RECEIVED PACKET | Type: " << packet_type << " | Length: " << frame->m_length << " | Sender: " << + LOG(VERBOSE) << "RECEIVED PACKET | Type: " << packet_type << " | Length: " << frame->m_length << " | Sender: " << (player ? player->get_name() : std::format(", , ", (int)frame->m_msg_id, frame->m_connection_identifier, frame->m_peer_id).c_str()) << " | " << HEX_TO_UPPER((int)msgType); } diff --git a/src/hooks/protections/script_event_handler.cpp b/src/hooks/protections/script_event_handler.cpp index 7d7f4ff3..8018fb14 100644 --- a/src/hooks/protections/script_event_handler.cpp +++ b/src/hooks/protections/script_event_handler.cpp @@ -403,7 +403,7 @@ namespace big } script_args += " };"; - LOG(G3LOG_DEBUG) << "Script Event:\n" + LOG(VERBOSE) << "Script Event:\n" << "\tPlayer: " << player->get_name() << "\n" << "\tArgs: " << script_args; } diff --git a/src/invoker.cpp b/src/invoker.cpp index 6da4626c..91cfada5 100644 --- a/src/invoker.cpp +++ b/src/invoker.cpp @@ -1,7 +1,6 @@ #include "common.hpp" #include "crossmap.hpp" #include "invoker.hpp" -#include "logger.hpp" #include "pointers.hpp" extern "C" void _call_asm(void* context, void* function, void* ret); diff --git a/src/logger.hpp b/src/logger.hpp deleted file mode 100644 index ec5b84bf..00000000 --- a/src/logger.hpp +++ /dev/null @@ -1,224 +0,0 @@ -#pragma once -#include "common.hpp" -#include "file_manager.hpp" -#include -#include -#include - -namespace big -{ - template - std::time_t to_time_t(TP tp) - { - using namespace std::chrono; - auto sctp = time_point_cast(tp - TP::clock::now() - + system_clock::now()); - return system_clock::to_time_t(sctp); - } - - class logger; - inline logger* g_log{}; - - enum class LogColor - { - RESET, - WHITE = 97, - CYAN = 36, - MAGENTA = 35, - BLUE = 34, - GREEN = 32, - YELLOW = 33, - RED = 31, - BLACK = 30 - }; - -#define ADD_COLOR_TO_STREAM(color) "\x1b[" << int(color) << "m" -#define RESET_STREAM_COLOR "\x1b[" << int(LogColor::RESET) << "m" -#define HEX_TO_UPPER(value) "0x" << std::hex << std::uppercase << (DWORD64)value << std::dec << std::nouppercase - - class logger final - { - - public: - logger(std::string_view console_title, file file, bool attach_console = true) - : m_attach_console(attach_console), m_did_console_exist(false), - m_console_title(console_title), m_original_console_mode(0), - m_console_handle(nullptr), m_file(file), - m_worker(g3::LogWorker::createLogWorker()) - { - initialize(); - - g_log = this; - } - ~logger() - { - g_log = nullptr; - } - - void initialize() - { - if (m_attach_console) - { - if (m_did_console_exist = ::AttachConsole(GetCurrentProcessId()); !m_did_console_exist) - AllocConsole(); - - if (m_console_handle = GetStdHandle(STD_OUTPUT_HANDLE); m_console_handle != nullptr) - { - SetConsoleTitleA(m_console_title.data()); - SetConsoleOutputCP(CP_UTF8); - - DWORD console_mode; - GetConsoleMode(m_console_handle, &console_mode); - m_original_console_mode = console_mode; - - // terminal like behaviour enable full color support - console_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; - // prevent clicking in terminal from suspending our main thread - console_mode &= ~(ENABLE_QUICK_EDIT_MODE); - - SetConsoleMode(m_console_handle, console_mode); - } - } - create_backup(); - open_outstreams(); - initialize_g3log(); - } - void destroy() - { - destroy_g3log(); - close_outstreams(); - - if (m_did_console_exist) - SetConsoleMode(m_console_handle, m_original_console_mode); - - if (!m_did_console_exist && m_attach_console) - FreeConsole(); - } - - void file_out(std::stringstream& str) - { - m_file_out << str.str() << '\n'; - } - private: - void create_backup() - { - if (m_file.exists()) - { - auto file_time = std::filesystem::last_write_time(m_file.get_path()); - auto time_t = to_time_t(file_time); - auto local_time = std::localtime(&time_t); - - m_file.move( - std::format( - "./backup/{:0>2}-{:0>2}-{}-{:0>2}-{:0>2}-{:0>2}_{}", - local_time->tm_mon + 1, - local_time->tm_mday, - local_time->tm_year + 1900, - local_time->tm_hour, - local_time->tm_min, - local_time->tm_sec, - m_file.get_path().filename().string().c_str() - ) - ); - } - } - - void initialize_g3log() - { - m_worker->addSink(std::make_unique(), &log_sink::callback); - g3::initializeLogging(m_worker.get()); - } - void destroy_g3log() - { - m_worker->removeAllSinks(); - m_worker.reset(); - } - - void open_outstreams() - { - if (m_attach_console) - m_console_out.open("CONOUT$", std::ios_base::out | std::ios_base::app); - - m_file_out.open(m_file.get_path(), std::ios_base::out | std::ios_base::trunc); - } - void close_outstreams() - { - if (m_attach_console) - m_console_out.close(); - - m_file_out.close(); - } - - private: - struct log_sink - { - void callback(g3::LogMessageMover log) - { - if (g_log->m_console_out.is_open()) - g_log->m_console_out << log.get().toString(log_sink::format_console) << std::flush; - - g_log->m_file_out << log.get().toString(log_sink::format_file) << std::flush; - } - - static LogColor get_color(const LEVELS level) - { - switch (level.value) - { - case g3::kDebugValue: - return LogColor::BLUE; - case g3::kInfoValue: - return LogColor::GREEN; - case g3::kWarningValue: - return LogColor::YELLOW; - } - return g3::internal::wasFatal(level) ? LogColor::RED : LogColor::WHITE; - } - - static std::string format_console(const g3::LogMessage& msg) - { - LogColor color = log_sink::get_color(msg._level); - std::stringstream out; - - out - << "[" << msg.timestamp("%H:%M:%S") << "]" - << ADD_COLOR_TO_STREAM(color) - << "[" << msg.level() << "/" - << msg.file() << ":" << msg.line() << "]" - << RESET_STREAM_COLOR - << ": "; - - return out.str(); - } - static std::string format_file(const g3::LogMessage& msg) - { - std::stringstream out; - - out - << "[" << msg.timestamp("%H:%M:%S") << "]" - << "[" << msg.level() << "/" - << msg.file() << ":" << msg.line() << "]" - << ": "; - - return out.str(); - } - }; - - private: - friend struct log_sink; - - bool m_attach_console; - bool m_did_console_exist; - - std::string_view m_console_title; - DWORD m_original_console_mode; - HANDLE m_console_handle; - - std::ofstream m_console_out; - std::ofstream m_file_out; - - file m_file; - - std::unique_ptr m_worker; - - }; -} \ No newline at end of file diff --git a/src/logger/exception_handler.cpp b/src/logger/exception_handler.cpp new file mode 100644 index 00000000..99d6aaa5 --- /dev/null +++ b/src/logger/exception_handler.cpp @@ -0,0 +1,35 @@ +#include "exception_handler.hpp" +#include "stack_trace.hpp" +#include + +namespace big +{ + exception_handler::exception_handler() + { + m_exception_handler = AddVectoredExceptionHandler(1, &vectored_exception_handler); + } + + exception_handler::~exception_handler() + { + RemoveVectoredExceptionHandler(m_exception_handler); + } + + LONG vectored_exception_handler(EXCEPTION_POINTERS* exception_info) + { + const auto exception_code = exception_info->ExceptionRecord->ExceptionCode; + if (exception_code == EXCEPTION_BREAKPOINT || + exception_code == DBG_PRINTEXCEPTION_C || + exception_code == DBG_PRINTEXCEPTION_WIDE_C) + return EXCEPTION_CONTINUE_SEARCH; + + LOG(FATAL) << stack_trace(exception_info); + + ZyanU64 opcode_address = exception_info->ContextRecord->Rip; + ZydisDisassembledInstruction instruction; + ZydisDisassembleIntel(ZYDIS_MACHINE_MODE_LONG_64, opcode_address, reinterpret_cast(opcode_address), 32, &instruction); + + exception_info->ContextRecord->Rip += instruction.info.length; + + return EXCEPTION_CONTINUE_EXECUTION; + } +} \ No newline at end of file diff --git a/src/logger/exception_handler.hpp b/src/logger/exception_handler.hpp new file mode 100644 index 00000000..2043200e --- /dev/null +++ b/src/logger/exception_handler.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "common.hpp" + +namespace big +{ + class exception_handler final + { + public: + exception_handler(); + virtual ~exception_handler(); + + private: + void* m_exception_handler; + + }; + + extern LONG vectored_exception_handler(EXCEPTION_POINTERS* exception_info); +} \ No newline at end of file diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 00000000..3b3b8656 --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,212 @@ +#pragma once +#include "logger.hpp" +#include "memory/module.hpp" + +namespace big +{ + template + std::time_t to_time_t(TP tp) + { + using namespace std::chrono; + auto sctp = time_point_cast(tp - TP::clock::now() + + system_clock::now()); + return system_clock::to_time_t(sctp); + } + + logger::logger(std::string_view console_title, file file, bool attach_console) : + m_attach_console(attach_console), + m_did_console_exist(false), + m_console_logger(&logger::format_console), + m_console_title(console_title), + m_original_console_mode(0), + m_console_handle(nullptr), + m_file(file) + { + auto module = memory::module("ntdll.dll"); + if (const auto env_no_color = std::getenv("NO_COLOR"); module.get_export("wine_get_version") || (env_no_color && strlen(env_no_color))) + { + LOG(VERBOSE) << "Using simple logger."; + m_console_logger = &logger::format_console_simple; + } + + initialize(); + + g_log = this; + } + + logger::~logger() + { + g_log = nullptr; + } + + void logger::initialize() + { + if (m_attach_console) + { + if (m_did_console_exist = ::AttachConsole(GetCurrentProcessId()); !m_did_console_exist) + AllocConsole(); + + if (m_console_handle = GetStdHandle(STD_OUTPUT_HANDLE); m_console_handle != nullptr) + { + SetConsoleTitleA(m_console_title.data()); + SetConsoleOutputCP(CP_UTF8); + + DWORD console_mode; + GetConsoleMode(m_console_handle, &console_mode); + m_original_console_mode = console_mode; + + // terminal like behaviour enable full color support + console_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + // prevent clicking in terminal from suspending our main thread + console_mode &= ~(ENABLE_QUICK_EDIT_MODE); + + SetConsoleMode(m_console_handle, console_mode); + } + } + create_backup(); + open_outstreams(); + + Logger::Init(); + Logger::AddSink([this](LogMessagePtr msg) + { + (this->*m_console_logger)(std::move(msg)); + }); + Logger::AddSink([this](LogMessagePtr msg) + { + format_file(std::move(msg)); + }); + } + + void logger::destroy() + { + Logger::Destroy(); + close_outstreams(); + + if (m_did_console_exist) + SetConsoleMode(m_console_handle, m_original_console_mode); + + if (!m_did_console_exist && m_attach_console) + FreeConsole(); + } + + void logger::create_backup() + { + if (m_file.exists()) + { + auto file_time = std::filesystem::last_write_time(m_file.get_path()); + auto time_t = to_time_t(file_time); + auto local_time = std::localtime(&time_t); + + m_file.move( + std::format( + "./backup/{:0>2}-{:0>2}-{}-{:0>2}-{:0>2}-{:0>2}_{}", + local_time->tm_mon + 1, + local_time->tm_mday, + local_time->tm_year + 1900, + local_time->tm_hour, + local_time->tm_min, + local_time->tm_sec, + m_file.get_path().filename().string().c_str() + ) + ); + } + } + + void logger::open_outstreams() + { + if (m_attach_console) + m_console_out.open("CONOUT$", std::ios_base::out | std::ios_base::app); + + m_file_out.open(m_file.get_path(), std::ios_base::out | std::ios_base::trunc); + } + + void logger::close_outstreams() + { + if (m_attach_console) + m_console_out.close(); + + m_file_out.close(); + } + + const LogColor get_color(const eLogLevel level) + { + switch (level) + { + case VERBOSE: + return LogColor::BLUE; + case INFO: + return LogColor::GREEN; + case WARNING: + return LogColor::YELLOW; + case FATAL: + return LogColor::RED; + } + return LogColor::WHITE; + } + + const char* get_level_string(const eLogLevel level) + { + constexpr std::array levelStrings = {{ + { "DEBUG" }, + { "INFO" }, + { "WARN" }, + { "ERROR" } + }}; + + return levelStrings[level]; + } + + void logger::format_console(const LogMessagePtr msg) + { + const auto color = get_color(msg->Level()); + + const auto timestamp = std::format("{0:%H:%M:%S}", msg->Timestamp()); + const auto& location = msg->Location(); + const auto level = msg->Level(); + + const auto file = std::filesystem::path(location.file_name()).filename().string(); + + m_console_out + << "[" << timestamp << "]" + << ADD_COLOR_TO_STREAM(color) + << "[" << get_level_string(level) << "/" << file << ":" << location.line() << "] " + << RESET_STREAM_COLOR + << msg->Message() + << std::flush; + } + + void logger::format_console_simple(const LogMessagePtr msg) + { + const auto color = get_color(msg->Level()); + + const auto timestamp = std::format("{0:%H:%M:%S}", msg->Timestamp()); + const auto& location = msg->Location(); + const auto level = msg->Level(); + + const auto file = std::filesystem::path(location.file_name()).filename().string(); + + m_console_out + << "[" << timestamp << "]" + << "[" << get_level_string(level) << "/" << file << ":" << location.line() << "] " + << msg->Message() + << std::flush; + } + + void logger::format_file(const LogMessagePtr msg) + { + if (!m_file_out.is_open()) + return; + + const auto timestamp = std::format("{0:%H:%M:%S}", msg->Timestamp()); + const auto& location = msg->Location(); + const auto level = msg->Level(); + + const auto file = std::filesystem::path(location.file_name()).filename().string(); + + m_file_out + << "[" << timestamp << "]" + << "[" << get_level_string(level) << "/" << file << ":" << location.line() << "] " + << msg->Message() + << std::flush; + } +} \ No newline at end of file diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp new file mode 100644 index 00000000..fcefcccd --- /dev/null +++ b/src/logger/logger.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "common.hpp" +#include "file_manager.hpp" +#include +using namespace al; + +namespace big +{ +#define ADD_COLOR_TO_STREAM(color) "\x1b[" << int(color) << "m" +#define RESET_STREAM_COLOR "\x1b[" << int(LogColor::RESET) << "m" +#define HEX_TO_UPPER(value) "0x" << std::hex << std::uppercase << (DWORD64)value << std::dec << std::nouppercase + + enum class LogColor + { + RESET, + WHITE = 97, + CYAN = 36, + MAGENTA = 35, + BLUE = 34, + GREEN = 32, + YELLOW = 33, + RED = 31, + BLACK = 30 + }; + + class logger final + { + + public: + logger(std::string_view console_title, file file, bool attach_console = true); + virtual ~logger(); + + void initialize(); + void destroy(); + + private: + void create_backup(); + + void open_outstreams(); + void close_outstreams(); + + void format_console(const LogMessagePtr msg); + void format_console_simple(const LogMessagePtr msg); + void format_file(const LogMessagePtr msg); + + private: + bool m_attach_console; + bool m_did_console_exist; + + void (logger::* m_console_logger)(const LogMessagePtr msg); + + std::string_view m_console_title; + DWORD m_original_console_mode; + HANDLE m_console_handle; + + std::ofstream m_console_out; + std::ofstream m_file_out; + + file m_file; + + }; + + inline logger* g_log = nullptr; +} \ No newline at end of file diff --git a/src/logger/stack_trace.cpp b/src/logger/stack_trace.cpp new file mode 100644 index 00000000..9228cfe9 --- /dev/null +++ b/src/logger/stack_trace.cpp @@ -0,0 +1,222 @@ +#include "stack_trace.hpp" +#include "memory/module.hpp" +#include +#include + +namespace big +{ + stack_trace::stack_trace(EXCEPTION_POINTERS* exception_info) : + m_exception_info(exception_info), + m_frame_pointers(32) + { + static std::mutex m; + std::lock_guard lock(m); + + SymInitialize(GetCurrentProcess(), nullptr, true); + + m_dump << exception_code_to_string(exception_info->ExceptionRecord->ExceptionCode) << '\n'; + dump_module_info(); + dump_registers(); + dump_stacktrace(); + m_dump << "\n--------End of exception--------\n"; + } + + stack_trace::~stack_trace() + { + SymCleanup(GetCurrentProcess()); + } + + std::string stack_trace::str() const + { + return m_dump.str(); + } + + // I'd prefer to make some sort of global instance that cache all modules once instead of doing this every time + void stack_trace::dump_module_info() + { + m_dump << "Dumping modules:\n"; + + // modules cached already + if (m_modules.size()) + { + for (const auto& module : m_modules) + { + m_dump + << module.m_path.filename().string() + << " Base Address: " << HEX_TO_UPPER(module.m_base) + << " Size: " << module.m_size << '\n'; + } + return; + } + + const auto peb = reinterpret_cast(NtCurrentTeb()->ProcessEnvironmentBlock); + if (!peb) + return; + + const auto ldr_data = peb->Ldr; + if (!ldr_data) + return; + + const auto module_list = &ldr_data->InMemoryOrderModuleList; + auto module_entry = module_list->Flink; + for (; module_list != module_entry; module_entry = module_entry->Flink) + { + const auto table_entry = CONTAINING_RECORD(module_entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (!table_entry) + continue; + + if (table_entry->FullDllName.Buffer) + { + auto mod_info = module_info(table_entry->FullDllName.Buffer, table_entry->DllBase); + + m_dump + << mod_info.m_path.filename().string() + << " Base Address: " << HEX_TO_UPPER(mod_info.m_base) + << " Size: " << mod_info.m_size << '\n'; + + m_modules.emplace_back(std::move(mod_info)); + } + } + } + + void stack_trace::dump_registers() + { + const auto context = m_exception_info->ContextRecord; + + m_dump + << "Dumping registers:\n" + << "RAX: " << HEX_TO_UPPER(context->Rax) << '\n' + << "RCX: " << HEX_TO_UPPER(context->Rcx) << '\n' + << "RDX: " << HEX_TO_UPPER(context->Rdx) << '\n' + << "RBX: " << HEX_TO_UPPER(context->Rbx) << '\n' + << "RSI: " << HEX_TO_UPPER(context->Rsi) << '\n' + << "RDI: " << HEX_TO_UPPER(context->Rdi) << '\n' + << "RSP: " << HEX_TO_UPPER(context->Rsp) << '\n' + << "RBP: " << HEX_TO_UPPER(context->Rbp) << '\n' + << "R8: " << HEX_TO_UPPER(context->R8) << '\n' + << "R9: " << HEX_TO_UPPER(context->R9) << '\n' + << "R10: " << HEX_TO_UPPER(context->R10) << '\n' + << "R11: " << HEX_TO_UPPER(context->R11) << '\n' + << "R12: " << HEX_TO_UPPER(context->R12) << '\n' + << "R13: " << HEX_TO_UPPER(context->R13) << '\n' + << "R14: " << HEX_TO_UPPER(context->R14) << '\n' + << "R15: " << HEX_TO_UPPER(context->R15) << '\n'; + } + + void stack_trace::dump_stacktrace() + { + m_dump << "Dumping stacktrace:"; + grab_stacktrace(); + + // alloc once + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; + auto symbol = reinterpret_cast(buffer); + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement64; + DWORD displacement; + + + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (size_t i = 0; i < m_frame_pointers.size() && m_frame_pointers[i]; ++i) + { + const auto addr = m_frame_pointers[i]; + + m_dump << "\n[" << i << "]\t"; + if (SymFromAddr(GetCurrentProcess(), addr, &displacement64, symbol)) + { + if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &displacement, &line)) + { + m_dump << line.FileName << " L: " << line.LineNumber << " " << std::string_view(symbol->Name, symbol->NameLen); + + continue; + } + const auto module_info = get_module_by_address(addr); + m_dump << module_info->m_path.filename().string() << " " << std::string_view(symbol->Name, symbol->NameLen); + + continue; + } + const auto module_info = get_module_by_address(addr); + m_dump << module_info->m_path.filename().string() << "+" << HEX_TO_UPPER(addr - module_info->m_base) << " " << HEX_TO_UPPER(addr); + } + } + + void stack_trace::grab_stacktrace() + { + CONTEXT context = *m_exception_info->ContextRecord; + + STACKFRAME64 frame{}; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrPC.Offset = context.Rip; + frame.AddrFrame.Offset = context.Rbp; + frame.AddrStack.Offset = context.Rsp; + + for (size_t i = 0; i < m_frame_pointers.size(); ++i) + { + if (!StackWalk64( + IMAGE_FILE_MACHINE_AMD64, + GetCurrentProcess(), + GetCurrentThread(), + &frame, + &context, + nullptr, + SymFunctionTableAccess64, + SymGetModuleBase64, + nullptr)) + { + break; + } + m_frame_pointers[i] = frame.AddrPC.Offset; + } + } + + const stack_trace::module_info* stack_trace::get_module_by_address(uint64_t addr) const + { + for (auto& mod_info : m_modules) + { + if (mod_info.m_base < addr && addr < mod_info.m_base + mod_info.m_size) + { + return &mod_info; + } + } + return nullptr; + } + + std::string stack_trace::exception_code_to_string(const DWORD code) + { + #define MAP_PAIR_STRINGIFY(x) {x, #x} + static const std::map exceptions = { + MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION) + , MAP_PAIR_STRINGIFY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) + , MAP_PAIR_STRINGIFY(EXCEPTION_DATATYPE_MISALIGNMENT) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DENORMAL_OPERAND) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DIVIDE_BY_ZERO) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INVALID_OPERATION) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_OVERFLOW) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_STACK_CHECK) + , MAP_PAIR_STRINGIFY(EXCEPTION_FLT_UNDERFLOW) + , MAP_PAIR_STRINGIFY(EXCEPTION_ILLEGAL_INSTRUCTION) + , MAP_PAIR_STRINGIFY(EXCEPTION_IN_PAGE_ERROR) + , MAP_PAIR_STRINGIFY(EXCEPTION_INT_DIVIDE_BY_ZERO) + , MAP_PAIR_STRINGIFY(EXCEPTION_INT_OVERFLOW) + , MAP_PAIR_STRINGIFY(EXCEPTION_INVALID_DISPOSITION) + , MAP_PAIR_STRINGIFY(EXCEPTION_NONCONTINUABLE_EXCEPTION) + , MAP_PAIR_STRINGIFY(EXCEPTION_PRIV_INSTRUCTION) + , MAP_PAIR_STRINGIFY(EXCEPTION_STACK_OVERFLOW) + , MAP_PAIR_STRINGIFY(EXCEPTION_BREAKPOINT) + , MAP_PAIR_STRINGIFY(EXCEPTION_SINGLE_STEP) + }; + + if (const auto&it = exceptions.find(code); it != exceptions.end()) + return it->second; + + return "UNKNOWN_EXCEPTION"; + } +} diff --git a/src/logger/stack_trace.hpp b/src/logger/stack_trace.hpp new file mode 100644 index 00000000..aa7582e9 --- /dev/null +++ b/src/logger/stack_trace.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "common.hpp" + +namespace big +{ + class stack_trace + { + public: + stack_trace(EXCEPTION_POINTERS* exception_info); + virtual ~stack_trace(); + + std::string str() const; + + friend std::ostream& operator<< (std::ostream& os, const stack_trace& st); + + private: + struct module_info + { + module_info(std::filesystem::path path, void* base) : + m_path(path), + m_base(reinterpret_cast(base)) + { + const auto dos_header = reinterpret_cast(base); + const auto nt_header = reinterpret_cast(m_base + dos_header->e_lfanew); + + m_size = nt_header->OptionalHeader.SizeOfCode; + } + + std::filesystem::path m_path; + uintptr_t m_base; + size_t m_size; + }; + + private: + void dump_module_info(); + void dump_registers(); + void dump_stacktrace(); + void grab_stacktrace(); + const module_info* get_module_by_address(uint64_t addr) const; + + static std::string exception_code_to_string(const DWORD code); + + private: + EXCEPTION_POINTERS* m_exception_info; + + std::stringstream m_dump; + std::vector m_frame_pointers; + + inline static std::vector m_modules; + + + }; + + inline std::ostream& operator<< (std::ostream& os, const stack_trace& st) + { + os << st.str(); + + return os; + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 7d3c6cb6..9468630f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,6 @@ #include "core/globals.hpp" #include "fiber_pool.hpp" #include "gui.hpp" -#include "logger.hpp" #include "hooking.hpp" #include "pointers.hpp" #include "renderer.hpp" @@ -29,6 +28,8 @@ #include "services/matchmaking/matchmaking_service.hpp" #include "services/api/api_service.hpp" +#include "logger/exception_handler.hpp" + BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) { using namespace big; @@ -39,6 +40,8 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) g_hmodule = hmod; g_main_thread = CreateThread(nullptr, 0, [](PVOID) -> DWORD { + auto handler = exception_handler(); + while (!FindWindow("grcWindow", nullptr)) std::this_thread::sleep_for(100ms); @@ -56,7 +59,7 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID) try { LOG(INFO) << "Yim's Menu Initializing"; - LOGF(INFO, "Git Info\n\tBranch:\t%s\n\tHash:\t%s\n\tDate:\t%s", version::GIT_BRANCH, version::GIT_SHA1, version::GIT_DATE); + LOGF(INFO, "Git Info\n\tBranch:\t{}\n\tHash:\t{}\n\tDate:\t{}", version::GIT_BRANCH, version::GIT_SHA1, version::GIT_DATE); g_translation_service.init(); LOG(INFO) << "Translation Service initialized."; diff --git a/src/memory/batch.cpp b/src/memory/batch.cpp index 7e5bc6e4..7c8c9668 100644 --- a/src/memory/batch.cpp +++ b/src/memory/batch.cpp @@ -1,5 +1,4 @@ -#include "../common.hpp" -#include "../logger.hpp" +#include "common.hpp" #include "batch.hpp" #include "range.hpp" diff --git a/src/memory/module.cpp b/src/memory/module.cpp index 2706a7ad..251b08ab 100644 --- a/src/memory/module.cpp +++ b/src/memory/module.cpp @@ -46,7 +46,7 @@ namespace memory const auto giveup_time = time.has_value() ? std::make_optional(std::chrono::high_resolution_clock::now() + time.value()) : std::nullopt; - LOG(G3LOG_DEBUG) << "Waiting for " << m_name << "..."; + LOG(VERBOSE) << "Waiting for " << m_name << "..."; while (!try_get_module()) { if (giveup_time.has_value() && giveup_time <= std::chrono::high_resolution_clock::now()) diff --git a/src/script.cpp b/src/script.cpp index 1d832135..23b28bb9 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,6 +1,5 @@ #pragma once #include "common.hpp" -#include "logger.hpp" #include "script.hpp" namespace big diff --git a/src/script_function.cpp b/src/script_function.cpp index d521f74c..029c6f21 100644 --- a/src/script_function.cpp +++ b/src/script_function.cpp @@ -30,7 +30,7 @@ namespace big if (!location) LOG(FATAL) << "Failed to find pattern " << m_name << " in script " << program->m_name; else - LOG(DEBUG) << "Found pattern " << m_name << " in script " << program->m_name; + LOG(VERBOSE) << "Found pattern " << m_name << " in script " << program->m_name; m_ip = location.value() + m_offset; } diff --git a/src/services/gta_data/gta_data_service.cpp b/src/services/gta_data/gta_data_service.cpp index 82e5d21d..471a69c2 100644 --- a/src/services/gta_data/gta_data_service.cpp +++ b/src/services/gta_data/gta_data_service.cpp @@ -131,13 +131,13 @@ namespace big void gta_data_service::load_data() { - LOG(G3LOG_DEBUG) << "Loading data from cache."; + LOG(VERBOSE) << "Loading data from cache."; load_peds(); load_vehicles(); load_weapons(); - LOG(G3LOG_DEBUG) << "Loaded all data from cache."; + LOG(VERBOSE) << "Loaded all data from cache."; } void gta_data_service::load_peds() @@ -256,7 +256,7 @@ namespace big if (dlc_name == "mpG9EC") { - LOG(G3LOG_DEBUG) << "Bad DLC, skipping..."; + LOG(VERBOSE) << "Bad DLC, skipping..."; return std::size_t(0); } @@ -410,7 +410,7 @@ skip: m_update_state = eGtaDataUpdateState::IDLE; LOG(INFO) << "Cache has been rebuilt.\n\tPeds: " << peds.size() << "\n\tVehicles: " << vehicles.size() << "\n\tWeapons: " << weapons.size(); - LOG(G3LOG_DEBUG) << "Starting cache saving procedure..."; + LOG(VERBOSE) << "Starting cache saving procedure..."; g_thread_pool->push([this, peds = std::move(peds), vehicles = std::move(vehicles), weapons = std::move(weapons)] { const auto game_version = std::strtoul(g_pointers->m_game_version, nullptr, 10); diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 2473bee4..a1faa2be 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -18,7 +18,7 @@ namespace big { const std::uint32_t thread_count = std::thread::hardware_concurrency(); - LOG(G3LOG_DEBUG) << "Allocating " << thread_count << " threads in thread pool."; + LOG(VERBOSE) << "Allocating " << thread_count << " threads in thread pool."; this->m_thread_pool.reserve(thread_count); for (std::uint32_t i = 0; i < thread_count; i++) @@ -80,6 +80,6 @@ namespace big } } - LOG(G3LOG_DEBUG) << "Thread " << std::this_thread::get_id() << " exiting..."; + LOG(VERBOSE) << "Thread " << std::this_thread::get_id() << " exiting..."; } } \ No newline at end of file diff --git a/src/views/debug/view_debug_misc.cpp b/src/views/debug/view_debug_misc.cpp index 49bc97cc..8e3d79e0 100644 --- a/src/views/debug/view_debug_misc.cpp +++ b/src/views/debug/view_debug_misc.cpp @@ -15,6 +15,17 @@ namespace big { if (ImGui::BeginTabItem("DEBUG_TAB_MISC"_T.data())) { + if (components::button("MOV QWORD")) + { + *static_cast(nullptr) = 0; + uint64_t i = *static_cast(nullptr); + } + + if (components::button("MOV 0xdead")) + { + *((unsigned int*)0) = 0xDEAD; + } + if (components::button("Dump entrypoints")) { system::dump_entry_points();