1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
/*
* 'Minefield' - a crude Windows memory debugger, similar in concept
* to the old Unix 'Electric Fence'. The main difference is that
* Electric Fence can be imposed on a program from outside, via
* LD_PRELOAD, whereas this has to be included in the program at
* compile time with its own cooperation.
*
* This module provides the Minefield allocator. Actually enabling it
* is done by a #define in force when the main utils/memory.c is
* compiled.
*/
#include "putty.h"
#include "puttymem.h"
#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;
}
void *minefield_c_malloc(size_t size)
{
if (!minefield_initialised)
minefield_init();
return minefield_alloc(size);
}
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.
*/
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;
}
|