|
| 1 | +/* A large allocation is a mapping, and a mapping is whole pages. |
| 2 | + * |
| 3 | + * musl's allocator obtains an allocation of MMAP_THRESHOLD (131,052) bytes or |
| 4 | + * more as a mapping of its own, and uses the mapping up to the end of its last |
| 5 | + * page: the block may start up to a page into it, and the slot's footer sits |
| 6 | + * just below that end. The port obtained exactly the length asked for, so on |
| 7 | + * Windows, where that length comes from the process heap, the footer and up to |
| 8 | + * a page of the block lay past the end of what was obtained --- over the next |
| 9 | + * heap block's header. The program went on until the heap next walked there. |
| 10 | + * |
| 11 | + * ⭐ WHAT IS OBSERVED. |
| 12 | + * (1) An anonymous mapping of a length that ends inside a page: the rest of |
| 13 | + * that page reads as zero and can be written, and memory allocated beside |
| 14 | + * it keeps its contents. |
| 15 | + * (2) Blocks from half the threshold to eight megabytes, each grown from the |
| 16 | + * one before the way a string grows, filled end to end, checked, and |
| 17 | + * released while the next is held --- lengths on a page and inside one. |
| 18 | + * (3) realloc across the threshold in both directions. |
| 19 | + */ |
| 20 | +#include <stdio.h> |
| 21 | +#include <stdlib.h> |
| 22 | +#include <string.h> |
| 23 | +#include <sys/mman.h> |
| 24 | + |
| 25 | +enum { PAGE = 4096 }; |
| 26 | + |
| 27 | +static int failures; |
| 28 | + |
| 29 | +static void fail(const char* what, size_t n) |
| 30 | +{ |
| 31 | + printf("FAIL: %s (%zu)\n", what, n); |
| 32 | + ++failures; |
| 33 | +} |
| 34 | + |
| 35 | +static int filled_with(const unsigned char* p, size_t n, unsigned char value) |
| 36 | +{ |
| 37 | + for (size_t i = 0; i < n; ++i) |
| 38 | + if (p[i] != value) return 0; |
| 39 | + return 1; |
| 40 | +} |
| 41 | + |
| 42 | +static void mapping_tail(void) |
| 43 | +{ |
| 44 | + const size_t len = PAGE + 904; /* ends inside the second page */ |
| 45 | + unsigned char* neighbours[16]; |
| 46 | + for (int i = 0; i < 16; ++i) { |
| 47 | + neighbours[i] = malloc(64); |
| 48 | + if (neighbours[i]) memset(neighbours[i], 0x5a, 64); |
| 49 | + } |
| 50 | + unsigned char* m = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); |
| 51 | + if (m == MAP_FAILED) { fail("mmap", len); return; } |
| 52 | + for (int i = 0; i < 16; ++i) { |
| 53 | + unsigned char* after = malloc(64); |
| 54 | + if (after) { memset(after, 0x5a, 64); free(after); } |
| 55 | + } |
| 56 | + if (!filled_with(m, 2 * PAGE, 0)) fail("the last page of a mapping reads as zero", 2 * PAGE); |
| 57 | + memset(m, 0xa5, 2 * PAGE); |
| 58 | + for (int i = 0; i < 16; ++i) |
| 59 | + if (neighbours[i] && !filled_with(neighbours[i], 64, 0x5a)) fail("a block allocated before the mapping kept its contents", 64); |
| 60 | + if (munmap(m, len) != 0) fail("munmap", len); |
| 61 | + for (int i = 0; i < 16; ++i) free(neighbours[i]); |
| 62 | + printf("mapping: %zu bytes asked, %d readable and writable\n", len, 2 * PAGE); |
| 63 | +} |
| 64 | + |
| 65 | +static void growth(void) |
| 66 | +{ |
| 67 | + unsigned char* held = 0; |
| 68 | + size_t held_size = 0; |
| 69 | + int blocks = 0; |
| 70 | + for (size_t size = 65536; size <= (size_t)8 << 20; size *= 2) { |
| 71 | + for (size_t inside = 0; inside < 3; ++inside) { |
| 72 | + const size_t n = size + inside * 1021; |
| 73 | + unsigned char* p = malloc(n); |
| 74 | + if (!p) { fail("malloc", n); continue; } |
| 75 | + const unsigned char value = (unsigned char)(n % 251); |
| 76 | + if (held) { |
| 77 | + if (!filled_with(held, held_size, (unsigned char)(held_size % 251))) fail("the held block kept its contents", held_size); |
| 78 | + memcpy(p, held, held_size); |
| 79 | + } |
| 80 | + memset(p + held_size, value, n - held_size); |
| 81 | + memset(p, value, held_size); |
| 82 | + /* Small allocations walk the heap beside the large ones. */ |
| 83 | + for (int i = 0; i < 32; ++i) { |
| 84 | + unsigned char* small = malloc(24 + i); |
| 85 | + if (small) { memset(small, 0x33, 24 + i); free(small); } |
| 86 | + } |
| 87 | + if (!filled_with(p, n, value)) fail("a block kept its contents", n); |
| 88 | + free(held); |
| 89 | + held = p; |
| 90 | + held_size = n; |
| 91 | + ++blocks; |
| 92 | + } |
| 93 | + } |
| 94 | + free(held); |
| 95 | + printf("growth: %d blocks up to %zu bytes\n", blocks, held_size); |
| 96 | +} |
| 97 | + |
| 98 | +static void reallocation(void) |
| 99 | +{ |
| 100 | + size_t n = 1000; |
| 101 | + unsigned char* p = malloc(n); |
| 102 | + if (!p) { fail("malloc", n); return; } |
| 103 | + memset(p, 0x11, n); |
| 104 | + const size_t sizes[] = { 140000, 600000, 131052, 131051, 2000, 300000, 4096 * 64 + 1, 100 }; |
| 105 | + for (size_t i = 0; i < sizeof sizes / sizeof sizes[0]; ++i) { |
| 106 | + const size_t m = sizes[i]; |
| 107 | + unsigned char* q = realloc(p, m); |
| 108 | + if (!q) { fail("realloc", m); free(p); return; } |
| 109 | + const size_t kept = n < m ? n : m; |
| 110 | + if (!filled_with(q, kept, 0x11)) fail("realloc kept the contents", m); |
| 111 | + memset(q, 0x11, m); |
| 112 | + p = q; |
| 113 | + n = m; |
| 114 | + } |
| 115 | + free(p); |
| 116 | + printf("realloc: %zu sizes across the threshold\n", sizeof sizes / sizeof sizes[0]); |
| 117 | +} |
| 118 | + |
| 119 | +int main(void) |
| 120 | +{ |
| 121 | + mapping_tail(); |
| 122 | + growth(); |
| 123 | + reallocation(); |
| 124 | + printf("-- failures: %d --\n", failures); |
| 125 | + return failures == 0 ? 0 : 1; |
| 126 | +} |
0 commit comments