jcs
/amend
/amendments
/135
util: Import new malloc debugging, ditch malloc notes
jcs made amendment 135 over 2 years ago
--- util.c Thu Feb 1 17:24:25 2024
+++ util.c Fri Feb 16 13:38:19 2024
@@ -83,28 +83,6 @@ enum {
APPICON_ALERT
};
-/*
- * Define to audit each malloc and free and verify that a pointer isn't
- * double-freed. The list of outstanding allocations can be checked by
- * looking through malloc_map.
- */
-//#define MALLOC_DEBUG
-
-#ifdef MALLOC_DEBUG
-/*
- * List of allocations, updated at xmalloc() and xfree(). If an address
- * passed to xfree() isn't in the list, it indicates a double-free.
- */
-#define MALLOC_MAP_CHUNK_SIZE 1024
-struct malloc_map_e {
- unsigned long addr;
- unsigned long size;
- char note[MALLOC_NOTE_SIZE];
-} *malloc_map = NULL;
-unsigned long malloc_map_size = 0;
-static bool malloc_map_compact = false;
-#endif
-
void vwarn(short alert_func, const char *format, va_list ap);
/*
@@ -119,15 +97,6 @@ util_init(void)
HLock(alert_ditl_h);
memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
HUnlock(alert_ditl_h);
-
-#ifdef MALLOC_DEBUG
- malloc_map_size = MALLOC_MAP_CHUNK_SIZE;
- malloc_map = (struct malloc_map_e *)NewPtr(malloc_map_size *
- sizeof(struct malloc_map_e));
- if (malloc_map == NULL)
- panic("NewPtr(%lu) failed", MALLOC_MAP_CHUNK_SIZE);
- memset(malloc_map, 0, malloc_map_size);
-#endif
}
/*
@@ -136,64 +105,141 @@ util_init(void)
#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+#ifdef MALLOC_DEBUG
+/*
+ * malloc tracking, storing a stack trace of each xmalloc call and its
+ * size, which can be dumped with xalloc_print(). xfree also gains the
+ * ability to detect junk frees.
+ *
+ * Requires project options 'Always generate stack frames',
+ * 'Macsbug Names', and 'Long format' to be enabled.
+ */
+#if !__option(force_frame) || !__option(long_macsbug_names)
+#error
+#endif
+
+#define XALLOC_SYMBOL_SIZE 32
+#define XALLOC_NSYMBOLS 64
+#define XALLOC_MAX_STACK_DEPTH 10
+static char *symbols = NULL;
+struct xalloc {
+ char *stack[XALLOC_MAX_STACK_DEPTH];
+ unsigned long addr;
+ unsigned long size;
+};
+#define XALLOC_NXALLOCS 512
+static struct xalloc *xallocs = NULL;
+#endif
+
void *
-xmalloc(size_t size, char *note)
+xmalloc(size_t size)
{
void *ptr;
#ifdef MALLOC_DEBUG
- struct malloc_map_e *new_malloc_map;
- unsigned short n, j;
+ static char cursym[XALLOC_SYMBOL_SIZE], *tsym, *sym;
+ struct xalloc *xa;
+ unsigned long _a6, ret;
+ unsigned char *code;
+ size_t n, cn, j;
+ short nframe;
#endif
-
+
if (size == 0)
panic("xmalloc: zero size");
ptr = NewPtr(size);
if (ptr == NULL)
- panic("xmalloc(%lu) failed", size);
+ return NULL;
#ifdef MALLOC_DEBUG
- if (malloc_map_compact) {
- for (n = 0; n < malloc_map_size; n++) {
- if (malloc_map[n].addr != 0)
- continue;
-
- for (j = n + 1; j < malloc_map_size; j++) {
- if (malloc_map[j].addr == 0)
- continue;
-
- malloc_map[n] = malloc_map[j];
- memset(&malloc_map[j], 0, sizeof(struct malloc_map_e));
- break;
- }
+ asm {
+ move.l a6,_a6
+ };
+
+ if (symbols == NULL) {
+ symbols = NewPtr(n = XALLOC_SYMBOL_SIZE * XALLOC_NSYMBOLS);
+ if (symbols == NULL)
+ panic("NewPtr failed for xalloc symbols");
+ memset(symbols, 0, n);
+ }
+ if (xallocs == NULL) {
+ xallocs = (struct xalloc *)NewPtr(n = (XALLOC_NXALLOCS *
+ sizeof(struct xalloc)));
+ if (xallocs == NULL)
+ panic("NewPtr failed for xallocs");
+ memset(xallocs, 0, n);
+ }
+
+ xa = NULL;
+ for (n = 0; n < XALLOC_NXALLOCS; n++) {
+ if (xallocs[n].addr == 0) {
+ xa = &xallocs[n];
+ break;
}
-
- malloc_map_compact = false;
}
+ if (xa == NULL)
+ panic("out of xalloc space");
- for (n = 0; n <= malloc_map_size; n++) {
- if (n == malloc_map_size) {
- malloc_map_size += MALLOC_MAP_CHUNK_SIZE;
- warn("xmalloc(%lu): out of malloc map entries, maybe a "
- "memory leak, resizing to %ld", size, malloc_map_size);
- new_malloc_map = (struct malloc_map_e *)NewPtr(
- malloc_map_size * sizeof(struct malloc_map_e));
- if (new_malloc_map == NULL)
- panic("out of memory resizing malloc map");
- memcpy(new_malloc_map, malloc_map,
- (malloc_map_size - MALLOC_MAP_CHUNK_SIZE) *
- sizeof(struct malloc_map_e));
- DisposePtr(malloc_map);
- malloc_map = new_malloc_map;
+ /*
+ * A6 register points to previous stack A6. Above A6 is our return
+ * address. Walk the code after the return address looking for an RTS
+ * instruction, then walk 3 bytes after that and we should have that
+ * function's symbol. Then jump to the previous A6 that our A6 points
+ * to and do it again to find the previous function symbol, repeating
+ * until we hit "main" or "uthread_begin".
+ */
+ for (nframe = 0; nframe < XALLOC_MAX_STACK_DEPTH; nframe++) {
+ ret = *(unsigned long *)(_a6 + 4);
+ code = (unsigned char *)ret;
+
+ cursym[0] = '\0';
+ for (; ; code += 2) {
+ if (code[0] == 0x4e && code[1] == 0x75) { /* RTS */
+ code += 3;
+ strlcpy(cursym, (char *)code, sizeof(cursym));
+ break;
+ }
}
- if (malloc_map[n].addr == 0) {
- malloc_map[n].addr = (unsigned long)ptr;
- malloc_map[n].size = size;
- strlcpy(malloc_map[n].note, note, sizeof(malloc_map[n].note));
+
+ if (!cursym[0])
break;
+
+ sym = NULL;
+ for (n = 0; n < XALLOC_NSYMBOLS; n++) {
+ tsym = symbols + (XALLOC_SYMBOL_SIZE * n);
+
+ if (tsym[0] == '\0')
+ break;
+ if (strcmp(tsym, cursym) == 0) {
+ sym = tsym;
+ break;
+ }
}
- n = n;
+ if (sym == NULL) {
+ for (n = 0; n < XALLOC_NSYMBOLS; n++) {
+ tsym = symbols + (XALLOC_SYMBOL_SIZE * n);
+ if (tsym[0] != '\0')
+ continue;
+
+ strlcpy(tsym, cursym, XALLOC_SYMBOL_SIZE);
+ sym = tsym;
+ break;
+ }
+ }
+ if (sym == NULL)
+ break;
+
+ xa->stack[nframe] = sym;
+
+ if (strcmp(sym, "main") == 0 || strcmp(sym, "uthread_begin") == 0)
+ break;
+
+ /* walk back up the chain */
+ _a6 = *(unsigned long *)_a6;
}
+
+ xa->addr = (unsigned long)ptr;
+ xa->size = size;
#endif
return ptr;
@@ -203,81 +249,63 @@ void
xfree(void *ptrptr)
{
unsigned long *addr = (unsigned long *)ptrptr;
- void *ptr = (void *)*addr;
+ void *ptr;
#ifdef MALLOC_DEBUG
- unsigned long n;
+ size_t n;
+#endif
- for (n = 0; n <= malloc_map_size; n++) {
- if (n == malloc_map_size)
- panic("xfree(0x%lx): can't find in alloc map, likely "
- "double free()", *addr);
- if (malloc_map[n].addr == *addr) {
- malloc_map[n].addr = 0;
- malloc_map[n].size = 0;
- malloc_map[n].note[0] = '\0';
+ if (ptrptr == NULL)
+ panic("xfree(NULL)");
+
+ ptr = (void *)*addr;
+ if (ptr == NULL)
+ panic("xfree(&NULL) likely a double-free");
+
+#ifdef MALLOC_DEBUG
+ for (n = 0; n < XALLOC_NXALLOCS; n++) {
+ if (xallocs[n].addr == (unsigned long)ptr) {
+ memset(&xallocs[n], 0, sizeof(struct xalloc));
break;
}
}
+ if (n == XALLOC_NXALLOCS)
+ panic("xfree() without xalloc, possibly a junk free",
+ (unsigned long)ptr);
#endif
DisposePtr(ptr);
-
*addr = 0L;
}
void *
-xmalloczero(size_t size, char *note)
+xmalloczero(size_t size)
{
void *ptr;
- ptr = xmalloc(size, note);
- memset(ptr, 0, size);
+ ptr = xmalloc(size);
+ if (ptr != NULL)
+ memset(ptr, 0, size);
return ptr;
}
void *
-xcalloc(size_t nmemb, size_t size, char *note)
+xcalloc(size_t nmemb, size_t size)
{
- void *ptr;
-
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size)
panic("xcalloc(%lu, %lu) overflow", nmemb, size);
- ptr = xmalloczero(nmemb * size, note);
- if (ptr == NULL)
- panic("xcalloc(%lu, %lu) failed", nmemb, size);
-
- return ptr;
+
+ return xmalloczero(nmemb * size);
}
void *
xrealloc(void *src, size_t size)
{
void *ptr, *tsrc;
-#ifdef MALLOC_DEBUG
- unsigned long n;
-#endif
- char note[MALLOC_NOTE_SIZE] = "realloc from null";
-#ifdef MALLOC_DEBUG
- if (src != NULL) {
- for (n = 0; n <= malloc_map_size; n++) {
- if (n == malloc_map_size) {
- panic("xrealloc(%lu): can't find in alloc map, likely "
- "double free()", (unsigned long)src);
- return NULL;
- }
- if (malloc_map[n].addr == (unsigned long)src) {
- strlcpy(note, malloc_map[n].note, sizeof(note));
- break;
- }
- }
- }
-#endif
-
- ptr = xmalloc(size, note);
- if (src != NULL) {
+ ptr = xmalloc(size);
+ if (ptr != NULL && src != NULL) {
memcpy(ptr, src, size);
tsrc = src;
xfree(&tsrc);
@@ -292,25 +320,26 @@ xreallocarray(void *optr, size_t nmemb, size_t size)
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size)
panic("xreallocarray(%lu, %lu) failed", nmemb, size);
+
return xrealloc(optr, size * nmemb);
}
char *
-xstrdup(const char *str, char *note)
+xstrdup(const char *str)
{
char *cp;
size_t len;
len = strlen(str);
+ cp = xmalloc(len + 1);
+ if (cp != NULL)
+ strlcpy(cp, str, len + 1);
- cp = xmalloc(len + 1, note);
- strlcpy(cp, str, len + 1);
-
return cp;
}
char *
-xstrndup(const char *str, size_t maxlen, char *note)
+xstrndup(const char *str, size_t maxlen)
{
char *copy;
const char *cp;
@@ -321,14 +350,57 @@ xstrndup(const char *str, size_t maxlen, char *note)
;
len = (size_t)(cp - str);
- copy = xmalloc(len + 1, note);
- (void)memcpy(copy, str, len);
- copy[len] = '\0';
+ copy = xmalloc(len + 1);
+ if (copy != NULL) {
+ (void)memcpy(copy, str, len);
+ copy[len] = '\0';
+ }
return copy;
}
+#ifdef MALLOC_DEBUG
+void
+xalloc_print(size_t (*printer)(const char *, ...))
+{
+ ssize_t n, j, len;
+ size_t total = 0, nallocs = 0;
+ char chain[128];
+
+ for (n = 0; n < XALLOC_NXALLOCS; n++) {
+ if (xallocs[n].addr == 0)
+ continue;
+
+ total += xallocs[n].size;
+ nallocs++;
+
+ chain[0] = '\0';
+ len = 0;
+ for (j = XALLOC_MAX_STACK_DEPTH - 1; j >= 0; j--) {
+ if (xallocs[n].stack[j] == 0)
+ continue;
+
+ if (chain[0] != '\0') {
+ if (len >= sizeof(chain) - 2)
+ break;
+ chain[len++] = ' ';
+ chain[len++] = ':';
+ chain[len++] = ' ';
+ chain[len++] = '\0';
+ }
+
+ len = strlcat(chain, xallocs[n].stack[j], sizeof(chain));
+ if (len >= sizeof(chain))
+ break;
+ }
+ printer("%ld @ %s", xallocs[n].size, chain);
+ }
+
+ printer("%ld bytes in %ld allocations in use", total, nallocs);
+}
+#endif
+
/*
* String functions
*/
@@ -341,7 +413,7 @@ getline(char *str, size_t len, char **ret)
for (i = 0; i < len; i++) {
if (str[i] == '\r' || i == len - 1) {
if (*ret == NULL)
- *ret = xmalloc(i + 1, "getline");
+ *ret = xmalloc(i + 1);
memcpy(*ret, str, i + 1);
(*ret)[i] = '\0';
return i + 1;
@@ -803,7 +875,7 @@ xGetStringAsChar(short id)
h = xGetString(id);
HLock(h);
l = (*h)[0];
- out = xmalloc(l + 1, "xGetStringAsChar");
+ out = xmalloc(l + 1);
memcpy((void *)out, (void *)(*h + 1), l);
out[l] = '\0';
ReleaseResource(h);
@@ -858,7 +930,7 @@ getpath(short vRefNum, Str255 fileName, Str255 *ret, b
return 0;
}
- name = xmalloc(FILENAME_MAX, "getpath");
+ name = xmalloc(FILENAME_MAX);
wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum;
wdir.ioWDIndex = 0;
@@ -892,8 +964,8 @@ getpath(short vRefNum, Str255 fileName, Str255 *ret, b
wcinfo.ioDrParID = wdir.ioWDDirID;
wcinfo.ioDrDirID = wdir.ioWDDirID;
- tmp = xmalloc(FILENAME_MAX, "getpath");
- tmpret = xmalloc(FILENAME_MAX, "getpath");
+ tmp = xmalloc(FILENAME_MAX);
+ tmpret = xmalloc(FILENAME_MAX);
/* go backwards, prepending each folder's parent */
while (wcinfo.ioDrParID != 1) {
@@ -946,7 +1018,7 @@ stat(char *path, struct stat *sb)
char *ppath;
short ret;
- ppath = xstrdup(path, "stat");
+ ppath = xstrdup(path);
CtoPstr(ppath);
ret = FStat((unsigned char *)ppath, sb);
xfree(&ppath);
@@ -1089,7 +1161,7 @@ copy_file_contents(short source_ref, short dest_ref)
if (error)
return error;
- buf = xmalloc(1024, "copy_file_contents");
+ buf = xmalloc(1024);
while (source_size > 0) {
count = 1024;
--- util.h Thu Feb 1 17:24:12 2024
+++ util.h Fri Feb 16 10:33:08 2024
@@ -21,6 +21,9 @@
#include <limits.h>
#include <time.h>
+/* uncomment to enable malloc tracking */
+#define MALLOC_DEBUG
+
#ifndef SIZE_MAX
#define SIZE_MAX ULONG_MAX
#endif
@@ -28,8 +31,6 @@
#define nitems(what) (sizeof((what)) / sizeof((what)[0]))
#define member_size(type, member) sizeof(((type *)0)->member)
-#define MALLOC_NOTE_SIZE 32
-
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define BOUND(a, min, max) ((a) > (max) ? (max) : ((a) < (min) ? (min) : (a)))
@@ -89,15 +90,18 @@ struct stat {
void util_init(void);
-void * xmalloc(size_t, char *note);
+void * xmalloc(size_t);
void xfree(void *ptrptr);
void xfree_verify(void);
-void * xmalloczero(size_t, char *note);
-void * xcalloc(size_t, size_t, char *note);
+void * xmalloczero(size_t);
+void * xcalloc(size_t, size_t);
void * xrealloc(void *src, size_t size);
void * xreallocarray(void *, size_t, size_t);
-char * xstrdup(const char *, char *note);
-char * xstrndup(const char *str, size_t maxlen, char *note);
+char * xstrdup(const char *);
+char * xstrndup(const char *str, size_t maxlen);
+#ifdef MALLOC_DEBUG
+void xalloc_print(size_t (*printer)(const char *, ...));
+#endif
short getline(char *str, size_t len, char **ret);
const char * ordinal(unsigned short n);