minefield.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * 'Minefield' - a crude Windows memory debugger, similar in concept
  3. * to the old Unix 'Electric Fence'. The main difference is that
  4. * Electric Fence can be imposed on a program from outside, via
  5. * LD_PRELOAD, whereas this has to be included in the program at
  6. * compile time with its own cooperation.
  7. *
  8. * This module provides the Minefield allocator. Actually enabling it
  9. * is done by a #define in force when the main utils/memory.c is
  10. * compiled.
  11. */
  12. #include "putty.h"
  13. #define PAGESIZE 4096
  14. /*
  15. * Design:
  16. *
  17. * We start by reserving as much virtual address space as Windows
  18. * will sensibly (or not sensibly) let us have. We flag it all as
  19. * invalid memory.
  20. *
  21. * Any allocation attempt is satisfied by committing one or more
  22. * pages, with an uncommitted page on either side. The returned
  23. * memory region is jammed up against the _end_ of the pages.
  24. *
  25. * Freeing anything causes instantaneous decommitment of the pages
  26. * involved, so stale pointers are caught as soon as possible.
  27. */
  28. static int minefield_initialised = 0;
  29. static void *minefield_region = NULL;
  30. static long minefield_size = 0;
  31. static long minefield_npages = 0;
  32. static long minefield_curpos = 0;
  33. static unsigned short *minefield_admin = NULL;
  34. static void *minefield_pages = NULL;
  35. static void minefield_admin_hide(int hide)
  36. {
  37. int access = hide ? PAGE_NOACCESS : PAGE_READWRITE;
  38. VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL);
  39. }
  40. static void minefield_init(void)
  41. {
  42. int size;
  43. int admin_size;
  44. int i;
  45. for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) {
  46. minefield_region = VirtualAlloc(NULL, size,
  47. MEM_RESERVE, PAGE_NOACCESS);
  48. if (minefield_region)
  49. break;
  50. }
  51. minefield_size = size;
  52. /*
  53. * Firstly, allocate a section of that to be the admin block.
  54. * We'll need a two-byte field for each page.
  55. */
  56. minefield_admin = minefield_region;
  57. minefield_npages = minefield_size / PAGESIZE;
  58. admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1);
  59. minefield_npages = (minefield_size - admin_size) / PAGESIZE;
  60. minefield_pages = (char *) minefield_region + admin_size;
  61. /*
  62. * Commit the admin region.
  63. */
  64. VirtualAlloc(minefield_admin, minefield_npages * 2,
  65. MEM_COMMIT, PAGE_READWRITE);
  66. /*
  67. * Mark all pages as unused (0xFFFF).
  68. */
  69. for (i = 0; i < minefield_npages; i++)
  70. minefield_admin[i] = 0xFFFF;
  71. /*
  72. * Hide the admin region.
  73. */
  74. minefield_admin_hide(1);
  75. minefield_initialised = 1;
  76. }
  77. static void minefield_bomb(void)
  78. {
  79. div(1, *(int *) minefield_pages);
  80. }
  81. static void *minefield_alloc(int size)
  82. {
  83. int npages;
  84. int pos, lim, region_end, region_start;
  85. int start;
  86. int i;
  87. npages = (size + PAGESIZE - 1) / PAGESIZE;
  88. minefield_admin_hide(0);
  89. /*
  90. * Search from current position until we find a contiguous
  91. * bunch of npages+2 unused pages.
  92. */
  93. pos = minefield_curpos;
  94. lim = minefield_npages;
  95. while (1) {
  96. /* Skip over used pages. */
  97. while (pos < lim && minefield_admin[pos] != 0xFFFF)
  98. pos++;
  99. /* Count unused pages. */
  100. start = pos;
  101. while (pos < lim && pos - start < npages + 2 &&
  102. minefield_admin[pos] == 0xFFFF)
  103. pos++;
  104. if (pos - start == npages + 2)
  105. break;
  106. /* If we've reached the limit, reset the limit or stop. */
  107. if (pos >= lim) {
  108. if (lim == minefield_npages) {
  109. /* go round and start again at zero */
  110. lim = minefield_curpos;
  111. pos = 0;
  112. } else {
  113. minefield_admin_hide(1);
  114. return NULL;
  115. }
  116. }
  117. }
  118. minefield_curpos = pos - 1;
  119. /*
  120. * We have npages+2 unused pages starting at start. We leave
  121. * the first and last of these alone and use the rest.
  122. */
  123. region_end = (start + npages + 1) * PAGESIZE;
  124. region_start = region_end - size;
  125. /* FIXME: could align here if we wanted */
  126. /*
  127. * Update the admin region.
  128. */
  129. for (i = start + 2; i < start + npages + 1; i++)
  130. minefield_admin[i] = 0xFFFE; /* used but no region starts here */
  131. minefield_admin[start + 1] = region_start % PAGESIZE;
  132. minefield_admin_hide(1);
  133. VirtualAlloc((char *) minefield_pages + region_start, size,
  134. MEM_COMMIT, PAGE_READWRITE);
  135. return (char *) minefield_pages + region_start;
  136. }
  137. static void minefield_free(void *ptr)
  138. {
  139. int region_start, i, j;
  140. minefield_admin_hide(0);
  141. region_start = (char *) ptr - (char *) minefield_pages;
  142. i = region_start / PAGESIZE;
  143. if (i < 0 || i >= minefield_npages ||
  144. minefield_admin[i] != region_start % PAGESIZE)
  145. minefield_bomb();
  146. for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) {
  147. minefield_admin[j] = 0xFFFF;
  148. }
  149. VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT);
  150. minefield_admin_hide(1);
  151. }
  152. static int minefield_get_size(void *ptr)
  153. {
  154. int region_start, i, j;
  155. minefield_admin_hide(0);
  156. region_start = (char *) ptr - (char *) minefield_pages;
  157. i = region_start / PAGESIZE;
  158. if (i < 0 || i >= minefield_npages ||
  159. minefield_admin[i] != region_start % PAGESIZE)
  160. minefield_bomb();
  161. for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++);
  162. minefield_admin_hide(1);
  163. return j * PAGESIZE - region_start;
  164. }
  165. void *minefield_c_malloc(size_t size)
  166. {
  167. if (!minefield_initialised)
  168. minefield_init();
  169. return minefield_alloc(size);
  170. }
  171. void minefield_c_free(void *p)
  172. {
  173. if (!minefield_initialised)
  174. minefield_init();
  175. minefield_free(p);
  176. }
  177. /*
  178. * realloc _always_ moves the chunk, for rapid detection of code
  179. * that assumes it won't.
  180. */
  181. void *minefield_c_realloc(void *p, size_t size)
  182. {
  183. size_t oldsize;
  184. void *q;
  185. if (!minefield_initialised)
  186. minefield_init();
  187. q = minefield_alloc(size);
  188. oldsize = minefield_get_size(p);
  189. memcpy(q, p, (oldsize < size ? oldsize : size));
  190. minefield_free(p);
  191. return q;
  192. }