/*
* malloc-dbg2.c
*
* Debug memory allocation. Hacky code, used only
* for debugging. Tracks how much memory different malloc users ask for.
*
* To use:
* Add following to a header file and recompile:
void * my_realloc_hook(void *, size_t, const char *, int, const char *);
void * my_malloc_hook(size_t, const char *, int, const char *);
void my_free_hook(void *, const char *, int, const char *);
void my_strdup_hook(const char *, const char *, int, const char *);
#define malloc(X) my_malloc_hook(X, __FILE__, __LINE__, __FUNCTION__)
#define realloc(X,N) my_realloc_hook(X, N, __FILE__, __LINE__, __FUNCTION__)
#define free(X) my_free_hook(X, __FILE__, __LINE__, __FUNCTION__)
#define strdup(X) my_strdup_hook(X, __FILE__, __LINE__, __FUNCTION__)
*
* Copyright (c) 2008, 2023 Linas Vepstas <linas@linas.org>
*/
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <execinfo.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <malloc.h>
#include <threads.h>
#include <sys/time.h>
#include <sys/resource.h>
extern void * my_realloc_hook(void *, size_t, const char *, int, const char *);
extern void * my_malloc_hook(size_t, const char *, int, const char *);
extern void my_free_hook(void *, const char *, int, const char *);
extern char * my_strdup_hook(const char *, const char *, int, const char *);
extern void my_reset_hook(void);
extern void hist_dump(void);
void my_init_hook(void);
/* ======================================================== */
#define NUSERS 436
typedef struct {
const char * caller;
const char * file;
int line;
int64_t sz;
int nalloc;
int nrealloc;
int nmove;
int nfree;
int ndup;
} user_t;
static user_t users[NUSERS];
int nusers = 0;
static volatile size_t mcnt = 0;
mtx_t mutx;
static FILE *fh = NULL;
static void init_io(void)
{
if (fh) return;
/* fh = fopen("/tmp/m", "w"); */
fh = stdout;
}
// #define allprt(f, varargs...) fprintf(f, ##varargs)
#define allprt(f, varargs...)
/* ======================================================== */
#define HISTOGRAM 1
#if HISTOGRAM
#define HISTSZ 1000
u_int64_t malask[HISTSZ];
u_int64_t maluse[HISTSZ];
u_int64_t relask[HISTSZ];
u_int64_t reluse[HISTSZ];
u_int64_t strask[HISTSZ];
u_int64_t struse[HISTSZ];
#if 0
// ffs() is broken, returns pseudo-random garbage.
// This is a work-around.
static int my_ffs(size_t n)
{
int bp = 0;
while (0 != n) { bp++; n >>= 1; }
return bp;
}
#endif
#define BANKBITS 8
static int get_bin(size_t sz)
{
static int banksz = 1 << BANKBITS;
int isz = sz;
int delta = 1;
int off = 0;
while (1)
{
if (isz < delta*banksz)
{
int bin = off + isz / delta - 1;
if (HISTSZ <= bin) bin = HISTSZ-1;
return bin;
}
isz -= delta * banksz;
off += banksz;
delta <<= 1;
}
}
static int get_binsz(int bin)
{
static int banksz = 1 << BANKBITS;
int binum = 0;
int delt = 1;
while(1)
{
if (bin < delt*banksz) return binum + bin;
bin -= delt*banksz;
binum += delt*banksz;
bin <<= 1;
delt <<= 1;
}
}
static void hist_init(void)
{
for (int i=0; i< HISTSZ; i++)
{
malask[i] = 0;
maluse[i] = 0;
relask[i] = 0;
reluse[i] = 0;
strask[i] = 0;
struse[i] = 0;
}
}
void hist_dump(void)
{
fprintf(fh, "#\n# row size-bin malloc-ask malloc-usable relloc-ask realloc-usable strdup-ask strdup-usable\n#\n");
for (int i=0; i< HISTSZ; i++)
{
fprintf(fh, "%d %d %lu %lu %lu %lu %lu %lu\n",
i, get_binsz(i+1), malask[i], maluse[i],
relask[i], reluse[i], strask[i], struse[i]);
}
}
#else // HISTOGRAM
static void hist_init(void) {}
void hist_dump(void) {}
#endif // HISTOGRAM
/* ======================================================== */
static int get_callerid(const char *file, int line, const char *caller)
{
for (int i=0; i<nusers; i++)
{
if (caller == users[i].caller)
return i;
}
int callerid = nusers;
nusers++;
users[callerid].file = file;
users[callerid].line = line;
users[callerid].caller = caller;
users[callerid].sz = 0;
users[callerid].nalloc = 0;
users[callerid].nmove = 0;
users[callerid].nrealloc = 0;
users[callerid].nfree = 0;
users[callerid].ndup = 0;
users[callerid].caller = caller;
if (nusers == NUSERS)
{
printf("ran out of user slots\n");
exit(1);
}
return callerid;
}
#define RFREQ 21123123
static int cmpu(const void * a, const void * b)
{
user_t* ua = (user_t*) a;
user_t* ub = (user_t*) b;
return strcmp(ua->file, ub->file);
}
static void report(void)
{
user_t lusers[NUSERS];
for (int i=0; i<nusers; i++)
lusers[i] = users[i];
qsort(lusers, nusers, sizeof(user_t), cmpu);
// int64_t totsz = 0;
__int128_t totsz = 0;
__int128_t abssz = 0;
int64_t subsz = 0;
char * prev = NULL;
fprintf(fh, "Performed %lu mallocs\n", mcnt);
for (int i=0; i<nusers; i++)
{
char* c = strchr(lusers[i].file, ':');
char* f = strndup(lusers[i].file, c-lusers[i].file);
if (prev)
{
if (strcmp(prev, f))
{
printf("Summary %s use=%ld runttot=%Ld abstot=%Ld\n\n", prev, subsz,
(long long int)totsz, (long long int)abssz);
subsz = 0;
}
free(prev);
}
prev = f;
fprintf(fh, "%d caller=%s:%d %s sz=%ld nalloc=%d %d %d %d %d\n",
i, lusers[i].file, lusers[i].line, lusers[i].caller,
lusers[i].sz, lusers[i].nalloc, lusers[i].nrealloc,
lusers[i].nmove, lusers[i].nfree, lusers[i].ndup);
totsz += lusers[i].sz;
subsz += lusers[i].sz;
if (0 < lusers[i].sz) abssz += lusers[i].sz;
}
printf("Summary %s use=%ld runttot=%Ld abstot=%Ld\n\n", prev, subsz,
(long long int)totsz, (long long int)abssz);
static int rptnum = 0;
rptnum++;
struct rusage rus;
getrusage(RUSAGE_SELF, &rus);
__int128_t avg = abssz;
if (0 < mcnt) avg /= mcnt;
totsz /= 1024;
abssz /= 1024*1024;
fprintf (fh, "%d Use= %Ld KB; Tot= %Ld MB in %lu mlocs; avg= %Ld Bytes; RSS= %ld KB\n\n",
rptnum,
(long long int)totsz, (long long int)abssz, mcnt,
(long long int)avg, rus.ru_maxrss);
}
/* ======================================================== */
void * my_realloc_hook(void * mem, size_t n_bytes,
const char *file, int line, const char *caller)
{
mtx_lock(&mutx);
int callerid = get_callerid(file, line, caller);
size_t oldsz = malloc_usable_size(mem);
void * nm = realloc(mem, n_bytes);
size_t newsz = malloc_usable_size(nm);
users[callerid].sz += newsz - oldsz;
users[callerid].nrealloc ++;
if (mem != nm)
users[callerid].nmove ++;
#if HISTOGRAM
relask[get_bin(n_bytes)] ++;
reluse[get_bin(newsz)] ++;
#endif
mcnt++;
if (mcnt%RFREQ == 0) report();
mtx_unlock(&mutx);
return nm;
}
void * my_malloc_hook(size_t n_bytes,
const char *file, int line, const char *caller)
{
init_io();
mtx_lock(&mutx);
void * mem = malloc(n_bytes);
size_t newsz = malloc_usable_size(mem);
int callerid = get_callerid(file, line, caller);
users[callerid].sz += newsz;
users[callerid].nalloc ++;
#if HISTOGRAM
malask[get_bin(n_bytes)] ++;
maluse[get_bin(newsz)] ++;
#endif
mcnt++;
if (mcnt%RFREQ == 0) report();
mtx_unlock(&mutx);
return mem;
}
char * my_strdup_hook(const char *s,
const char *file, int line, const char *caller)
{
init_io();
mtx_lock(&mutx);
char * news = strdup(s);
size_t newsz = malloc_usable_size(news);
int callerid = get_callerid(file, line, caller);
users[callerid].sz += newsz;
users[callerid].ndup ++;
#if HISTOGRAM
strask[get_bin(strlen(s)+1)] ++;
struse[get_bin(newsz)] ++;
#endif
mcnt++;
if (mcnt%RFREQ == 0) report();
mtx_unlock(&mutx);
return news;
}
void my_free_hook(void * mem,
const char *file, int line, const char *caller)
{
if (0x0 == mem) return;
mtx_lock(&mutx);
size_t oldsz = malloc_usable_size(mem);
int callerid = get_callerid(file, line, caller);
users[callerid].sz -= oldsz;
users[callerid].nfree ++;
free(mem);
mtx_unlock(&mutx);
}
void my_reset_hook(void)
{
mtx_lock(&mutx);
printf("reset malloc trace hook\n");
report();
nusers = 0;
mcnt = 0;
hist_init();
report();
mtx_unlock(&mutx);
}
__attribute__((constructor))
void my_init_hook(void)
{
static bool is_init = false;
if (is_init) return;
is_init = true;
printf("init malloc trace hook\n");
nusers = 0;
mcnt = 0;
hist_init();
mtx_init(&mutx, mtx_plain);
}
#endif /*_MSC_VER && __MINGW32__*/