summaryrefslogtreecommitdiff
path: root/Src/mem.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/mem.c')
-rw-r--r--Src/mem.c194
1 files changed, 194 insertions, 0 deletions
diff --git a/Src/mem.c b/Src/mem.c
index 9b80f188c..83a4bbad7 100644
--- a/Src/mem.c
+++ b/Src/mem.c
@@ -123,6 +123,57 @@ static Heap heaps;
static Heap fheap;
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * The heap ID we'll allocate next.
+ *
+ * We'll avoid using 0 as that means zero-initialised memory
+ * containing a heap ID is (correctly) marked as invalid.
+ */
+static Heapid next_heap_id = (Heapid)1;
+
+/*
+ * The ID of the heap from which we last allocated heap memory.
+ * In theory, since we carefully avoid allocating heap memory during
+ * interrupts, after any call to zhalloc() or wrappers this should
+ * be the ID of the heap containing the memory just returned.
+ */
+/**/
+mod_export Heapid last_heap_id;
+
+/*
+ * Stack of heaps saved by new_heaps().
+ * Assumes old_heaps() will come along and restore it later
+ * (outputs an error if old_heaps() is called out of sequence).
+ */
+LinkList heaps_saved;
+
+/*
+ * Debugging verbosity. This must be set from a debugger.
+ * An 'or' of bits from the enum heap_debug_verbosity.
+ */
+volatile int heap_debug_verbosity;
+
+/*
+ * Generate a heap identifier that's unique up to unsigned integer wrap.
+ *
+ * For the purposes of debugging we won't bother trying to make a
+ * heap_id globally unique, which would require checking all existing
+ * heaps every time we create an ID and still wouldn't do what we
+ * ideally want, which is to make sure the IDs of valid heaps are
+ * different from the IDs of no-longer-valid heaps. Given that,
+ * we'll just assume that if we haven't tracked the problem when the
+ * ID wraps we're out of luck. We could change the type to a long long
+ * if we wanted more room
+ */
+
+static Heapid
+new_heap_id(void)
+{
+ return next_heap_id++;
+}
+#endif
+
/* Use new heaps from now on. This returns the old heap-list. */
/**/
@@ -137,6 +188,15 @@ new_heaps(void)
fheap = heaps = NULL;
unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_NEW) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " saved, new heaps created.\n", h->heap_id);
+ }
+ if (!heaps_saved)
+ heaps_saved = znewlinklist();
+ zpushnode(heaps_saved, h);
+#endif
return h;
}
@@ -152,6 +212,12 @@ old_heaps(Heap old)
for (h = heaps; h; h = n) {
n = h->next;
DPUTS(h->sp, "BUG: old_heaps() with pushed heaps");
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_FREE) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ "freed in old_heaps().\n", h->heap_id);
+ }
+#endif
#ifdef USE_MMAP
munmap((void *) h, h->size);
#else
@@ -159,6 +225,21 @@ old_heaps(Heap old)
#endif
}
heaps = old;
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_OLD) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ "restored.\n", heaps->heap_id);
+ }
+ {
+ Heap myold = heaps_saved ? getlinknode(heaps_saved) : NULL;
+ if (old != myold)
+ {
+ fprintf(stderr, "HEAP DEBUG: invalid old heap " HEAPID_FMT
+ ", expecting " HEAPID_FMT ".\n", old->heap_id,
+ myold->heap_id);
+ }
+ }
+#endif
fheap = NULL;
unqueue_signals();
}
@@ -174,6 +255,12 @@ switch_heaps(Heap new)
queue_signals();
h = heaps;
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_SWITCH) {
+ fprintf(stderr, "HEAP DEBUG: heap temporarily switched from "
+ HEAPID_FMT " to " HEAPID_FMT ".\n", h->heap_id, new->heap_id);
+ }
+#endif
heaps = new;
fheap = NULL;
unqueue_signals();
@@ -202,6 +289,15 @@ pushheap(void)
hs->next = h->sp;
h->sp = hs;
hs->used = h->used;
+#ifdef ZSH_HEAP_DEBUG
+ hs->heap_id = h->heap_id;
+ h->heap_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_PUSH) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT " pushed, new id is "
+ HEAPID_FMT ".\n",
+ hs->heap_id, h->heap_id);
+ }
+#endif
}
unqueue_signals();
}
@@ -251,6 +347,22 @@ freeheap(void)
if (!fheap && h->used < ARENA_SIZEOF(h))
fheap = h;
hl = h;
+#ifdef ZSH_HEAP_DEBUG
+ /*
+ * As the free makes the heap invalid, give it a new
+ * identifier. We're not popping it, so don't use
+ * the one in the heap stack.
+ */
+ {
+ Heapid new_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_FREE) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " freed, new id is " HEAPID_FMT ".\n",
+ h->heap_id, new_id);
+ }
+ h->heap_id = new_id;
+ }
+#endif
} else {
#ifdef USE_MMAP
munmap((void *) h, h->size);
@@ -291,6 +403,14 @@ popheap(void)
memset(arena(h) + hs->used, 0xff, h->used - hs->used);
#endif
h->used = hs->used;
+#ifdef ZSH_HEAP_DEBUG
+ if (heap_debug_verbosity & HDV_POP) {
+ fprintf(stderr, "HEAP DEBUG: heap " HEAPID_FMT
+ " popped, old heap was " HEAPID_FMT ".\n",
+ h->heap_id, hs->heap_id);
+ }
+ h->heap_id = hs->heap_id;
+#endif
if (!fheap && h->used < ARENA_SIZEOF(h))
fheap = h;
zfree(hs, sizeof(*hs));
@@ -393,6 +513,13 @@ zhalloc(size_t size)
h->used = n;
ret = arena(h) + n - size;
unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+ last_heap_id = h->heap_id;
+ if (heap_debug_verbosity & HDV_ALLOC) {
+ fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+ HEAPID_FMT ".\n", h->heap_id);
+ }
+#endif
return ret;
}
}
@@ -424,6 +551,13 @@ zhalloc(size_t size)
h->used = size;
h->next = NULL;
h->sp = NULL;
+#ifdef ZSH_HEAP_DEBUG
+ h->heap_id = new_heap_id();
+ if (heap_debug_verbosity & HDV_CREATE) {
+ fprintf(stderr, "HEAP DEBUG: create new heap " HEAPID_FMT ".\n",
+ h->heap_id);
+ }
+#endif
if (hp)
hp->next = h;
@@ -432,6 +566,13 @@ zhalloc(size_t size)
fheap = h;
unqueue_signals();
+#ifdef ZSH_HEAP_DEBUG
+ last_heap_id = h->heap_id;
+ if (heap_debug_verbosity & HDV_ALLOC) {
+ fprintf(stderr, "HEAP DEBUG: allocated memory from heap "
+ HEAPID_FMT ".\n", h->heap_id);
+ }
+#endif
return arena(h);
}
}
@@ -495,6 +636,9 @@ hrealloc(char *p, size_t old, size_t new)
* don't use the heap for anything else.)
*/
if (p == arena(h)) {
+#ifdef ZSH_HEAP_DEBUG
+ Heapid heap_id = h->heap_id;
+#endif
/*
* Zero new seems to be a special case saying we've finished
* with the specially reallocated memory, see scanner() in glob.c.
@@ -554,6 +698,9 @@ hrealloc(char *p, size_t old, size_t new)
heaps = h;
}
h->used = new;
+#ifdef ZSH_HEAP_DEBUG
+ h->heap_id = heap_id;
+#endif
unqueue_signals();
return arena(h);
}
@@ -576,6 +723,53 @@ hrealloc(char *p, size_t old, size_t new)
}
}
+#ifdef ZSH_HEAP_DEBUG
+/*
+ * Check if heap_id is the identifier of a currently valid heap,
+ * including any heap buried on the stack, or of permanent memory.
+ * Return 0 if so, else 1.
+ *
+ * This gets confused by use of switch_heaps(). That's because so do I.
+ */
+
+/**/
+mod_export int
+memory_validate(Heapid heap_id)
+{
+ Heap h;
+ Heapstack hs;
+ LinkNode node;
+
+ if (heap_id == HEAPID_PERMANENT)
+ return 0;
+
+ queue_signals();
+ for (h = heaps; h; h = h->next) {
+ if (h->heap_id == heap_id)
+ return 0;
+ for (hs = heaps->sp; hs; hs = hs->next) {
+ if (hs->heap_id == heap_id)
+ return 0;
+ }
+ }
+
+ if (heaps_saved) {
+ for (node = firstnode(heaps_saved); node; incnode(node)) {
+ for (h = (Heap)getdata(node); h; h = h->next) {
+ if (h->heap_id == heap_id)
+ return 0;
+ for (hs = heaps->sp; hs; hs = hs->next) {
+ if (hs->heap_id == heap_id)
+ return 0;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+#endif
+
/* allocate memory from the current memory pool and clear it */
/**/