Add stack trace, fix tracking for expandable arenas

This commit is contained in:
Musa Mahmood 2025-12-18 18:15:11 -05:00
parent 493efe4d74
commit 8ebdcb27a8
13 changed files with 232 additions and 43 deletions

View File

@ -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;
}
@ -322,3 +323,22 @@ void* fixed_arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 o
return nullptr;
}
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);
}

View File

@ -184,3 +184,12 @@ s64 committed_bytes (ArrayView<Arena*> arenas) {
return sum;
}
#if BUILD_DEBUG
global Mutex arenas_in_use_mutex;
global Array<Arena*> 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

View File

@ -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);

View File

@ -235,7 +235,6 @@ void array_unordered_remove_by_index (Array<T>& src, s64 index) {
template <typename T>
s64 array_unordered_remove_by_value (Array<T>& 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;

View File

@ -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);

View File

@ -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<Thread*> 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();
if (this->context != nullptr) {
old_allocator = context->allocator;
context->allocator = new_allocator;
} else {
old_allocator = default_allocator();
}
}
~Push_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;
// }
}

View File

@ -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;

View File

@ -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<Arena*> next_arenas;
#if BUILD_DEBUG
string info;
string file_path;
string function_name;
s32 line_number;
#endif
};
#if BUILD_DEBUG

View File

@ -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);

View File

@ -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<u8> str);
ArrayView<u8> to_view (string s);

View File

@ -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);

View File

@ -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

View File

@ -186,13 +186,12 @@ void Ex1_Control_Panel () { using namespace ImGui;
push_allocator(temp());
ArrayView<OS_Drive*> 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<Error*> errors = get_all_errors(thread_context());