diff options
Diffstat (limited to 'Src/mem.c')
-rw-r--r-- | Src/mem.c | 194 |
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 */ /**/ |