blender/intern/guardedalloc/intern/mallocn_guarded_impl.c
Brecht Van Lommel 183ba284f2 Cleanup: make guarded memory allocation always thread safe
Previously this would be enabled when threads were used, but threads are now
basically always in use so there is no point. Further, this is only needed for
guarded allocation with --debug-memory which is not performance critical.
2020-05-20 01:03:05 +02:00

1166 lines
27 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*/
/** \file
* \ingroup MEM
*
* Guarded memory allocation, and boundary-write detection.
*/
#include <stdarg.h>
#include <stdlib.h>
#include <string.h> /* memcpy */
#include <sys/types.h>
#include <pthread.h>
#include "MEM_guardedalloc.h"
/* to ensure strict conversions */
#include "../../source/blender/blenlib/BLI_strict_flags.h"
#include "atomic_ops.h"
#include "mallocn_intern.h"
/* Only for debugging:
* store original buffer's name when doing MEM_dupallocN
* helpful to profile issues with non-freed "dup_alloc" buffers,
* but this introduces some overhead to memory header and makes
* things slower a bit, so better to keep disabled by default
*/
//#define DEBUG_MEMDUPLINAME
/* Only for debugging:
* lets you count the allocations so as to find the allocator of unfreed memory
* in situations where the leak is predictable */
//#define DEBUG_MEMCOUNTER
/* Only for debugging:
* Defining DEBUG_BACKTRACE will store a backtrace from where
* memory block was allocated and print this trace for all
* unfreed blocks.
*/
//#define DEBUG_BACKTRACE
#ifdef DEBUG_BACKTRACE
# define BACKTRACE_SIZE 100
#endif
#ifdef DEBUG_MEMCOUNTER
/* set this to the value that isn't being freed */
# define DEBUG_MEMCOUNTER_ERROR_VAL 0
static int _mallocn_count = 0;
/* breakpoint here */
static void memcount_raise(const char *name)
{
fprintf(stderr, "%s: memcount-leak, %d\n", name, _mallocn_count);
}
#endif
/* --------------------------------------------------------------------- */
/* Data definition */
/* --------------------------------------------------------------------- */
/* all memory chunks are put in linked lists */
typedef struct localLink {
struct localLink *next, *prev;
} localLink;
typedef struct localListBase {
void *first, *last;
} localListBase;
/* note: keep this struct aligned (e.g., irix/gcc) - Hos */
typedef struct MemHead {
int tag1;
size_t len;
struct MemHead *next, *prev;
const char *name;
const char *nextname;
int tag2;
short pad1;
short alignment; /* if non-zero aligned alloc was used
* and alignment is stored here.
*/
#ifdef DEBUG_MEMCOUNTER
int _count;
#endif
#ifdef DEBUG_MEMDUPLINAME
int need_free_name, pad;
#endif
#ifdef DEBUG_BACKTRACE
void *backtrace[BACKTRACE_SIZE];
int backtrace_size;
#endif
} MemHead;
typedef MemHead MemHeadAligned;
#ifdef DEBUG_BACKTRACE
# if defined(__linux__) || defined(__APPLE__)
# include <execinfo.h>
// Windows is not supported yet.
//# elif defined(_MSV_VER)
//# include <DbgHelp.h>
# endif
#endif
typedef struct MemTail {
int tag3, pad;
} MemTail;
/* --------------------------------------------------------------------- */
/* local functions */
/* --------------------------------------------------------------------- */
static void addtail(volatile localListBase *listbase, void *vlink);
static void remlink(volatile localListBase *listbase, void *vlink);
static void rem_memblock(MemHead *memh);
static void MemorY_ErroR(const char *block, const char *error);
static const char *check_memlist(MemHead *memh);
/* --------------------------------------------------------------------- */
/* locally used defines */
/* --------------------------------------------------------------------- */
#ifdef __BIG_ENDIAN__
# define MAKE_ID(a, b, c, d) ((int)(a) << 24 | (int)(b) << 16 | (c) << 8 | (d))
#else
# define MAKE_ID(a, b, c, d) ((int)(d) << 24 | (int)(c) << 16 | (b) << 8 | (a))
#endif
#define MEMTAG1 MAKE_ID('M', 'E', 'M', 'O')
#define MEMTAG2 MAKE_ID('R', 'Y', 'B', 'L')
#define MEMTAG3 MAKE_ID('O', 'C', 'K', '!')
#define MEMFREE MAKE_ID('F', 'R', 'E', 'E')
#define MEMNEXT(x) ((MemHead *)(((char *)x) - ((char *)&(((MemHead *)0)->next))))
/* --------------------------------------------------------------------- */
/* vars */
/* --------------------------------------------------------------------- */
static unsigned int totblock = 0;
static size_t mem_in_use = 0, peak_mem = 0;
static volatile struct localListBase _membase;
static volatile struct localListBase *membase = &_membase;
static void (*error_callback)(const char *) = NULL;
static bool malloc_debug_memset = false;
#ifdef malloc
# undef malloc
#endif
#ifdef calloc
# undef calloc
#endif
#ifdef free
# undef free
#endif
/* --------------------------------------------------------------------- */
/* implementation */
/* --------------------------------------------------------------------- */
#ifdef __GNUC__
__attribute__((format(printf, 1, 2)))
#endif
static void
print_error(const char *str, ...)
{
char buf[1024];
va_list ap;
va_start(ap, str);
vsnprintf(buf, sizeof(buf), str, ap);
va_end(ap);
buf[sizeof(buf) - 1] = '\0';
if (error_callback)
error_callback(buf);
else
fputs(buf, stderr);
}
static pthread_mutex_t thread_lock = PTHREAD_MUTEX_INITIALIZER;
static void mem_lock_thread(void)
{
pthread_mutex_lock(&thread_lock);
}
static void mem_unlock_thread(void)
{
pthread_mutex_unlock(&thread_lock);
}
bool MEM_guarded_consistency_check(void)
{
const char *err_val = NULL;
MemHead *listend;
/* check_memlist starts from the front, and runs until it finds
* the requested chunk. For this test, that's the last one. */
listend = membase->last;
err_val = check_memlist(listend);
return (err_val == NULL);
}
void MEM_guarded_set_error_callback(void (*func)(const char *))
{
error_callback = func;
}
void MEM_guarded_set_memory_debug(void)
{
malloc_debug_memset = true;
}
size_t MEM_guarded_allocN_len(const void *vmemh)
{
if (vmemh) {
const MemHead *memh = vmemh;
memh--;
return memh->len;
}
else {
return 0;
}
}
void *MEM_guarded_dupallocN(const void *vmemh)
{
void *newp = NULL;
if (vmemh) {
const MemHead *memh = vmemh;
memh--;
#ifndef DEBUG_MEMDUPLINAME
if (LIKELY(memh->alignment == 0))
newp = MEM_guarded_mallocN(memh->len, "dupli_alloc");
else
newp = MEM_guarded_mallocN_aligned(memh->len, (size_t)memh->alignment, "dupli_alloc");
if (newp == NULL)
return NULL;
#else
{
MemHead *nmemh;
char *name = malloc(strlen(memh->name) + 24);
if (LIKELY(memh->alignment == 0)) {
sprintf(name, "%s %s", "dupli_alloc", memh->name);
newp = MEM_guarded_mallocN(memh->len, name);
}
else {
sprintf(name, "%s %s", "dupli_alloc", memh->name);
newp = MEM_guarded_mallocN_aligned(memh->len, (size_t)memh->alignment, name);
}
if (newp == NULL)
return NULL;
nmemh = newp;
nmemh--;
nmemh->need_free_name = 1;
}
#endif
memcpy(newp, vmemh, memh->len);
}
return newp;
}
void *MEM_guarded_reallocN_id(void *vmemh, size_t len, const char *str)
{
void *newp = NULL;
if (vmemh) {
MemHead *memh = vmemh;
memh--;
if (LIKELY(memh->alignment == 0)) {
newp = MEM_guarded_mallocN(len, memh->name);
}
else {
newp = MEM_guarded_mallocN_aligned(len, (size_t)memh->alignment, memh->name);
}
if (newp) {
if (len < memh->len) {
/* shrink */
memcpy(newp, vmemh, len);
}
else {
/* grow (or remain same size) */
memcpy(newp, vmemh, memh->len);
}
}
MEM_guarded_freeN(vmemh);
}
else {
newp = MEM_guarded_mallocN(len, str);
}
return newp;
}
void *MEM_guarded_recallocN_id(void *vmemh, size_t len, const char *str)
{
void *newp = NULL;
if (vmemh) {
MemHead *memh = vmemh;
memh--;
if (LIKELY(memh->alignment == 0)) {
newp = MEM_guarded_mallocN(len, memh->name);
}
else {
newp = MEM_guarded_mallocN_aligned(len, (size_t)memh->alignment, memh->name);
}
if (newp) {
if (len < memh->len) {
/* shrink */
memcpy(newp, vmemh, len);
}
else {
memcpy(newp, vmemh, memh->len);
if (len > memh->len) {
/* grow */
/* zero new bytes */
memset(((char *)newp) + memh->len, 0, len - memh->len);
}
}
}
MEM_guarded_freeN(vmemh);
}
else {
newp = MEM_guarded_callocN(len, str);
}
return newp;
}
#ifdef DEBUG_BACKTRACE
# if defined(__linux__) || defined(__APPLE__)
static void make_memhead_backtrace(MemHead *memh)
{
memh->backtrace_size = backtrace(memh->backtrace, BACKTRACE_SIZE);
}
static void print_memhead_backtrace(MemHead *memh)
{
char **strings;
int i;
strings = backtrace_symbols(memh->backtrace, memh->backtrace_size);
for (i = 0; i < memh->backtrace_size; i++) {
print_error(" %s\n", strings[i]);
}
free(strings);
}
# else
static void make_memhead_backtrace(MemHead *memh)
{
(void)memh; /* Ignored. */
}
static void print_memhead_backtrace(MemHead *memh)
{
(void)memh; /* Ignored. */
}
# endif /* defined(__linux__) || defined(__APPLE__) */
#endif /* DEBUG_BACKTRACE */
static void make_memhead_header(MemHead *memh, size_t len, const char *str)
{
MemTail *memt;
memh->tag1 = MEMTAG1;
memh->name = str;
memh->nextname = NULL;
memh->len = len;
memh->pad1 = 0;
memh->alignment = 0;
memh->tag2 = MEMTAG2;
#ifdef DEBUG_MEMDUPLINAME
memh->need_free_name = 0;
#endif
#ifdef DEBUG_BACKTRACE
make_memhead_backtrace(memh);
#endif
memt = (MemTail *)(((char *)memh) + sizeof(MemHead) + len);
memt->tag3 = MEMTAG3;
atomic_add_and_fetch_u(&totblock, 1);
atomic_add_and_fetch_z(&mem_in_use, len);
mem_lock_thread();
addtail(membase, &memh->next);
if (memh->next) {
memh->nextname = MEMNEXT(memh->next)->name;
}
peak_mem = mem_in_use > peak_mem ? mem_in_use : peak_mem;
mem_unlock_thread();
}
void *MEM_guarded_mallocN(size_t len, const char *str)
{
MemHead *memh;
len = SIZET_ALIGN_4(len);
memh = (MemHead *)malloc(len + sizeof(MemHead) + sizeof(MemTail));
if (LIKELY(memh)) {
make_memhead_header(memh, len, str);
if (UNLIKELY(malloc_debug_memset && len))
memset(memh + 1, 255, len);
#ifdef DEBUG_MEMCOUNTER
if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL)
memcount_raise(__func__);
memh->_count = _mallocn_count++;
#endif
return (++memh);
}
print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
SIZET_ARG(len),
str,
(unsigned int)mem_in_use);
return NULL;
}
void *MEM_guarded_malloc_arrayN(size_t len, size_t size, const char *str)
{
size_t total_size;
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
print_error(
"Malloc array aborted due to integer overflow: "
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
SIZET_ARG(len),
SIZET_ARG(size),
str,
(unsigned int)mem_in_use);
abort();
return NULL;
}
return MEM_guarded_mallocN(total_size, str);
}
void *MEM_guarded_mallocN_aligned(size_t len, size_t alignment, const char *str)
{
/* We only support alignment to a power of two. */
assert(IS_POW2(alignment));
/* Use a minimal alignment of 8. Otherwise MEM_guarded_freeN thinks it is an illegal pointer. */
if (alignment < 8) {
alignment = 8;
}
/* It's possible that MemHead's size is not properly aligned,
* do extra padding to deal with this.
*
* We only support small alignments which fits into short in
* order to save some bits in MemHead structure.
*/
size_t extra_padding = MEMHEAD_ALIGN_PADDING(alignment);
/* Huge alignment values doesn't make sense and they
* wouldn't fit into 'short' used in the MemHead.
*/
assert(alignment < 1024);
len = SIZET_ALIGN_4(len);
MemHead *memh = (MemHead *)aligned_malloc(
len + extra_padding + sizeof(MemHead) + sizeof(MemTail), alignment);
if (LIKELY(memh)) {
/* We keep padding in the beginning of MemHead,
* this way it's always possible to get MemHead
* from the data pointer.
*/
memh = (MemHead *)((char *)memh + extra_padding);
make_memhead_header(memh, len, str);
memh->alignment = (short)alignment;
if (UNLIKELY(malloc_debug_memset && len))
memset(memh + 1, 255, len);
#ifdef DEBUG_MEMCOUNTER
if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL)
memcount_raise(__func__);
memh->_count = _mallocn_count++;
#endif
return (++memh);
}
print_error("aligned_malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
SIZET_ARG(len),
str,
(unsigned int)mem_in_use);
return NULL;
}
void *MEM_guarded_callocN(size_t len, const char *str)
{
MemHead *memh;
len = SIZET_ALIGN_4(len);
memh = (MemHead *)calloc(len + sizeof(MemHead) + sizeof(MemTail), 1);
if (memh) {
make_memhead_header(memh, len, str);
#ifdef DEBUG_MEMCOUNTER
if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL)
memcount_raise(__func__);
memh->_count = _mallocn_count++;
#endif
return (++memh);
}
print_error("Calloc returns null: len=" SIZET_FORMAT " in %s, total %u\n",
SIZET_ARG(len),
str,
(unsigned int)mem_in_use);
return NULL;
}
void *MEM_guarded_calloc_arrayN(size_t len, size_t size, const char *str)
{
size_t total_size;
if (UNLIKELY(!MEM_size_safe_multiply(len, size, &total_size))) {
print_error(
"Calloc array aborted due to integer overflow: "
"len=" SIZET_FORMAT "x" SIZET_FORMAT " in %s, total %u\n",
SIZET_ARG(len),
SIZET_ARG(size),
str,
(unsigned int)mem_in_use);
abort();
return NULL;
}
return MEM_guarded_callocN(total_size, str);
}
/* Memory statistics print */
typedef struct MemPrintBlock {
const char *name;
uintptr_t len;
int items;
} MemPrintBlock;
static int compare_name(const void *p1, const void *p2)
{
const MemPrintBlock *pb1 = (const MemPrintBlock *)p1;
const MemPrintBlock *pb2 = (const MemPrintBlock *)p2;
return strcmp(pb1->name, pb2->name);
}
static int compare_len(const void *p1, const void *p2)
{
const MemPrintBlock *pb1 = (const MemPrintBlock *)p1;
const MemPrintBlock *pb2 = (const MemPrintBlock *)p2;
if (pb1->len < pb2->len)
return 1;
else if (pb1->len == pb2->len)
return 0;
else
return -1;
}
void MEM_guarded_printmemlist_stats(void)
{
MemHead *membl;
MemPrintBlock *pb, *printblock;
unsigned int totpb, a, b;
size_t mem_in_use_slop = 0;
mem_lock_thread();
if (totblock != 0) {
/* put memory blocks into array */
printblock = malloc(sizeof(MemPrintBlock) * totblock);
if (UNLIKELY(!printblock)) {
mem_unlock_thread();
print_error("malloc returned null while generating stats");
return;
}
}
else {
printblock = NULL;
}
pb = printblock;
totpb = 0;
membl = membase->first;
if (membl)
membl = MEMNEXT(membl);
while (membl && pb) {
pb->name = membl->name;
pb->len = membl->len;
pb->items = 1;
totpb++;
pb++;
#ifdef USE_MALLOC_USABLE_SIZE
if (membl->alignment == 0) {
mem_in_use_slop += (sizeof(MemHead) + sizeof(MemTail) + malloc_usable_size((void *)membl)) -
membl->len;
}
#endif
if (membl->next)
membl = MEMNEXT(membl->next);
else
break;
}
/* sort by name and add together blocks with the same name */
if (totpb > 1) {
qsort(printblock, totpb, sizeof(MemPrintBlock), compare_name);
}
for (a = 0, b = 0; a < totpb; a++) {
if (a == b) {
continue;
}
else if (strcmp(printblock[a].name, printblock[b].name) == 0) {
printblock[b].len += printblock[a].len;
printblock[b].items++;
}
else {
b++;
memcpy(&printblock[b], &printblock[a], sizeof(MemPrintBlock));
}
}
totpb = b + 1;
/* sort by length and print */
if (totpb > 1) {
qsort(printblock, totpb, sizeof(MemPrintBlock), compare_len);
}
printf("\ntotal memory len: %.3f MB\n", (double)mem_in_use / (double)(1024 * 1024));
printf("peak memory len: %.3f MB\n", (double)peak_mem / (double)(1024 * 1024));
printf("slop memory len: %.3f MB\n", (double)mem_in_use_slop / (double)(1024 * 1024));
printf(" ITEMS TOTAL-MiB AVERAGE-KiB TYPE\n");
for (a = 0, pb = printblock; a < totpb; a++, pb++) {
printf("%6d (%8.3f %8.3f) %s\n",
pb->items,
(double)pb->len / (double)(1024 * 1024),
(double)pb->len / 1024.0 / (double)pb->items,
pb->name);
}
if (printblock != NULL) {
free(printblock);
}
mem_unlock_thread();
#ifdef HAVE_MALLOC_STATS
printf("System Statistics:\n");
malloc_stats();
#endif
}
static const char mem_printmemlist_pydict_script[] =
"mb_userinfo = {}\n"
"totmem = 0\n"
"for mb_item in membase:\n"
" mb_item_user_size = mb_userinfo.setdefault(mb_item['name'], [0,0])\n"
" mb_item_user_size[0] += 1 # Add a user\n"
" mb_item_user_size[1] += mb_item['len'] # Increment the size\n"
" totmem += mb_item['len']\n"
"print('(membase) items:', len(membase), '| unique-names:',\n"
" len(mb_userinfo), '| total-mem:', totmem)\n"
"mb_userinfo_sort = list(mb_userinfo.items())\n"
"for sort_name, sort_func in (('size', lambda a: -a[1][1]),\n"
" ('users', lambda a: -a[1][0]),\n"
" ('name', lambda a: a[0])):\n"
" print('\\nSorting by:', sort_name)\n"
" mb_userinfo_sort.sort(key = sort_func)\n"
" for item in mb_userinfo_sort:\n"
" print('name:%%s, users:%%i, len:%%i' %%\n"
" (item[0], item[1][0], item[1][1]))\n";
/* Prints in python syntax for easy */
static void MEM_guarded_printmemlist_internal(int pydict)
{
MemHead *membl;
mem_lock_thread();
membl = membase->first;
if (membl)
membl = MEMNEXT(membl);
if (pydict) {
print_error("# membase_debug.py\n");
print_error("membase = [\n");
}
while (membl) {
if (pydict) {
print_error(" {'len':" SIZET_FORMAT
", "
"'name':'''%s''', "
"'pointer':'%p'},\n",
SIZET_ARG(membl->len),
membl->name,
(void *)(membl + 1));
}
else {
#ifdef DEBUG_MEMCOUNTER
print_error("%s len: " SIZET_FORMAT " %p, count: %d\n",
membl->name,
SIZET_ARG(membl->len),
membl + 1,
membl->_count);
#else
print_error("%s len: " SIZET_FORMAT " %p\n",
membl->name,
SIZET_ARG(membl->len),
(void *)(membl + 1));
#endif
#ifdef DEBUG_BACKTRACE
print_memhead_backtrace(membl);
#endif
}
if (membl->next)
membl = MEMNEXT(membl->next);
else
break;
}
if (pydict) {
print_error("]\n\n");
print_error(mem_printmemlist_pydict_script);
}
mem_unlock_thread();
}
void MEM_guarded_callbackmemlist(void (*func)(void *))
{
MemHead *membl;
mem_lock_thread();
membl = membase->first;
if (membl)
membl = MEMNEXT(membl);
while (membl) {
func(membl + 1);
if (membl->next)
membl = MEMNEXT(membl->next);
else
break;
}
mem_unlock_thread();
}
#if 0
short MEM_guarded_testN(void *vmemh)
{
MemHead *membl;
mem_lock_thread();
membl = membase->first;
if (membl)
membl = MEMNEXT(membl);
while (membl) {
if (vmemh == membl + 1) {
mem_unlock_thread();
return 1;
}
if (membl->next)
membl = MEMNEXT(membl->next);
else
break;
}
mem_unlock_thread();
print_error("Memoryblock %p: pointer not in memlist\n", vmemh);
return 0;
}
#endif
void MEM_guarded_printmemlist(void)
{
MEM_guarded_printmemlist_internal(0);
}
void MEM_guarded_printmemlist_pydict(void)
{
MEM_guarded_printmemlist_internal(1);
}
void MEM_guarded_freeN(void *vmemh)
{
MemTail *memt;
MemHead *memh = vmemh;
const char *name;
if (memh == NULL) {
MemorY_ErroR("free", "attempt to free NULL pointer");
/* print_error(err_stream, "%d\n", (memh+4000)->tag1); */
return;
}
if (sizeof(intptr_t) == 8) {
if (((intptr_t)memh) & 0x7) {
MemorY_ErroR("free", "attempt to free illegal pointer");
return;
}
}
else {
if (((intptr_t)memh) & 0x3) {
MemorY_ErroR("free", "attempt to free illegal pointer");
return;
}
}
memh--;
if (memh->tag1 == MEMFREE && memh->tag2 == MEMFREE) {
MemorY_ErroR(memh->name, "double free");
return;
}
if ((memh->tag1 == MEMTAG1) && (memh->tag2 == MEMTAG2) && ((memh->len & 0x3) == 0)) {
memt = (MemTail *)(((char *)memh) + sizeof(MemHead) + memh->len);
if (memt->tag3 == MEMTAG3) {
memh->tag1 = MEMFREE;
memh->tag2 = MEMFREE;
memt->tag3 = MEMFREE;
/* after tags !!! */
rem_memblock(memh);
return;
}
MemorY_ErroR(memh->name, "end corrupt");
name = check_memlist(memh);
if (name != NULL) {
if (name != memh->name)
MemorY_ErroR(name, "is also corrupt");
}
}
else {
mem_lock_thread();
name = check_memlist(memh);
mem_unlock_thread();
if (name == NULL)
MemorY_ErroR("free", "pointer not in memlist");
else
MemorY_ErroR(name, "error in header");
}
totblock--;
/* here a DUMP should happen */
return;
}
/* --------------------------------------------------------------------- */
/* local functions */
/* --------------------------------------------------------------------- */
static void addtail(volatile localListBase *listbase, void *vlink)
{
struct localLink *link = vlink;
/* for a generic API error checks here is fine but
* the limited use here they will never be NULL */
#if 0
if (link == NULL)
return;
if (listbase == NULL)
return;
#endif
link->next = NULL;
link->prev = listbase->last;
if (listbase->last)
((struct localLink *)listbase->last)->next = link;
if (listbase->first == NULL)
listbase->first = link;
listbase->last = link;
}
static void remlink(volatile localListBase *listbase, void *vlink)
{
struct localLink *link = vlink;
/* for a generic API error checks here is fine but
* the limited use here they will never be NULL */
#if 0
if (link == NULL)
return;
if (listbase == NULL)
return;
#endif
if (link->next)
link->next->prev = link->prev;
if (link->prev)
link->prev->next = link->next;
if (listbase->last == link)
listbase->last = link->prev;
if (listbase->first == link)
listbase->first = link->next;
}
static void rem_memblock(MemHead *memh)
{
mem_lock_thread();
remlink(membase, &memh->next);
if (memh->prev) {
if (memh->next)
MEMNEXT(memh->prev)->nextname = MEMNEXT(memh->next)->name;
else
MEMNEXT(memh->prev)->nextname = NULL;
}
mem_unlock_thread();
atomic_sub_and_fetch_u(&totblock, 1);
atomic_sub_and_fetch_z(&mem_in_use, memh->len);
#ifdef DEBUG_MEMDUPLINAME
if (memh->need_free_name)
free((char *)memh->name);
#endif
if (UNLIKELY(malloc_debug_memset && memh->len))
memset(memh + 1, 255, memh->len);
if (LIKELY(memh->alignment == 0)) {
free(memh);
}
else {
aligned_free(MEMHEAD_REAL_PTR(memh));
}
}
static void MemorY_ErroR(const char *block, const char *error)
{
print_error("Memoryblock %s: %s\n", block, error);
#ifdef WITH_ASSERT_ABORT
abort();
#endif
}
static const char *check_memlist(MemHead *memh)
{
MemHead *forw, *back, *forwok, *backok;
const char *name;
forw = membase->first;
if (forw)
forw = MEMNEXT(forw);
forwok = NULL;
while (forw) {
if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2)
break;
forwok = forw;
if (forw->next)
forw = MEMNEXT(forw->next);
else
forw = NULL;
}
back = (MemHead *)membase->last;
if (back)
back = MEMNEXT(back);
backok = NULL;
while (back) {
if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2)
break;
backok = back;
if (back->prev)
back = MEMNEXT(back->prev);
else
back = NULL;
}
if (forw != back)
return ("MORE THAN 1 MEMORYBLOCK CORRUPT");
if (forw == NULL && back == NULL) {
/* no wrong headers found then but in search of memblock */
forw = membase->first;
if (forw)
forw = MEMNEXT(forw);
forwok = NULL;
while (forw) {
if (forw == memh)
break;
if (forw->tag1 != MEMTAG1 || forw->tag2 != MEMTAG2)
break;
forwok = forw;
if (forw->next)
forw = MEMNEXT(forw->next);
else
forw = NULL;
}
if (forw == NULL)
return NULL;
back = (MemHead *)membase->last;
if (back)
back = MEMNEXT(back);
backok = NULL;
while (back) {
if (back == memh)
break;
if (back->tag1 != MEMTAG1 || back->tag2 != MEMTAG2)
break;
backok = back;
if (back->prev)
back = MEMNEXT(back->prev);
else
back = NULL;
}
}
if (forwok)
name = forwok->nextname;
else
name = "No name found";
if (forw == memh) {
/* to be sure but this block is removed from the list */
if (forwok) {
if (backok) {
forwok->next = (MemHead *)&backok->next;
backok->prev = (MemHead *)&forwok->next;
forwok->nextname = backok->name;
}
else {
forwok->next = NULL;
membase->last = (struct localLink *)&forwok->next;
}
}
else {
if (backok) {
backok->prev = NULL;
membase->first = &backok->next;
}
else {
membase->first = membase->last = NULL;
}
}
}
else {
MemorY_ErroR(name, "Additional error in header");
return ("Additional error in header");
}
return (name);
}
size_t MEM_guarded_get_peak_memory(void)
{
size_t _peak_mem;
mem_lock_thread();
_peak_mem = peak_mem;
mem_unlock_thread();
return _peak_mem;
}
void MEM_guarded_reset_peak_memory(void)
{
mem_lock_thread();
peak_mem = mem_in_use;
mem_unlock_thread();
}
size_t MEM_guarded_get_memory_in_use(void)
{
size_t _mem_in_use;
mem_lock_thread();
_mem_in_use = mem_in_use;
mem_unlock_thread();
return _mem_in_use;
}
unsigned int MEM_guarded_get_memory_blocks_in_use(void)
{
unsigned int _totblock;
mem_lock_thread();
_totblock = totblock;
mem_unlock_thread();
return _totblock;
}
#ifndef NDEBUG
const char *MEM_guarded_name_ptr(void *vmemh)
{
if (vmemh) {
MemHead *memh = vmemh;
memh--;
return memh->name;
}
else {
return "MEM_guarded_name_ptr(NULL)";
}
}
#endif /* NDEBUG */