1/* $OpenBSD: malloc.c,v 1.297 2024/09/20 02:00:46 jsg Exp $ */ 2/* 3 * Copyright (c) 2008, 2010, 2011, 2016, 2023 Otto Moerbeek <otto@drijf.net> 4 * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> 5 * Copyright (c) 2008 Damien Miller <djm@openbsd.org> 6 * Copyright (c) 2000 Poul-Henning Kamp <phk@FreeBSD.org> 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21/* 22 * If we meet some day, and you think this stuff is worth it, you 23 * can buy me a beer in return. Poul-Henning Kamp 24 */ 25 26#ifndef MALLOC_SMALL 27#define MALLOC_STATS 28#endif 29 30#include <sys/types.h> 31#include <sys/queue.h> 32#include <sys/mman.h> 33#include <sys/sysctl.h> 34#include <uvm/uvmexp.h> 35#include <errno.h> 36#include <stdarg.h> 37#include <stdint.h> 38#include <stdio.h> 39#include <stdlib.h> 40#include <string.h> 41#include <unistd.h> 42 43#ifdef MALLOC_STATS 44#include <sys/tree.h> 45#include <sys/ktrace.h> 46#include <dlfcn.h> 47#endif 48 49#include "thread_private.h" 50#include <tib.h> 51 52#define MALLOC_PAGESHIFT _MAX_PAGE_SHIFT 53 54#define MALLOC_MINSHIFT 4 55#define MALLOC_MAXSHIFT (MALLOC_PAGESHIFT - 1) 56#define MALLOC_PAGESIZE (1UL << MALLOC_PAGESHIFT) 57#define MALLOC_MINSIZE (1UL << MALLOC_MINSHIFT) 58#define MALLOC_PAGEMASK (MALLOC_PAGESIZE - 1) 59#define MASK_POINTER(p) ((void *)(((uintptr_t)(p)) & ~MALLOC_PAGEMASK)) 60 61#define MALLOC_MAXCHUNK (1 << MALLOC_MAXSHIFT) 62#define MALLOC_MAXCACHE 256 63#define MALLOC_DELAYED_CHUNK_MASK 15 64#ifdef MALLOC_STATS 65#define MALLOC_INITIAL_REGIONS 512 66#else 67#define MALLOC_INITIAL_REGIONS (MALLOC_PAGESIZE / sizeof(struct region_info)) 68#endif 69#define MALLOC_DEFAULT_CACHE 64 70#define MALLOC_CHUNK_LISTS 4 71#define CHUNK_CHECK_LENGTH 32 72 73#define B2SIZE(b) ((b) * MALLOC_MINSIZE) 74#define B2ALLOC(b) ((b) == 0 ? MALLOC_MINSIZE : \ 75 (b) * MALLOC_MINSIZE) 76#define BUCKETS (MALLOC_MAXCHUNK / MALLOC_MINSIZE) 77 78/* 79 * We move allocations between half a page and a whole page towards the end, 80 * subject to alignment constraints. This is the extra headroom we allow. 81 * Set to zero to be the most strict. 82 */ 83#define MALLOC_LEEWAY 0 84#define MALLOC_MOVE_COND(sz) ((sz) - mopts.malloc_guard < \ 85 MALLOC_PAGESIZE - MALLOC_LEEWAY) 86#define MALLOC_MOVE(p, sz) (((char *)(p)) + \ 87 ((MALLOC_PAGESIZE - MALLOC_LEEWAY - \ 88 ((sz) - mopts.malloc_guard)) & \ 89 ~(MALLOC_MINSIZE - 1))) 90 91#define PAGEROUND(x) (((x) + (MALLOC_PAGEMASK)) & ~MALLOC_PAGEMASK) 92 93/* 94 * What to use for Junk. This is the byte value we use to fill with 95 * when the 'J' option is enabled. Use SOME_JUNK right after alloc, 96 * and SOME_FREEJUNK right before free. 97 */ 98#define SOME_JUNK 0xdb /* deadbeef */ 99#define SOME_FREEJUNK 0xdf /* dead, free */ 100#define SOME_FREEJUNK_ULL 0xdfdfdfdfdfdfdfdfULL 101 102#define MMAP(sz,f) mmap(NULL, (sz), PROT_READ | PROT_WRITE, \ 103 MAP_ANON | MAP_PRIVATE | (f), -1, 0) 104 105#define MMAPNONE(sz,f) mmap(NULL, (sz), PROT_NONE, \ 106 MAP_ANON | MAP_PRIVATE | (f), -1, 0) 107 108#define MMAPA(a,sz,f) mmap((a), (sz), PROT_READ | PROT_WRITE, \ 109 MAP_ANON | MAP_PRIVATE | (f), -1, 0) 110 111struct region_info { 112 void *p; /* page; low bits used to mark chunks */ 113 uintptr_t size; /* size for pages, or chunk_info pointer */ 114#ifdef MALLOC_STATS 115 void **f; /* where allocated from */ 116#endif 117}; 118 119LIST_HEAD(chunk_head, chunk_info); 120 121/* 122 * Two caches, one for "small" regions, one for "big". 123 * Small cache is an array per size, big cache is one array with different 124 * sized regions 125 */ 126#define MAX_SMALLCACHEABLE_SIZE 32 127#define MAX_BIGCACHEABLE_SIZE 512 128/* If the total # of pages is larger than this, evict before inserting */ 129#define BIGCACHE_FILL(sz) (MAX_BIGCACHEABLE_SIZE * (sz) / 4) 130 131struct smallcache { 132 void **pages; 133 ushort length; 134 ushort max; 135}; 136 137struct bigcache { 138 void *page; 139 size_t psize; 140}; 141 142#ifdef MALLOC_STATS 143#define NUM_FRAMES 4 144struct btnode { 145 RBT_ENTRY(btnode) entry; 146 void *caller[NUM_FRAMES]; 147}; 148RBT_HEAD(btshead, btnode); 149RBT_PROTOTYPE(btshead, btnode, entry, btcmp); 150#endif /* MALLOC_STATS */ 151 152struct dir_info { 153 u_int32_t canary1; 154 int active; /* status of malloc */ 155 struct region_info *r; /* region slots */ 156 size_t regions_total; /* number of region slots */ 157 size_t regions_free; /* number of free slots */ 158 size_t rbytesused; /* random bytes used */ 159 const char *func; /* current function */ 160 int malloc_junk; /* junk fill? */ 161 int mmap_flag; /* extra flag for mmap */ 162 int mutex; 163 int malloc_mt; /* multi-threaded mode? */ 164 /* lists of free chunk info structs */ 165 struct chunk_head chunk_info_list[BUCKETS + 1]; 166 /* lists of chunks with free slots */ 167 struct chunk_head chunk_dir[BUCKETS + 1][MALLOC_CHUNK_LISTS]; 168 /* delayed free chunk slots */ 169 void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1]; 170 u_char rbytes[32]; /* random bytes */ 171 /* free pages cache */ 172 struct smallcache smallcache[MAX_SMALLCACHEABLE_SIZE]; 173 size_t bigcache_used; 174 size_t bigcache_size; 175 struct bigcache *bigcache; 176 void *chunk_pages; 177 size_t chunk_pages_used; 178#ifdef MALLOC_STATS 179 void *caller; 180 size_t inserts; 181 size_t insert_collisions; 182 size_t deletes; 183 size_t delete_moves; 184 size_t cheap_realloc_tries; 185 size_t cheap_reallocs; 186 size_t malloc_used; /* bytes allocated */ 187 size_t malloc_guarded; /* bytes used for guards */ 188 struct btshead btraces; /* backtraces seen */ 189 struct btnode *btnodes; /* store of backtrace nodes */ 190 size_t btnodesused; 191#define STATS_ADD(x,y) ((x) += (y)) 192#define STATS_SUB(x,y) ((x) -= (y)) 193#define STATS_INC(x) ((x)++) 194#define STATS_ZERO(x) ((x) = 0) 195#define STATS_SETF(x,y) ((x)->f = (y)) 196#define STATS_SETFN(x,k,y) ((x)->f[k] = (y)) 197#define SET_CALLER(x,y) if (DO_STATS) ((x)->caller = (y)) 198#else 199#define STATS_ADD(x,y) /* nothing */ 200#define STATS_SUB(x,y) /* nothing */ 201#define STATS_INC(x) /* nothing */ 202#define STATS_ZERO(x) /* nothing */ 203#define STATS_SETF(x,y) /* nothing */ 204#define STATS_SETFN(x,k,y) /* nothing */ 205#define SET_CALLER(x,y) /* nothing */ 206#endif /* MALLOC_STATS */ 207 u_int32_t canary2; 208}; 209 210static void unmap(struct dir_info *d, void *p, size_t sz, size_t clear); 211 212/* 213 * This structure describes a page worth of chunks. 214 * 215 * How many bits per u_short in the bitmap 216 */ 217#define MALLOC_BITS (NBBY * sizeof(u_short)) 218struct chunk_info { 219 LIST_ENTRY(chunk_info) entries; 220 void *page; /* pointer to the page */ 221 /* number of shorts should add up to 8, check alloc_chunk_info() */ 222 u_short canary; 223 u_short bucket; 224 u_short free; /* how many free chunks */ 225 u_short total; /* how many chunks */ 226 u_short offset; /* requested size table offset */ 227#define CHUNK_INFO_TAIL 3 228 u_short bits[CHUNK_INFO_TAIL]; /* which chunks are free */ 229}; 230 231#define CHUNK_FREE(i, n) ((i)->bits[(n) / MALLOC_BITS] & \ 232 (1U << ((n) % MALLOC_BITS))) 233 234struct malloc_readonly { 235 /* Main bookkeeping information */ 236 struct dir_info *malloc_pool[_MALLOC_MUTEXES]; 237 u_int malloc_mutexes; /* how much in actual use? */ 238 int malloc_freecheck; /* Extensive double free check */ 239 int malloc_freeunmap; /* mprotect free pages PROT_NONE? */ 240 int def_malloc_junk; /* junk fill? */ 241 int malloc_realloc; /* always realloc? */ 242 int malloc_xmalloc; /* xmalloc behaviour? */ 243 u_int chunk_canaries; /* use canaries after chunks? */ 244 int internal_funcs; /* use better recallocarray/freezero? */ 245 u_int def_maxcache; /* free pages we cache */ 246 u_int junk_loc; /* variation in location of junk */ 247 size_t malloc_guard; /* use guard pages after allocations? */ 248#ifdef MALLOC_STATS 249 int malloc_stats; /* save callers, dump leak report */ 250 int malloc_verbose; /* dump verbose statistics at end */ 251#define DO_STATS mopts.malloc_stats 252#else 253#define DO_STATS 0 254#endif 255 u_int32_t malloc_canary; /* Matched against ones in pool */ 256}; 257 258 259/* This object is mapped PROT_READ after initialisation to prevent tampering */ 260static union { 261 struct malloc_readonly mopts; 262 u_char _pad[MALLOC_PAGESIZE]; 263} malloc_readonly __attribute__((aligned(MALLOC_PAGESIZE))) 264 __attribute__((section(".openbsd.mutable"))); 265#define mopts malloc_readonly.mopts 266 267char *malloc_options; /* compile-time options */ 268 269static __dead void wrterror(struct dir_info *d, char *msg, ...) 270 __attribute__((__format__ (printf, 2, 3))); 271 272#ifdef MALLOC_STATS 273void malloc_dump(void); 274PROTO_NORMAL(malloc_dump); 275static void malloc_exit(void); 276static void print_chunk_details(struct dir_info *, void *, size_t, size_t); 277static void* store_caller(struct dir_info *, struct btnode *); 278 279/* below are the arches for which deeper caller info has been tested */ 280#if defined(__aarch64__) || \ 281 defined(__amd64__) || \ 282 defined(__arm__) || \ 283 defined(__i386__) || \ 284 defined(__powerpc__) 285__attribute__((always_inline)) 286static inline void* 287caller(struct dir_info *d) 288{ 289 struct btnode p; 290 int level = DO_STATS; 291 292 if (level == 0) 293 return NULL; 294 295 memset(&p.caller, 0, sizeof(p.caller)); 296 if (level >= 1) 297 p.caller[0] = __builtin_extract_return_addr( 298 __builtin_return_address(0)); 299 if (p.caller[0] != NULL && level >= 2) 300 p.caller[1] = __builtin_extract_return_addr( 301 __builtin_return_address(1)); 302 if (p.caller[1] != NULL && level >= 3) 303 p.caller[2] = __builtin_extract_return_addr( 304 __builtin_return_address(2)); 305 if (p.caller[2] != NULL && level >= 4) 306 p.caller[3] = __builtin_extract_return_addr( 307 __builtin_return_address(3)); 308 return store_caller(d, &p); 309} 310#else 311__attribute__((always_inline)) 312static inline void* caller(struct dir_info *d) 313{ 314 struct btnode p; 315 316 if (DO_STATS == 0) 317 return NULL; 318 memset(&p.caller, 0, sizeof(p.caller)); 319 p.caller[0] = __builtin_extract_return_addr(__builtin_return_address(0)); 320 return store_caller(d, &p); 321} 322#endif 323#endif /* MALLOC_STATS */ 324 325/* low bits of r->p determine size: 0 means >= page size and r->size holding 326 * real size, otherwise low bits is the bucket + 1 327 */ 328#define REALSIZE(sz, r) \ 329 (sz) = (uintptr_t)(r)->p & MALLOC_PAGEMASK, \ 330 (sz) = ((sz) == 0 ? (r)->size : B2SIZE((sz) - 1)) 331 332static inline size_t 333hash(void *p) 334{ 335 size_t sum; 336 uintptr_t u; 337 338 u = (uintptr_t)p >> MALLOC_PAGESHIFT; 339 sum = u; 340 sum = (sum << 7) - sum + (u >> 16); 341#ifdef __LP64__ 342 sum = (sum << 7) - sum + (u >> 32); 343 sum = (sum << 7) - sum + (u >> 48); 344#endif 345 return sum; 346} 347 348static inline struct dir_info * 349getpool(void) 350{ 351 if (mopts.malloc_pool[1] == NULL || !mopts.malloc_pool[1]->malloc_mt) 352 return mopts.malloc_pool[1]; 353 else /* first one reserved for special pool */ 354 return mopts.malloc_pool[1 + TIB_GET()->tib_tid % 355 (mopts.malloc_mutexes - 1)]; 356} 357 358static __dead void 359wrterror(struct dir_info *d, char *msg, ...) 360{ 361 int saved_errno = errno; 362 va_list ap; 363 364 dprintf(STDERR_FILENO, "%s(%d) in %s(): ", __progname, 365 getpid(), (d != NULL && d->func) ? d->func : "unknown"); 366 va_start(ap, msg); 367 vdprintf(STDERR_FILENO, msg, ap); 368 va_end(ap); 369 dprintf(STDERR_FILENO, "\n"); 370 371#ifdef MALLOC_STATS 372 if (DO_STATS && mopts.malloc_verbose) 373 malloc_dump(); 374#endif 375 376 errno = saved_errno; 377 378 abort(); 379} 380 381static void 382rbytes_init(struct dir_info *d) 383{ 384 arc4random_buf(d->rbytes, sizeof(d->rbytes)); 385 /* add 1 to account for using d->rbytes[0] */ 386 d->rbytesused = 1 + d->rbytes[0] % (sizeof(d->rbytes) / 2); 387} 388 389static inline u_char 390getrbyte(struct dir_info *d) 391{ 392 u_char x; 393 394 if (d->rbytesused >= sizeof(d->rbytes)) 395 rbytes_init(d); 396 x = d->rbytes[d->rbytesused++]; 397 return x; 398} 399 400static void 401omalloc_parseopt(char opt) 402{ 403 switch (opt) { 404 case '+': 405 mopts.malloc_mutexes <<= 1; 406 if (mopts.malloc_mutexes > _MALLOC_MUTEXES) 407 mopts.malloc_mutexes = _MALLOC_MUTEXES; 408 break; 409 case '-': 410 mopts.malloc_mutexes >>= 1; 411 if (mopts.malloc_mutexes < 2) 412 mopts.malloc_mutexes = 2; 413 break; 414 case '>': 415 mopts.def_maxcache <<= 1; 416 if (mopts.def_maxcache > MALLOC_MAXCACHE) 417 mopts.def_maxcache = MALLOC_MAXCACHE; 418 break; 419 case '<': 420 mopts.def_maxcache >>= 1; 421 break; 422 case 'c': 423 mopts.chunk_canaries = 0; 424 break; 425 case 'C': 426 mopts.chunk_canaries = 1; 427 break; 428#ifdef MALLOC_STATS 429 case 'd': 430 mopts.malloc_stats = 0; 431 break; 432 case 'D': 433 case '1': 434 mopts.malloc_stats = 1; 435 break; 436 case '2': 437 mopts.malloc_stats = 2; 438 break; 439 case '3': 440 mopts.malloc_stats = 3; 441 break; 442 case '4': 443 mopts.malloc_stats = 4; 444 break; 445#endif /* MALLOC_STATS */ 446 case 'f': 447 mopts.malloc_freecheck = 0; 448 mopts.malloc_freeunmap = 0; 449 break; 450 case 'F': 451 mopts.malloc_freecheck = 1; 452 mopts.malloc_freeunmap = 1; 453 break; 454 case 'g': 455 mopts.malloc_guard = 0; 456 break; 457 case 'G': 458 mopts.malloc_guard = MALLOC_PAGESIZE; 459 break; 460 case 'j': 461 if (mopts.def_malloc_junk > 0) 462 mopts.def_malloc_junk--; 463 break; 464 case 'J': 465 if (mopts.def_malloc_junk < 2) 466 mopts.def_malloc_junk++; 467 break; 468 case 'r': 469 mopts.malloc_realloc = 0; 470 break; 471 case 'R': 472 mopts.malloc_realloc = 1; 473 break; 474 case 'u': 475 mopts.malloc_freeunmap = 0; 476 break; 477 case 'U': 478 mopts.malloc_freeunmap = 1; 479 break; 480#ifdef MALLOC_STATS 481 case 'v': 482 mopts.malloc_verbose = 0; 483 break; 484 case 'V': 485 mopts.malloc_verbose = 1; 486 break; 487#endif /* MALLOC_STATS */ 488 case 'x': 489 mopts.malloc_xmalloc = 0; 490 break; 491 case 'X': 492 mopts.malloc_xmalloc = 1; 493 break; 494 default: 495 dprintf(STDERR_FILENO, "malloc() warning: " 496 "unknown char in MALLOC_OPTIONS\n"); 497 break; 498 } 499} 500 501static void 502omalloc_init(void) 503{ 504 char *p, *q, b[16]; 505 int i, j; 506 const int mib[2] = { CTL_VM, VM_MALLOC_CONF }; 507 size_t sb; 508 509 /* 510 * Default options 511 */ 512 mopts.malloc_mutexes = 8; 513 mopts.def_malloc_junk = 1; 514 mopts.def_maxcache = MALLOC_DEFAULT_CACHE; 515 516 for (i = 0; i < 3; i++) { 517 switch (i) { 518 case 0: 519 sb = sizeof(b); 520 j = sysctl(mib, 2, b, &sb, NULL, 0); 521 if (j != 0) 522 continue; 523 p = b; 524 break; 525 case 1: 526 if (issetugid() == 0) 527 p = getenv("MALLOC_OPTIONS"); 528 else 529 continue; 530 break; 531 case 2: 532 p = malloc_options; 533 break; 534 default: 535 p = NULL; 536 } 537 538 for (; p != NULL && *p != '\0'; p++) { 539 switch (*p) { 540 case 'S': 541 for (q = "CFGJ"; *q != '\0'; q++) 542 omalloc_parseopt(*q); 543 mopts.def_maxcache = 0; 544 break; 545 case 's': 546 for (q = "cfgj"; *q != '\0'; q++) 547 omalloc_parseopt(*q); 548 mopts.def_maxcache = MALLOC_DEFAULT_CACHE; 549 break; 550 default: 551 omalloc_parseopt(*p); 552 break; 553 } 554 } 555 } 556 557#ifdef MALLOC_STATS 558 if (DO_STATS && (atexit(malloc_exit) == -1)) { 559 dprintf(STDERR_FILENO, "malloc() warning: atexit(3) failed." 560 " Will not be able to dump stats on exit\n"); 561 } 562#endif 563 564 while ((mopts.malloc_canary = arc4random()) == 0) 565 ; 566 mopts.junk_loc = arc4random(); 567 if (mopts.chunk_canaries) 568 do { 569 mopts.chunk_canaries = arc4random(); 570 } while ((u_char)mopts.chunk_canaries == 0 || 571 (u_char)mopts.chunk_canaries == SOME_FREEJUNK); 572} 573 574static void 575omalloc_poolinit(struct dir_info *d, int mmap_flag) 576{ 577 u_int i, j; 578 579 d->r = NULL; 580 d->rbytesused = sizeof(d->rbytes); 581 d->regions_free = d->regions_total = 0; 582 for (i = 0; i <= BUCKETS; i++) { 583 LIST_INIT(&d->chunk_info_list[i]); 584 for (j = 0; j < MALLOC_CHUNK_LISTS; j++) 585 LIST_INIT(&d->chunk_dir[i][j]); 586 } 587 d->mmap_flag = mmap_flag; 588 d->malloc_junk = mopts.def_malloc_junk; 589#ifdef MALLOC_STATS 590 RBT_INIT(btshead, &d->btraces); 591#endif 592 d->canary1 = mopts.malloc_canary ^ (u_int32_t)(uintptr_t)d; 593 d->canary2 = ~d->canary1; 594} 595 596static int 597omalloc_grow(struct dir_info *d) 598{ 599 size_t newtotal; 600 size_t newsize; 601 size_t mask; 602 size_t i, oldpsz; 603 struct region_info *p; 604 605 if (d->regions_total > SIZE_MAX / sizeof(struct region_info) / 2) 606 return 1; 607 608 newtotal = d->regions_total == 0 ? MALLOC_INITIAL_REGIONS : 609 d->regions_total * 2; 610 newsize = PAGEROUND(newtotal * sizeof(struct region_info)); 611 mask = newtotal - 1; 612 613 /* Don't use cache here, we don't want user uaf touch this */ 614 p = MMAP(newsize, d->mmap_flag); 615 if (p == MAP_FAILED) 616 return 1; 617 618 STATS_ADD(d->malloc_used, newsize); 619 STATS_ZERO(d->inserts); 620 STATS_ZERO(d->insert_collisions); 621 for (i = 0; i < d->regions_total; i++) { 622 void *q = d->r[i].p; 623 if (q != NULL) { 624 size_t index = hash(q) & mask; 625 STATS_INC(d->inserts); 626 while (p[index].p != NULL) { 627 index = (index - 1) & mask; 628 STATS_INC(d->insert_collisions); 629 } 630 p[index] = d->r[i]; 631 } 632 } 633 634 if (d->regions_total > 0) { 635 oldpsz = PAGEROUND(d->regions_total * 636 sizeof(struct region_info)); 637 /* clear to avoid meta info ending up in the cache */ 638 unmap(d, d->r, oldpsz, oldpsz); 639 } 640 d->regions_free += newtotal - d->regions_total; 641 d->regions_total = newtotal; 642 d->r = p; 643 return 0; 644} 645 646/* 647 * The hashtable uses the assumption that p is never NULL. This holds since 648 * non-MAP_FIXED mappings with hint 0 start at BRKSIZ. 649 */ 650static int 651insert(struct dir_info *d, void *p, size_t sz, void *f) 652{ 653 size_t index; 654 size_t mask; 655 void *q; 656 657 if (d->regions_free * 4 < d->regions_total || d->regions_total == 0) { 658 if (omalloc_grow(d)) 659 return 1; 660 } 661 mask = d->regions_total - 1; 662 index = hash(p) & mask; 663 q = d->r[index].p; 664 STATS_INC(d->inserts); 665 while (q != NULL) { 666 index = (index - 1) & mask; 667 q = d->r[index].p; 668 STATS_INC(d->insert_collisions); 669 } 670 d->r[index].p = p; 671 d->r[index].size = sz; 672 STATS_SETF(&d->r[index], f); 673 d->regions_free--; 674 return 0; 675} 676 677static struct region_info * 678find(struct dir_info *d, void *p) 679{ 680 size_t index; 681 size_t mask = d->regions_total - 1; 682 void *q, *r; 683 684 if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) || 685 d->canary1 != ~d->canary2) 686 wrterror(d, "internal struct corrupt"); 687 if (d->r == NULL) 688 return NULL; 689 p = MASK_POINTER(p); 690 index = hash(p) & mask; 691 r = d->r[index].p; 692 q = MASK_POINTER(r); 693 while (q != p && r != NULL) { 694 index = (index - 1) & mask; 695 r = d->r[index].p; 696 q = MASK_POINTER(r); 697 } 698 return (q == p && r != NULL) ? &d->r[index] : NULL; 699} 700 701static void 702delete(struct dir_info *d, struct region_info *ri) 703{ 704 /* algorithm R, Knuth Vol III section 6.4 */ 705 size_t mask = d->regions_total - 1; 706 size_t i, j, r; 707 708 if (d->regions_total & (d->regions_total - 1)) 709 wrterror(d, "regions_total not 2^x"); 710 d->regions_free++; 711 STATS_INC(d->deletes); 712 713 i = ri - d->r; 714 for (;;) { 715 d->r[i].p = NULL; 716 d->r[i].size = 0; 717 j = i; 718 for (;;) { 719 i = (i - 1) & mask; 720 if (d->r[i].p == NULL) 721 return; 722 r = hash(d->r[i].p) & mask; 723 if ((i <= r && r < j) || (r < j && j < i) || 724 (j < i && i <= r)) 725 continue; 726 d->r[j] = d->r[i]; 727 STATS_INC(d->delete_moves); 728 break; 729 } 730 731 } 732} 733 734static inline void 735junk_free(int junk, void *p, size_t sz) 736{ 737 size_t i, step = 1; 738 uint64_t *lp = p; 739 740 if (junk == 0 || sz == 0) 741 return; 742 sz /= sizeof(uint64_t); 743 if (junk == 1) { 744 if (sz > MALLOC_PAGESIZE / sizeof(uint64_t)) 745 sz = MALLOC_PAGESIZE / sizeof(uint64_t); 746 step = sz / 4; 747 if (step == 0) 748 step = 1; 749 } 750 /* Do not always put the free junk bytes in the same spot. 751 There is modulo bias here, but we ignore that. */ 752 for (i = mopts.junk_loc % step; i < sz; i += step) 753 lp[i] = SOME_FREEJUNK_ULL; 754} 755 756static inline void 757validate_junk(struct dir_info *pool, void *p, size_t argsz) 758{ 759 size_t i, sz, step = 1; 760 uint64_t *lp = p; 761 762 if (pool->malloc_junk == 0 || argsz == 0) 763 return; 764 sz = argsz / sizeof(uint64_t); 765 if (pool->malloc_junk == 1) { 766 if (sz > MALLOC_PAGESIZE / sizeof(uint64_t)) 767 sz = MALLOC_PAGESIZE / sizeof(uint64_t); 768 step = sz / 4; 769 if (step == 0) 770 step = 1; 771 } 772 /* see junk_free */ 773 for (i = mopts.junk_loc % step; i < sz; i += step) { 774 if (lp[i] != SOME_FREEJUNK_ULL) { 775#ifdef MALLOC_STATS 776 if (DO_STATS && argsz <= MALLOC_MAXCHUNK) 777 print_chunk_details(pool, lp, argsz, i); 778 else 779#endif 780 wrterror(pool, 781 "write to free mem %p[%zu..%zu]@%zu", 782 lp, i * sizeof(uint64_t), 783 (i + 1) * sizeof(uint64_t) - 1, argsz); 784 } 785 } 786} 787 788 789/* 790 * Cache maintenance. 791 * Opposed to the regular region data structure, the sizes in the 792 * cache are in MALLOC_PAGESIZE units. 793 */ 794static void 795unmap(struct dir_info *d, void *p, size_t sz, size_t clear) 796{ 797 size_t psz = sz >> MALLOC_PAGESHIFT; 798 void *r; 799 u_short i; 800 struct smallcache *cache; 801 802 if (sz != PAGEROUND(sz) || psz == 0) 803 wrterror(d, "munmap round"); 804 805 if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE && 806 psz <= MAX_BIGCACHEABLE_SIZE) { 807 u_short base = getrbyte(d); 808 u_short j; 809 810 /* don't look through all slots */ 811 for (j = 0; j < d->bigcache_size / 4; j++) { 812 i = (base + j) & (d->bigcache_size - 1); 813 if (d->bigcache_used < 814 BIGCACHE_FILL(d->bigcache_size)) { 815 if (d->bigcache[i].psize == 0) 816 break; 817 } else { 818 if (d->bigcache[i].psize != 0) 819 break; 820 } 821 } 822 /* if we didn't find a preferred slot, use random one */ 823 if (d->bigcache[i].psize != 0) { 824 size_t tmp; 825 826 r = d->bigcache[i].page; 827 d->bigcache_used -= d->bigcache[i].psize; 828 tmp = d->bigcache[i].psize << MALLOC_PAGESHIFT; 829 if (!mopts.malloc_freeunmap) 830 validate_junk(d, r, tmp); 831 if (munmap(r, tmp)) 832 wrterror(d, "munmap %p", r); 833 STATS_SUB(d->malloc_used, tmp); 834 } 835 836 if (clear > 0) 837 explicit_bzero(p, clear); 838 if (mopts.malloc_freeunmap) { 839 if (mprotect(p, sz, PROT_NONE)) 840 wrterror(d, "mprotect %p", r); 841 } else 842 junk_free(d->malloc_junk, p, sz); 843 d->bigcache[i].page = p; 844 d->bigcache[i].psize = psz; 845 d->bigcache_used += psz; 846 return; 847 } 848 if (psz > MAX_SMALLCACHEABLE_SIZE || d->smallcache[psz - 1].max == 0) { 849 if (munmap(p, sz)) 850 wrterror(d, "munmap %p", p); 851 STATS_SUB(d->malloc_used, sz); 852 return; 853 } 854 cache = &d->smallcache[psz - 1]; 855 if (cache->length == cache->max) { 856 int fresh; 857 /* use a random slot */ 858 i = getrbyte(d) & (cache->max - 1); 859 r = cache->pages[i]; 860 fresh = (uintptr_t)r & 1; 861 *(uintptr_t*)&r &= ~1UL; 862 if (!fresh && !mopts.malloc_freeunmap) 863 validate_junk(d, r, sz); 864 if (munmap(r, sz)) 865 wrterror(d, "munmap %p", r); 866 STATS_SUB(d->malloc_used, sz); 867 cache->length--; 868 } else 869 i = cache->length; 870 871 /* fill slot */ 872 if (clear > 0) 873 explicit_bzero(p, clear); 874 if (mopts.malloc_freeunmap) 875 mprotect(p, sz, PROT_NONE); 876 else 877 junk_free(d->malloc_junk, p, sz); 878 cache->pages[i] = p; 879 cache->length++; 880} 881 882static void * 883map(struct dir_info *d, size_t sz, int zero_fill) 884{ 885 size_t psz = sz >> MALLOC_PAGESHIFT; 886 u_short i; 887 void *p; 888 struct smallcache *cache; 889 890 if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) || 891 d->canary1 != ~d->canary2) 892 wrterror(d, "internal struct corrupt"); 893 if (sz != PAGEROUND(sz) || psz == 0) 894 wrterror(d, "map round"); 895 896 897 if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE && 898 psz <= MAX_BIGCACHEABLE_SIZE) { 899 size_t base = getrbyte(d); 900 size_t cached = d->bigcache_used; 901 ushort j; 902 903 for (j = 0; j < d->bigcache_size && cached >= psz; j++) { 904 i = (j + base) & (d->bigcache_size - 1); 905 if (d->bigcache[i].psize == psz) { 906 p = d->bigcache[i].page; 907 d->bigcache_used -= psz; 908 d->bigcache[i].page = NULL; 909 d->bigcache[i].psize = 0; 910 911 if (!mopts.malloc_freeunmap) 912 validate_junk(d, p, sz); 913 if (mopts.malloc_freeunmap) 914 mprotect(p, sz, PROT_READ | PROT_WRITE); 915 if (zero_fill) 916 memset(p, 0, sz); 917 else if (mopts.malloc_freeunmap) 918 junk_free(d->malloc_junk, p, sz); 919 return p; 920 } 921 cached -= d->bigcache[i].psize; 922 } 923 } 924 if (psz <= MAX_SMALLCACHEABLE_SIZE && d->smallcache[psz - 1].max > 0) { 925 cache = &d->smallcache[psz - 1]; 926 if (cache->length > 0) { 927 int fresh; 928 if (cache->length == 1) 929 p = cache->pages[--cache->length]; 930 else { 931 i = getrbyte(d) % cache->length; 932 p = cache->pages[i]; 933 cache->pages[i] = cache->pages[--cache->length]; 934 } 935 /* check if page was not junked, i.e. "fresh 936 we use the lsb of the pointer for that */ 937 fresh = (uintptr_t)p & 1UL; 938 *(uintptr_t*)&p &= ~1UL; 939 if (!fresh && !mopts.malloc_freeunmap) 940 validate_junk(d, p, sz); 941 if (mopts.malloc_freeunmap) 942 mprotect(p, sz, PROT_READ | PROT_WRITE); 943 if (zero_fill) 944 memset(p, 0, sz); 945 else if (mopts.malloc_freeunmap) 946 junk_free(d->malloc_junk, p, sz); 947 return p; 948 } 949 if (psz <= 1) { 950 p = MMAP(cache->max * sz, d->mmap_flag); 951 if (p != MAP_FAILED) { 952 STATS_ADD(d->malloc_used, cache->max * sz); 953 cache->length = cache->max - 1; 954 for (i = 0; i < cache->max - 1; i++) { 955 void *q = (char*)p + i * sz; 956 cache->pages[i] = q; 957 /* mark pointer in slot as not junked */ 958 *(uintptr_t*)&cache->pages[i] |= 1UL; 959 } 960 if (mopts.malloc_freeunmap) 961 mprotect(p, (cache->max - 1) * sz, 962 PROT_NONE); 963 p = (char*)p + (cache->max - 1) * sz; 964 /* zero fill not needed, freshly mmapped */ 965 return p; 966 } 967 } 968 969 } 970 p = MMAP(sz, d->mmap_flag); 971 if (p != MAP_FAILED) 972 STATS_ADD(d->malloc_used, sz); 973 /* zero fill not needed */ 974 return p; 975} 976 977static void 978init_chunk_info(struct dir_info *d, struct chunk_info *p, u_int bucket) 979{ 980 u_int i; 981 982 p->bucket = bucket; 983 p->total = p->free = MALLOC_PAGESIZE / B2ALLOC(bucket); 984 p->offset = howmany(p->total, MALLOC_BITS); 985 p->canary = (u_short)d->canary1; 986 987 /* set all valid bits in the bitmap */ 988 i = p->total - 1; 989 memset(p->bits, 0xff, sizeof(p->bits[0]) * (i / MALLOC_BITS)); 990 p->bits[i / MALLOC_BITS] = (2U << (i % MALLOC_BITS)) - 1; 991} 992 993static struct chunk_info * 994alloc_chunk_info(struct dir_info *d, u_int bucket) 995{ 996 struct chunk_info *p; 997 998 if (LIST_EMPTY(&d->chunk_info_list[bucket])) { 999 const size_t chunk_pages = 64; 1000 size_t size, count, i; 1001 char *q; 1002 1003 count = MALLOC_PAGESIZE / B2ALLOC(bucket); 1004 1005 size = howmany(count, MALLOC_BITS); 1006 /* see declaration of struct chunk_info */ 1007 if (size <= CHUNK_INFO_TAIL) 1008 size = 0; 1009 else 1010 size -= CHUNK_INFO_TAIL; 1011 size = sizeof(struct chunk_info) + size * sizeof(u_short); 1012 if (mopts.chunk_canaries && bucket > 0) 1013 size += count * sizeof(u_short); 1014 size = _ALIGN(size); 1015 count = MALLOC_PAGESIZE / size; 1016 1017 /* Don't use cache here, we don't want user uaf touch this */ 1018 if (d->chunk_pages_used == chunk_pages || 1019 d->chunk_pages == NULL) { 1020 q = MMAP(MALLOC_PAGESIZE * chunk_pages, d->mmap_flag); 1021 if (q == MAP_FAILED) 1022 return NULL; 1023 d->chunk_pages = q; 1024 d->chunk_pages_used = 0; 1025 STATS_ADD(d->malloc_used, MALLOC_PAGESIZE * 1026 chunk_pages); 1027 } 1028 q = (char *)d->chunk_pages + d->chunk_pages_used * 1029 MALLOC_PAGESIZE; 1030 d->chunk_pages_used++; 1031 1032 for (i = 0; i < count; i++, q += size) { 1033 p = (struct chunk_info *)q; 1034 LIST_INSERT_HEAD(&d->chunk_info_list[bucket], p, 1035 entries); 1036 } 1037 } 1038 p = LIST_FIRST(&d->chunk_info_list[bucket]); 1039 LIST_REMOVE(p, entries); 1040 if (p->total == 0) 1041 init_chunk_info(d, p, bucket); 1042 return p; 1043} 1044 1045/* 1046 * Allocate a page of chunks 1047 */ 1048static struct chunk_info * 1049omalloc_make_chunks(struct dir_info *d, u_int bucket, u_int listnum) 1050{ 1051 struct chunk_info *bp; 1052 void *pp; 1053 void *ff = NULL; 1054 1055 /* Allocate a new bucket */ 1056 pp = map(d, MALLOC_PAGESIZE, 0); 1057 if (pp == MAP_FAILED) 1058 return NULL; 1059 if (DO_STATS) { 1060 ff = map(d, MALLOC_PAGESIZE, 0); 1061 if (ff == MAP_FAILED) 1062 goto err; 1063 memset(ff, 0, sizeof(void *) * MALLOC_PAGESIZE / 1064 B2ALLOC(bucket)); 1065 } 1066 1067 /* memory protect the page allocated in the malloc(0) case */ 1068 if (bucket == 0 && mprotect(pp, MALLOC_PAGESIZE, PROT_NONE) == -1) 1069 goto err; 1070 1071 bp = alloc_chunk_info(d, bucket); 1072 if (bp == NULL) 1073 goto err; 1074 bp->page = pp; 1075 1076 if (insert(d, (void *)((uintptr_t)pp | (bucket + 1)), (uintptr_t)bp, 1077 ff)) 1078 goto err; 1079 LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries); 1080 1081 if (bucket > 0 && d->malloc_junk != 0) 1082 memset(pp, SOME_FREEJUNK, MALLOC_PAGESIZE); 1083 1084 return bp; 1085 1086err: 1087 unmap(d, pp, MALLOC_PAGESIZE, 0); 1088 if (ff != NULL && ff != MAP_FAILED) 1089 unmap(d, ff, MALLOC_PAGESIZE, 0); 1090 return NULL; 1091} 1092 1093#if defined(__GNUC__) && __GNUC__ < 4 1094static inline unsigned int 1095lb(u_int x) 1096{ 1097#if defined(__m88k__) 1098 __asm__ __volatile__ ("ff1 %0, %0" : "=r" (x) : "0" (x)); 1099 return x; 1100#else 1101 /* portable version */ 1102 unsigned int count = 0; 1103 while ((x & (1U << (sizeof(int) * CHAR_BIT - 1))) == 0) { 1104 count++; 1105 x <<= 1; 1106 } 1107 return (sizeof(int) * CHAR_BIT - 1) - count; 1108#endif 1109} 1110#else 1111/* using built-in function version */ 1112static inline unsigned int 1113lb(u_int x) 1114{ 1115 /* I need an extension just for integer-length (: */ 1116 return (sizeof(int) * CHAR_BIT - 1) - __builtin_clz(x); 1117} 1118#endif 1119 1120/* https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/ 1121 via Tony Finch */ 1122static inline unsigned int 1123bin_of(unsigned int size) 1124{ 1125 const unsigned int linear = 6; 1126 const unsigned int subbin = 2; 1127 1128 unsigned int mask, rounded, rounded_size; 1129 unsigned int n_bits, shift; 1130 1131 n_bits = lb(size | (1U << linear)); 1132 shift = n_bits - subbin; 1133 mask = (1ULL << shift) - 1; 1134 rounded = size + mask; /* XXX: overflow. */ 1135 1136 rounded_size = rounded & ~mask; 1137 return rounded_size; 1138} 1139 1140static inline u_short 1141find_bucket(u_short size) 1142{ 1143 /* malloc(0) is special */ 1144 if (size == 0) 1145 return 0; 1146 if (size < MALLOC_MINSIZE) 1147 size = MALLOC_MINSIZE; 1148 if (mopts.def_maxcache != 0) 1149 size = bin_of(size); 1150 return howmany(size, MALLOC_MINSIZE); 1151} 1152 1153static void 1154fill_canary(char *ptr, size_t sz, size_t allocated) 1155{ 1156 size_t check_sz = allocated - sz; 1157 1158 if (check_sz > CHUNK_CHECK_LENGTH) 1159 check_sz = CHUNK_CHECK_LENGTH; 1160 memset(ptr + sz, mopts.chunk_canaries, check_sz); 1161} 1162 1163/* 1164 * Allocate a chunk 1165 */ 1166static void * 1167malloc_bytes(struct dir_info *d, size_t size) 1168{ 1169 u_int i, j, k, r, bucket, listnum; 1170 u_short *lp; 1171 struct chunk_info *bp; 1172 void *p; 1173 1174 if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) || 1175 d->canary1 != ~d->canary2) 1176 wrterror(d, "internal struct corrupt"); 1177 1178 bucket = find_bucket(size); 1179 1180 r = getrbyte(d); 1181 listnum = r % MALLOC_CHUNK_LISTS; 1182 1183 /* If it's empty, make a page more of that size chunks */ 1184 if ((bp = LIST_FIRST(&d->chunk_dir[bucket][listnum])) == NULL) { 1185 bp = omalloc_make_chunks(d, bucket, listnum); 1186 if (bp == NULL) 1187 return NULL; 1188 } 1189 1190 if (bp->canary != (u_short)d->canary1 || bucket != bp->bucket) 1191 wrterror(d, "chunk info corrupted"); 1192 1193 r /= MALLOC_CHUNK_LISTS; 1194 /* do we need more random bits? */ 1195 if (bp->total > 256 / MALLOC_CHUNK_LISTS) 1196 r = r << 8 | getrbyte(d); 1197 /* bias, as bp->total is not a power of 2 */ 1198 i = r % bp->total; 1199 1200 j = i % MALLOC_BITS; 1201 i /= MALLOC_BITS; 1202 lp = &bp->bits[i]; 1203 /* potentially start somewhere in a short */ 1204 if (j > 0 && *lp >> j) 1205 k = ffs(*lp >> j) + j; 1206 else { 1207 /* no bit halfway, go to next full short */ 1208 for (;;) { 1209 if (*lp) { 1210 k = ffs(*lp); 1211 break; 1212 } 1213 if (++i >= bp->offset) 1214 i = 0; 1215 lp = &bp->bits[i]; 1216 } 1217 } 1218 *lp ^= 1 << --k; 1219 1220 /* If there are no more free, remove from free-list */ 1221 if (--bp->free == 0) 1222 LIST_REMOVE(bp, entries); 1223 1224 /* Adjust to the real offset of that chunk */ 1225 k += i * MALLOC_BITS; 1226 1227 if (mopts.chunk_canaries && size > 0) 1228 bp->bits[bp->offset + k] = size; 1229 1230 if (DO_STATS) { 1231 struct region_info *r = find(d, bp->page); 1232 STATS_SETFN(r, k, d->caller); 1233 } 1234 1235 p = (char *)bp->page + k * B2ALLOC(bucket); 1236 if (bucket > 0) { 1237 validate_junk(d, p, B2SIZE(bucket)); 1238 if (mopts.chunk_canaries) 1239 fill_canary(p, size, B2SIZE(bucket)); 1240 } 1241 return p; 1242} 1243 1244static void 1245validate_canary(struct dir_info *d, u_char *ptr, size_t sz, size_t allocated) 1246{ 1247 size_t check_sz = allocated - sz; 1248 u_char *p, *q; 1249 1250 if (check_sz > CHUNK_CHECK_LENGTH) 1251 check_sz = CHUNK_CHECK_LENGTH; 1252 p = ptr + sz; 1253 q = p + check_sz; 1254 1255 while (p < q) { 1256 if (*p != (u_char)mopts.chunk_canaries && *p != SOME_JUNK) { 1257 wrterror(d, "canary corrupted %p[%tu]@%zu/%zu%s", 1258 ptr, p - ptr, sz, allocated, 1259 *p == SOME_FREEJUNK ? " (double free?)" : ""); 1260 } 1261 p++; 1262 } 1263} 1264 1265static inline uint32_t 1266find_chunknum(struct dir_info *d, struct chunk_info *info, void *ptr, int check) 1267{ 1268 uint32_t chunknum; 1269 1270 if (info->canary != (u_short)d->canary1) 1271 wrterror(d, "chunk info corrupted"); 1272 1273 /* Find the chunk number on the page */ 1274 chunknum = ((uintptr_t)ptr & MALLOC_PAGEMASK) / B2ALLOC(info->bucket); 1275 1276 if ((uintptr_t)ptr & (MALLOC_MINSIZE - 1)) 1277 wrterror(d, "modified chunk-pointer %p", ptr); 1278 if (CHUNK_FREE(info, chunknum)) 1279 wrterror(d, "double free %p", ptr); 1280 if (check && info->bucket > 0) { 1281 validate_canary(d, ptr, info->bits[info->offset + chunknum], 1282 B2SIZE(info->bucket)); 1283 } 1284 return chunknum; 1285} 1286 1287/* 1288 * Free a chunk, and possibly the page it's on, if the page becomes empty. 1289 */ 1290static void 1291free_bytes(struct dir_info *d, struct region_info *r, void *ptr) 1292{ 1293 struct chunk_head *mp; 1294 struct chunk_info *info; 1295 uint32_t chunknum; 1296 uint32_t listnum; 1297 1298 info = (struct chunk_info *)r->size; 1299 chunknum = find_chunknum(d, info, ptr, 0); 1300 1301 info->bits[chunknum / MALLOC_BITS] |= 1U << (chunknum % MALLOC_BITS); 1302 info->free++; 1303 1304 if (info->free == 1) { 1305 /* Page became non-full */ 1306 listnum = getrbyte(d) % MALLOC_CHUNK_LISTS; 1307 mp = &d->chunk_dir[info->bucket][listnum]; 1308 LIST_INSERT_HEAD(mp, info, entries); 1309 return; 1310 } 1311 1312 if (info->free != info->total) 1313 return; 1314 1315 LIST_REMOVE(info, entries); 1316 1317 if (info->bucket == 0 && !mopts.malloc_freeunmap) 1318 mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE); 1319 unmap(d, info->page, MALLOC_PAGESIZE, 0); 1320#ifdef MALLOC_STATS 1321 if (r->f != NULL) { 1322 unmap(d, r->f, MALLOC_PAGESIZE, MALLOC_PAGESIZE); 1323 r->f = NULL; 1324 } 1325#endif 1326 1327 delete(d, r); 1328 mp = &d->chunk_info_list[info->bucket]; 1329 LIST_INSERT_HEAD(mp, info, entries); 1330} 1331 1332static void * 1333omalloc(struct dir_info *pool, size_t sz, int zero_fill) 1334{ 1335 void *p, *caller = NULL; 1336 size_t psz; 1337 1338 if (sz > MALLOC_MAXCHUNK) { 1339 if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { 1340 errno = ENOMEM; 1341 return NULL; 1342 } 1343 sz += mopts.malloc_guard; 1344 psz = PAGEROUND(sz); 1345 p = map(pool, psz, zero_fill); 1346 if (p == MAP_FAILED) { 1347 errno = ENOMEM; 1348 return NULL; 1349 } 1350#ifdef MALLOC_STATS 1351 if (DO_STATS) 1352 caller = pool->caller; 1353#endif 1354 if (insert(pool, p, sz, caller)) { 1355 unmap(pool, p, psz, 0); 1356 errno = ENOMEM; 1357 return NULL; 1358 } 1359 if (mopts.malloc_guard) { 1360 if (mprotect((char *)p + psz - mopts.malloc_guard, 1361 mopts.malloc_guard, PROT_NONE)) 1362 wrterror(pool, "mprotect"); 1363 STATS_ADD(pool->malloc_guarded, mopts.malloc_guard); 1364 } 1365 1366 if (MALLOC_MOVE_COND(sz)) { 1367 /* fill whole allocation */ 1368 if (pool->malloc_junk == 2) 1369 memset(p, SOME_JUNK, psz - mopts.malloc_guard); 1370 /* shift towards the end */ 1371 p = MALLOC_MOVE(p, sz); 1372 /* fill zeros if needed and overwritten above */ 1373 if (zero_fill && pool->malloc_junk == 2) 1374 memset(p, 0, sz - mopts.malloc_guard); 1375 } else { 1376 if (pool->malloc_junk == 2) { 1377 if (zero_fill) 1378 memset((char *)p + sz - 1379 mopts.malloc_guard, SOME_JUNK, 1380 psz - sz); 1381 else 1382 memset(p, SOME_JUNK, 1383 psz - mopts.malloc_guard); 1384 } else if (mopts.chunk_canaries) 1385 fill_canary(p, sz - mopts.malloc_guard, 1386 psz - mopts.malloc_guard); 1387 } 1388 1389 } else { 1390 /* takes care of SOME_JUNK */ 1391 p = malloc_bytes(pool, sz); 1392 if (zero_fill && p != NULL && sz > 0) 1393 memset(p, 0, sz); 1394 } 1395 1396 return p; 1397} 1398 1399/* 1400 * Common function for handling recursion. Only 1401 * print the error message once, to avoid making the problem 1402 * potentially worse. 1403 */ 1404static void 1405malloc_recurse(struct dir_info *d) 1406{ 1407 static int noprint; 1408 1409 if (noprint == 0) { 1410 noprint = 1; 1411 wrterror(d, "recursive call"); 1412 } 1413 d->active--; 1414 _MALLOC_UNLOCK(d->mutex); 1415 errno = EDEADLK; 1416} 1417 1418void 1419_malloc_init(int from_rthreads) 1420{ 1421 u_int i, j, nmutexes; 1422 struct dir_info *d; 1423 1424 _MALLOC_LOCK(1); 1425 if (!from_rthreads && mopts.malloc_pool[1]) { 1426 _MALLOC_UNLOCK(1); 1427 return; 1428 } 1429 if (!mopts.malloc_canary) { 1430 char *p; 1431 size_t sz, roundup_sz, d_avail; 1432 1433 omalloc_init(); 1434 /* 1435 * Allocate dir_infos with a guard page on either side. Also 1436 * randomise offset inside the page at which the dir_infos 1437 * lay (subject to alignment by 1 << MALLOC_MINSHIFT) 1438 */ 1439 sz = mopts.malloc_mutexes * sizeof(*d); 1440 roundup_sz = (sz + MALLOC_PAGEMASK) & ~MALLOC_PAGEMASK; 1441 if ((p = MMAPNONE(roundup_sz + 2 * MALLOC_PAGESIZE, 0)) == 1442 MAP_FAILED) 1443 wrterror(NULL, "malloc_init mmap1 failed"); 1444 if (mprotect(p + MALLOC_PAGESIZE, roundup_sz, 1445 PROT_READ | PROT_WRITE)) 1446 wrterror(NULL, "malloc_init mprotect1 failed"); 1447 if (mimmutable(p, roundup_sz + 2 * MALLOC_PAGESIZE)) 1448 wrterror(NULL, "malloc_init mimmutable1 failed"); 1449 d_avail = (roundup_sz - sz) >> MALLOC_MINSHIFT; 1450 d = (struct dir_info *)(p + MALLOC_PAGESIZE + 1451 (arc4random_uniform(d_avail) << MALLOC_MINSHIFT)); 1452 STATS_ADD(d[1].malloc_used, roundup_sz + 2 * MALLOC_PAGESIZE); 1453 for (i = 0; i < mopts.malloc_mutexes; i++) 1454 mopts.malloc_pool[i] = &d[i]; 1455 mopts.internal_funcs = 1; 1456 if (((uintptr_t)&malloc_readonly & MALLOC_PAGEMASK) == 0) { 1457 if (mprotect(&malloc_readonly, sizeof(malloc_readonly), 1458 PROT_READ)) 1459 wrterror(NULL, 1460 "malloc_init mprotect r/o failed"); 1461 if (mimmutable(&malloc_readonly, 1462 sizeof(malloc_readonly))) 1463 wrterror(NULL, 1464 "malloc_init mimmutable r/o failed"); 1465 } 1466 } 1467 1468 nmutexes = from_rthreads ? mopts.malloc_mutexes : 2; 1469 for (i = 0; i < nmutexes; i++) { 1470 d = mopts.malloc_pool[i]; 1471 d->malloc_mt = from_rthreads; 1472 if (d->canary1 == ~d->canary2) 1473 continue; 1474 if (i == 0) { 1475 omalloc_poolinit(d, MAP_CONCEAL); 1476 d->malloc_junk = 2; 1477 d->bigcache_size = 0; 1478 for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) 1479 d->smallcache[j].max = 0; 1480 } else { 1481 size_t sz = 0; 1482 1483 omalloc_poolinit(d, 0); 1484 d->malloc_junk = mopts.def_malloc_junk; 1485 d->bigcache_size = mopts.def_maxcache; 1486 for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) { 1487 d->smallcache[j].max = 1488 mopts.def_maxcache >> (j / 8); 1489 sz += d->smallcache[j].max * sizeof(void *); 1490 } 1491 sz += d->bigcache_size * sizeof(struct bigcache); 1492 if (sz > 0) { 1493 void *p = MMAP(sz, 0); 1494 if (p == MAP_FAILED) 1495 wrterror(NULL, 1496 "malloc_init mmap2 failed"); 1497 if (mimmutable(p, sz)) 1498 wrterror(NULL, 1499 "malloc_init mimmutable2 failed"); 1500 for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) { 1501 d->smallcache[j].pages = p; 1502 p = (char *)p + d->smallcache[j].max * 1503 sizeof(void *); 1504 } 1505 d->bigcache = p; 1506 } 1507 } 1508 d->mutex = i; 1509 } 1510 1511 _MALLOC_UNLOCK(1); 1512} 1513DEF_STRONG(_malloc_init); 1514 1515#define PROLOGUE(p, fn) \ 1516 d = (p); \ 1517 if (d == NULL) { \ 1518 _malloc_init(0); \ 1519 d = (p); \ 1520 } \ 1521 _MALLOC_LOCK(d->mutex); \ 1522 d->func = fn; \ 1523 if (d->active++) { \ 1524 malloc_recurse(d); \ 1525 return NULL; \ 1526 } \ 1527 1528#define EPILOGUE() \ 1529 d->active--; \ 1530 _MALLOC_UNLOCK(d->mutex); \ 1531 if (r == NULL && mopts.malloc_xmalloc) \ 1532 wrterror(d, "out of memory"); \ 1533 if (r != NULL) \ 1534 errno = saved_errno; \ 1535 1536void * 1537malloc(size_t size) 1538{ 1539 void *r; 1540 struct dir_info *d; 1541 int saved_errno = errno; 1542 1543 PROLOGUE(getpool(), "malloc") 1544 SET_CALLER(d, caller(d)); 1545 r = omalloc(d, size, 0); 1546 EPILOGUE() 1547 return r; 1548} 1549DEF_STRONG(malloc); 1550 1551void * 1552malloc_conceal(size_t size) 1553{ 1554 void *r; 1555 struct dir_info *d; 1556 int saved_errno = errno; 1557 1558 PROLOGUE(mopts.malloc_pool[0], "malloc_conceal") 1559 SET_CALLER(d, caller(d)); 1560 r = omalloc(d, size, 0); 1561 EPILOGUE() 1562 return r; 1563} 1564DEF_WEAK(malloc_conceal); 1565 1566static struct region_info * 1567findpool(void *p, struct dir_info *argpool, struct dir_info **foundpool, 1568 const char ** saved_function) 1569{ 1570 struct dir_info *pool = argpool; 1571 struct region_info *r = find(pool, p); 1572 1573 if (r == NULL) { 1574 u_int i, nmutexes; 1575 1576 nmutexes = mopts.malloc_pool[1]->malloc_mt ? 1577 mopts.malloc_mutexes : 2; 1578 for (i = 1; i < nmutexes; i++) { 1579 u_int j = (argpool->mutex + i) & (nmutexes - 1); 1580 1581 pool->active--; 1582 _MALLOC_UNLOCK(pool->mutex); 1583 pool = mopts.malloc_pool[j]; 1584 _MALLOC_LOCK(pool->mutex); 1585 pool->active++; 1586 r = find(pool, p); 1587 if (r != NULL) { 1588 *saved_function = pool->func; 1589 pool->func = argpool->func; 1590 break; 1591 } 1592 } 1593 if (r == NULL) 1594 wrterror(argpool, "bogus pointer (double free?) %p", p); 1595 } 1596 *foundpool = pool; 1597 return r; 1598} 1599 1600static void 1601ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz) 1602{ 1603 struct region_info *r; 1604 struct dir_info *pool; 1605 const char *saved_function; 1606 size_t sz; 1607 1608 r = findpool(p, *argpool, &pool, &saved_function); 1609 1610 REALSIZE(sz, r); 1611 if (pool->mmap_flag) { 1612 clear = 1; 1613 if (!check) { 1614 argsz = sz; 1615 if (sz > MALLOC_MAXCHUNK) 1616 argsz -= mopts.malloc_guard; 1617 } 1618 } 1619 if (check) { 1620 if (sz <= MALLOC_MAXCHUNK) { 1621 if (mopts.chunk_canaries && sz > 0) { 1622 struct chunk_info *info = 1623 (struct chunk_info *)r->size; 1624 uint32_t chunknum = 1625 find_chunknum(pool, info, p, 0); 1626 1627 if (info->bits[info->offset + chunknum] < argsz) 1628 wrterror(pool, "recorded size %hu" 1629 " < %zu", 1630 info->bits[info->offset + chunknum], 1631 argsz); 1632 } else { 1633 if (sz < argsz) 1634 wrterror(pool, "chunk size %zu < %zu", 1635 sz, argsz); 1636 } 1637 } else if (sz - mopts.malloc_guard < argsz) { 1638 wrterror(pool, "recorded size %zu < %zu", 1639 sz - mopts.malloc_guard, argsz); 1640 } 1641 } 1642 if (sz > MALLOC_MAXCHUNK) { 1643 if (!MALLOC_MOVE_COND(sz)) { 1644 if (r->p != p) 1645 wrterror(pool, "bogus pointer %p", p); 1646 if (mopts.chunk_canaries) 1647 validate_canary(pool, p, 1648 sz - mopts.malloc_guard, 1649 PAGEROUND(sz - mopts.malloc_guard)); 1650 } else { 1651 /* shifted towards the end */ 1652 if (p != MALLOC_MOVE(r->p, sz)) 1653 wrterror(pool, "bogus moved pointer %p", p); 1654 p = r->p; 1655 } 1656 if (mopts.malloc_guard) { 1657 if (sz < mopts.malloc_guard) 1658 wrterror(pool, "guard size"); 1659 if (!mopts.malloc_freeunmap) { 1660 if (mprotect((char *)p + PAGEROUND(sz) - 1661 mopts.malloc_guard, mopts.malloc_guard, 1662 PROT_READ | PROT_WRITE)) 1663 wrterror(pool, "mprotect"); 1664 } 1665 STATS_SUB(pool->malloc_guarded, mopts.malloc_guard); 1666 } 1667 unmap(pool, p, PAGEROUND(sz), clear ? argsz : 0); 1668 delete(pool, r); 1669 } else { 1670 void *tmp; 1671 u_int i; 1672 1673 /* Validate and optionally canary check */ 1674 struct chunk_info *info = (struct chunk_info *)r->size; 1675 if (B2SIZE(info->bucket) != sz) 1676 wrterror(pool, "internal struct corrupt"); 1677 find_chunknum(pool, info, p, mopts.chunk_canaries); 1678 1679 if (mopts.malloc_freecheck) { 1680 for (i = 0; i <= MALLOC_DELAYED_CHUNK_MASK; i++) { 1681 tmp = pool->delayed_chunks[i]; 1682 if (tmp == p) 1683 wrterror(pool, 1684 "double free %p", p); 1685 if (tmp != NULL) { 1686 size_t tmpsz; 1687 1688 r = find(pool, tmp); 1689 if (r == NULL) 1690 wrterror(pool, 1691 "bogus pointer (" 1692 "double free?) %p", tmp); 1693 REALSIZE(tmpsz, r); 1694 validate_junk(pool, tmp, tmpsz); 1695 } 1696 } 1697 } 1698 1699 if (clear && argsz > 0) 1700 explicit_bzero(p, argsz); 1701 junk_free(pool->malloc_junk, p, sz); 1702 1703 i = getrbyte(pool) & MALLOC_DELAYED_CHUNK_MASK; 1704 tmp = p; 1705 p = pool->delayed_chunks[i]; 1706 if (tmp == p) 1707 wrterror(pool, "double free %p", p); 1708 pool->delayed_chunks[i] = tmp; 1709 if (p != NULL) { 1710 r = find(pool, p); 1711 if (r == NULL) 1712 wrterror(pool, 1713 "bogus pointer (double free?) %p", p); 1714 if (!mopts.malloc_freecheck) { 1715 REALSIZE(sz, r); 1716 validate_junk(pool, p, sz); 1717 } 1718 free_bytes(pool, r, p); 1719 } 1720 } 1721 1722 if (*argpool != pool) { 1723 pool->func = saved_function; 1724 *argpool = pool; 1725 } 1726} 1727 1728void 1729free(void *ptr) 1730{ 1731 struct dir_info *d; 1732 int saved_errno = errno; 1733 1734 /* This is legal. */ 1735 if (ptr == NULL) 1736 return; 1737 1738 d = getpool(); 1739 if (d == NULL) 1740 wrterror(d, "free() called before allocation"); 1741 _MALLOC_LOCK(d->mutex); 1742 d->func = "free"; 1743 if (d->active++) { 1744 malloc_recurse(d); 1745 return; 1746 } 1747 ofree(&d, ptr, 0, 0, 0); 1748 d->active--; 1749 _MALLOC_UNLOCK(d->mutex); 1750 errno = saved_errno; 1751} 1752DEF_STRONG(free); 1753 1754static void 1755freezero_p(void *ptr, size_t sz) 1756{ 1757 explicit_bzero(ptr, sz); 1758 free(ptr); 1759} 1760 1761void 1762freezero(void *ptr, size_t sz) 1763{ 1764 struct dir_info *d; 1765 int saved_errno = errno; 1766 1767 /* This is legal. */ 1768 if (ptr == NULL) 1769 return; 1770 1771 if (!mopts.internal_funcs) { 1772 freezero_p(ptr, sz); 1773 return; 1774 } 1775 1776 d = getpool(); 1777 if (d == NULL) 1778 wrterror(d, "freezero() called before allocation"); 1779 _MALLOC_LOCK(d->mutex); 1780 d->func = "freezero"; 1781 if (d->active++) { 1782 malloc_recurse(d); 1783 return; 1784 } 1785 ofree(&d, ptr, 1, 1, sz); 1786 d->active--; 1787 _MALLOC_UNLOCK(d->mutex); 1788 errno = saved_errno; 1789} 1790DEF_WEAK(freezero); 1791 1792static void * 1793orealloc(struct dir_info **argpool, void *p, size_t newsz) 1794{ 1795 struct region_info *r; 1796 struct dir_info *pool; 1797 const char *saved_function; 1798 struct chunk_info *info; 1799 size_t oldsz, goldsz, gnewsz; 1800 void *q, *ret; 1801 uint32_t chunknum; 1802 int forced; 1803 1804 if (p == NULL) 1805 return omalloc(*argpool, newsz, 0); 1806 1807 if (newsz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { 1808 errno = ENOMEM; 1809 return NULL; 1810 } 1811 1812 r = findpool(p, *argpool, &pool, &saved_function); 1813 1814 REALSIZE(oldsz, r); 1815 if (oldsz <= MALLOC_MAXCHUNK) { 1816 if (DO_STATS || mopts.chunk_canaries) { 1817 info = (struct chunk_info *)r->size; 1818 chunknum = find_chunknum(pool, info, p, 0); 1819 } 1820 } 1821 1822 goldsz = oldsz; 1823 if (oldsz > MALLOC_MAXCHUNK) { 1824 if (oldsz < mopts.malloc_guard) 1825 wrterror(pool, "guard size"); 1826 oldsz -= mopts.malloc_guard; 1827 } 1828 1829 gnewsz = newsz; 1830 if (gnewsz > MALLOC_MAXCHUNK) 1831 gnewsz += mopts.malloc_guard; 1832 1833 forced = mopts.malloc_realloc || pool->mmap_flag; 1834 if (newsz > MALLOC_MAXCHUNK && oldsz > MALLOC_MAXCHUNK && !forced) { 1835 /* First case: from n pages sized allocation to m pages sized 1836 allocation, m > n */ 1837 size_t roldsz = PAGEROUND(goldsz); 1838 size_t rnewsz = PAGEROUND(gnewsz); 1839 1840 if (rnewsz < roldsz && rnewsz > roldsz / 2 && 1841 roldsz - rnewsz < mopts.def_maxcache * MALLOC_PAGESIZE && 1842 !mopts.malloc_guard) { 1843 1844 ret = p; 1845 goto done; 1846 } 1847 1848 if (rnewsz > roldsz) { 1849 /* try to extend existing region */ 1850 if (!mopts.malloc_guard) { 1851 void *hint = (char *)r->p + roldsz; 1852 size_t needed = rnewsz - roldsz; 1853 1854 STATS_INC(pool->cheap_realloc_tries); 1855 q = MMAPA(hint, needed, MAP_FIXED | 1856 __MAP_NOREPLACE | pool->mmap_flag); 1857 if (q == hint) { 1858 STATS_ADD(pool->malloc_used, needed); 1859 if (pool->malloc_junk == 2) 1860 memset(q, SOME_JUNK, needed); 1861 r->size = gnewsz; 1862 if (r->p != p) { 1863 /* old pointer is moved */ 1864 memmove(r->p, p, oldsz); 1865 p = r->p; 1866 } 1867 if (mopts.chunk_canaries) 1868 fill_canary(p, newsz, 1869 PAGEROUND(newsz)); 1870 STATS_SETF(r, (*argpool)->caller); 1871 STATS_INC(pool->cheap_reallocs); 1872 ret = p; 1873 goto done; 1874 } 1875 } 1876 } else if (rnewsz < roldsz) { 1877 /* shrink number of pages */ 1878 if (mopts.malloc_guard) { 1879 if (mprotect((char *)r->p + rnewsz - 1880 mopts.malloc_guard, mopts.malloc_guard, 1881 PROT_NONE)) 1882 wrterror(pool, "mprotect"); 1883 } 1884 if (munmap((char *)r->p + rnewsz, roldsz - rnewsz)) 1885 wrterror(pool, "munmap %p", (char *)r->p + 1886 rnewsz); 1887 STATS_SUB(pool->malloc_used, roldsz - rnewsz); 1888 r->size = gnewsz; 1889 if (MALLOC_MOVE_COND(gnewsz)) { 1890 void *pp = MALLOC_MOVE(r->p, gnewsz); 1891 memmove(pp, p, newsz); 1892 p = pp; 1893 } else if (mopts.chunk_canaries) 1894 fill_canary(p, newsz, PAGEROUND(newsz)); 1895 STATS_SETF(r, (*argpool)->caller); 1896 ret = p; 1897 goto done; 1898 } else { 1899 /* number of pages remains the same */ 1900 void *pp = r->p; 1901 1902 r->size = gnewsz; 1903 if (MALLOC_MOVE_COND(gnewsz)) 1904 pp = MALLOC_MOVE(r->p, gnewsz); 1905 if (p != pp) { 1906 memmove(pp, p, oldsz < newsz ? oldsz : newsz); 1907 p = pp; 1908 } 1909 if (p == r->p) { 1910 if (newsz > oldsz && pool->malloc_junk == 2) 1911 memset((char *)p + newsz, SOME_JUNK, 1912 rnewsz - mopts.malloc_guard - 1913 newsz); 1914 if (mopts.chunk_canaries) 1915 fill_canary(p, newsz, PAGEROUND(newsz)); 1916 } 1917 STATS_SETF(r, (*argpool)->caller); 1918 ret = p; 1919 goto done; 1920 } 1921 } 1922 if (oldsz <= MALLOC_MAXCHUNK && oldsz > 0 && 1923 newsz <= MALLOC_MAXCHUNK && newsz > 0 && 1924 !forced && find_bucket(newsz) == find_bucket(oldsz)) { 1925 /* do not reallocate if new size fits good in existing chunk */ 1926 if (pool->malloc_junk == 2) 1927 memset((char *)p + newsz, SOME_JUNK, oldsz - newsz); 1928 if (mopts.chunk_canaries) { 1929 info->bits[info->offset + chunknum] = newsz; 1930 fill_canary(p, newsz, B2SIZE(info->bucket)); 1931 } 1932 if (DO_STATS) 1933 STATS_SETFN(r, chunknum, (*argpool)->caller); 1934 ret = p; 1935 } else if (newsz != oldsz || forced) { 1936 /* create new allocation */ 1937 q = omalloc(pool, newsz, 0); 1938 if (q == NULL) { 1939 ret = NULL; 1940 goto done; 1941 } 1942 if (newsz != 0 && oldsz != 0) 1943 memcpy(q, p, oldsz < newsz ? oldsz : newsz); 1944 ofree(&pool, p, 0, 0, 0); 1945 ret = q; 1946 } else { 1947 /* oldsz == newsz */ 1948 if (newsz != 0) 1949 wrterror(pool, "realloc internal inconsistency"); 1950 if (DO_STATS) 1951 STATS_SETFN(r, chunknum, (*argpool)->caller); 1952 ret = p; 1953 } 1954done: 1955 if (*argpool != pool) { 1956 pool->func = saved_function; 1957 *argpool = pool; 1958 } 1959 return ret; 1960} 1961 1962void * 1963realloc(void *ptr, size_t size) 1964{ 1965 struct dir_info *d; 1966 void *r; 1967 int saved_errno = errno; 1968 1969 PROLOGUE(getpool(), "realloc") 1970 SET_CALLER(d, caller(d)); 1971 r = orealloc(&d, ptr, size); 1972 EPILOGUE() 1973 return r; 1974} 1975DEF_STRONG(realloc); 1976 1977/* 1978 * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX 1979 * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW 1980 */ 1981#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) 1982 1983void * 1984calloc(size_t nmemb, size_t size) 1985{ 1986 struct dir_info *d; 1987 void *r; 1988 int saved_errno = errno; 1989 1990 PROLOGUE(getpool(), "calloc") 1991 SET_CALLER(d, caller(d)); 1992 if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 1993 nmemb > 0 && SIZE_MAX / nmemb < size) { 1994 d->active--; 1995 _MALLOC_UNLOCK(d->mutex); 1996 if (mopts.malloc_xmalloc) 1997 wrterror(d, "out of memory"); 1998 errno = ENOMEM; 1999 return NULL; 2000 } 2001 2002 size *= nmemb; 2003 r = omalloc(d, size, 1); 2004 EPILOGUE() 2005 return r; 2006} 2007DEF_STRONG(calloc); 2008 2009void * 2010calloc_conceal(size_t nmemb, size_t size) 2011{ 2012 struct dir_info *d; 2013 void *r; 2014 int saved_errno = errno; 2015 2016 PROLOGUE(mopts.malloc_pool[0], "calloc_conceal") 2017 SET_CALLER(d, caller(d)); 2018 if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 2019 nmemb > 0 && SIZE_MAX / nmemb < size) { 2020 d->active--; 2021 _MALLOC_UNLOCK(d->mutex); 2022 if (mopts.malloc_xmalloc) 2023 wrterror(d, "out of memory"); 2024 errno = ENOMEM; 2025 return NULL; 2026 } 2027 2028 size *= nmemb; 2029 r = omalloc(d, size, 1); 2030 EPILOGUE() 2031 return r; 2032} 2033DEF_WEAK(calloc_conceal); 2034 2035static void * 2036orecallocarray(struct dir_info **argpool, void *p, size_t oldsize, 2037 size_t newsize) 2038{ 2039 struct region_info *r; 2040 struct dir_info *pool; 2041 const char *saved_function; 2042 void *newptr; 2043 size_t sz; 2044 2045 if (p == NULL) 2046 return omalloc(*argpool, newsize, 1); 2047 2048 if (oldsize == newsize) 2049 return p; 2050 2051 r = findpool(p, *argpool, &pool, &saved_function); 2052 2053 REALSIZE(sz, r); 2054 if (sz <= MALLOC_MAXCHUNK) { 2055 if (mopts.chunk_canaries && sz > 0) { 2056 struct chunk_info *info = (struct chunk_info *)r->size; 2057 uint32_t chunknum = find_chunknum(pool, info, p, 0); 2058 2059 if (info->bits[info->offset + chunknum] != oldsize) 2060 wrterror(pool, "recorded size %hu != %zu", 2061 info->bits[info->offset + chunknum], 2062 oldsize); 2063 } else { 2064 if (sz < oldsize) 2065 wrterror(pool, "chunk size %zu < %zu", 2066 sz, oldsize); 2067 } 2068 } else { 2069 if (sz - mopts.malloc_guard < oldsize) 2070 wrterror(pool, "recorded size %zu < %zu", 2071 sz - mopts.malloc_guard, oldsize); 2072 if (oldsize < (sz - mopts.malloc_guard) / 2) 2073 wrterror(pool, 2074 "recorded size %zu inconsistent with %zu", 2075 sz - mopts.malloc_guard, oldsize); 2076 } 2077 2078 newptr = omalloc(pool, newsize, 0); 2079 if (newptr == NULL) 2080 goto done; 2081 2082 if (newsize > oldsize) { 2083 memcpy(newptr, p, oldsize); 2084 memset((char *)newptr + oldsize, 0, newsize - oldsize); 2085 } else 2086 memcpy(newptr, p, newsize); 2087 2088 ofree(&pool, p, 1, 0, oldsize); 2089 2090done: 2091 if (*argpool != pool) { 2092 pool->func = saved_function; 2093 *argpool = pool; 2094 } 2095 2096 return newptr; 2097} 2098 2099static void * 2100recallocarray_p(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) 2101{ 2102 size_t oldsize, newsize; 2103 void *newptr; 2104 2105 if (ptr == NULL) 2106 return calloc(newnmemb, size); 2107 2108 if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 2109 newnmemb > 0 && SIZE_MAX / newnmemb < size) { 2110 errno = ENOMEM; 2111 return NULL; 2112 } 2113 newsize = newnmemb * size; 2114 2115 if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 2116 oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { 2117 errno = EINVAL; 2118 return NULL; 2119 } 2120 oldsize = oldnmemb * size; 2121 2122 /* 2123 * Don't bother too much if we're shrinking just a bit, 2124 * we do not shrink for series of small steps, oh well. 2125 */ 2126 if (newsize <= oldsize) { 2127 size_t d = oldsize - newsize; 2128 2129 if (d < oldsize / 2 && d < MALLOC_PAGESIZE) { 2130 memset((char *)ptr + newsize, 0, d); 2131 return ptr; 2132 } 2133 } 2134 2135 newptr = malloc(newsize); 2136 if (newptr == NULL) 2137 return NULL; 2138 2139 if (newsize > oldsize) { 2140 memcpy(newptr, ptr, oldsize); 2141 memset((char *)newptr + oldsize, 0, newsize - oldsize); 2142 } else 2143 memcpy(newptr, ptr, newsize); 2144 2145 explicit_bzero(ptr, oldsize); 2146 free(ptr); 2147 2148 return newptr; 2149} 2150 2151void * 2152recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) 2153{ 2154 struct dir_info *d; 2155 size_t oldsize = 0, newsize; 2156 void *r; 2157 int saved_errno = errno; 2158 2159 if (!mopts.internal_funcs) 2160 return recallocarray_p(ptr, oldnmemb, newnmemb, size); 2161 2162 PROLOGUE(getpool(), "recallocarray") 2163 SET_CALLER(d, caller(d)); 2164 2165 if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 2166 newnmemb > 0 && SIZE_MAX / newnmemb < size) { 2167 d->active--; 2168 _MALLOC_UNLOCK(d->mutex); 2169 if (mopts.malloc_xmalloc) 2170 wrterror(d, "out of memory"); 2171 errno = ENOMEM; 2172 return NULL; 2173 } 2174 newsize = newnmemb * size; 2175 2176 if (ptr != NULL) { 2177 if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && 2178 oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { 2179 d->active--; 2180 _MALLOC_UNLOCK(d->mutex); 2181 errno = EINVAL; 2182 return NULL; 2183 } 2184 oldsize = oldnmemb * size; 2185 } 2186 2187 r = orecallocarray(&d, ptr, oldsize, newsize); 2188 EPILOGUE() 2189 return r; 2190} 2191DEF_WEAK(recallocarray); 2192 2193static void * 2194mapalign(struct dir_info *d, size_t alignment, size_t sz, int zero_fill) 2195{ 2196 char *p, *q; 2197 2198 if (alignment < MALLOC_PAGESIZE || ((alignment - 1) & alignment) != 0) 2199 wrterror(d, "mapalign bad alignment"); 2200 if (sz != PAGEROUND(sz)) 2201 wrterror(d, "mapalign round"); 2202 2203 /* Allocate sz + alignment bytes of memory, which must include a 2204 * subrange of size bytes that is properly aligned. Unmap the 2205 * other bytes, and then return that subrange. 2206 */ 2207 2208 /* We need sz + alignment to fit into a size_t. */ 2209 if (alignment > SIZE_MAX - sz) 2210 return MAP_FAILED; 2211 2212 p = map(d, sz + alignment, zero_fill); 2213 if (p == MAP_FAILED) 2214 return MAP_FAILED; 2215 q = (char *)(((uintptr_t)p + alignment - 1) & ~(alignment - 1)); 2216 if (q != p) { 2217 if (munmap(p, q - p)) 2218 wrterror(d, "munmap %p", p); 2219 } 2220 if (munmap(q + sz, alignment - (q - p))) 2221 wrterror(d, "munmap %p", q + sz); 2222 STATS_SUB(d->malloc_used, alignment); 2223 2224 return q; 2225} 2226 2227static void * 2228omemalign(struct dir_info *pool, size_t alignment, size_t sz, int zero_fill) 2229{ 2230 size_t psz; 2231 void *p, *caller = NULL; 2232 2233 /* If between half a page and a page, avoid MALLOC_MOVE. */ 2234 if (sz > MALLOC_MAXCHUNK && sz < MALLOC_PAGESIZE) 2235 sz = MALLOC_PAGESIZE; 2236 if (alignment <= MALLOC_PAGESIZE) { 2237 size_t pof2; 2238 /* 2239 * max(size, alignment) rounded up to power of 2 is enough 2240 * to assure the requested alignment. Large regions are 2241 * always page aligned. 2242 */ 2243 if (sz < alignment) 2244 sz = alignment; 2245 if (sz < MALLOC_PAGESIZE) { 2246 pof2 = MALLOC_MINSIZE; 2247 while (pof2 < sz) 2248 pof2 <<= 1; 2249 } else 2250 pof2 = sz; 2251 return omalloc(pool, pof2, zero_fill); 2252 } 2253 2254 if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { 2255 errno = ENOMEM; 2256 return NULL; 2257 } 2258 2259 if (sz < MALLOC_PAGESIZE) 2260 sz = MALLOC_PAGESIZE; 2261 sz += mopts.malloc_guard; 2262 psz = PAGEROUND(sz); 2263 2264 p = mapalign(pool, alignment, psz, zero_fill); 2265 if (p == MAP_FAILED) { 2266 errno = ENOMEM; 2267 return NULL; 2268 } 2269 2270#ifdef MALLOC_STATS 2271 if (DO_STATS) 2272 caller = pool->caller; 2273#endif 2274 if (insert(pool, p, sz, caller)) { 2275 unmap(pool, p, psz, 0); 2276 errno = ENOMEM; 2277 return NULL; 2278 } 2279 2280 if (mopts.malloc_guard) { 2281 if (mprotect((char *)p + psz - mopts.malloc_guard, 2282 mopts.malloc_guard, PROT_NONE)) 2283 wrterror(pool, "mprotect"); 2284 STATS_ADD(pool->malloc_guarded, mopts.malloc_guard); 2285 } 2286 2287 if (pool->malloc_junk == 2) { 2288 if (zero_fill) 2289 memset((char *)p + sz - mopts.malloc_guard, 2290 SOME_JUNK, psz - sz); 2291 else 2292 memset(p, SOME_JUNK, psz - mopts.malloc_guard); 2293 } else if (mopts.chunk_canaries) 2294 fill_canary(p, sz - mopts.malloc_guard, 2295 psz - mopts.malloc_guard); 2296 2297 return p; 2298} 2299 2300int 2301posix_memalign(void **memptr, size_t alignment, size_t size) 2302{ 2303 struct dir_info *d; 2304 int res, saved_errno = errno; 2305 void *r; 2306 2307 /* Make sure that alignment is a large enough power of 2. */ 2308 if (((alignment - 1) & alignment) != 0 || alignment < sizeof(void *)) 2309 return EINVAL; 2310 2311 d = getpool(); 2312 if (d == NULL) { 2313 _malloc_init(0); 2314 d = getpool(); 2315 } 2316 _MALLOC_LOCK(d->mutex); 2317 d->func = "posix_memalign"; 2318 if (d->active++) { 2319 malloc_recurse(d); 2320 goto err; 2321 } 2322 SET_CALLER(d, caller(d)); 2323 r = omemalign(d, alignment, size, 0); 2324 d->active--; 2325 _MALLOC_UNLOCK(d->mutex); 2326 if (r == NULL) { 2327 if (mopts.malloc_xmalloc) 2328 wrterror(d, "out of memory"); 2329 goto err; 2330 } 2331 errno = saved_errno; 2332 *memptr = r; 2333 return 0; 2334 2335err: 2336 res = errno; 2337 errno = saved_errno; 2338 return res; 2339} 2340DEF_STRONG(posix_memalign); 2341 2342void * 2343aligned_alloc(size_t alignment, size_t size) 2344{ 2345 struct dir_info *d; 2346 int saved_errno = errno; 2347 void *r; 2348 2349 /* Make sure that alignment is a positive power of 2. */ 2350 if (((alignment - 1) & alignment) != 0 || alignment == 0) { 2351 errno = EINVAL; 2352 return NULL; 2353 } 2354 /* Per spec, size should be a multiple of alignment */ 2355 if ((size & (alignment - 1)) != 0) { 2356 errno = EINVAL; 2357 return NULL; 2358 } 2359 2360 PROLOGUE(getpool(), "aligned_alloc") 2361 SET_CALLER(d, caller(d)); 2362 r = omemalign(d, alignment, size, 0); 2363 EPILOGUE() 2364 return r; 2365} 2366DEF_STRONG(aligned_alloc); 2367 2368#ifdef MALLOC_STATS 2369 2370static int 2371btcmp(const struct btnode *e1, const struct btnode *e2) 2372{ 2373 return memcmp(e1->caller, e2->caller, sizeof(e1->caller)); 2374} 2375 2376RBT_GENERATE(btshead, btnode, entry, btcmp); 2377 2378static void* 2379store_caller(struct dir_info *d, struct btnode *f) 2380{ 2381 struct btnode *p; 2382 2383 if (DO_STATS == 0 || d->btnodes == MAP_FAILED) 2384 return NULL; 2385 2386 p = RBT_FIND(btshead, &d->btraces, f); 2387 if (p != NULL) 2388 return p; 2389 if (d->btnodes == NULL || 2390 d->btnodesused >= MALLOC_PAGESIZE / sizeof(struct btnode)) { 2391 d->btnodes = map(d, MALLOC_PAGESIZE, 0); 2392 if (d->btnodes == MAP_FAILED) 2393 return NULL; 2394 d->btnodesused = 0; 2395 } 2396 p = &d->btnodes[d->btnodesused++]; 2397 memcpy(p->caller, f->caller, sizeof(p->caller[0]) * DO_STATS); 2398 RBT_INSERT(btshead, &d->btraces, p); 2399 return p; 2400} 2401 2402static void fabstorel(const void *, char *, size_t); 2403 2404static void 2405print_chunk_details(struct dir_info *pool, void *p, size_t sz, size_t index) 2406{ 2407 struct region_info *r; 2408 struct chunk_info *chunkinfo; 2409 struct btnode* btnode; 2410 uint32_t chunknum; 2411 int frame; 2412 char buf1[128]; 2413 char buf2[128]; 2414 const char *msg = ""; 2415 2416 r = find(pool, p); 2417 chunkinfo = (struct chunk_info *)r->size; 2418 chunknum = find_chunknum(pool, chunkinfo, p, 0); 2419 btnode = (struct btnode *)r->f[chunknum]; 2420 frame = DO_STATS - 1; 2421 if (btnode != NULL) 2422 fabstorel(btnode->caller[frame], buf1, sizeof(buf1)); 2423 strlcpy(buf2, ". 0x0", sizeof(buf2)); 2424 if (chunknum > 0) { 2425 chunknum--; 2426 btnode = (struct btnode *)r->f[chunknum]; 2427 if (btnode != NULL) 2428 fabstorel(btnode->caller[frame], buf2, sizeof(buf2)); 2429 if (CHUNK_FREE(chunkinfo, chunknum)) 2430 msg = " (now free)"; 2431 } 2432 2433 wrterror(pool, 2434 "write to free chunk %p[%zu..%zu]@%zu allocated at %s " 2435 "(preceding chunk %p allocated at %s%s)", 2436 p, index * sizeof(uint64_t), (index + 1) * sizeof(uint64_t) - 1, 2437 sz, buf1, p - sz, buf2, msg); 2438} 2439 2440static void 2441ulog(const char *format, ...) 2442{ 2443 va_list ap; 2444 static char* buf; 2445 static size_t filled; 2446 int len; 2447 2448 if (buf == NULL) 2449 buf = MMAP(KTR_USER_MAXLEN, 0); 2450 if (buf == MAP_FAILED) 2451 return; 2452 2453 va_start(ap, format); 2454 len = vsnprintf(buf + filled, KTR_USER_MAXLEN - filled, format, ap); 2455 va_end(ap); 2456 if (len < 0) 2457 return; 2458 if ((size_t)len > KTR_USER_MAXLEN - filled) 2459 len = KTR_USER_MAXLEN - filled; 2460 filled += len; 2461 if (filled > 0) { 2462 if (filled == KTR_USER_MAXLEN || buf[filled - 1] == '\n') { 2463 utrace("malloc", buf, filled); 2464 filled = 0; 2465 } 2466 } 2467} 2468 2469struct malloc_leak { 2470 void *f; 2471 size_t total_size; 2472 int count; 2473}; 2474 2475struct leaknode { 2476 RBT_ENTRY(leaknode) entry; 2477 struct malloc_leak d; 2478}; 2479 2480static inline int 2481leakcmp(const struct leaknode *e1, const struct leaknode *e2) 2482{ 2483 return e1->d.f < e2->d.f ? -1 : e1->d.f > e2->d.f; 2484} 2485 2486RBT_HEAD(leaktree, leaknode); 2487RBT_PROTOTYPE(leaktree, leaknode, entry, leakcmp); 2488RBT_GENERATE(leaktree, leaknode, entry, leakcmp); 2489 2490static void 2491wrtwarning(const char *func, char *msg, ...) 2492{ 2493 int saved_errno = errno; 2494 va_list ap; 2495 2496 dprintf(STDERR_FILENO, "%s(%d) in %s(): ", __progname, 2497 getpid(), func != NULL ? func : "unknown"); 2498 va_start(ap, msg); 2499 vdprintf(STDERR_FILENO, msg, ap); 2500 va_end(ap); 2501 dprintf(STDERR_FILENO, "\n"); 2502 2503 errno = saved_errno; 2504} 2505 2506static void 2507putleakinfo(struct leaktree *leaks, void *f, size_t sz, int cnt) 2508{ 2509 struct leaknode key, *p; 2510 static struct leaknode *page; 2511 static unsigned int used; 2512 2513 if (cnt == 0 || page == MAP_FAILED) 2514 return; 2515 2516 key.d.f = f; 2517 p = RBT_FIND(leaktree, leaks, &key); 2518 if (p == NULL) { 2519 if (page == NULL || 2520 used >= MALLOC_PAGESIZE / sizeof(struct leaknode)) { 2521 page = MMAP(MALLOC_PAGESIZE, 0); 2522 if (page == MAP_FAILED) { 2523 wrtwarning(__func__, strerror(errno)); 2524 return; 2525 } 2526 used = 0; 2527 } 2528 p = &page[used++]; 2529 p->d.f = f; 2530 p->d.total_size = sz * cnt; 2531 p->d.count = cnt; 2532 RBT_INSERT(leaktree, leaks, p); 2533 } else { 2534 p->d.total_size += sz * cnt; 2535 p->d.count += cnt; 2536 } 2537} 2538 2539static void 2540fabstorel(const void *f, char *buf, size_t size) 2541{ 2542 Dl_info info; 2543 const char *object = "."; 2544 const char *caller; 2545 2546 caller = f; 2547 if (caller != NULL && dladdr(f, &info) != 0) { 2548 caller -= (uintptr_t)info.dli_fbase; 2549 object = info.dli_fname; 2550 } 2551 snprintf(buf, size, "%s %p", object, caller); 2552} 2553 2554static void 2555dump_leak(struct leaknode *p) 2556{ 2557 int i; 2558 char buf[128]; 2559 2560 if (p->d.f == NULL) { 2561 fabstorel(NULL, buf, sizeof(buf)); 2562 ulog("%18p %7zu %6u %6zu addr2line -e %s\n", 2563 p->d.f, p->d.total_size, p->d.count, 2564 p->d.total_size / p->d.count, buf); 2565 return; 2566 } 2567 2568 for (i = 0; i < DO_STATS; i++) { 2569 const char *abscaller; 2570 2571 abscaller = ((struct btnode*)p->d.f)->caller[i]; 2572 if (abscaller == NULL) 2573 break; 2574 fabstorel(abscaller, buf, sizeof(buf)); 2575 if (i == 0) 2576 ulog("%18p %7zu %6u %6zu addr2line -e %s\n", 2577 abscaller, p->d.total_size, p->d.count, 2578 p->d.total_size / p->d.count, buf); 2579 else 2580 ulog("%*p %*s %6s %6s addr2line -e %s\n", 2581 i + 18, abscaller, 7 - i, "-", "-", "-", buf); 2582 } 2583} 2584 2585static void 2586dump_leaks(struct leaktree *leaks) 2587{ 2588 struct leaknode *p; 2589 2590 ulog("Leak report:\n"); 2591 ulog(" f sum # avg\n"); 2592 2593 RBT_FOREACH(p, leaktree, leaks) 2594 dump_leak(p); 2595} 2596 2597static void 2598dump_chunk(struct leaktree* leaks, struct chunk_info *p, void **f, 2599 int fromfreelist) 2600{ 2601 while (p != NULL) { 2602 if (mopts.malloc_verbose) 2603 ulog("chunk %18p %18p %4zu %d/%d\n", 2604 p->page, NULL, 2605 B2SIZE(p->bucket), p->free, p->total); 2606 if (!fromfreelist) { 2607 size_t i, sz = B2SIZE(p->bucket); 2608 for (i = 0; i < p->total; i++) { 2609 if (!CHUNK_FREE(p, i)) 2610 putleakinfo(leaks, f[i], sz, 1); 2611 } 2612 break; 2613 } 2614 p = LIST_NEXT(p, entries); 2615 if (mopts.malloc_verbose && p != NULL) 2616 ulog(" ->"); 2617 } 2618} 2619 2620static void 2621dump_free_chunk_info(struct dir_info *d, struct leaktree *leaks) 2622{ 2623 u_int i, j, count; 2624 struct chunk_info *p; 2625 2626 ulog("Free chunk structs:\n"); 2627 ulog("Bkt) #CI page" 2628 " f size free/n\n"); 2629 for (i = 0; i <= BUCKETS; i++) { 2630 count = 0; 2631 LIST_FOREACH(p, &d->chunk_info_list[i], entries) 2632 count++; 2633 for (j = 0; j < MALLOC_CHUNK_LISTS; j++) { 2634 p = LIST_FIRST(&d->chunk_dir[i][j]); 2635 if (p == NULL && count == 0) 2636 continue; 2637 if (j == 0) 2638 ulog("%3d) %3d ", i, count); 2639 else 2640 ulog(" "); 2641 if (p != NULL) 2642 dump_chunk(leaks, p, NULL, 1); 2643 else 2644 ulog(".\n"); 2645 } 2646 } 2647 2648} 2649 2650static void 2651dump_free_page_info(struct dir_info *d) 2652{ 2653 struct smallcache *cache; 2654 size_t i, total = 0; 2655 2656 ulog("Cached in small cache:\n"); 2657 for (i = 0; i < MAX_SMALLCACHEABLE_SIZE; i++) { 2658 cache = &d->smallcache[i]; 2659 if (cache->length != 0) 2660 ulog("%zu(%u): %u = %zu\n", i + 1, cache->max, 2661 cache->length, cache->length * (i + 1)); 2662 total += cache->length * (i + 1); 2663 } 2664 2665 ulog("Cached in big cache: %zu/%zu\n", d->bigcache_used, 2666 d->bigcache_size); 2667 for (i = 0; i < d->bigcache_size; i++) { 2668 if (d->bigcache[i].psize != 0) 2669 ulog("%zu: %zu\n", i, d->bigcache[i].psize); 2670 total += d->bigcache[i].psize; 2671 } 2672 ulog("Free pages cached: %zu\n", total); 2673} 2674 2675static void 2676malloc_dump1(int poolno, struct dir_info *d, struct leaktree *leaks) 2677{ 2678 size_t i, realsize; 2679 2680 if (mopts.malloc_verbose) { 2681 ulog("Malloc dir of %s pool %d at %p\n", __progname, poolno, d); 2682 ulog("MT=%d J=%d Fl=%#x\n", d->malloc_mt, d->malloc_junk, 2683 d->mmap_flag); 2684 ulog("Region slots free %zu/%zu\n", 2685 d->regions_free, d->regions_total); 2686 ulog("Inserts %zu/%zu\n", d->inserts, d->insert_collisions); 2687 ulog("Deletes %zu/%zu\n", d->deletes, d->delete_moves); 2688 ulog("Cheap reallocs %zu/%zu\n", 2689 d->cheap_reallocs, d->cheap_realloc_tries); 2690 ulog("In use %zu\n", d->malloc_used); 2691 ulog("Guarded %zu\n", d->malloc_guarded); 2692 dump_free_chunk_info(d, leaks); 2693 dump_free_page_info(d); 2694 ulog("Hash table:\n"); 2695 ulog("slot) hash d type page " 2696 "f size [free/n]\n"); 2697 } 2698 for (i = 0; i < d->regions_total; i++) { 2699 if (d->r[i].p != NULL) { 2700 size_t h = hash(d->r[i].p) & 2701 (d->regions_total - 1); 2702 if (mopts.malloc_verbose) 2703 ulog("%4zx) #%4zx %zd ", 2704 i, h, h - i); 2705 REALSIZE(realsize, &d->r[i]); 2706 if (realsize > MALLOC_MAXCHUNK) { 2707 putleakinfo(leaks, d->r[i].f, realsize, 1); 2708 if (mopts.malloc_verbose) 2709 ulog("pages %18p %18p %zu\n", d->r[i].p, 2710 d->r[i].f, realsize); 2711 } else 2712 dump_chunk(leaks, 2713 (struct chunk_info *)d->r[i].size, 2714 d->r[i].f, 0); 2715 } 2716 } 2717 if (mopts.malloc_verbose) 2718 ulog("\n"); 2719} 2720 2721static void 2722malloc_dump0(int poolno, struct dir_info *pool, struct leaktree *leaks) 2723{ 2724 int i; 2725 void *p; 2726 struct region_info *r; 2727 2728 if (pool == NULL || pool->r == NULL) 2729 return; 2730 for (i = 0; i < MALLOC_DELAYED_CHUNK_MASK + 1; i++) { 2731 p = pool->delayed_chunks[i]; 2732 if (p == NULL) 2733 continue; 2734 r = find(pool, p); 2735 if (r == NULL) 2736 wrterror(pool, "bogus pointer in malloc_dump %p", p); 2737 free_bytes(pool, r, p); 2738 pool->delayed_chunks[i] = NULL; 2739 } 2740 malloc_dump1(poolno, pool, leaks); 2741} 2742 2743void 2744malloc_dump(void) 2745{ 2746 u_int i; 2747 int saved_errno = errno; 2748 2749 /* XXX leak when run multiple times */ 2750 struct leaktree leaks = RBT_INITIALIZER(&leaks); 2751 2752 for (i = 0; i < mopts.malloc_mutexes; i++) 2753 malloc_dump0(i, mopts.malloc_pool[i], &leaks); 2754 2755 dump_leaks(&leaks); 2756 ulog("\n"); 2757 errno = saved_errno; 2758} 2759DEF_WEAK(malloc_dump); 2760 2761static void 2762malloc_exit(void) 2763{ 2764 int save_errno = errno; 2765 2766 ulog("******** Start dump %s *******\n", __progname); 2767 ulog("M=%u I=%d F=%d U=%d J=%d R=%d X=%d C=%#x cache=%u " 2768 "G=%zu\n", 2769 mopts.malloc_mutexes, 2770 mopts.internal_funcs, mopts.malloc_freecheck, 2771 mopts.malloc_freeunmap, mopts.def_malloc_junk, 2772 mopts.malloc_realloc, mopts.malloc_xmalloc, 2773 mopts.chunk_canaries, mopts.def_maxcache, 2774 mopts.malloc_guard); 2775 2776 malloc_dump(); 2777 ulog("******** End dump %s *******\n", __progname); 2778 errno = save_errno; 2779} 2780 2781#endif /* MALLOC_STATS */ 2782