diff --git a/src/common.hpp b/src/common.hpp index dc77bcd5..044b4fcc 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -1,96 +1,97 @@ -#ifndef COMMON_INC -#define COMMON_INC - -// clang-format off - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#include "logger/logger.hpp" - -#include "core/globals.hpp" -#include "gta/natives.hpp" -#include "ped/CPed.hpp" - -#include "services/notifications/notification_service.hpp" -#include "services/translation_service/translation_service.hpp" - -// clang-format on - -namespace big -{ - using namespace std::chrono_literals; - - inline HMODULE g_hmodule{}; - inline HANDLE g_main_thread{}; - inline DWORD g_main_thread_id{}; - inline std::atomic_bool g_running{false}; - - inline CPed* g_local_player; -} - -namespace self -{ - inline Ped ped; - inline Player id; - inline Vector3 pos; - inline Vehicle veh; -} - -template -struct template_str -{ - constexpr template_str(const char (&str)[N]) - { - std::copy_n(str, N, value); - } - - char value[N]; -}; - -#endif +#ifndef COMMON_INC +#define COMMON_INC + +// clang-format off + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "logger/logger.hpp" + +#include "core/globals.hpp" +#include "gta/natives.hpp" +#include "ped/CPed.hpp" + +#include "services/notifications/notification_service.hpp" +#include "services/translation_service/translation_service.hpp" + +// clang-format on + +namespace big +{ + using namespace std::chrono_literals; + + inline HMODULE g_hmodule{}; + inline HANDLE g_main_thread{}; + inline DWORD g_main_thread_id{}; + inline std::atomic_bool g_running{false}; + + inline CPed* g_local_player; +} + +namespace self +{ + inline Ped ped; + inline Player id; + inline Vector3 pos; + inline Vehicle veh; +} + +template +struct template_str +{ + constexpr template_str(const char (&str)[N]) + { + std::copy_n(str, N, value); + } + + char value[N]; +}; + +#endif diff --git a/src/logger/exception_handler.cpp b/src/logger/exception_handler.cpp index c5f30733..d8b211f3 100644 --- a/src/logger/exception_handler.cpp +++ b/src/logger/exception_handler.cpp @@ -6,6 +6,14 @@ namespace big { + inline auto hash_stack_trace(std::vector stack_trace) + { + auto data = reinterpret_cast(stack_trace.data()); + std::size_t size = stack_trace.size() * sizeof(uint64_t); + + return std::hash()({ data, size }); + } + exception_handler::exception_handler() { SetErrorMode(0); @@ -14,8 +22,8 @@ namespace big exception_handler::~exception_handler() { - // passing NULL / 0 will make it go back to normal exception handling - SetUnhandledExceptionFilter(0); + // passing nullptr will make it go back to normal exception handling + SetUnhandledExceptionFilter(nullptr); } inline static stack_trace trace; @@ -25,8 +33,16 @@ namespace big if (exception_code == EXCEPTION_BREAKPOINT || exception_code == DBG_PRINTEXCEPTION_C || exception_code == DBG_PRINTEXCEPTION_WIDE_C) return EXCEPTION_CONTINUE_SEARCH; + static std::set logged_exceptions; + trace.new_stack_trace(exception_info); - LOG(FATAL) << trace; + const auto trace_hash = hash_stack_trace(trace.frame_pointers()); + if (const auto it = logged_exceptions.find(trace_hash); it == logged_exceptions.end()) + { + LOG(FATAL) << trace; + + logged_exceptions.insert(trace_hash); + } ZyanU64 opcode_address = exception_info->ContextRecord->Rip; ZydisDisassembledInstruction instruction; diff --git a/src/logger/stack_trace.cpp b/src/logger/stack_trace.cpp index c977a915..e36f4a15 100644 --- a/src/logger/stack_trace.cpp +++ b/src/logger/stack_trace.cpp @@ -1,200 +1,205 @@ -#include "stack_trace.hpp" - -#include "gta/script_thread.hpp" -#include "memory/module.hpp" - -#include -#include - -namespace big -{ - stack_trace::stack_trace() : - m_frame_pointers(32) - { - SymInitialize(GetCurrentProcess(), nullptr, true); - } - - stack_trace::~stack_trace() - { - SymCleanup(GetCurrentProcess()); - } - - void stack_trace::new_stack_trace(EXCEPTION_POINTERS* exception_info) - { - static std::mutex m; - std::lock_guard lock(m); - - m_exception_info = exception_info; - - m_dump << exception_code_to_string(exception_info->ExceptionRecord->ExceptionCode) << '\n'; - - if (g.in_script_vm) - dump_script_info(); - dump_module_info(); - dump_registers(); - dump_stacktrace(); - - m_dump << "\n--------End of exception--------\n"; - } - - 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() - { - // modules cached already - if (m_modules.size()) - return; - - m_dump << "Dumping modules:\n"; - - 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::dump_script_info() - { - m_dump << "Currently executing script: " << rage::scrThread::get()->m_name << '\n'; - m_dump << "Thread program counter (could be inaccurate): " - << m_exception_info->ContextRecord->Rdi - m_exception_info->ContextRecord->Rsi << '\n'; - } - - 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"; - } -} +#include "stack_trace.hpp" + +#include "gta/script_thread.hpp" +#include "memory/module.hpp" + +#include +#include + +namespace big +{ + stack_trace::stack_trace() : + m_frame_pointers(32) + { + SymInitialize(GetCurrentProcess(), nullptr, true); + } + + stack_trace::~stack_trace() + { + SymCleanup(GetCurrentProcess()); + } + + const std::vector& stack_trace::frame_pointers() + { + return m_frame_pointers; + } + + void stack_trace::new_stack_trace(EXCEPTION_POINTERS* exception_info) + { + static std::mutex m; + std::lock_guard lock(m); + + m_exception_info = exception_info; + + m_dump << exception_code_to_string(exception_info->ExceptionRecord->ExceptionCode) << '\n'; + + if (g.in_script_vm) + dump_script_info(); + dump_module_info(); + dump_registers(); + dump_stacktrace(); + + m_dump << "\n--------End of exception--------\n"; + } + + 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() + { + // modules cached already + if (m_modules.size()) + return; + + m_dump << "Dumping modules:\n"; + + 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::dump_script_info() + { + m_dump << "Currently executing script: " << rage::scrThread::get()->m_name << '\n'; + m_dump << "Thread program counter (could be inaccurate): " + << m_exception_info->ContextRecord->Rdi - m_exception_info->ContextRecord->Rsi << '\n'; + } + + 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 index 667c703a..fe5c712d 100644 --- a/src/logger/stack_trace.hpp +++ b/src/logger/stack_trace.hpp @@ -1,68 +1,69 @@ -#pragma once -#include "common.hpp" - -namespace big -{ - class stack_trace - { - public: - stack_trace(); - virtual ~stack_trace(); - - void new_stack_trace(EXCEPTION_POINTERS* exception_info); - std::string str() const; - - friend std::ostream& operator<<(std::ostream& os, const stack_trace& st); - 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 dump_script_info(); - 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; - } - - inline std::ostream& operator<<(std::ostream& os, const stack_trace* st) - { - os << st->str(); - - return os; - } +#pragma once +#include "common.hpp" + +namespace big +{ + class stack_trace + { + public: + stack_trace(); + virtual ~stack_trace(); + + const std::vector& frame_pointers(); + void new_stack_trace(EXCEPTION_POINTERS* exception_info); + std::string str() const; + + friend std::ostream& operator<<(std::ostream& os, const stack_trace& st); + 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 dump_script_info(); + 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; + } + + inline std::ostream& operator<<(std::ostream& os, const stack_trace* st) + { + os << st->str(); + + return os; + } } \ No newline at end of file