Dave Hinton's debugging patch.
[u/mdw/putty] / misc.c
diff --git a/misc.c b/misc.c
index eb52225..8968fc2 100644 (file)
--- a/misc.c
+++ b/misc.c
 #include <stdlib.h>
 #include "putty.h"
 
-/* My own versions of malloc, realloc and free. Because I want malloc and
- * realloc to bomb out and exit the program if they run out of memory,
- * realloc to reliably call malloc if passed a NULL pointer, and free
- * to reliably do nothing if passed a NULL pointer. Of course we can also
- * put trace printouts in, if we need to. */
+/*
+ * My own versions of malloc, realloc and free. Because I want
+ * malloc and realloc to bomb out and exit the program if they run
+ * out of memory, realloc to reliably call malloc if passed a NULL
+ * pointer, and free to reliably do nothing if passed a NULL
+ * pointer. We can also put trace printouts in, if we need to; and
+ * we can also replace the allocator with an ElectricFence-like
+ * one.
+ */
+
+#ifdef MINEFIELD
+/*
+ * Minefield - a Windows equivalent for Electric Fence
+ */
+
+#define PAGESIZE 4096
+
+/*
+ * Design:
+ * 
+ * We start by reserving as much virtual address space as Windows
+ * will sensibly (or not sensibly) let us have. We flag it all as
+ * invalid memory.
+ * 
+ * Any allocation attempt is satisfied by committing one or more
+ * pages, with an uncommitted page on either side. The returned
+ * memory region is jammed up against the _end_ of the pages.
+ * 
+ * Freeing anything causes instantaneous decommitment of the pages
+ * involved, so stale pointers are caught as soon as possible.
+ */
+
+static int minefield_initialised = 0;
+static void *minefield_region = NULL;
+static long minefield_size = 0;
+static long minefield_npages = 0;
+static long minefield_curpos = 0;
+static unsigned short *minefield_admin = NULL;
+static void *minefield_pages = NULL;
+
+static void minefield_admin_hide(int hide) {
+    int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
+    VirtualProtect(minefield_admin, minefield_npages*2, access, NULL);
+}
+
+static void minefield_init(void) {
+    int size;
+    int admin_size;
+    int i;
+
+    for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) &~ 0xFFF) {
+        minefield_region = VirtualAlloc(NULL, size,
+                                        MEM_RESERVE, PAGE_NOACCESS);
+        if (minefield_region)
+            break;
+    }
+    minefield_size = size;
+
+    /*
+     * Firstly, allocate a section of that to be the admin block.
+     * We'll need a two-byte field for each page.
+     */
+    minefield_admin = minefield_region;
+    minefield_npages = minefield_size / PAGESIZE;
+    admin_size = (minefield_npages * 2 + PAGESIZE-1) &~ (PAGESIZE-1);
+    minefield_npages = (minefield_size - admin_size) / PAGESIZE;
+    minefield_pages = (char *)minefield_region + admin_size;
+
+    /*
+     * Commit the admin region.
+     */
+    VirtualAlloc(minefield_admin, minefield_npages * 2,
+                 MEM_COMMIT, PAGE_READWRITE);
+
+    /*
+     * Mark all pages as unused (0xFFFF).
+     */
+    for (i = 0; i < minefield_npages; i++)
+        minefield_admin[i] = 0xFFFF;
+
+    /*
+     * Hide the admin region.
+     */
+    minefield_admin_hide(1);
+
+    minefield_initialised = 1;
+}
+
+static void minefield_bomb(void) {
+    div(1, *(int*)minefield_pages);
+}
+
+static void *minefield_alloc(int size) {
+    int npages;
+    int pos, lim, region_end, region_start;
+    int start;
+    int i;
+
+    npages = (size + PAGESIZE-1) / PAGESIZE;
+
+    minefield_admin_hide(0);
+
+    /*
+     * Search from current position until we find a contiguous
+     * bunch of npages+2 unused pages.
+     */
+    pos = minefield_curpos;
+    lim = minefield_npages;
+    while (1) {
+        /* Skip over used pages. */
+        while (pos < lim && minefield_admin[pos] != 0xFFFF)
+            pos++;
+        /* Count unused pages. */
+        start = pos;
+        while (pos < lim && pos - start < npages+2 &&
+               minefield_admin[pos] == 0xFFFF)
+            pos++;
+        if (pos - start == npages+2)
+            break;
+        /* If we've reached the limit, reset the limit or stop. */
+        if (pos >= lim) {
+            if (lim == minefield_npages) {
+                /* go round and start again at zero */
+                lim = minefield_curpos;
+                pos = 0;
+            } else {
+                minefield_admin_hide(1);
+                return NULL;
+            }
+        }
+    }
+
+    minefield_curpos = pos-1;
+
+    /*
+     * We have npages+2 unused pages starting at start. We leave
+     * the first and last of these alone and use the rest.
+     */
+    region_end = (start + npages+1) * PAGESIZE;
+    region_start = region_end - size;
+    /* FIXME: could align here if we wanted */
+
+    /*
+     * Update the admin region.
+     */
+    for (i = start + 2; i < start + npages-1; i++)
+        minefield_admin[i] = 0xFFFE;   /* used but no region starts here */
+    minefield_admin[start+1] = region_start % PAGESIZE;
+
+    minefield_admin_hide(1);
+
+    VirtualAlloc((char *)minefield_pages + region_start, size,
+                 MEM_COMMIT, PAGE_READWRITE);
+    return (char *)minefield_pages + region_start;
+}
+
+static void minefield_free(void *ptr) {
+    int region_start, i, j;
+
+    minefield_admin_hide(0);
+
+    region_start = (char *)ptr - (char *)minefield_pages;
+    i = region_start / PAGESIZE;
+    if (i < 0 || i >= minefield_npages ||
+        minefield_admin[i] != region_start % PAGESIZE)
+        minefield_bomb();
+    for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
+        minefield_admin[j] = 0xFFFF;
+    }
+
+    VirtualFree(ptr, j*PAGESIZE - region_start, MEM_DECOMMIT);
+
+    minefield_admin_hide(1);
+}
+
+static int minefield_get_size(void *ptr) {
+    int region_start, i, j;
+
+    minefield_admin_hide(0);
+
+    region_start = (char *)ptr - (char *)minefield_pages;
+    i = region_start / PAGESIZE;
+    if (i < 0 || i >= minefield_npages ||
+        minefield_admin[i] != region_start % PAGESIZE)
+        minefield_bomb();
+    for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
+
+    minefield_admin_hide(1);
+
+    return j*PAGESIZE - region_start;
+}
+
+static void *minefield_c_malloc(size_t size) {
+    if (!minefield_initialised) minefield_init();
+    return minefield_alloc(size);
+}
+
+static void minefield_c_free(void *p) {
+    if (!minefield_initialised) minefield_init();
+    minefield_free(p);
+}
+
+/*
+ * realloc _always_ moves the chunk, for rapid detection of code
+ * that assumes it won't.
+ */
+static void *minefield_c_realloc(void *p, size_t size) {
+    size_t oldsize;
+    void *q;
+    if (!minefield_initialised) minefield_init();
+    q = minefield_alloc(size);
+    oldsize = minefield_get_size(p);
+    memcpy(q, p, (oldsize < size ? oldsize : size));
+    minefield_free(p);
+    return q;
+}
+
+#endif /* MINEFIELD */
 
 #ifdef MALLOC_LOG
 static FILE *fp = NULL;
