dgaudet 97/10/14 17:19:36
Modified: src/main alloc.c
Log:
Add in alloc debugging code which can be used standalone to detect some
uninitialized read, and use-after-free errors. It can also be combined with
debuggers like efence and Purify. By default nothing should change.
This change introduces one change to the non-debugging code:
- blok = new_block(0);
+ blok = new_block(POOL_HDR_BYTES);
This is during make_sub_pool. Technically speaking, this fixes a bug;
the bug was that make_sub_pool was assuming that
BLOCK_MINALLOC > POOL_HDR_BYTES. Not an unreasonable assumption ... but
the debugging code sets BLOCK_MINALLOC to 0.
Reviewed by: Jim Jagielski, Rob Hartill, Martin Kraemer
Revision Changes Path
1.50 +109 -5 apachen/src/main/alloc.c
Index: alloc.c
===================================================================
RCS file: /export/home/cvs/apachen/src/main/alloc.c,v
retrieving revision 1.49
retrieving revision 1.50
diff -u -r1.49 -r1.50
--- alloc.c 1997/10/05 02:06:36 1.49
+++ alloc.c 1997/10/15 00:19:35 1.50
@@ -63,6 +63,37 @@
#include <stdarg.h>
+/* debugging support, define this to enable code which helps detect re-use
+ * of freed memory and other such nonsense.
+ *
+ * The theory is simple. The FILL_BYTE (0xa5) is written over all malloc'd
+ * memory as we receive it, and is written over everything that we free up
+ * during a clear_pool. We check that blocks on the free list always
+ * have the FILL_BYTE in them, and we check during palloc() that the bytes
+ * still have FILL_BYTE in them. If you ever see garbage URLs or whatnot
+ * containing lots of 0xa5s then you know something used data that's been
+ * freed.
+ */
+/* #define ALLOC_DEBUG */
+
+/* debugging support, if defined all allocations will be done with
+ * malloc and free()d appropriately at the end. This is intended to be
+ * used with something like Electric Fence or Purify to help detect
+ * memory problems. Note that if you're using efence then you should also
+ * add in ALLOC_DEBUG. But don't add in ALLOC_DEBUG if you're using Purify
+ * because ALLOC_DEBUG would hide all the uninitialized read errors that
+ * Purify can diagnose.
+ */
+/* #define ALLOC_USE_MALLOC */
+
+#ifdef ALLOC_USE_MALLOC
+#undef BLOCK_MINFREE
+#undef BLOCK_MINALLOC
+#define BLOCK_MINFREE 0
+#define BLOCK_MINALLOC 0
+#endif
+
+
/*****************************************************************
*
* Managing free storage blocks...
@@ -98,6 +129,28 @@
mutex *alloc_mutex = NULL;
mutex *spawn_mutex = NULL;
+#ifdef ALLOC_DEBUG
+#define FILL_BYTE ((char)(0xa5))
+
+#define debug_fill(ptr,size) ((void)memset((ptr), FILL_BYTE, (size)))
+
+static ap_inline void debug_verify_filled(const char *ptr,
+ const char *endp, const char *error_msg)
+{
+ for (; ptr < endp; ++ptr) {
+ if (*ptr != FILL_BYTE) {
+ fputs(error_msg, stderr);
+ abort();
+ exit(1);
+ }
+ }
+}
+
+#else
+#define debug_fill(a,b)
+#define debug_verify_filled(a,b,c)
+#endif
+
/* Get a completely new block from the system pool. Note that we rely on
malloc() to provide aligned memory. */
@@ -111,6 +164,7 @@
fprintf(stderr, "Ouch! malloc failed in malloc_block()\n");
exit(1);
}
+ debug_fill(blok, size + sizeof(union block_hdr));
blok->h.next = NULL;
blok->h.first_avail = (char *) (blok + 1);
blok->h.endp = size + blok->h.first_avail;
@@ -138,6 +192,14 @@
void free_blocks(union block_hdr *blok)
{
+#ifdef ALLOC_USE_MALLOC
+ union block_hdr *next;
+
+ for (; blok; blok = next) {
+ next = blok->h.next;
+ free(blok);
+ }
+#else
/* First, put new blocks at the head of the free list ---
* we'll eventually bash the 'next' pointer of the last block
* in the chain to point to the free blocks we already had.
@@ -161,21 +223,22 @@
while (blok->h.next != NULL) {
chk_on_blk_list(blok, old_free_list);
blok->h.first_avail = (char *) (blok + 1);
+ debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
blok = blok->h.next;
}
chk_on_blk_list(blok, old_free_list);
blok->h.first_avail = (char *) (blok + 1);
+ debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
/* Finally, reset next pointer to get the old free blocks back */
blok->h.next = old_free_list;
(void) release_mutex(alloc_mutex);
+#endif
}
-
-
/* Get a new block, from our own free list if possible, from the system
* if necessary. Must be called with alarms blocked.
*/
@@ -193,6 +256,8 @@
if (min_size + BLOCK_MINFREE <= blok->h.endp - blok->h.first_avail) {
*lastptr = blok->h.next;
blok->h.next = NULL;
+ debug_verify_filled(blok->h.first_avail, blok->h.endp,
+ "Ouch! Someone trounced a block on the free list!\n");
return blok;
}
else {
@@ -204,11 +269,13 @@
/* Nope. */
min_size += BLOCK_MINFREE;
- return malloc_block((min_size > BLOCK_MINALLOC) ? min_size :
BLOCK_MINALLOC);
+ blok = malloc_block((min_size > BLOCK_MINALLOC) ? min_size :
BLOCK_MINALLOC);
+ debug_verify_filled(blok->h.first_avail, blok->h.endp,
+ "Ouch! Someone trounced a block on the free list!\n");
+ return blok;
}
-
/* Accounting */
long bytes_in_block_list(union block_hdr *blok)
@@ -248,6 +315,9 @@
struct pool *sub_prev;
struct pool *parent;
char *free_first_avail;
+#ifdef ALLOC_USE_MALLOC
+ void *allocation_list;
+#endif
};
pool *permanent_pool;
@@ -271,7 +341,7 @@
(void) acquire_mutex(alloc_mutex);
- blok = new_block(0);
+ blok = new_block(POOL_HDR_BYTES);
new_pool = (pool *) blok->h.first_avail;
blok->h.first_avail += POOL_HDR_BYTES;
@@ -318,6 +388,20 @@
a->last = a->first;
a->first->h.first_avail = a->free_first_avail;
+ debug_fill(a->first->h.first_avail,
+ a->first->h.endp - a->first->h.first_avail);
+
+#ifdef ALLOC_USE_MALLOC
+ {
+ void *c, *n;
+
+ for (c = a->allocation_list; c; c = n) {
+ n = *(void **)c;
+ free(c);
+ }
+ a->allocation_list = NULL;
+ }
+#endif
unblock_alarms();
}
@@ -357,6 +441,23 @@
API_EXPORT(void *) palloc(struct pool *a, int reqsize)
{
+#ifdef ALLOC_USE_MALLOC
+ int size = reqsize + CLICK_SZ;
+ void *ptr;
+
+ block_alarms();
+ ptr = malloc(size);
+ if (ptr == NULL) {
+ fputs("Ouch! Out of memory!\n", stderr);
+ exit(1);
+ }
+ debug_fill(ptr, size); /* might as well get uninitialized protection */
+ *(void **)ptr = a->allocation_list;
+ a->allocation_list = ptr;
+ unblock_alarms();
+ return (char *)ptr + CLICK_SZ;
+#else
+
/* Round up requested size to an even number of alignment units (core
clicks)
*/
@@ -377,6 +478,8 @@
new_first_avail = first_avail + size;
if (new_first_avail <= blok->h.endp) {
+ debug_verify_filled(first_avail, blok->h.endp,
+ "Ouch! Someone trounced past the end of their allocation!\n");
blok->h.first_avail = new_first_avail;
return (void *) first_avail;
}
@@ -399,6 +502,7 @@
blok->h.first_avail += size;
return (void *) first_avail;
+#endif
}
API_EXPORT(void *) pcalloc(struct pool *a, int size)