From 8ebdcb27a8cc867c0762188cb4d84d8e085f4346 Mon Sep 17 00:00:00 2001 From: Musa Date: Thu, 18 Dec 2025 18:15:11 -0500 Subject: [PATCH] Add stack trace, fix tracking for expandable arenas --- lib/Base/Arena.cpp | 36 +++++++-- lib/Base/Arena.h | 9 +++ lib/Base/Arena_Windows.cpp | 8 ++ lib/Base/Array.h | 1 - lib/Base/Base_Thread_Context.cpp | 3 + lib/Base/Base_Thread_Context.h | 121 ++++++++++++++++++++++++++++++- lib/Base/ErrorType.cpp | 7 +- lib/Base/Expandable_Arena.h | 16 +--- lib/Base/String.cpp | 15 ++++ lib/Base/String.h | 1 + lib/OS/OS_Win32.cpp | 11 ++- src/Base_Entry_Point.cpp | 6 +- src/Ex1.cpp | 41 ++++++++--- 13 files changed, 232 insertions(+), 43 deletions(-) diff --git a/lib/Base/Arena.cpp b/lib/Base/Arena.cpp index 42f37c6..508d40f 100644 --- a/lib/Base/Arena.cpp +++ b/lib/Base/Arena.cpp @@ -85,7 +85,6 @@ bool arena_commit_first_pages (Arena* arena, s64 commit_size, s64 start_offset) // Arena* bootstrap_arena (Arena_Reserve new_reserve, s32 default_commit_page_count) { Arena* bootstrap_arena_internal (Arena_Reserve new_reserve, s32 default_commit_page_count, string file_path, string function_name, s32 line_number) { - // #TODO: Store info in debug mode: // + Save thread ID/name MAKE A COPY OBVIOUSLY! + PUSH default_allocator! // WE USE default_allocator because this arena may be used to back an array! s64 commit_size = default_commit_page_count * PLATFORM_MEMORY_PAGE_SIZE; @@ -103,12 +102,14 @@ Arena* bootstrap_arena_internal (Arena_Reserve new_reserve, s32 default_commit_p arena_set_bootstrap_flag(arena_ptr); -// #if BUILD_DEBUG -// { push_allocator(default_allocator()); -// arena_ptr->file_path = copy_string(file_path); -// arena_ptr->function_name = -// } -// #endif +#if BUILD_DEBUG + // #TODO: use thread_context()->stack_trace if present instead? + { arena_ptr->file_path = file_path; + arena_ptr->function_name = function_name; + arena_ptr->line_number = line_number; + add_arena_to_in_use_list(arena_ptr); + } +#endif return arena_ptr; } @@ -321,4 +322,23 @@ void* fixed_arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 o } return nullptr; -} \ No newline at end of file +} + +force_inline void initialize_arenas_in_use_list () { + if (arenas_in_use.allocated > 0) return; + mutex_init(&arenas_in_use_mutex); + arenas_in_use.allocator = default_allocator(); + array_reserve(arenas_in_use, 256); +} + +force_inline void add_arena_to_in_use_list (Arena* arena) { + Assert(arenas_in_use.allocated > 0); // check we initialized! + lock_guard(&arenas_in_use_mutex); + array_add(arenas_in_use, arena); +} + +force_inline void remove_arena_from_in_use_list (Arena* arena) { + Assert(arenas_in_use.allocated > 0); // check we initialized! + lock_guard(&arenas_in_use_mutex); + array_unordered_remove_by_value(arenas_in_use, arena, 1); +} diff --git a/lib/Base/Arena.h b/lib/Base/Arena.h index ac05285..c066c8e 100644 --- a/lib/Base/Arena.h +++ b/lib/Base/Arena.h @@ -184,3 +184,12 @@ s64 committed_bytes (ArrayView arenas) { return sum; } + +#if BUILD_DEBUG + global Mutex arenas_in_use_mutex; + global Array arenas_in_use; + + force_inline void initialize_arenas_in_use_list (); + force_inline void add_arena_to_in_use_list(Arena* arena); + force_inline void remove_arena_from_in_use_list (Arena* arena); +#endif diff --git a/lib/Base/Arena_Windows.cpp b/lib/Base/Arena_Windows.cpp index e0e5bbd..5c7bbe2 100644 --- a/lib/Base/Arena_Windows.cpp +++ b/lib/Base/Arena_Windows.cpp @@ -60,6 +60,14 @@ void free_pages_down_to (Arena* arena, s64 pages_to_keep) { void arena_delete (Arena* arena) { if (!is_valid(arena)) return; + +#if BUILD_DEBUG + { //default_allocator_free(arena->file_path.data); + //default_allocator_free(arena->function_name.data); + remove_arena_from_in_use_list(arena); + } +#endif + bool arena_was_boostrapped = (arena->flags & Arena_Flags::Is_Bootstrapped) == Arena_Flags::Is_Bootstrapped; // s64 size_tmp = reserve_size(arena); diff --git a/lib/Base/Array.h b/lib/Base/Array.h index 4d4b34c..203340b 100644 --- a/lib/Base/Array.h +++ b/lib/Base/Array.h @@ -235,7 +235,6 @@ void array_unordered_remove_by_index (Array& src, s64 index) { template s64 array_unordered_remove_by_value (Array& src, T item, s64 max_count_to_remove) { s64 removed_count = 0; - for (s64 i = 0; i < src.count; i += 1) { if (src[i] == item) { removed_count += 1; diff --git a/lib/Base/Base_Thread_Context.cpp b/lib/Base/Base_Thread_Context.cpp index d2c257c..e46b65b 100644 --- a/lib/Base/Base_Thread_Context.cpp +++ b/lib/Base/Base_Thread_Context.cpp @@ -15,6 +15,9 @@ internal void Bootstrap_Main_Thread_Context () { // memset(arena_free_list, 0, sizeof(Arena_Free_List)); // initialize_arena_free_list(default_allocator()); + // 1b. Setup arena in-use list: + initialize_arenas_in_use_list(); + // 2. #NewContext Setup thread local context ExpandableArena* arena_ex = bootstrap_expandable_arena(Arena_Reserve::Size_64M); diff --git a/lib/Base/Base_Thread_Context.h b/lib/Base/Base_Thread_Context.h index 9796f4c..fa6aacf 100644 --- a/lib/Base/Base_Thread_Context.h +++ b/lib/Base/Base_Thread_Context.h @@ -1,4 +1,17 @@ // #hacky fwd declares +struct Source_Code_Location { + string file_name; + string function_name; + s32 line_number; +}; + +struct Stack_Trace_Node { + Stack_Trace_Node* next; + // Information + Source_Code_Location data; + s32 call_depth; +}; + struct Error; struct Graphics; @@ -12,7 +25,7 @@ struct Thread_Context { u16 default_allocator_alignment = 16; Logger logger = {nullptr, nullptr}; String_Builder* log_builder; - // Stack_Trace* stack_trace; + Stack_Trace_Node* stack_trace; // use `list(stack_trace)` in watch window of raddbg to view as array! Array child_threads; // maybe should be linked-list? Thread_Context* parent_thread_context = nullptr; // so we can remove from above array @@ -44,12 +57,112 @@ struct Push_Allocator { Push_Allocator (Allocator new_allocator) { context = thread_context(); - old_allocator = context->allocator; - context->allocator = new_allocator; + if (this->context != nullptr) { + old_allocator = context->allocator; + context->allocator = new_allocator; + } else { + old_allocator = default_allocator(); + } } ~Push_Allocator () { - context->allocator = old_allocator; + if (this->context != nullptr) { + context->allocator = old_allocator; + } } }; +// #stack_trace +#define ENABLE_STACK_TRACE BUILD_DEBUG + +void push_stack_trace_internal (Thread_Context* context, string file_name, string function_name, s32 line_number) { + if (context == nullptr) return; + Assert(context != nullptr); + // #no_context allocation + Stack_Trace_Node* new_node = (Stack_Trace_Node*)default_allocator_new(sizeof(Stack_Trace_Node)); + + new_node->data.file_name = file_name; + new_node->data.function_name = function_name; + new_node->data.line_number = line_number; + new_node->next = nullptr; + + if (context->stack_trace == nullptr) { + new_node->call_depth = 1; + + } else { + new_node->call_depth = context->stack_trace->call_depth + 1; + + new_node->next = context->stack_trace; + } + + context->stack_trace = new_node; +} + +void pop_stack_trace_internal (Thread_Context* context) { + if (context == nullptr) return; + Stack_Trace_Node* old_node = context->stack_trace; + context->stack_trace = old_node->next; + + default_allocator_free(old_node); +} + +#if ENABLE_STACK_TRACE +#define stack_trace() \ + Push_Stack_Trace Concat(_push_stack_trace_guard_, __LINE__)(__FILE__, __FUNCTION__, __LINE__) +#else +#define stack_trace() +#endif + +struct Push_Stack_Trace { + Thread_Context* context; + + Push_Stack_Trace (string file_name, string function_name, s32 line_number) { + context = thread_context(); + push_stack_trace_internal(context, file_name, function_name, line_number); + } + + ~Push_Stack_Trace () { + pop_stack_trace_internal(context); + } +}; + +// #TODO: precede with something like: os_write_string_unsynchronized("Fatal Error!\n\nStack trace:", true); +string generate_stack_trace (Thread_Context* context) { // #no_context + String_Builder* sb = new_string_builder(Arena_Reserve::Size_64K); + + Stack_Trace_Node* node = context->stack_trace; + + print_to_builder(sb, "Thread index: %d, thread name: %s\n\n", context->thread_idx, context->thread_name.data); + + while (node) { + append(sb, format_string("%s:%d: %s\n", node->data.file_name.data, node->data.line_number, node->data.function_name.data)); + + node = node->next; + } + + append(sb, "\n"); + + push_allocator(default_allocator()); + return builder_to_string(sb); +} + +// We don't want to use context logger here! +void print_stack_trace () { + Thread_Context* context = thread_context(); + + Stack_Trace_Node* node = context->stack_trace; + + constexpr bool TO_STANDARD_ERROR = true; + + os_write_string_unsynchronized(generate_stack_trace(context), TO_STANDARD_ERROR); + // while (node) { + // os_write_string_unsynchronized(node->data.file_name, TO_STANDARD_ERROR); + // string line_number_str = format_string(":%d: ", node->data.line_number); // maybe I shouldn't do this? + // os_write_string_unsynchronized(line_number_str, TO_STANDARD_ERROR); + // // os_write_string_unsynchronized("'", TO_STANDARD_ERROR); + // os_write_string_unsynchronized(node->data.function_name, TO_STANDARD_ERROR); + // os_write_string_unsynchronized("\n", TO_STANDARD_ERROR); + + // node = node->next; + // } +} diff --git a/lib/Base/ErrorType.cpp b/lib/Base/ErrorType.cpp index 54182a2..2c20e16 100644 --- a/lib/Base/ErrorType.cpp +++ b/lib/Base/ErrorType.cpp @@ -82,7 +82,7 @@ void log_error_internal (string file_path, string function_name, s32 line_number Assert(tctx != nullptr); push_arena(tctx->error_arena); - String_Builder* sb = new_string_builder(Arena_Reserve::Size_64K); + String_Builder* sb = thread_context()->log_builder; print_to_builder(sb, "%s ", error_severity(severity)); @@ -93,7 +93,10 @@ void log_error_internal (string file_path, string function_name, s32 line_number append(sb, "\n"); - string error_string = builder_to_string(sb); + string error_string = copy_string(string_view(sb)); + + reset_string_builder(sb); + Error* error = new_error(severity, error_string); // Additional information error->thread_id = tctx->thread_idx; diff --git a/lib/Base/Expandable_Arena.h b/lib/Base/Expandable_Arena.h index 52405a7..92ba9bf 100644 --- a/lib/Base/Expandable_Arena.h +++ b/lib/Base/Expandable_Arena.h @@ -5,23 +5,9 @@ // DO NOT MERGE WITH `Arena`, we need fixed size arenas so that we can back // `ArenaArray`s. -struct ExpandableArena { - u8* current_point = nullptr; - u8* memory_base = nullptr; - u8* first_uncommitted_page = nullptr; - u16 alignment = CPU_REGISTER_WIDTH_BYTES; - Arena_Reserve reserve_size = Arena_Reserve::Size_64K; - Arena_Flags flags = Arena_Flags::None; - u32 initial_commit_page_count = ARENA_DEFAULT_COMMIT_PAGE_COUNT; - // Note that this downcasts to Arena*, so can be initialized in the same way. +struct ExpandableArena : Arena { Arena* current; Array next_arenas; -#if BUILD_DEBUG - string info; - string file_path; - string function_name; - s32 line_number; -#endif }; #if BUILD_DEBUG diff --git a/lib/Base/String.cpp b/lib/Base/String.cpp index c1c1992..b2446c3 100644 --- a/lib/Base/String.cpp +++ b/lib/Base/String.cpp @@ -33,6 +33,21 @@ string copy_string (string s) { return str; } +string copy_string_no_context (string s) { + if (s.count <= 0) + return ""; + string str = {}; + + str.count = s.count; + str.data = (u8*)default_allocator_new(s.count + 1); + + memcpy(str.data, s.data, s.count); + + str.data[str.count] = '\0'; // null-terminate for backwards compatibility? + + return str; +} + string copy_string (char* c_string) { string str = {}; s64 string_length = strlen(c_string); diff --git a/lib/Base/String.h b/lib/Base/String.h index 7626616..d1dae81 100644 --- a/lib/Base/String.h +++ b/lib/Base/String.h @@ -77,6 +77,7 @@ bool is_valid (string s); bool is_c_string (string s); u8* to_c_string (string s); // #allocates string copy_string (string s); // #allocates, returned string is #null-terminated. +string copy_string_no_context (string s); string copy_string (char* c_string); // #allocates, returned string is #null-terminated. string to_string (ArrayView str); ArrayView to_view (string s); diff --git a/lib/OS/OS_Win32.cpp b/lib/OS/OS_Win32.cpp index 100bf2d..9218596 100644 --- a/lib/OS/OS_Win32.cpp +++ b/lib/OS/OS_Win32.cpp @@ -136,7 +136,13 @@ internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) for (;;) Sleep(1000); } + // #TODO: Runtime assertion failed? + // #Exception handling code (TODO) + if (thread_context()->stack_trace) { + os_write_string_unsynchronized("\n[Win32_Exception_Filter] Stack Trace\n", true); + print_stack_trace(); + } ExitProcess(1); @@ -144,7 +150,10 @@ internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) } // internal void Main_Entry_Point (int argc, WCHAR **argv); -internal void Win32_Entry_Point (int argc, WCHAR **argv) { +internal void Win32_Entry_Point (int argc, WCHAR **argv) { stack_trace(); + os_write_string_unsynchronized("Fatal Error!\n\nStack trace: ", true); + print_stack_trace(); + // Timed_Block_Print("Win32_Entry_Point"); // See: w32_entry_point_caller(); (raddebugger) SetUnhandledExceptionFilter(&Win32_Exception_Filter); diff --git a/src/Base_Entry_Point.cpp b/src/Base_Entry_Point.cpp index 3bd25ae..6e8fc13 100644 --- a/src/Base_Entry_Point.cpp +++ b/src/Base_Entry_Point.cpp @@ -16,7 +16,7 @@ internal void Main_Entry_Point (int argc, WCHAR **argv); #endif #endif -internal void Main_Entry_Point (int argc, WCHAR **argv) { // #entry_point +internal void Main_Entry_Point (int argc, WCHAR **argv) { // #entry_point: Main + Context Setup // #TODO: Check if base frequency is even available. u32 base_frequency = (u32)CPU_Base_Frequency(); set_cpu_base_frequency(base_frequency); // REQUIRED FOR TIMING MODULE! will depend on CPU @@ -28,6 +28,8 @@ internal void Main_Entry_Point (int argc, WCHAR **argv) { // #entry_point // before setting up the thread context! Bootstrap_Main_Thread_Context(); + stack_trace(); // #stack_trace: #entry_point (first stack trace) + #if OS_WINDOWS Win32_Entry_Point(argc, argv); #endif @@ -37,7 +39,7 @@ internal void Main_Entry_Point (int argc, WCHAR **argv) { // #entry_point #if BASE_RUN_TESTS run_post_setup_tests(); #endif -#if BUILD_EXPLORER_APP_WIN32 // #entry_point +#if BUILD_EXPLORER_APP_WIN32 // #entry_point: Custom program Explorer_ImGui_Application_Win32(); // #rename #endif #if BUILD_CUSTOM_GUI diff --git a/src/Ex1.cpp b/src/Ex1.cpp index 0f859cc..0919965 100644 --- a/src/Ex1.cpp +++ b/src/Ex1.cpp @@ -186,13 +186,12 @@ void Ex1_Control_Panel () { using namespace ImGui; push_allocator(temp()); ArrayView drives = os_get_available_drives(); // only includes drives that are ready. - - if (!USN_Journal_Monitoring_Ready(drives[0]) && Button("Enable USN Monitoring for all drives")) { - Win32_Enable_USN_Journal_Monitoring(drives); - } - if (USN_Journal_Monitoring_Ready(drives[0]) && Button("Query USN Journal")) { - Query_USN_Journal(drives); - } + // if (!USN_Journal_Monitoring_Ready(drives[0]) && Button("Enable USN Monitoring for all drives")) { + // Win32_Enable_USN_Journal_Monitoring(drives); + // } + // if (USN_Journal_Monitoring_Ready(drives[0]) && Button("Query USN Journal")) { + // Query_USN_Journal(drives); + // } if (!all_drives_enumerated) { // Text("drive_table is valid: %d", table_is_valid(drive_table)); @@ -228,9 +227,9 @@ void Ex1_Control_Panel () { using namespace ImGui; // string file_path = format_string_temp("%s_DriveData.bin", os_get_machine_name().data); string file_path = "D:/TempSync/Filesystem_Data/MUSA-PC3_DriveData.bin"; Text("fixed file_path: %s", file_path.data); - if (!all_drives_enumerated && file_exists(file_path)) { // #autoload - Deserialize_ST_File_Enumeration(file_path); - } + // if (!all_drives_enumerated && file_exists(file_path)) { // #autoload + // Deserialize_ST_File_Enumeration(file_path); + // } if (drives.count > 0 && !all_drives_enumerated && file_exists(file_path) && Button("Load from file (this machine)")) { Deserialize_ST_File_Enumeration(file_path); // Deserialize_Win32_Drives(file_path); @@ -335,6 +334,28 @@ void ImGui_Debug_Panel () { using namespace ImGui; Text(t); } }*/ +#if BUILD_DEBUG + SeparatorText("Default Allocator Allocations"); + { lock_guard(&allocator_mutex); + auto allocations = to_view(get_general_allocator_data()->allocations); + Text("%s in %lld allocations", + format_bytes(get_general_allocator_data()->total_bytes_allocated).data, + allocations.count); + for_each(a, allocations) { + Text(" [%lld] ptr: %p (size: %lld, alignment: %d)", + a, allocations[a].memory, allocations[a].size, allocations[a].alignment); + } + } + SeparatorText("Arenas in Use"); + { lock_guard(&arenas_in_use_mutex); + Assert(arenas_in_use.allocated > 0); + for_each(a, arenas_in_use) { + auto arena = arenas_in_use[a]; + if (!is_valid(arena)) continue; + Text("[%d], source %s:%d (%s)", a, arena->file_path.data, arena->line_number, arena->function_name.data); + } + } +#endif // BUILD_DEBUG SeparatorText("Child Threads"); SeparatorText("Errors"); ArrayView errors = get_all_errors(thread_context());