From 9e298f9228aad94ba56f4c6f0150af338abfa110 Mon Sep 17 00:00:00 2001 From: PJB3005 Date: Tue, 31 Mar 2026 21:18:25 +0200 Subject: [PATCH] Expand heap debug ImGui Now allows seeing all blocks in ExpHeaps and running heap integrity checks. --- CMakeLists.txt | 2 +- .../include/JSystem/JKernel/JKRExpHeap.h | 7 + src/dusk/imgui/ImGuiHeapOverlay.cpp | 169 +++++++++++++++++- 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1637eb202d..7d434d874b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,7 +107,7 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiAudio.cpp) -target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts) +target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts absl::flat_hash_map) target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") target_precompile_headers(game PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") diff --git a/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h index a31b2c5e84..09687e688b 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h @@ -121,6 +121,13 @@ public: static s32 getUsedSize_(JKRExpHeap* heap) { return heap->mSize - heap->getTotalFreeSize(); } static void* getState_(TState* state) { return getState_buf_(state); } + +#if TARGET_PC + [[nodiscard]] CMemBlock* getFreeHead() { return mHeadFreeList; } + [[nodiscard]] const CMemBlock* getFreeHead() const { return mHeadFreeList; } + [[nodiscard]] CMemBlock* getUsedHead() { return mHeadUsedList; } + [[nodiscard]] const CMemBlock* getUsedHead() const { return mHeadUsedList; } +#endif }; inline JKRExpHeap* JKRCreateExpHeap(u32 size, JKRHeap* parent, bool errorFlag) { diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 588857cedb..655993950e 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -1,14 +1,25 @@ #include #include -#include "JSystem/JFramework/JFWSystem.h" -#include "JSystem/JKernel/JKRHeap.h" -#include "imgui.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "JSystem/JFramework/JFWSystem.h" +#include "JSystem/JKernel/JKRExpHeap.h" +#include "JSystem/JKernel/JKRHeap.h" +#include "absl/container/flat_hash_map.h" +#include "imgui.h" + +struct OpenHeapData { + bool Safe; + bool HeapCheckRan; + bool HeapCheckFailed; +}; + +static absl::flat_hash_map OpenHeapWindows; namespace dusk { static void DrawTableCore(); + void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open); void ImGuiMenuTools::ShowHeapOverlay() { if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F4, m_showHeapOverlay)) { @@ -16,9 +27,13 @@ namespace dusk { } if (ImGui::Begin("Heaps", &m_showHeapOverlay)) { + for (auto& x : OpenHeapWindows) { + x.second.Safe = false; + } + if (ImGui::BeginTable( "heaps", - 5, + 6, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { DrawTableCore(); @@ -28,6 +43,19 @@ namespace dusk { } ImGui::End(); + + std::vector closeQueue; + for (auto& [heap, safe] : OpenHeapWindows) { + auto open = true; + ShowHeapDetailed(heap, safe, open); + if (!open) { + closeQueue.push_back(heap); + } + } + + for (auto toRemove : closeQueue) { + OpenHeapWindows.erase(toRemove); + } } static void DrawHeap(JKRHeap* heap, int depth = 0); @@ -44,6 +72,7 @@ namespace dusk { ImGui::TextUnformatted("Total"); ImGui::TableNextColumn(); ImGui::TextUnformatted("Type"); + ImGui::TableNextColumn(); DrawHeap(reinterpret_cast(JFWSystem::rootHeap)); } @@ -70,8 +99,15 @@ namespace dusk { static void DrawHeap(JKRHeap* heap, const int depth) { ImGui::TableNextRow(); + char idBuf[32]; + snprintf(idBuf, sizeof(idBuf), "%p", heap); + ImGui::PushID(idBuf); ImGui::TableNextColumn(); + if (OpenHeapWindows.find(heap) != OpenHeapWindows.end()) { + OpenHeapWindows[heap].Safe = true; + } + auto indentSize = depth * 16; if (indentSize != 0) ImGui::Indent(indentSize); @@ -103,9 +139,134 @@ namespace dusk { auto typeString = GetHeapType(heap); ImGui::TextUnformatted(typeString.data(), typeString.data() + 4); + ImGui::TableNextColumn(); + if (ImGui::Button("View")) { + OpenHeapWindows[heap].Safe = true; + } + const JSUTree& tree = heap->getHeapTree(); for (JSUTreeIterator iter(tree.getFirstChild()); iter != tree.getEndChild(); ++iter) { DrawHeap(*iter, depth + 1); } + + ImGui::PopID(); + } + + struct MemBlockPair { + JKRExpHeap::CMemBlock* block; + bool used; + + auto& operator->() { + return block; + } + }; + + static std::vector FindAllHeapBlocks(JKRExpHeap* heap) { + std::vector result; + + for (JKRExpHeap::CMemBlock* b = heap->getFreeHead(); b; b = b->getNextBlock()) { + result.push_back({b, false}); + } + + for (JKRExpHeap::CMemBlock* b = heap->getUsedHead(); b; b = b->getNextBlock()) { + result.push_back({b, true}); + } + + std::ranges::sort(result, [](auto a, auto b) { return a.block < b.block; }); + + return result; + } + + static void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { + char title[128]; + const char* name = data.Safe ? heap->getName() : "INVALID"; + snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast(heap)); + + if (!ImGui::Begin(name, &open)) { + ImGui::End(); + return; + } + + if (!data.Safe) { + ImGui::TextUnformatted("Heap no longer exists"); + ImGui::End(); + return; + } + + heap->lock(); + + ImGui::Text("Name: %s", heap->getName()); + const auto size = BytesToString(heap->getSize()); + const auto freeSize = BytesToString(heap->getFreeSize()); + ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str()); + + if (ImGui::Button("Check")) { + data.HeapCheckFailed = !heap->check(); + data.HeapCheckRan = true; + } + + if (data.HeapCheckFailed) { + ImGui::SameLine(); + ImColor red = IM_COL32(0xFF, 0, 0, 0xFF); + ImGui::TextColored(red, "Heap check failed"); + } else if (data.HeapCheckRan) { + ImGui::SameLine(); + ImColor red = IM_COL32(0, 0xFF, 0, 0xFF); + ImGui::TextColored(red, "Heap check passed"); + } + + if (heap->getHeapType() == 'EXPH') { + auto expHeap = dynamic_cast(heap); + + ImGui::SeparatorText("Blocks"); + + if (ImGui::BeginTable("Blocks", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("Start", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("End", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Size"); + ImGui::TableSetupColumn("Allocated", ImGuiTableColumnFlags_WidthFixed, ImGui::CalcTextSize("Allocated").x); + ImGui::TableSetupColumn("IsValid", ImGuiTableColumnFlags_WidthFixed, ImGui::CalcTextSize("IsValid").x); + ImGui::TableHeadersRow(); + + const auto blocks = FindAllHeapBlocks(expHeap); + + for (auto block : blocks) { + assert(block->getSize() != 0); + ImGui::TableNextRow(); + + if (block.used) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(0xFF, 0, 0, 0x44)); + } else { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(0, 0xFF, 0, 0x44)); + } + + char bufId[32]; + snprintf(bufId, sizeof(bufId), "%p", block); + ImGui::PushID(bufId); + ImGui::TableNextColumn(); + ImGui::Text("%08X", (u32)((uintptr_t)block.block - (uintptr_t)expHeap->getStartAddr())); + ImGui::TableNextColumn(); + ImGui::Text("%08X", (u32)((uintptr_t)block.block + block->getSize() - (uintptr_t)expHeap->getStartAddr())); + ImGui::TableNextColumn(); + auto sizeNice = BytesToString(block->getSize()); + ImGui::Text("%08X (%s)", block->getSize(), sizeNice.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(block.used ? "True" : "False"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(block->isValid() ? "True" : "False"); + if (block->isValid() != block.used) { + ImGui::SameLine(); + ImGui::TextUnformatted("(!!!)"); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + } + } + + ImGui::End(); + + heap->unlock(); } }