[+] Added AuDebug::MemoryCrunch implementation under debug and memory
This commit is contained in:
parent
7be2d3fbdc
commit
1f1d1bbc28
@ -8,7 +8,7 @@
|
|||||||
#include <Source/RuntimeInternal.hpp>
|
#include <Source/RuntimeInternal.hpp>
|
||||||
#include "Debug.hpp"
|
#include "Debug.hpp"
|
||||||
#include <Source/Telemetry/Telemetry.hpp>
|
#include <Source/Telemetry/Telemetry.hpp>
|
||||||
|
#include "MemoryCrunch.hpp"
|
||||||
#if defined(AURORA_PLATFORM_WIN32)
|
#if defined(AURORA_PLATFORM_WIN32)
|
||||||
#include "ExceptionWatcher.NT.hpp"
|
#include "ExceptionWatcher.NT.hpp"
|
||||||
#include "ExceptionWatcher.Win32.hpp"
|
#include "ExceptionWatcher.Win32.hpp"
|
||||||
@ -447,6 +447,8 @@ namespace Aurora::Debug
|
|||||||
|
|
||||||
void InitDebug()
|
void InitDebug()
|
||||||
{
|
{
|
||||||
|
InitMemoryCrunch();
|
||||||
|
|
||||||
#if defined(AURORA_PLATFORM_WIN32)
|
#if defined(AURORA_PLATFORM_WIN32)
|
||||||
InitWin32();
|
InitWin32();
|
||||||
#endif
|
#endif
|
||||||
@ -455,14 +457,4 @@ namespace Aurora::Debug
|
|||||||
InitNT();
|
InitNT();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void AddMemoryCrunch()
|
|
||||||
{
|
|
||||||
// TODO: ...
|
|
||||||
}
|
|
||||||
|
|
||||||
AUKN_SYM void DecMemoryCrunch()
|
|
||||||
{
|
|
||||||
// TODO: ...
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -225,17 +225,23 @@ namespace Aurora::Debug
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "MemoryCrunch.hpp"
|
||||||
|
|
||||||
static thread_local AuUInt tlsThrowCounter;
|
static thread_local AuUInt tlsThrowCounter;
|
||||||
|
|
||||||
extern "C" AUKN_SYM void __stdcall _ReportMSVCSEH(void *exception, const void *throwInfo, void *caller)
|
extern "C" AUKN_SYM void __stdcall _ReportMSVCSEH(void *exception, const void *throwInfo, void *caller)
|
||||||
{
|
{
|
||||||
|
AuDebug::AddMemoryCrunch();
|
||||||
|
|
||||||
if (!throwInfo)
|
if (!throwInfo)
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exception)
|
if (!exception)
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,19 +251,22 @@ extern "C" AUKN_SYM void __stdcall _ReportMSVCSEH(void *exception, const void *t
|
|||||||
{
|
{
|
||||||
if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCSTR>(caller), reinterpret_cast<HMODULE *>(&handle)))
|
if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCSTR>(caller), reinterpret_cast<HMODULE *>(&handle)))
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle == INVALID_HANDLE_VALUE)
|
if (handle == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we will probably overflow for a bit if an inner try catch borks up somewhere
|
// we will probably overflow for a bit if an inner try catch borks up somewhere
|
||||||
if ((tlsThrowCounter++) == 7)
|
if ((tlsThrowCounter++) == 7) // TODO: this might be stupid. we should configure for panic on second
|
||||||
{
|
{
|
||||||
tlsThrowCounter--;
|
tlsThrowCounter--;
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,4 +296,5 @@ extern "C" AUKN_SYM void __stdcall _ReportMSVCSEH(void *exception, const void *t
|
|||||||
|
|
||||||
Aurora::Exit::PostLevel(AuThreads::GetThread(), Aurora::Exit::ETriggerLevel::eProblematicEvent);
|
Aurora::Exit::PostLevel(AuThreads::GetThread(), Aurora::Exit::ETriggerLevel::eProblematicEvent);
|
||||||
tlsThrowCounter--;
|
tlsThrowCounter--;
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
}
|
}
|
58
Source/Debug/MemoryCrunch.cpp
Normal file
58
Source/Debug/MemoryCrunch.cpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/***
|
||||||
|
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
||||||
|
|
||||||
|
File: MemoryCrunch.cpp
|
||||||
|
Date: 2022-12-07
|
||||||
|
Author: Reece
|
||||||
|
***/
|
||||||
|
#include <Source/RuntimeInternal.hpp>
|
||||||
|
#include "Debug.hpp"
|
||||||
|
#include "ErrorStack.hpp"
|
||||||
|
#include <Source/Memory/Heap.hpp>
|
||||||
|
|
||||||
|
namespace Aurora::Debug
|
||||||
|
{
|
||||||
|
static thread_local AuSInt tlsMemoryCrunchCounter;
|
||||||
|
static auto const kAutoReservePool = 3 * 1024 * 1024;
|
||||||
|
|
||||||
|
AuUInt8 *gReservePoolStart {};
|
||||||
|
AuUInt8 *gReservePoolEnd {};
|
||||||
|
AuSPtr<AuMemory::Heap> gReserveHeap {};
|
||||||
|
|
||||||
|
void InitMemoryCrunch()
|
||||||
|
{
|
||||||
|
gReserveHeap = AuMemory::AllocHeapShared(kAutoReservePool);
|
||||||
|
SysAssert(gReserveHeap);
|
||||||
|
|
||||||
|
auto pHeap = AuStaticCast<AuMemory::BaseHeap>(gReserveHeap);
|
||||||
|
gReservePoolStart = AuReinterpretCast<AuUInt8 *>(pHeap->base_);
|
||||||
|
gReservePoolEnd = AuReinterpretCast<AuUInt8 *>(pHeap->base_) + pHeap->length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPointerReserveRange(void *ptr)
|
||||||
|
{
|
||||||
|
auto pChar = (AuUInt8 *)ptr;
|
||||||
|
return pChar >= gReservePoolStart && pChar < gReservePoolEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuSPtr<AuMemory::Heap> GetCrunchInterface()
|
||||||
|
{
|
||||||
|
return gReserveHeap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTlsMemoryCrunchActive()
|
||||||
|
{
|
||||||
|
return bool(tlsMemoryCrunchCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
AUKN_SYM void AddMemoryCrunch()
|
||||||
|
{
|
||||||
|
tlsMemoryCrunchCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
AUKN_SYM void DecMemoryCrunch()
|
||||||
|
{
|
||||||
|
tlsMemoryCrunchCounter--;
|
||||||
|
SysAssert(tlsMemoryCrunchCounter >= 0, "crunch counter underflow");
|
||||||
|
}
|
||||||
|
}
|
19
Source/Debug/MemoryCrunch.hpp
Normal file
19
Source/Debug/MemoryCrunch.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/***
|
||||||
|
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
||||||
|
|
||||||
|
File: MemoryCrunch.hpp
|
||||||
|
Date: 2022-12-07
|
||||||
|
Author: Reece
|
||||||
|
***/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Aurora::Debug
|
||||||
|
{
|
||||||
|
extern AuUInt8 *gReservePoolStart;
|
||||||
|
extern AuUInt8 *gReservePoolEnd;
|
||||||
|
extern AuSPtr<AuMemory::Heap> gReserveHeap;
|
||||||
|
|
||||||
|
bool IsPointerReserveRange(void *ptr);
|
||||||
|
void InitMemoryCrunch();
|
||||||
|
bool IsTlsMemoryCrunchActive();
|
||||||
|
}
|
@ -14,6 +14,8 @@
|
|||||||
#include "AuExit.Unix.hpp"
|
#include "AuExit.Unix.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <Source/Debug/MemoryCrunch.hpp>
|
||||||
|
|
||||||
namespace Aurora::Exit
|
namespace Aurora::Exit
|
||||||
{
|
{
|
||||||
static AuThreadPrimitives::MutexUnique_t gMutex;
|
static AuThreadPrimitives::MutexUnique_t gMutex;
|
||||||
@ -46,6 +48,7 @@ namespace Aurora::Exit
|
|||||||
{
|
{
|
||||||
bool bOldTerminatingValue;
|
bool bOldTerminatingValue;
|
||||||
|
|
||||||
|
AuDebug::AddMemoryCrunch();
|
||||||
{
|
{
|
||||||
AU_LOCK_GUARD(gMutex);
|
AU_LOCK_GUARD(gMutex);
|
||||||
|
|
||||||
@ -72,6 +75,7 @@ namespace Aurora::Exit
|
|||||||
{
|
{
|
||||||
if (AuAtomicTestAndSet(&gProblemCounter, 1))
|
if (AuAtomicTestAndSet(&gProblemCounter, 1))
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +102,7 @@ namespace Aurora::Exit
|
|||||||
// Has already sent eSafeTermination?
|
// Has already sent eSafeTermination?
|
||||||
if (isPreempting)
|
if (isPreempting)
|
||||||
{
|
{
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +123,7 @@ namespace Aurora::Exit
|
|||||||
Process::Exit(0);
|
Process::Exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AuDebug::DecMemoryCrunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM bool ExitHandlerAdd(ETriggerLevel level, const AuSPtr<IExitSubscriber> &callback)
|
AUKN_SYM bool ExitHandlerAdd(ETriggerLevel level, const AuSPtr<IExitSubscriber> &callback)
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <Source/RuntimeInternal.hpp>
|
#include <Source/RuntimeInternal.hpp>
|
||||||
#include <Source/Console/Flusher.hpp>
|
#include <Source/Console/Flusher.hpp>
|
||||||
#include <Source/Console/Console.hpp>
|
#include <Source/Console/Console.hpp>
|
||||||
|
#include <Source/Debug/MemoryCrunch.hpp>
|
||||||
|
|
||||||
#if defined(AURORA_IS_LINUX_DERIVED)
|
#if defined(AURORA_IS_LINUX_DERIVED)
|
||||||
void LinuxSuperSecretIOTick();
|
void LinuxSuperSecretIOTick();
|
||||||
@ -51,6 +52,8 @@ namespace Aurora::Grug
|
|||||||
|
|
||||||
AU_LOCK_GUARD(gMutex);
|
AU_LOCK_GUARD(gMutex);
|
||||||
|
|
||||||
|
AuDebug::AddMemoryCrunch(); // this thread must never encounter an out of memory condition. begin out of memory mitigations.
|
||||||
|
|
||||||
Utility::RateLimiter limiter;
|
Utility::RateLimiter limiter;
|
||||||
limiter.SetNextStep(kGrugFlushMs);
|
limiter.SetNextStep(kGrugFlushMs);
|
||||||
|
|
||||||
|
@ -53,11 +53,11 @@ namespace Aurora::Memory
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InternalHeap : Heap, AuEnableSharedFromThis<InternalHeap>
|
struct InternalHeap : BaseHeap, AuEnableSharedFromThis<InternalHeap>
|
||||||
{
|
{
|
||||||
virtual AuSPtr<Heap> AllocateDivision(AuUInt32 heap, AuUInt32 alignment) override;
|
virtual AuSPtr<Heap> AllocateDivision(AuUInt32 heap, AuUInt32 alignment) override;
|
||||||
|
|
||||||
InternalHeap() : base_(nullptr), mutex_(nullptr), heap_(nullptr), count_(0)
|
InternalHeap() : mutex_(nullptr), heap_(nullptr), count_(0)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual ~InternalHeap();
|
virtual ~InternalHeap();
|
||||||
@ -96,11 +96,11 @@ namespace Aurora::Memory
|
|||||||
void RequestTermination();
|
void RequestTermination();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
AuThreadPrimitives::MutexUnique_t mutex_;
|
AuThreadPrimitives::MutexUnique_t mutex_;
|
||||||
void *base_ {};
|
|
||||||
O1HeapInstance *heap_ {};
|
O1HeapInstance *heap_ {};
|
||||||
int count_ {};
|
int count_ {};
|
||||||
AuUInt length_ {};
|
|
||||||
bool isDangling_ {};
|
bool isDangling_ {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,5 +9,11 @@
|
|||||||
|
|
||||||
namespace Aurora::Memory
|
namespace Aurora::Memory
|
||||||
{
|
{
|
||||||
|
struct BaseHeap : Heap
|
||||||
|
{
|
||||||
|
void *base_ {};
|
||||||
|
AuUInt length_ {};
|
||||||
|
};
|
||||||
|
|
||||||
AuSPtr<Heap> AllocateDivisionGlobal(Heap *heap, AuUInt32 length, AuUInt32 alignment);
|
AuSPtr<Heap> AllocateDivisionGlobal(Heap *heap, AuUInt32 length, AuUInt32 alignment);
|
||||||
}
|
}
|
@ -14,64 +14,151 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <Source/Debug/MemoryCrunch.hpp>
|
||||||
|
|
||||||
namespace Aurora::Memory
|
namespace Aurora::Memory
|
||||||
{
|
{
|
||||||
AUKN_SYM AuUInt GetChunkSize(const void *head)
|
AUKN_SYM AuUInt GetChunkSize(const void *head)
|
||||||
{
|
{
|
||||||
return mi_malloc_size(head);
|
if (AuDebug::IsPointerReserveRange((void *)head))
|
||||||
|
{
|
||||||
|
return AuDebug::gReserveHeap->GetChunkSize(head);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ::mi_malloc_size(head);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CHECK_WRAP_RETURN(exp, string) \
|
#define CHECK_WRAP_RETURN(exp, exp2, string) \
|
||||||
auto pRet = exp; \
|
auto pRet = exp; \
|
||||||
if (!pRet && gRuntimeConfig.debug.bIsMemoryErrorFatal) \
|
if (!pRet) \
|
||||||
|
{ \
|
||||||
|
if (AuDebug::IsTlsMemoryCrunchActive() && AuDebug::gReserveHeap) \
|
||||||
|
{ \
|
||||||
|
return AuDebug::gReserveHeap-> AU_WHAT exp2; \
|
||||||
|
} \
|
||||||
|
else if (gRuntimeConfig.debug.bIsMemoryErrorFatal) \
|
||||||
{ \
|
{ \
|
||||||
SysPanic(string); \
|
SysPanic(string); \
|
||||||
} \
|
} \
|
||||||
|
} \
|
||||||
return pRet;
|
return pRet;
|
||||||
|
|
||||||
AUKN_SYM void *_ZAlloc(Types::size_t length)
|
AUKN_SYM void *_ZAlloc(Types::size_t length)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_zalloc(length), "ZAlloc out of memory");
|
CHECK_WRAP_RETURN(::mi_zalloc(length), (ZAlloc(length)), "ZAlloc out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_ZAlloc(Types::size_t length, Types::size_t align)
|
AUKN_SYM void *_ZAlloc(Types::size_t length, Types::size_t align)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_zalloc_aligned(length, align), "ZAlloc out of memory");
|
CHECK_WRAP_RETURN(::mi_zalloc_aligned(length, align), (ZAlloc(length, align)), "ZAlloc out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_FAlloc(Types::size_t length)
|
AUKN_SYM void *_FAlloc(Types::size_t length)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_malloc(length), "FAlloc out of memory");
|
CHECK_WRAP_RETURN(::mi_malloc(length), (FAlloc(length)), "FAlloc out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_FAlloc(Types::size_t length, Types::size_t align)
|
AUKN_SYM void *_FAlloc(Types::size_t length, Types::size_t align)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_malloc_aligned(length, align), "FAlloc out of memory");
|
CHECK_WRAP_RETURN(::mi_malloc_aligned(length, align), (FAlloc(length, align)), "FAllocEx out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_ZRealloc(void *buffer, Types::size_t length, Types::size_t align)
|
AUKN_SYM void *_ZRealloc(void *buffer, Types::size_t length, Types::size_t align)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_rezalloc_aligned(buffer, length, align), "ZAlloc out of memory");
|
if (AuDebug::IsPointerReserveRange(buffer))
|
||||||
|
{
|
||||||
|
return AuDebug::gReserveHeap->ZRealloc(buffer, length, align);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto pRet = ::mi_rezalloc_aligned(buffer, length, align);
|
||||||
|
if (!pRet)
|
||||||
|
{
|
||||||
|
if (gRuntimeConfig.debug.bIsMemoryErrorFatal)
|
||||||
|
{
|
||||||
|
SysPanic("ZAllocEx out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_ZRealloc(void *buffer, Types::size_t length)
|
AUKN_SYM void *_ZRealloc(void *buffer, Types::size_t length)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_rezalloc(buffer, length), "ZAlloc out of memory");
|
if (AuDebug::IsPointerReserveRange(buffer))
|
||||||
|
{
|
||||||
|
return AuDebug::gReserveHeap->ZRealloc(buffer, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto pRet = ::mi_rezalloc(buffer, length);
|
||||||
|
if (!pRet)
|
||||||
|
{
|
||||||
|
if (gRuntimeConfig.debug.bIsMemoryErrorFatal)
|
||||||
|
{
|
||||||
|
SysPanic("ZAlloc out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_FRealloc(void *buffer, Types::size_t length, Types::size_t align)
|
AUKN_SYM void *_FRealloc(void *buffer, Types::size_t length, Types::size_t align)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_realloc_aligned(buffer, length, align), "MAlloc out of memory");
|
if (AuDebug::IsPointerReserveRange(buffer))
|
||||||
|
{
|
||||||
|
return AuDebug::gReserveHeap->FRealloc(buffer, length, align);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto pRet = ::mi_realloc_aligned(buffer, length, align);
|
||||||
|
if (!pRet)
|
||||||
|
{
|
||||||
|
if (gRuntimeConfig.debug.bIsMemoryErrorFatal)
|
||||||
|
{
|
||||||
|
SysPanic("FReallocEx out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void *_FRealloc(void *buffer, Types::size_t length)
|
AUKN_SYM void *_FRealloc(void *buffer, Types::size_t length)
|
||||||
{
|
{
|
||||||
CHECK_WRAP_RETURN(mi_realloc(buffer, length), "MAlloc out of memory");
|
if (AuDebug::IsPointerReserveRange(buffer))
|
||||||
|
{
|
||||||
|
return AuDebug::gReserveHeap->FRealloc(buffer, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto pRet = ::mi_realloc(buffer, length);
|
||||||
|
if (!pRet)
|
||||||
|
{
|
||||||
|
if (gRuntimeConfig.debug.bIsMemoryErrorFatal)
|
||||||
|
{
|
||||||
|
SysPanic("FRealloc out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM void _Free(void *buffer)
|
AUKN_SYM void _Free(void *buffer)
|
||||||
{
|
{
|
||||||
mi_free(buffer);
|
if (AuDebug::IsPointerReserveRange(buffer))
|
||||||
|
{
|
||||||
|
AuDebug::gReserveHeap->Free(buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
::mi_free(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AUKN_SYM AuUInt GetPageSize()
|
AUKN_SYM AuUInt GetPageSize()
|
||||||
|
Loading…
Reference in New Issue
Block a user