/*
* DebugMemory.cpp
* iphone-gl-app
*
* Created by John Ryland on 22/06/09.
* Copyright 2009 InvertedLogic. All rights reserved.
*
*/
#define DEBUG_MEMORY_IMPL // Avoid recursion
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <execinfo.h>
#include <mach-o/dyld.h>
#include "Mutex.h"
#include "Iterators.h"
#include "DebugMemory.h"
#include "Debug.h"
enum {
SET_OWNER = 0,
CALLOC = 1,
VALLOC = 2,
REALLOC = 3,
REALLOCF = 4,
MALLOC = 5,
NEW = 6,
NEW_ARRAY = 7,
NEW2 = 8,
NEW2_ARRAY = 9,
FREE = 10,
DELETE = 11,
DELETE_ARRAY = 12,
STRDUP = 13
};
class AllocationRecord
{
public:
AllocationRecord() { ptr = 0; }
AllocationRecord(void *p, int s, const char *f, int l, int fl) {
ptr = p;
size = s;
file = f;
line = l;
flags = fl;
}
void *ptr;
int size;
const char *file;
int line;
int flags;
};
class IntRecord {
public:
IntRecord() { value = 0; }
IntRecord(const char *k, int x) { key = k; value = x; }
const char *key;
int value;
};
Dict<AllocationRecord> records;
Dict<IntRecord> fileLineTotals;
Dict<IntRecord> fileTotals;
Mutex debugMemoryMutex;
bool debugMemoryEnabled = false;
void DebugMemory::enable()
{
debugMemoryEnabled = true;
}
void DebugMemory::disable()
{
debugMemoryEnabled = false;
}
int debug_mem_log_level = 0;
void DebugMemory::logMemoryAllocations(int level)
{
debug_mem_log_level = level;
}
#define normal_log if ( debug_mem_log_level > 0 ) DebugMessage::debug
#define verbose_log if ( debug_mem_log_level > 1 ) DebugMessage::debug
void DebugMemory::showMemoryLeaks()
{
#ifdef DEBUG
if ( records.count() == 0 )
DebugMessage::debug("No leaks found.\n");
else
foreach (AllocationRecord rec, records)
DebugMessage::debug("allocation from %s, line %i, leaked %i bytes at %p.\n", rec.file, rec.line, rec.size, rec.ptr);
#else
DebugMessage::debug("Rebuild in DEBUG mode to enable memory logging.\n");
#endif
}
void DebugMemory::showLocatableMemoryLeaks()
{
#ifdef DEBUG
if ( records.count() == 0 )
DebugMessage::debug("No leaks found.\n");
else
foreach (AllocationRecord rec, records) {
if ( rec.file && strcmp(rec.file, "unknown") )
DebugMessage::debug("allocation from %s, line %i, leaked %i bytes at %p.\n", rec.file, rec.line, rec.size, rec.ptr);
}
#else
DebugMessage::debug("Rebuild in DEBUG mode to enable memory logging.\n");
#endif
}
void DebugMemory::showMemoryAllocations()
{
#ifdef DEBUG
foreach (IntRecord rec, fileLineTotals)
printf("file and line %s allocated %i bytes\n", rec.key, rec.value);
foreach (IntRecord rec, fileTotals)
printf("file %s allocated %i bytes\n", rec.key, rec.value);
#else
printf("Rebuild in DEBUG mode to enable memory logging.\n");
#endif
}
const char *debug_mem_file = 0;
int debug_mem_line = 0;
/*
Potential non-fatal race condition in multi-threaded apps when using new and delete.
The lock needs to extend around the two calls to addLog that happen, instead it locks when setting the owner, and
then locks again when doing the allocation, potentially the file and line number associated with the allocation could be wrong.
*/
void *DebugMemory::addLog(int type, void *p, unsigned int nNum, unsigned int size, const char *file, int line)
{
#ifdef DEBUG
void *ptr = 0;
switch (type)
{
/*
SET_OWNER = 0,
CALLOC = 1,
VALLOC = 2,
REALLOC = 3,
REALLOCF = 4,
MALLOC = 5,
NEW = 6,
NEW_ARRAY = 7,
NEW2 = 8,
NEW2_ARRAY = 9,
FREE = 10,
DELETE = 11,
DELETE_ARRAY = 12,
STRDUP = 13
*/
case 0:/* debug_mem_file = file; debug_mem_line = line; */return 0;
case 1: ptr = calloc(nNum, size); size *= nNum; break;
case 2: ptr = valloc(size); break;
case 3: ptr = realloc(p, size); if (!ptr && size) return 0; break;
case 4: ptr = reallocf(p, size); break;
case 5: case 6: case 7: case 8:
case 9: ptr = malloc(size); break;
case 10: case 11: case 12: free(p); break;
case 13: ptr = (void *)strdup((const char*)p); break;
default: return 0;
}
if (!debugMemoryEnabled)
return ptr;
Mutex::MutexAutoLock lock = debugMemoryMutex.lock();
const char *operation[] = { "-", "calloc", "valloc", "realloc", "reallocf", "malloc", "new", "new[]", "new", "new[]", "free", "delete", "delete[]", "strdup" };
const int typeMap[] = { 0, 1, 1, 1, 1, 1, 2, 3, 2, 3, 1, 2, 3, 1 };
char tmpLoc[1024];
const char *loc = tmpLoc;
if (!file) {
void *callstack[2];
int frames = backtrace(callstack, 2);
// _dyld_bind_fully_image_containing_address(callstack[0]);
// _dyld_bind_fully_image_containing_address(callstack[1]);
char **strs = backtrace_symbols(callstack, frames);
if (frames) {
//Dl_info info;
//if (dladdr(callstack[0], &info)) {
// const char* dli_sname
loc = strdup(strs[1]);
file = strdup(strs[1]);
//loc = strdup(info.dli_fname);
//file = strdup(info.dli_sname);
line = 1;
} else {
loc = "unknown location";
file = "unknown";
line = -1;
}
//free(strs);
} else
snprintf(tmpLoc, 1024, "%s, line %i", file ? file : "-", line);
if ( type == NEW || type == NEW_ARRAY || type == NEW2 || type == NEW2_ARRAY || type == DELETE || type == DELETE_ARRAY )
debug_mem_file = 0;
// if ( (type == REALLOC || type == REALLOCF) && size == 0 )
// type = FREE;
char ptr_str[128] = "";
char siz_str[128] = "";
if ( p ) snprintf(ptr_str, 128, "of ptr %p ", p);
if ( size ) snprintf(siz_str, 128, "of size %i ", size);
normal_log("doing %s %s%sfrom %s.\n", operation[type], ptr_str, siz_str, loc);
if ( type == REALLOC || type == REALLOCF )
if ( p == ptr ) {
// printf("realloced memory unchanged.\n");
return ptr;
}
/*
if ( type == 10 || type == 11 || type == 12 ) {
normal_log("Debug: doing %s of ptr %p from %s.\n", operation[type], p, loc);
} else if ( type == 3 || type == 4 ) {
normal_log("Debug: doing %s of ptr %p of size %i from %s.\n", operation[type], p, size, loc);
} else
normal_log("Debug: doing %s of size %i from %s.\n", operation[type], size, loc);
*/
if ( type == REALLOC || type == REALLOCF || type == FREE || type == DELETE || type == DELETE_ARRAY ) {
// if ( type == 3 || type == 4 || type == 10 || type == 11 || type == 12 ) {
if (p) {
char key[32];
sprintf(key, "%p", p);
//AllocationRecord rec = records.find(key);
const AllocationRecord &rec = records.find(key);
// if ( type == 3 || type == 4 )
// normal_log("Debug: realloc'd memory was previously at %p and allocated %i bytes from %s, line %i\n", rec.ptr, rec.size, rec.file ? rec.file : "-", rec.line);
if ( rec.ptr ) {
if ( rec.flags != typeMap[type] ) {
if ( p == ptr )
printf("AGGGGGGGG!!!.\n");
printf("Debug: realloc'd memory was previously at %p and allocated %i bytes from %s, line %i\n", rec.ptr, rec.size, rec.file ? rec.file : "-", rec.line);
printf("Unmatched allocation type for ptr %p from %s, type was %i and is now (%i) %i.\n", p, loc, rec.flags, type, typeMap[type]);
printf("eg Used free on pointer allocated with new, or malloc and delete\n");
printf("new[] and delete, or new and delete[], or new and realloc etc.\n");
records.remove(key); // Treat as warning and still proceed as if this could be okay.
} else {
records.remove(key);
verbose_log("removed allocation record for ptr %p\n", p);
}
} else {
printf("Delete/free of unmatched allocation for ptr %p from %s.\n", p, loc);
}
}
}
if ( type == REALLOC || type == REALLOCF )
if ( size == 0 )
return 0;
if ( type == FREE || type == DELETE || type == DELETE_ARRAY )
// if ( type == 10 || type == 11 || type == 12 )
return 0;
/*
if ( type == 3 || type == 4 ) {
char key[32];
sprintf(key, "%p", p);
const AllocationRecord &rec = records.find(key);
normal_log("Debug: realloc'd memory was previously at %p and allocated %i bytes from %s, line %i\n", rec.ptr, rec.size, rec.file, rec.line);
}
*/
char key[32];
sprintf(key, "%p", ptr);
records.append(key, AllocationRecord(ptr, size, file, line, typeMap[type]));
verbose_log("added allocation record for ptr %p\n", ptr);
int x = fileLineTotals.find(loc).value;
if (x)
fileLineTotals.remove(loc);
fileLineTotals.append(loc, IntRecord(strdup(loc), x + size));
int y = fileTotals.find(file).value;
if (y)
fileTotals.remove(file);
fileTotals.append(file, IntRecord(file, y + size));
return ptr;
#endif
return 0;
}
#ifdef DEBUG
#ifndef DEBUG_MEMORY_DISABLE
void *operator new (size_t size) { return DebugMemory::addLog(8, 0, 0, size, debug_mem_file, debug_mem_line); }
void *operator new[] (size_t size) { return DebugMemory::addLog(9, 0, 0, size, debug_mem_file, debug_mem_line); }
void operator delete (void *p) { DebugMemory::addLog(11, p, 0, 0, debug_mem_file, debug_mem_line); }
void operator delete[](void *p) { DebugMemory::addLog(12, p, 0, 0, debug_mem_file, debug_mem_line); }
#endif // DEBUG_MEMORY_DISABLE
#endif