/*- * Copyright (c) 2014-2021 Carsten Sonne Larsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include #include #define ALLOC_MEM(x) AllocVec(x, MEMF_ANY | MEMF_CLEAR) #define FREE_MEM(x) FreeVec(x) #include "mem.h" #include "log.h" #include "logmod.h" #define MODULENAME "Memory" #define LogMemTrace(...) LogMemTrace(MODULENAME, __VA_ARGS__) //#define MEM_TRACE 1 #if defined(__x86_64__) || defined(__aarch64__) || \ defined(_M_AMD64) || defined(_M_ARM64) || \ defined(__powerpc64__) #define P64BIT #endif /* * Block of allocated memory. */ struct MemoryBlock { struct MemoryBlock *next; size_t size; void *address; char *file; char *function; int line; }; /* * List of allocated memory. Uses the LIFO principle. */ struct MemoryList { struct MemoryBlock *first; size_t peak; size_t size; long count; }; /* * Global list of allocated memory. */ volatile static struct MemoryList *list = NULL; /* * Get the length of a null terminated string. */ int MemStrLen(const char *string) { char *i = (char *)string; char *s = i; while (*i) i++; return (int)(i - s); } /* * Get memory usage in the global memory list. */ void MemUsage(long *blocks, long *size, long *peak) { if (blocks != NULL) { *blocks = list->count; } if (size != NULL) { *size = (long)list->size; } if (peak != NULL) { *peak = (long)list->peak; } } /* * Log a memory allocation error. */ static void AllocationError(const char *descr, size_t size, const char *file, const char *function, int line) { LogDebug("Memory allocation error (%s) from %s, %s, line %ld, %ld bytes", descr, function, file, line, (long)size); } /* * Log a memory deallocation error. */ static void DeAllocationError(const char *descr, void *p, const char *file, const char *function, int line) { LogDebug("Memory deallocation error (%s) from %s, %s, line %ld at 0x%lx", descr, function, file, line, p); } static char *StrDupRaw(const char *s1) { char *s2; size_t len = s1 != NULL ? MemStrLen(s1) : 1; s2 = ALLOC_MEM(++len); if (s2 == NULL) { return NULL; } CopyMem((void *)s1, s2, --len); return s2; } static void MemZero(void *address, size_t size) { if (address == NULL || size == 0) { return; } char *c = (char *)address; int n = size; do { *c++ = '\0'; } while (--n); } void InitMemSafe(void) { volatile struct MemoryList *m = (volatile struct MemoryList *)ALLOC_MEM(sizeof(struct MemoryList)); bool newList = false; if (m == NULL) { return; } Forbid(); if (list == NULL) { list = m; ; list->first = NULL; list->peak = 0; list->size = 0; list->count = 0; newList = true; } Permit(); if (newList && !list) { AllocationError("list", sizeof(struct MemoryList), __FILE__, __FUNCTION__, __LINE__); } if (!newList) { FREE_MEM((void *)m); } } /* * Allocate memory and add it to the global memory list. */ void *AllocMemTraced(size_t size, const char *file, const char *function, int line, bool trace) { struct MemoryBlock *newblock; #ifdef P64BIT // Align to bytes of 8 size_t allocsize = (size + 7) & ~0x07; #else // Align to bytes of 4 size_t allocsize = (size + 3) & ~0x03; #endif newblock = (struct MemoryBlock *)ALLOC_MEM(sizeof(struct MemoryBlock)); if (!newblock) { AllocationError("block", sizeof(struct MemoryBlock), file, function, line); return 0; } newblock->address = (struct MemoryBlock *)ALLOC_MEM(allocsize); if (!newblock->address) { FREE_MEM(newblock); AllocationError("memory", allocsize, file, function, line); return 0; } newblock->size = allocsize; newblock->file = StrDupRaw(file); newblock->function = StrDupRaw(function); newblock->line = line; Forbid(); newblock->next = list->first; list->first = newblock; list->size += allocsize; list->count++; if (list->size > list->peak) { list->peak = list->size; } Permit(); #ifdef MEM_TRACE if (trace) { LogMemTrace("[Memory] Allocated memory block from %s, %s, line %ld, %ld bytes at 0x%lx", newblock->function, newblock->file, newblock->line, newblock->size, newblock->address); } #endif // Memory allocated return newblock->address; } static void RemoveMemSafe(void *block, const char *file, const char *function, int line, bool deallocate) { struct MemoryBlock *current, *previous; bool found = true; if (list == NULL) { DeAllocationError("list", 0, file, function, line); return; } if (block == NULL) { DeAllocationError("block", 0, file, function, line); return; } Forbid(); previous = NULL; current = list->first; while (current != NULL && current->address != block) { previous = current; current = current->next; } if (current != NULL) { if (previous == NULL) { list->first = current->next; } else { previous->next = current->next; } list->size -= current->size; list->count--; } else { found = false; } Permit(); if (found) { #ifdef MEM_TRACE LogMemTrace("[Memory] Deallocated memory block from %s, %s, line %ld, %ld bytes at 0x%lx", current->function, current->file, current->line, current->size, current->address); #endif if (deallocate) { MemZero(current->address, current->size); FREE_MEM(current->address); } if (current->file != NULL) { MemZero(current->file, MemStrLen(current->file)); FREE_MEM(current->file); } if (current->function != NULL) { MemZero(current->function, MemStrLen(current->function)); FREE_MEM(current->function); } MemZero(current, sizeof(struct MemoryBlock)); FREE_MEM(current); } else { DeAllocationError("address not found", block, file, function, line); } } /* * Deallocate memory from the global memory list. */ void FreeMemTraced(void *block, const char *file, const char *function, int line) { RemoveMemSafe(block, file, function, line, true); } void *MemDupTraced(const void *s1, size_t len, const char *file, const char *function, int line, bool trace) { char *dup; dup = AllocMemTraced(len, file, function, line, trace); CopyMem((void *)s1, dup, len); return dup; } char *StrDupTraced(const char *s1, const char *file, const char *function, int line, bool trace) { char *s2; size_t len = s1 != NULL ? MemStrLen(s1) : 1; s2 = AllocMemTraced(++len, file, function, line, trace); if (s2 == NULL) { return NULL; } CopyMem((void *)s1, s2, --len); return s2; } /* * Deallocate all memory in the global memory list. */ void FreeAllSafe(void) { volatile struct MemoryList *m; struct MemoryBlock *current, *next; Forbid(); m = list; list = NULL; Permit(); if (m == NULL) { return; } current = m->first; while (current != NULL) { if (current->file && current->function) { LogDebug("Released forgotten memory block from %s, %s, line %ld, %ld bytes at 0x%lx", current->function, current->file, current->line, (long)current->size, current->address); } else { LogDebug("Released forgotten memory block on %ld bytes at 0x%lx", (long)current->size, current->address); } next = current->next; MemZero(current->address, current->size); FREE_MEM(current->address); if (current->file != NULL) { MemZero(current->file, MemStrLen(current->file)); FREE_MEM(current->file); } if (current->function != NULL) { MemZero(current->function, MemStrLen(current->function)); FREE_MEM(current->function); } MemZero(current->file, sizeof(struct MemoryBlock)); FREE_MEM(current); current = next; } LogDebug("Memory clear"); FREE_MEM((void *)m); }