diff --git a/External/FEXCore/Source/Utils/Allocator.cpp b/External/FEXCore/Source/Utils/Allocator.cpp index ad6ca2da9..fab422c4e 100644 --- a/External/FEXCore/Source/Utils/Allocator.cpp +++ b/External/FEXCore/Source/Utils/Allocator.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,13 @@ extern "C" { #endif } +namespace fextl::pmr { + static fextl::pmr::default_resource FEXDefaultResource; + std::pmr::memory_resource* get_default_resource() { + return &FEXDefaultResource; + } +} + namespace FEXCore::Allocator { MMAP_Hook mmap {::mmap}; MUNMAP_Hook munmap {::munmap}; @@ -63,7 +71,7 @@ namespace FEXCore::Allocator { using GLIBC_REALLOC_Hook = void*(*)(void*, size_t, const void *caller); using GLIBC_FREE_Hook = void(*)(void*, const void *caller); - std::unique_ptr Alloc64{}; + fextl::unique_ptr Alloc64{}; void *FEX_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { void *Result = Alloc64->Mmap(addr, length, prot, flags, fd, offset); diff --git a/External/FEXCore/Source/Utils/AllocatorOverride.cpp b/External/FEXCore/Source/Utils/AllocatorOverride.cpp new file mode 100644 index 000000000..0e68b8ee3 --- /dev/null +++ b/External/FEXCore/Source/Utils/AllocatorOverride.cpp @@ -0,0 +1,140 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +extern "C" { +#ifdef GLIBC_ALLOCATOR_FAULT + // glibc allocator faulting. If anything uses a glibc allocator symbol then cause a fault. + // Ensures that FEX only ever allocates memory through its allocator routines. + // If thunks are enabled then this should crash immediately. Thunks should still go through glibc allocator. + // These need to be symbol definitions /only/, don't let their visibility to the rest of the source happen. +#define GLIBC_ALIAS_FUNCTION(func) __attribute__((alias(#func), visibility("default"))) + extern void *__libc_calloc(size_t, size_t); + void *calloc(size_t, size_t) GLIBC_ALIAS_FUNCTION(fault_calloc); + + extern void __libc_free(void*); + void free(void*) GLIBC_ALIAS_FUNCTION(fault_free); + + extern void *__libc_malloc(size_t); + void *malloc(size_t) GLIBC_ALIAS_FUNCTION(fault_malloc); + + extern void *__libc_memalign(size_t, size_t); + void *memalign(size_t, size_t) GLIBC_ALIAS_FUNCTION(fault_memalign); + + extern void *__libc_realloc(void*, size_t); + void *realloc(void*, size_t) GLIBC_ALIAS_FUNCTION(fault_realloc); + + extern void *__libc_valloc(size_t); + void *valloc(size_t) GLIBC_ALIAS_FUNCTION(fault_valloc); + + extern int __posix_memalign(void **, size_t, size_t); + int posix_memalign(void **, size_t, size_t) GLIBC_ALIAS_FUNCTION(fault_posix_memalign); + + extern size_t __malloc_usable_size(void*); + size_t malloc_usable_size(void*) GLIBC_ALIAS_FUNCTION(fault_malloc_usable_size); + + // Reuse __libc_memalign + void *aligned_alloc(size_t, size_t) GLIBC_ALIAS_FUNCTION(fault_aligned_alloc); +} + +namespace FEXCore::Allocator { + static bool Evaluate{}; + thread_local uint64_t SkipEvalForThread{}; + + CALLOC_Hook calloc_ptr = __libc_calloc; + FREE_Hook free_ptr = __libc_free; + MALLOC_Hook malloc_ptr = __libc_malloc; + MEMALIGN_Hook memalign_ptr = __libc_memalign; + REALLOC_Hook realloc_ptr = __libc_realloc; + VALLOC_Hook valloc_ptr = __libc_valloc; + POSIX_MEMALIGN_Hook posix_memalign_ptr = ::posix_memalign; + MALLOC_USABLE_SIZE_Hook malloc_usable_size_ptr = ::malloc_usable_size; + ALIGNED_ALLOC_Hook aligned_alloc_ptr = __libc_memalign; + + YesIKnowImNotSupposedToUseTheGlibcAllocator::YesIKnowImNotSupposedToUseTheGlibcAllocator() { + ++SkipEvalForThread; + } + + YesIKnowImNotSupposedToUseTheGlibcAllocator::~YesIKnowImNotSupposedToUseTheGlibcAllocator() { + --SkipEvalForThread; + } + + FEX_DEFAULT_VISIBILITY void YesIKnowImNotSupposedToUseTheGlibcAllocator::HardDisable() { + // Just set it to half of its maximum value so it never wraps back around. + SkipEvalForThread = std::numeric_limits::max() / 2; + } + + void SetupFaultEvaluate() { + Evaluate = true; + } + + void ClearFaultEvaluate() { + Evaluate = false; + } + + void EvaluateReturnAddress(void* Return) { + if (!Evaluate) { + return; + } + + if (SkipEvalForThread) { + return; + } + + // We don't know where we are when allocating. Make sure to be safe and generate the string on the stack. + char Tmp[512]; + auto Res = fmt::format_to_n(Tmp, 512, "Allocation from 0x{:x}\n", reinterpret_cast(Return)); + Tmp[Res.size] = 0; + write(STDERR_FILENO, Tmp, Res.size); + LogMan::Msg::AFmt(Tmp); + FEX_UNREACHABLE; + } +} + +extern "C" { + void *fault_calloc(size_t n, size_t size) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::calloc_ptr(n, size); + } + void fault_free(void* ptr) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + FEXCore::Allocator::free_ptr(ptr); + } + void *fault_malloc(size_t size) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::malloc_ptr(size); + } + void *fault_memalign(size_t align, size_t s) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::memalign_ptr(align, s); + } + void *fault_realloc(void* ptr, size_t size) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::realloc_ptr(ptr, size); + } + void *fault_valloc(size_t size) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::valloc_ptr(size); + } + int fault_posix_memalign(void ** r, size_t a, size_t s) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::posix_memalign_ptr(r, a, s); + } + size_t fault_malloc_usable_size(void *ptr) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::malloc_usable_size_ptr(ptr); + } + void *fault_aligned_alloc(size_t a, size_t s) { + FEXCore::Allocator::EvaluateReturnAddress(__builtin_extract_return_addr (__builtin_return_address (0))); + return FEXCore::Allocator::aligned_alloc_ptr(a, s); + } +#endif +} diff --git a/External/FEXCore/include/FEXCore/fextl/memory_resource.h b/External/FEXCore/include/FEXCore/fextl/memory_resource.h new file mode 100644 index 000000000..5857979f5 --- /dev/null +++ b/External/FEXCore/include/FEXCore/fextl/memory_resource.h @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include + +#include +#include + +namespace fextl { + namespace pmr { + class default_resource : public std::pmr::memory_resource { + private: + void* do_allocate( std::size_t bytes, std::size_t alignment ) override { + return FEXCore::Allocator::memalign(alignment, bytes); + } + + void do_deallocate( void* p, std::size_t bytes, std::size_t alignment ) override { + return FEXCore::Allocator::free(p); + } + + bool do_is_equal( const std::pmr::memory_resource& other ) const noexcept override { + return this == &other; + } + }; + + FEX_DEFAULT_VISIBILITY std::pmr::memory_resource* get_default_resource(); + + /** + * @brief This is similar to the std::pmr::monotonic_buffer_resource. + * + * The difference is that class doesn't have ownership of the backing memory and + * it also doesn't have any growth factor. + * + * If the amount of memory allocated is overrun then this will overwrite memory unless assertions are enabled. + * + * Ensure that you know how much memory you're going to use before using this class. + */ + class fixed_size_monotonic_buffer_resource final : public std::pmr::memory_resource { + public: + fixed_size_monotonic_buffer_resource(void* Base, size_t Size) + : Ptr {reinterpret_cast(Base)} +#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED + , PtrEnd {reinterpret_cast(Base) + Size} + , Size {Size} +#endif + {} + void* do_allocate( std::size_t bytes, std::size_t alignment ) override { + uint64_t NewPtr = FEXCore::AlignUp((uint64_t)Ptr, alignment); + Ptr = NewPtr + bytes; +#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED + if (Ptr >= PtrEnd) { + LogMan::Msg::AFmt("Fail: Only allocated: {} ({} this time) bytes. Tried allocating at ptr offset: {}.\n", + Size, + bytes, + (uint64_t)(Ptr - (PtrEnd - Size))); + FEX_TRAP_EXECUTION; + } +#endif + return reinterpret_cast(NewPtr); + } + + void do_deallocate( void* p, std::size_t bytes, std::size_t alignment ) override { + // noop + } + + bool do_is_equal( const std::pmr::memory_resource& other ) const noexcept override { + return this == &other; + } + private: + uint64_t Ptr; +#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED + uint64_t PtrEnd; + size_t Size; +#endif + }; + } +}