github.com/afumu/libc@v0.0.6/musl/src/malloc/oldmalloc/malloc.c (about) 1 #define _GNU_SOURCE 2 #include <stdlib.h> 3 #include <string.h> 4 #include <limits.h> 5 #include <stdint.h> 6 #include <errno.h> 7 #include <sys/mman.h> 8 #include "libc.h" 9 #include "atomic.h" 10 #include "pthread_impl.h" 11 #include "malloc_impl.h" 12 13 #if defined(__GNUC__) && defined(__PIC__) 14 #define inline inline __attribute__((always_inline)) 15 #endif 16 17 static struct { 18 volatile uint64_t binmap; 19 struct bin bins[64]; 20 volatile int split_merge_lock[2]; 21 } mal; 22 23 /* Synchronization tools */ 24 25 static inline void lock(volatile int *lk) 26 { 27 int need_locks = libc.need_locks; 28 if (need_locks) { 29 while(a_swap(lk, 1)) __wait(lk, lk+1, 1, 1); 30 if (need_locks < 0) libc.need_locks = 0; 31 } 32 } 33 34 static inline void unlock(volatile int *lk) 35 { 36 if (lk[0]) { 37 a_store(lk, 0); 38 if (lk[1]) __wake(lk, 1, 1); 39 } 40 } 41 42 static inline void lock_bin(int i) 43 { 44 lock(mal.bins[i].lock); 45 if (!mal.bins[i].head) 46 mal.bins[i].head = mal.bins[i].tail = BIN_TO_CHUNK(i); 47 } 48 49 static inline void unlock_bin(int i) 50 { 51 unlock(mal.bins[i].lock); 52 } 53 54 static int first_set(uint64_t x) 55 { 56 #if 1 57 return a_ctz_64(x); 58 #else 59 static const char debruijn64[64] = { 60 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 61 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 62 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 63 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 64 }; 65 static const char debruijn32[32] = { 66 0, 1, 23, 2, 29, 24, 19, 3, 30, 27, 25, 11, 20, 8, 4, 13, 67 31, 22, 28, 18, 26, 10, 7, 12, 21, 17, 9, 6, 16, 5, 15, 14 68 }; 69 if (sizeof(long) < 8) { 70 uint32_t y = x; 71 if (!y) { 72 y = x>>32; 73 return 32 + debruijn32[(y&-y)*0x076be629 >> 27]; 74 } 75 return debruijn32[(y&-y)*0x076be629 >> 27]; 76 } 77 return debruijn64[(x&-x)*0x022fdd63cc95386dull >> 58]; 78 #endif 79 } 80 81 static const unsigned char bin_tab[60] = { 82 32,33,34,35,36,36,37,37,38,38,39,39, 83 40,40,40,40,41,41,41,41,42,42,42,42,43,43,43,43, 84 44,44,44,44,44,44,44,44,45,45,45,45,45,45,45,45, 85 46,46,46,46,46,46,46,46,47,47,47,47,47,47,47,47, 86 }; 87 88 static int bin_index(size_t x) 89 { 90 x = x / SIZE_ALIGN - 1; 91 if (x <= 32) return x; 92 if (x < 512) return bin_tab[x/8-4]; 93 if (x > 0x1c00) return 63; 94 return bin_tab[x/128-4] + 16; 95 } 96 97 static int bin_index_up(size_t x) 98 { 99 x = x / SIZE_ALIGN - 1; 100 if (x <= 32) return x; 101 x--; 102 if (x < 512) return bin_tab[x/8-4] + 1; 103 return bin_tab[x/128-4] + 17; 104 } 105 106 #if 0 107 void __dump_heap(int x) 108 { 109 struct chunk *c; 110 int i; 111 for (c = (void *)mal.heap; CHUNK_SIZE(c); c = NEXT_CHUNK(c)) 112 fprintf(stderr, "base %p size %zu (%d) flags %d/%d\n", 113 c, CHUNK_SIZE(c), bin_index(CHUNK_SIZE(c)), 114 c->csize & 15, 115 NEXT_CHUNK(c)->psize & 15); 116 for (i=0; i<64; i++) { 117 if (mal.bins[i].head != BIN_TO_CHUNK(i) && mal.bins[i].head) { 118 fprintf(stderr, "bin %d: %p\n", i, mal.bins[i].head); 119 if (!(mal.binmap & 1ULL<<i)) 120 fprintf(stderr, "missing from binmap!\n"); 121 } else if (mal.binmap & 1ULL<<i) 122 fprintf(stderr, "binmap wrongly contains %d!\n", i); 123 } 124 } 125 #endif 126 127 /* This function returns true if the interval [old,new] 128 * intersects the 'len'-sized interval below &libc.auxv 129 * (interpreted as the main-thread stack) or below &b 130 * (the current stack). It is used to defend against 131 * buggy brk implementations that can cross the stack. */ 132 133 static int traverses_stack_p(uintptr_t old, uintptr_t new) 134 { 135 const uintptr_t len = 8<<20; 136 uintptr_t a, b; 137 138 b = (uintptr_t)libc.auxv; 139 a = b > len ? b-len : 0; 140 if (new>a && old<b) return 1; 141 142 b = (uintptr_t)&b; 143 a = b > len ? b-len : 0; 144 if (new>a && old<b) return 1; 145 146 return 0; 147 } 148 149 /* Expand the heap in-place if brk can be used, or otherwise via mmap, 150 * using an exponential lower bound on growth by mmap to make 151 * fragmentation asymptotically irrelevant. The size argument is both 152 * an input and an output, since the caller needs to know the size 153 * allocated, which will be larger than requested due to page alignment 154 * and mmap minimum size rules. The caller is responsible for locking 155 * to prevent concurrent calls. */ 156 157 static void *__expand_heap(size_t *pn) 158 { 159 static uintptr_t brk; 160 static unsigned mmap_step; 161 size_t n = *pn; 162 163 if (n > SIZE_MAX/2 - PAGE_SIZE) { 164 errno = ENOMEM; 165 return 0; 166 } 167 n += -n & PAGE_SIZE-1; 168 169 if (!brk) { 170 brk = __syscall(SYS_brk, 0); 171 brk += -brk & PAGE_SIZE-1; 172 } 173 174 if (n < SIZE_MAX-brk && !traverses_stack_p(brk, brk+n) 175 && __syscall(SYS_brk, brk+n)==brk+n) { 176 *pn = n; 177 brk += n; 178 return (void *)(brk-n); 179 } 180 181 size_t min = (size_t)PAGE_SIZE << mmap_step/2; 182 if (n < min) n = min; 183 void *area = __mmap(0, n, PROT_READ|PROT_WRITE, 184 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 185 if (area == MAP_FAILED) return 0; 186 *pn = n; 187 mmap_step++; 188 return area; 189 } 190 191 static struct chunk *expand_heap(size_t n) 192 { 193 static void *end; 194 void *p; 195 struct chunk *w; 196 197 /* The argument n already accounts for the caller's chunk 198 * overhead needs, but if the heap can't be extended in-place, 199 * we need room for an extra zero-sized sentinel chunk. */ 200 n += SIZE_ALIGN; 201 202 p = __expand_heap(&n); 203 if (!p) return 0; 204 205 /* If not just expanding existing space, we need to make a 206 * new sentinel chunk below the allocated space. */ 207 if (p != end) { 208 /* Valid/safe because of the prologue increment. */ 209 n -= SIZE_ALIGN; 210 p = (char *)p + SIZE_ALIGN; 211 w = MEM_TO_CHUNK(p); 212 w->psize = 0 | C_INUSE; 213 } 214 215 /* Record new heap end and fill in footer. */ 216 end = (char *)p + n; 217 w = MEM_TO_CHUNK(end); 218 w->psize = n | C_INUSE; 219 w->csize = 0 | C_INUSE; 220 221 /* Fill in header, which may be new or may be replacing a 222 * zero-size sentinel header at the old end-of-heap. */ 223 w = MEM_TO_CHUNK(p); 224 w->csize = n | C_INUSE; 225 226 return w; 227 } 228 229 static int adjust_size(size_t *n) 230 { 231 /* Result of pointer difference must fit in ptrdiff_t. */ 232 if (*n-1 > PTRDIFF_MAX - SIZE_ALIGN - PAGE_SIZE) { 233 if (*n) { 234 errno = ENOMEM; 235 return -1; 236 } else { 237 *n = SIZE_ALIGN; 238 return 0; 239 } 240 } 241 *n = (*n + OVERHEAD + SIZE_ALIGN - 1) & SIZE_MASK; 242 return 0; 243 } 244 245 static void unbin(struct chunk *c, int i) 246 { 247 if (c->prev == c->next) 248 a_and_64(&mal.binmap, ~(1ULL<<i)); 249 c->prev->next = c->next; 250 c->next->prev = c->prev; 251 c->csize |= C_INUSE; 252 NEXT_CHUNK(c)->psize |= C_INUSE; 253 } 254 255 static void bin_chunk(struct chunk *self, int i) 256 { 257 self->next = BIN_TO_CHUNK(i); 258 self->prev = mal.bins[i].tail; 259 self->next->prev = self; 260 self->prev->next = self; 261 if (self->prev == BIN_TO_CHUNK(i)) 262 a_or_64(&mal.binmap, 1ULL<<i); 263 } 264 265 static void trim(struct chunk *self, size_t n) 266 { 267 size_t n1 = CHUNK_SIZE(self); 268 struct chunk *next, *split; 269 270 if (n >= n1 - DONTCARE) return; 271 272 next = NEXT_CHUNK(self); 273 split = (void *)((char *)self + n); 274 275 split->psize = n | C_INUSE; 276 split->csize = n1-n; 277 next->psize = n1-n; 278 self->csize = n | C_INUSE; 279 280 int i = bin_index(n1-n); 281 lock_bin(i); 282 283 bin_chunk(split, i); 284 285 unlock_bin(i); 286 } 287 288 void *malloc(size_t n) 289 { 290 struct chunk *c; 291 int i, j; 292 uint64_t mask; 293 294 if (adjust_size(&n) < 0) return 0; 295 296 if (n > MMAP_THRESHOLD) { 297 size_t len = n + OVERHEAD + PAGE_SIZE - 1 & -PAGE_SIZE; 298 char *base = __mmap(0, len, PROT_READ|PROT_WRITE, 299 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 300 if (base == (void *)-1) return 0; 301 c = (void *)(base + SIZE_ALIGN - OVERHEAD); 302 c->csize = len - (SIZE_ALIGN - OVERHEAD); 303 c->psize = SIZE_ALIGN - OVERHEAD; 304 return CHUNK_TO_MEM(c); 305 } 306 307 i = bin_index_up(n); 308 if (i<63 && (mal.binmap & (1ULL<<i))) { 309 lock_bin(i); 310 c = mal.bins[i].head; 311 if (c != BIN_TO_CHUNK(i) && CHUNK_SIZE(c)-n <= DONTCARE) { 312 unbin(c, i); 313 unlock_bin(i); 314 return CHUNK_TO_MEM(c); 315 } 316 unlock_bin(i); 317 } 318 lock(mal.split_merge_lock); 319 for (mask = mal.binmap & -(1ULL<<i); mask; mask -= (mask&-mask)) { 320 j = first_set(mask); 321 lock_bin(j); 322 c = mal.bins[j].head; 323 if (c != BIN_TO_CHUNK(j)) { 324 unbin(c, j); 325 unlock_bin(j); 326 break; 327 } 328 unlock_bin(j); 329 } 330 if (!mask) { 331 c = expand_heap(n); 332 if (!c) { 333 unlock(mal.split_merge_lock); 334 return 0; 335 } 336 } 337 trim(c, n); 338 unlock(mal.split_merge_lock); 339 return CHUNK_TO_MEM(c); 340 } 341 342 int __malloc_allzerop(void *p) 343 { 344 return IS_MMAPPED(MEM_TO_CHUNK(p)); 345 } 346 347 void *realloc(void *p, size_t n) 348 { 349 struct chunk *self, *next; 350 size_t n0, n1; 351 void *new; 352 353 if (!p) return malloc(n); 354 355 if (adjust_size(&n) < 0) return 0; 356 357 self = MEM_TO_CHUNK(p); 358 n1 = n0 = CHUNK_SIZE(self); 359 360 if (n<=n0 && n0-n<=DONTCARE) return p; 361 362 if (IS_MMAPPED(self)) { 363 size_t extra = self->psize; 364 char *base = (char *)self - extra; 365 size_t oldlen = n0 + extra; 366 size_t newlen = n + extra; 367 /* Crash on realloc of freed chunk */ 368 if (extra & 1) a_crash(); 369 if (newlen < PAGE_SIZE && (new = malloc(n-OVERHEAD))) { 370 n0 = n; 371 goto copy_free_ret; 372 } 373 newlen = (newlen + PAGE_SIZE-1) & -PAGE_SIZE; 374 if (oldlen == newlen) return p; 375 base = __mremap(base, oldlen, newlen, MREMAP_MAYMOVE); 376 if (base == (void *)-1) 377 goto copy_realloc; 378 self = (void *)(base + extra); 379 self->csize = newlen - extra; 380 return CHUNK_TO_MEM(self); 381 } 382 383 next = NEXT_CHUNK(self); 384 385 /* Crash on corrupted footer (likely from buffer overflow) */ 386 if (next->psize != self->csize) a_crash(); 387 388 if (n < n0) { 389 int i = bin_index_up(n); 390 int j = bin_index(n0); 391 if (i<j && (mal.binmap & (1ULL << i))) 392 goto copy_realloc; 393 struct chunk *split = (void *)((char *)self + n); 394 self->csize = split->psize = n | C_INUSE; 395 split->csize = next->psize = n0-n | C_INUSE; 396 __bin_chunk(split); 397 return CHUNK_TO_MEM(self); 398 } 399 400 lock(mal.split_merge_lock); 401 402 size_t nsize = next->csize & C_INUSE ? 0 : CHUNK_SIZE(next); 403 if (n0+nsize >= n) { 404 int i = bin_index(nsize); 405 lock_bin(i); 406 if (!(next->csize & C_INUSE)) { 407 unbin(next, i); 408 unlock_bin(i); 409 next = NEXT_CHUNK(next); 410 self->csize = next->psize = n0+nsize | C_INUSE; 411 trim(self, n); 412 unlock(mal.split_merge_lock); 413 return CHUNK_TO_MEM(self); 414 } 415 unlock_bin(i); 416 } 417 unlock(mal.split_merge_lock); 418 419 copy_realloc: 420 /* As a last resort, allocate a new chunk and copy to it. */ 421 new = malloc(n-OVERHEAD); 422 if (!new) return 0; 423 copy_free_ret: 424 memcpy(new, p, (n<n0 ? n : n0) - OVERHEAD); 425 free(CHUNK_TO_MEM(self)); 426 return new; 427 } 428 429 void __bin_chunk(struct chunk *self) 430 { 431 struct chunk *next = NEXT_CHUNK(self); 432 433 /* Crash on corrupted footer (likely from buffer overflow) */ 434 if (next->psize != self->csize) a_crash(); 435 436 lock(mal.split_merge_lock); 437 438 size_t osize = CHUNK_SIZE(self), size = osize; 439 440 /* Since we hold split_merge_lock, only transition from free to 441 * in-use can race; in-use to free is impossible */ 442 size_t psize = self->psize & C_INUSE ? 0 : CHUNK_PSIZE(self); 443 size_t nsize = next->csize & C_INUSE ? 0 : CHUNK_SIZE(next); 444 445 if (psize) { 446 int i = bin_index(psize); 447 lock_bin(i); 448 if (!(self->psize & C_INUSE)) { 449 struct chunk *prev = PREV_CHUNK(self); 450 unbin(prev, i); 451 self = prev; 452 size += psize; 453 } 454 unlock_bin(i); 455 } 456 if (nsize) { 457 int i = bin_index(nsize); 458 lock_bin(i); 459 if (!(next->csize & C_INUSE)) { 460 unbin(next, i); 461 next = NEXT_CHUNK(next); 462 size += nsize; 463 } 464 unlock_bin(i); 465 } 466 467 int i = bin_index(size); 468 lock_bin(i); 469 470 self->csize = size; 471 next->psize = size; 472 bin_chunk(self, i); 473 unlock(mal.split_merge_lock); 474 475 /* Replace middle of large chunks with fresh zero pages */ 476 if (size > RECLAIM && (size^(size-osize)) > size-osize) { 477 uintptr_t a = (uintptr_t)self + SIZE_ALIGN+PAGE_SIZE-1 & -PAGE_SIZE; 478 uintptr_t b = (uintptr_t)next - SIZE_ALIGN & -PAGE_SIZE; 479 #if 1 480 __madvise((void *)a, b-a, MADV_DONTNEED); 481 #else 482 __mmap((void *)a, b-a, PROT_READ|PROT_WRITE, 483 MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0); 484 #endif 485 } 486 487 unlock_bin(i); 488 } 489 490 static void unmap_chunk(struct chunk *self) 491 { 492 size_t extra = self->psize; 493 char *base = (char *)self - extra; 494 size_t len = CHUNK_SIZE(self) + extra; 495 /* Crash on double free */ 496 if (extra & 1) a_crash(); 497 __munmap(base, len); 498 } 499 500 void free(void *p) 501 { 502 if (!p) return; 503 504 struct chunk *self = MEM_TO_CHUNK(p); 505 506 if (IS_MMAPPED(self)) 507 unmap_chunk(self); 508 else 509 __bin_chunk(self); 510 } 511 512 void __malloc_donate(char *start, char *end) 513 { 514 size_t align_start_up = (SIZE_ALIGN-1) & (-(uintptr_t)start - OVERHEAD); 515 size_t align_end_down = (SIZE_ALIGN-1) & (uintptr_t)end; 516 517 /* Getting past this condition ensures that the padding for alignment 518 * and header overhead will not overflow and will leave a nonzero 519 * multiple of SIZE_ALIGN bytes between start and end. */ 520 if (end - start <= OVERHEAD + align_start_up + align_end_down) 521 return; 522 start += align_start_up + OVERHEAD; 523 end -= align_end_down; 524 525 struct chunk *c = MEM_TO_CHUNK(start), *n = MEM_TO_CHUNK(end); 526 c->psize = n->csize = C_INUSE; 527 c->csize = n->psize = C_INUSE | (end-start); 528 __bin_chunk(c); 529 }