From 90d6767d11f8bc038aed7ae5b2fd703afef0930f Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 14 Feb 2026 09:20:55 +0100 Subject: [PATCH] Initial support for tracking plugin heap usage and detect double frees or memory leaks --- source/PluginManagement.cpp | 18 +- source/PluginManagement.h | 4 +- .../DefaultPluginHeapMemoryAllocator.cpp | 44 +++++ .../plugin/DefaultPluginHeapMemoryAllocator.h | 20 ++ source/plugin/IPluginHeapMemoryAllocator.h | 46 +++++ source/plugin/PluginContainer.cpp | 36 ++++ source/plugin/PluginContainer.h | 11 ++ source/plugin/PluginMetaInformation.cpp | 8 + source/plugin/PluginMetaInformation.h | 16 +- .../plugin/PluginMetaInformationFactory.cpp | 6 + .../TrackingPluginHeapMemoryAllocator.cpp | 177 ++++++++++++++++++ .../TrackingPluginHeapMemoryAllocator.h | 29 +++ source/utils/utils.cpp | 67 +++++++ source/utils/utils.h | 6 +- 14 files changed, 473 insertions(+), 15 deletions(-) create mode 100644 source/plugin/DefaultPluginHeapMemoryAllocator.cpp create mode 100644 source/plugin/DefaultPluginHeapMemoryAllocator.h create mode 100644 source/plugin/IPluginHeapMemoryAllocator.h create mode 100644 source/plugin/TrackingPluginHeapMemoryAllocator.cpp create mode 100644 source/plugin/TrackingPluginHeapMemoryAllocator.h diff --git a/source/PluginManagement.cpp b/source/PluginManagement.cpp index e0689be..64fffe3 100644 --- a/source/PluginManagement.cpp +++ b/source/PluginManagement.cpp @@ -12,6 +12,7 @@ #include "plugin/PluginMetaInformationFactory.h" #include "plugin/RelocationData.h" #include "plugin/SectionInfo.h" +#include "plugin/TrackingPluginHeapMemoryAllocator.h" #include "utils/ElfUtils.h" #include "utils/StringTools.h" #include "utils/logger.h" @@ -67,23 +68,18 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi bool PluginManagement::doRelocation(const std::vector &relocData, std::span trampData, - std::map &usedRPls) { + std::map &usedRPls, + const IPluginHeapMemoryAllocator &memory_allocator) { for (auto const &cur : relocData) { uint32_t functionAddress = 0; auto &functionName = cur.getName(); if (functionName == "MEMAllocFromDefaultHeap") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemory", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetAllocFunctionAddress()); } else if (functionName == "MEMAllocFromDefaultHeapEx") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemoryEx", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetAllocExFunctionAddress()); } else if (functionName == "MEMFreeToDefaultHeap") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToMappedMemory", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetFreeFunctionAddress()); } if (functionAddress == 0) { @@ -160,7 +156,7 @@ bool PluginManagement::doRelocations(const std::vector &plugins DEBUG_FUNCTION_LINE_VERBOSE("Doing relocations for plugin: %s", pluginContainer.getMetaInformation().getName().c_str()); if (!PluginManagement::doRelocation(pluginContainer.getPluginLinkInformation().getRelocationDataList(), trampData, - usedRPls)) { + usedRPls, pluginContainer.getMemoryAllocator())) { return false; } } diff --git a/source/PluginManagement.h b/source/PluginManagement.h index 0b19e44..88efc9b 100644 --- a/source/PluginManagement.h +++ b/source/PluginManagement.h @@ -9,6 +9,7 @@ #include #include +class IPluginHeapMemoryAllocator; class RelocationData; class PluginLoadWrapper; class PluginContainer; @@ -25,7 +26,8 @@ public: static bool doRelocation(const std::vector &relocData, std::span trampData, - std::map &usedRPls); + std::map &usedRPls, + const IPluginHeapMemoryAllocator &); static bool DoFunctionPatches(std::vector &plugins); diff --git a/source/plugin/DefaultPluginHeapMemoryAllocator.cpp b/source/plugin/DefaultPluginHeapMemoryAllocator.cpp new file mode 100644 index 0000000..dbd096d --- /dev/null +++ b/source/plugin/DefaultPluginHeapMemoryAllocator.cpp @@ -0,0 +1,44 @@ +#include "DefaultPluginHeapMemoryAllocator.h" + +#include "utils/logger.h" + +#include + +DefaultPluginHeapMemoryAllocator DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator; + +DefaultPluginHeapMemoryAllocator::DefaultPluginHeapMemoryAllocator() { + OSDynLoad_Module rplHandle; + if (OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle) == OS_DYNLOAD_OK) { + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemory", reinterpret_cast(&mMemoryMappingModuleAllocPtr)); + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemoryEx", reinterpret_cast(&mMemoryMappingModuleAllocExPtr)); + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToMappedMemory", reinterpret_cast(&mMemoryMappingModuleFreePtr)); + } +} + +MEMAllocFromDefaultHeapFn *DefaultPluginHeapMemoryAllocator::GetAllocFunctionAddress() const { + if (mMemoryMappingModuleAllocPtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleAllocPtr was NULL!"); + return &MEMAllocFromDefaultHeap; + } + return mMemoryMappingModuleAllocPtr; +} + +MEMAllocFromDefaultHeapExFn *DefaultPluginHeapMemoryAllocator::GetAllocExFunctionAddress() const { + if (mMemoryMappingModuleAllocExPtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleAllocExPtr was NULL!"); + return &MEMAllocFromDefaultHeapEx; + } + return mMemoryMappingModuleAllocExPtr; +} + +MEMFreeToDefaultHeapFn *DefaultPluginHeapMemoryAllocator::GetFreeFunctionAddress() const { + if (mMemoryMappingModuleFreePtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleFreePtr was NULL!"); + return &MEMFreeToDefaultHeap; + } + return mMemoryMappingModuleFreePtr; +} + +std::optional DefaultPluginHeapMemoryAllocator::GetHeapMemoryUsageSnapshot() const { + return {}; +} \ No newline at end of file diff --git a/source/plugin/DefaultPluginHeapMemoryAllocator.h b/source/plugin/DefaultPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..3a5dc4e --- /dev/null +++ b/source/plugin/DefaultPluginHeapMemoryAllocator.h @@ -0,0 +1,20 @@ +#pragma once +#include "IPluginHeapMemoryAllocator.h" + +class DefaultPluginHeapMemoryAllocator : public IPluginHeapMemoryAllocator { +public: + ~DefaultPluginHeapMemoryAllocator() override = default; + MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const override; + MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const override; + MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const override; + std::optional GetHeapMemoryUsageSnapshot() const override; + + static DefaultPluginHeapMemoryAllocator gDefaultPluginHeapMemoryAllocator; + +private: + DefaultPluginHeapMemoryAllocator(); + + MEMAllocFromDefaultHeapFn *mMemoryMappingModuleAllocPtr = nullptr; + MEMAllocFromDefaultHeapExFn *mMemoryMappingModuleAllocExPtr = nullptr; + MEMFreeToDefaultHeapFn *mMemoryMappingModuleFreePtr = nullptr; +}; diff --git a/source/plugin/IPluginHeapMemoryAllocator.h b/source/plugin/IPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..d332885 --- /dev/null +++ b/source/plugin/IPluginHeapMemoryAllocator.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +struct AllocationInfo { + uint32_t size; + std::vector stackTrace; +}; + +struct PluginMemorySnapshot { + uint32_t currentAllocated = 0; + uint32_t peakAllocated = 0; + uint32_t allocCount = 0; + uint32_t freeCount = 0; + std::string pluginName = {}; + + std::map allocationMap; +}; + +class IPluginHeapMemoryAllocator { +public: + virtual ~IPluginHeapMemoryAllocator() = default; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const = 0; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const = 0; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const = 0; + + virtual std::optional GetHeapMemoryUsageSnapshot() const = 0; +}; \ No newline at end of file diff --git a/source/plugin/PluginContainer.cpp b/source/plugin/PluginContainer.cpp index 7dc0951..a036b15 100644 --- a/source/plugin/PluginContainer.cpp +++ b/source/plugin/PluginContainer.cpp @@ -1,4 +1,5 @@ #include "PluginContainer.h" + #include "plugin/FunctionData.h" #include "plugin/HookData.h" #include "plugin/PluginConfigData.h" @@ -6,7 +7,9 @@ #include "plugin/PluginLinkInformation.h" #include "plugin/RelocationData.h" #include "plugin/SectionInfo.h" + #include "utils/buttoncombo/ButtonComboUtils.h" +#include "utils/logger.h" #include "utils/storage/StorageUtils.h" #include @@ -17,10 +20,15 @@ PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLi mPluginData(std::move(pluginData)) { // Abuse this as a stable handle that references itself and survives std::move *mHandle = reinterpret_cast(mHandle.get()); + + if (const bool res = useTrackingPluginHeapMemoryAllocator(mMetaInformation.getHeapTrackingOptions()); !res) { + DEBUG_FUNCTION_LINE_WARN("Failed to set heap tracking options for \"%s\"", mMetaInformation.getName().c_str()); + } } PluginContainer::PluginContainer(PluginContainer &&src) noexcept : mMetaInformation(std::move(src.mMetaInformation)), mPluginLinkInformation(std::move(src.mPluginLinkInformation)), + mTrackingHeapAllocatorOpt(std::move(src.mTrackingHeapAllocatorOpt)), mPluginData(std::move(src.mPluginData)), mHandle(std::move(src.mHandle)), mPluginConfigData(std::move(src.mPluginConfigData)), @@ -39,6 +47,7 @@ PluginContainer &PluginContainer::operator=(PluginContainer &&src) noexcept { this->mPluginData = std::move(src.mPluginData); this->mPluginConfigData = std::move(src.mPluginConfigData); this->mHandle = std::move(src.mHandle); + this->mTrackingHeapAllocatorOpt = std::move(src.mTrackingHeapAllocatorOpt); this->mStorageRootItem = src.mStorageRootItem; this->mInitDone = src.mInitDone; this->mButtonComboManagerHandle = src.mButtonComboManagerHandle; @@ -135,4 +144,31 @@ void PluginContainer::DeinitButtonComboData() { uint32_t PluginContainer::getButtonComboManagerHandle() const { return mButtonComboManagerHandle; +} + +bool PluginContainer::useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options) { + if (options != PluginMetaInformation::TRACK_HEAP_OPTIONS_NONE) { + if (!this->mTrackingHeapAllocatorOpt) { + uint32_t stackTraceDepth = 0; + if (options == PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES) { + stackTraceDepth = 8; + } + this->mTrackingHeapAllocatorOpt = TrackingPluginHeapMemoryAllocator::Create(this->mMetaInformation.getName(), stackTraceDepth); + } + + return this->mTrackingHeapAllocatorOpt.has_value(); + } + this->mTrackingHeapAllocatorOpt.reset(); + return true; +} + +bool PluginContainer::isUsingTrackingPluginHeapMemoryAllocator() const { + return mTrackingHeapAllocatorOpt.has_value(); +} + +const IPluginHeapMemoryAllocator &PluginContainer::getMemoryAllocator() const { + if (mTrackingHeapAllocatorOpt) { + return *mTrackingHeapAllocatorOpt; + } + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator; } \ No newline at end of file diff --git a/source/plugin/PluginContainer.h b/source/plugin/PluginContainer.h index 3aa3a4f..e205197 100644 --- a/source/plugin/PluginContainer.h +++ b/source/plugin/PluginContainer.h @@ -20,6 +20,7 @@ #include "PluginConfigData.h" #include "PluginLinkInformation.h" #include "PluginMetaInformation.h" +#include "TrackingPluginHeapMemoryAllocator.h" #include @@ -69,9 +70,19 @@ public: [[nodiscard]] uint32_t getButtonComboManagerHandle() const; + /** + * @return Returns true if setting the value was successful + */ + bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options); + + [[nodiscard]] bool isUsingTrackingPluginHeapMemoryAllocator() const; + + [[nodiscard]] const IPluginHeapMemoryAllocator &getMemoryAllocator() const; + private: PluginMetaInformation mMetaInformation; PluginLinkInformation mPluginLinkInformation; + std::optional mTrackingHeapAllocatorOpt; std::shared_ptr mPluginData; std::unique_ptr mHandle = std::make_unique(); diff --git a/source/plugin/PluginMetaInformation.cpp b/source/plugin/PluginMetaInformation.cpp index fd87657..4fcfe92 100644 --- a/source/plugin/PluginMetaInformation.cpp +++ b/source/plugin/PluginMetaInformation.cpp @@ -38,6 +38,10 @@ return mSize; } +[[nodiscard]] PluginMetaInformation::HeapTrackingOptions PluginMetaInformation::getHeapTrackingOptions() const { + return mHeapTrackingOptions; +} + PluginMetaInformation::PluginMetaInformation() = default; void PluginMetaInformation::setName(std::string name) { @@ -78,4 +82,8 @@ void PluginMetaInformation::setSize(const size_t size) { void PluginMetaInformation::setStorageId(std::string storageId) { mStorageId = std::move(storageId); +} + +void PluginMetaInformation::setHeapTrackingOptions(HeapTrackingOptions value) { + mHeapTrackingOptions = value; } \ No newline at end of file diff --git a/source/plugin/PluginMetaInformation.h b/source/plugin/PluginMetaInformation.h index a5764f1..ed3b87b 100644 --- a/source/plugin/PluginMetaInformation.h +++ b/source/plugin/PluginMetaInformation.h @@ -26,7 +26,14 @@ class WUPSVersion; class PluginMetaInformation { + public: + enum HeapTrackingOptions { + TRACK_HEAP_OPTIONS_NONE = 0, + TRACK_HEAP_OPTIONS_TRACK_SIZE = 1, + TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES = 2, + }; + [[nodiscard]] const std::string &getName() const; [[nodiscard]] const std::string &getAuthor() const; @@ -45,6 +52,8 @@ public: [[nodiscard]] size_t getSize() const; + [[nodiscard]] HeapTrackingOptions getHeapTrackingOptions() const; + private: PluginMetaInformation(); @@ -68,6 +77,8 @@ private: void setStorageId(std::string storageId); + void setHeapTrackingOptions(HeapTrackingOptions value); + std::string mName; std::string mAuthor; std::string mVersion; @@ -75,8 +86,9 @@ private: std::string mBuildTimestamp; std::string mDescription; std::string mStorageId; - size_t mSize = {}; - WUPSVersion mWUPSVersion = WUPSVersion(0, 0, 0); + size_t mSize = {}; + WUPSVersion mWUPSVersion = WUPSVersion(0, 0, 0); + HeapTrackingOptions mHeapTrackingOptions = TRACK_HEAP_OPTIONS_NONE; friend class PluginMetaInformationFactory; diff --git a/source/plugin/PluginMetaInformationFactory.cpp b/source/plugin/PluginMetaInformationFactory.cpp index e4199b6..0ae8044 100644 --- a/source/plugin/PluginMetaInformationFactory.cpp +++ b/source/plugin/PluginMetaInformationFactory.cpp @@ -108,6 +108,12 @@ std::optional PluginMetaInformationFactory::loadPlugin(st pluginInfo.setDescription(value); } else if (key == "storage_id") { pluginInfo.setStorageId(value); + } else if (key == "debug") { + if (value == "track_heap") { + pluginInfo.setHeapTrackingOptions(PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE); + } else if (value == "track_heap_with_stack_trace") { + pluginInfo.setHeapTrackingOptions(PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES); + } } else if (key == "wups") { if (value == "0.7.1") { pluginInfo.setWUPSVersion(0, 7, 1); diff --git a/source/plugin/TrackingPluginHeapMemoryAllocator.cpp b/source/plugin/TrackingPluginHeapMemoryAllocator.cpp new file mode 100644 index 0000000..7bf3b60 --- /dev/null +++ b/source/plugin/TrackingPluginHeapMemoryAllocator.cpp @@ -0,0 +1,177 @@ +#include "TrackingPluginHeapMemoryAllocator.h" + +#include "utils/logger.h" +#include "utils/utils.h" + +#include + +namespace { + struct PluginMemoryStatsInternal { + mutable std::mutex mutex; + bool inUse = false; + uint32_t stackTraceDepth = 0; + PluginMemorySnapshot data; + }; + + // Generate 64 different thunk functions so we can track 64 plugins at a time which should be more than enough + constexpr int MAX_SLOTS = 64; + template + struct MemoryThunk { + inline static PluginMemoryStatsInternal stats; + + static void *Alloc(uint32_t size) { + const auto ptr = (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocFunctionAddress())(size); + + if (ptr) { + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + stats.data.allocCount++; + stats.data.currentAllocated += size; + if (stats.data.currentAllocated > stats.data.peakAllocated) { + stats.data.peakAllocated = stats.data.currentAllocated; + } + + stats.data.allocationMap[ptr] = {size, stats.stackTraceDepth > 0 ? CaptureStackTrace(stats.stackTraceDepth) : std::vector()}; + } + } + return ptr; + } + + static void *AllocEx(uint32_t size, int align) { + const auto ptr = (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocExFunctionAddress())(size, align); + if (ptr) { + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + stats.data.allocCount++; + stats.data.currentAllocated += size; + if (stats.data.currentAllocated > stats.data.peakAllocated) { + stats.data.peakAllocated = stats.data.currentAllocated; + } + stats.data.allocationMap[ptr] = {size, stats.stackTraceDepth > 0 ? CaptureStackTrace(stats.stackTraceDepth) : std::vector()}; + } + } + return ptr; + } + + static void Free(void *ptr) { + if (!ptr) return; + + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + if (const auto it = stats.data.allocationMap.find(ptr); it != stats.data.allocationMap.end()) { + uint32_t size = it->second.size; + if (stats.data.currentAllocated >= size) { + stats.data.currentAllocated -= size; + } else { + stats.data.currentAllocated = 0; + } + stats.data.allocationMap.erase(it); + } else { + DEBUG_FUNCTION_LINE_ERR("free() of for unknown ptr detected (double free?). \"%s\": %p", stats.data.pluginName.c_str(), ptr); + auto stackTrace = CaptureStackTrace(16); + PrintCapturedStackTrace(stackTrace); + } + stats.data.freeCount++; + } + + (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetFreeFunctionAddress())(ptr); + } + }; + + template + constexpr std::array CreateAllocTable(std::integer_sequence) { return {MemoryThunk::Alloc...}; } + template + constexpr std::array CreateAllocExTable(std::integer_sequence) { return {MemoryThunk::AllocEx...}; } + template + constexpr std::array CreateFreeTable(std::integer_sequence) { return {MemoryThunk::Free...}; } + + template + constexpr std::array CreateStatsTable(std::integer_sequence) { + return {&MemoryThunk::stats...}; + } + + auto sAllocThunks = CreateAllocTable(std::make_integer_sequence{}); + auto sAllocExThunks = CreateAllocExTable(std::make_integer_sequence{}); + auto sFreeThunks = CreateFreeTable(std::make_integer_sequence{}); + auto sStatsTable = CreateStatsTable(std::make_integer_sequence{}); +} // namespace + +TrackingPluginHeapMemoryAllocator::TrackingPluginHeapMemoryAllocator(const int32_t index) : sIndex(index) { +} + +std::optional TrackingPluginHeapMemoryAllocator::Create(const std::string_view pluginName, uint32_t stackTraceDepth) { + int32_t index = 0; + for (auto *stat : sStatsTable) { + std::lock_guard lock(stat->mutex); + if (!stat->inUse) { + stat->inUse = true; + stat->stackTraceDepth = stackTraceDepth; + stat->data = {}; + stat->data.pluginName = pluginName; + return TrackingPluginHeapMemoryAllocator{index}; + } + ++index; + } + return std::nullopt; +} + +TrackingPluginHeapMemoryAllocator::~TrackingPluginHeapMemoryAllocator() { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return; + } + auto *stats = sStatsTable[sIndex]; + std::lock_guard lock(stats->mutex); + stats->inUse = false; +} + +TrackingPluginHeapMemoryAllocator::TrackingPluginHeapMemoryAllocator(TrackingPluginHeapMemoryAllocator &&src) noexcept { + this->sIndex = src.sIndex; + src.sIndex = -1; +} + +TrackingPluginHeapMemoryAllocator &TrackingPluginHeapMemoryAllocator::operator=(TrackingPluginHeapMemoryAllocator &&src) noexcept { + if (this != &src) { + // Release current slot if we have one + if (this->sIndex >= 0 && this->sIndex < MAX_SLOTS) { + auto *stats = sStatsTable[this->sIndex]; + std::lock_guard lock(stats->mutex); + stats->inUse = false; + stats->data = {}; + } + + this->sIndex = src.sIndex; + src.sIndex = -1; + } + return *this; +} + +MEMAllocFromDefaultHeapFn *TrackingPluginHeapMemoryAllocator::GetAllocFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocFunctionAddress(); + } + + return &sAllocThunks[sIndex]; +} + +MEMAllocFromDefaultHeapExFn *TrackingPluginHeapMemoryAllocator::GetAllocExFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocExFunctionAddress(); + } + return &sAllocExThunks[sIndex]; +} + +MEMFreeToDefaultHeapFn *TrackingPluginHeapMemoryAllocator::GetFreeFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetFreeFunctionAddress(); + } + return &sFreeThunks[sIndex]; +} + +std::optional TrackingPluginHeapMemoryAllocator::GetHeapMemoryUsageSnapshot() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return std::nullopt; + } + const auto *stats = sStatsTable[sIndex]; + std::lock_guard lock(stats->mutex); + return stats->data; +} diff --git a/source/plugin/TrackingPluginHeapMemoryAllocator.h b/source/plugin/TrackingPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..c3eacdc --- /dev/null +++ b/source/plugin/TrackingPluginHeapMemoryAllocator.h @@ -0,0 +1,29 @@ +#pragma once +#include "DefaultPluginHeapMemoryAllocator.h" + +#include +#include + +#include + +class TrackingPluginHeapMemoryAllocator : public IPluginHeapMemoryAllocator { +public: + static std::optional Create(std::string_view pluginName, uint32_t stackTraceDepth); + + ~TrackingPluginHeapMemoryAllocator() override; + + TrackingPluginHeapMemoryAllocator(const TrackingPluginHeapMemoryAllocator &) = delete; + + TrackingPluginHeapMemoryAllocator(TrackingPluginHeapMemoryAllocator &&src) noexcept; + + TrackingPluginHeapMemoryAllocator &operator=(TrackingPluginHeapMemoryAllocator &&src) noexcept; + + MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const override; + MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const override; + MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const override; + std::optional GetHeapMemoryUsageSnapshot() const override; + +private: + int sIndex = -1; + TrackingPluginHeapMemoryAllocator(int index); +}; diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index 8b37a1d..00128e9 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -6,6 +6,7 @@ #include "json.hpp" #include "logger.h" +#include #include #include @@ -198,4 +199,70 @@ std::vector getNonBaseAromaPluginFilenames(std::string_view basePat } } return result; +} + +__attribute__((noinline)) +std::vector +CaptureStackTrace(uint32_t maxDepth) { + std::vector trace; + trace.reserve(maxDepth); + + uint32_t *stackPointer; + // Grab the current Stack Pointer (r1) + asm volatile("mr %0, 1" + : "=r"(stackPointer)); + + for (uint32_t i = 0; i < maxDepth + 1; ++i) { + // Basic alignment check + if (!stackPointer || (reinterpret_cast(stackPointer) & 0x3)) { + break; + } + + uint32_t backChain = stackPointer[0]; + uint32_t returnAddr = stackPointer[1]; + + if (returnAddr == 0) break; + if (i != 0) { + trace.push_back(returnAddr); + } + + if (backChain == 0) break; + stackPointer = reinterpret_cast(backChain); + } + + return trace; +} + +#define SC17_FindClosestSymbol ((uint32_t(*)(uint32_t addr, int32_t * outDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength))(0x101C400 + 0x1f934)) + +void PrintCapturedStackTrace(std::span trace) { + if (trace.empty()) { + DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐"); + DEBUG_FUNCTION_LINE_INFO("│ "); + DEBUG_FUNCTION_LINE_INFO("└────────────────────────────────────────────────────────────┘"); + return; + } + + DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐"); + for (size_t i = 0; i < trace.size(); ++i) { + uint32_t addr = trace[i]; + int distance = 0; + char moduleName[100] = {}; + char symbolName[100] = {}; + + if (SC17_FindClosestSymbol(addr, &distance, symbolName, sizeof(symbolName) - 1, moduleName, sizeof(moduleName) - 1) != 0) { + DEBUG_FUNCTION_LINE_INFO("│ [%02d] 0x%08X", i, addr); + } else { + moduleName[sizeof(moduleName) - 1] = '\0'; + symbolName[sizeof(symbolName) - 1] = '\0'; + DEBUG_FUNCTION_LINE_INFO("│ [%02d] %s : %s + 0x%X (0x%08X)", + i, + moduleName, + symbolName, + distance, + addr); + } + } + + DEBUG_FUNCTION_LINE_INFO("└────────────────────────────────────────────────────────────┘"); } \ No newline at end of file diff --git a/source/utils/utils.h b/source/utils/utils.h index 3206f5c..a79e0e2 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -161,4 +161,8 @@ UtilsIOError ParseJsonFromFile(const std::string &filePath, nlohmann::json &outJ std::vector getPluginFilePaths(std::string_view basePath); -std::vector getNonBaseAromaPluginFilenames(std::string_view basePath); \ No newline at end of file +std::vector getNonBaseAromaPluginFilenames(std::string_view basePath); + +std::vector CaptureStackTrace(uint32_t maxDepth); + +void PrintCapturedStackTrace(std::span trace); \ No newline at end of file