@@ -23,7 +236,12 @@ void mlog(char *file, int line) {
 #endif
 
 void *safemalloc(size_t size) {
-    void *p = malloc (size);
+    void *p;
+#ifdef MINEFIELD
+    p = minefield_c_malloc (size);
+#else
+    p = malloc (size);
+#endif
     if (!p) {
        MessageBox(NULL, "Out of memory!", "PuTTY Fatal Error",
                   MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
@@ -38,10 +256,19 @@ void *safemalloc(size_t size) {
 
 void *saferealloc(void *ptr, size_t size) {
     void *p;
-    if (!ptr)
+    if (!ptr) {
+#ifdef MINEFIELD
+       p = minefield_c_malloc (size);
+#else
        p = malloc (size);
-    else
+#endif
+    } else {
+#ifdef MINEFIELD
+       p = minefield_c_realloc (ptr, size);
+#else
        p = realloc (ptr, size);
+#endif
+    }
     if (!p) {
        MessageBox(NULL, "Out of memory!", "PuTTY Fatal Error",
                   MB_SYSTEMMODAL | MB_ICONERROR | MB_OK);
@@ -60,7 +287,11 @@ void safefree(void *ptr) {
        if (fp)
            fprintf(fp, "free(%p)\n", ptr);
 #endif
+#ifdef MINEFIELD
+       minefield_c_free (ptr);
+#else
        free (ptr);
+#endif
     }
 #ifdef MALLOC_LOG
     else if (fp)
@@ -72,10 +303,8 @@ void safefree(void *ptr) {
 static FILE *debug_fp = NULL;
 static int debug_got_console = 0;
 
-void dprintf(char *fmt, ...) {
-    char buf[2048];
+static void dputs (char *buf) {
     DWORD dw;
-    va_list ap;
 
     if (!debug_got_console) {
        AllocConsole();
@@ -85,11 +314,50 @@ void dprintf(char *fmt, ...) {
        debug_fp = fopen("debug.log", "w");
     }
 
-    va_start(ap, fmt);
-    vsprintf(buf, fmt, ap);
     WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &dw, NULL);
     fputs(buf, debug_fp);
     fflush(debug_fp);
+}
+
+
+void dprintf(char *fmt, ...) {
+    char buf[2048];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsprintf(buf, fmt, ap);
+    dputs (buf);
     va_end(ap);
 }
-#endif
+
+
+void debug_memdump (void *buf, int len, int L) {
+    int i;
+    unsigned char *p = buf;
+    if (L) {
+       int delta;
+        dprintf ("\t%d (0x%x) bytes:\n", len, len);
+       delta = 15 & (int) p;
+       p -= delta;
+       len += delta;
+    }
+    for (; 0 < len; p += 16, len -= 16) {
+       dputs ("\t");
+       if (L) dprintf ("%p:  ", p);
+       for (i = 0; i < 16 && i < len; ++i) {
+           if (&p[i] < (unsigned char *) buf) {
+               dputs ("   "); /* 3 spaces */
+           } else {
+               dprintf (
+                   "%c%02.2x",
+                   &p[i] != (unsigned char *) buf && i % 4 ? '.' : ' ',
+                   p[i]
+               );
+           }
+       }
+       dputs ("\n");
+    }
+}
+
+#endif /* def DEBUG */
+