1 /* $NetBSD: mem.c,v 1.20 2026/05/20 16:53:46 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file */ 17 18 #include <inttypes.h> 19 #include <limits.h> 20 #include <stdbool.h> 21 #include <stddef.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <unistd.h> 25 26 #include <isc/backtrace.h> 27 #include <isc/hash.h> 28 #include <isc/magic.h> 29 #include <isc/mem.h> 30 #include <isc/mutex.h> 31 #include <isc/once.h> 32 #include <isc/os.h> 33 #include <isc/overflow.h> 34 #include <isc/random.h> 35 #include <isc/refcount.h> 36 #include <isc/strerr.h> 37 #include <isc/string.h> 38 #include <isc/types.h> 39 #include <isc/urcu.h> 40 #include <isc/util.h> 41 42 #ifdef HAVE_LIBXML2 43 #include <libxml/xmlwriter.h> 44 #define ISC_XMLCHAR (const xmlChar *) 45 #endif /* HAVE_LIBXML2 */ 46 47 #ifdef HAVE_JSON_C 48 #include <json_object.h> 49 #endif /* HAVE_JSON_C */ 50 51 /* On DragonFly BSD the header does not provide jemalloc API */ 52 #if defined(HAVE_MALLOC_NP_H) && !defined(__DragonFly__) 53 #include <malloc_np.h> 54 #define JEMALLOC_API_SUPPORTED 1 55 #elif defined(HAVE_JEMALLOC) 56 #include <jemalloc/jemalloc.h> 57 #define JEMALLOC_API_SUPPORTED 1 58 #else 59 #include "jemalloc_shim.h" 60 #endif 61 62 #include "mem_p.h" 63 64 #define MCTXLOCK(m) LOCK(&m->lock) 65 #define MCTXUNLOCK(m) UNLOCK(&m->lock) 66 67 #ifndef ISC_MEM_DEBUGGING 68 #define ISC_MEM_DEBUGGING 0 69 #endif /* ifndef ISC_MEM_DEBUGGING */ 70 unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; 71 unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT; 72 73 #define ISC_MEM_ILLEGAL_ARENA (UINT_MAX) 74 75 volatile void *isc__mem_malloc = mallocx; 76 77 /* 78 * Constants. 79 */ 80 81 #define ZERO_ALLOCATION_SIZE sizeof(void *) 82 #define ALIGNMENT 8U /*%< must be a power of 2 */ 83 #define ALIGNMENT_SIZE sizeof(size_info) 84 #define DEBUG_TABLE_COUNT 512U 85 86 /* 87 * Types. 88 */ 89 #if ISC_MEM_TRACKLINES 90 typedef struct debuglink debuglink_t; 91 struct debuglink { 92 size_t dlsize; 93 ISC_LINK(debuglink_t) link; 94 const void *ptr; 95 size_t size; 96 unsigned int line; 97 const char file[]; 98 }; 99 100 typedef ISC_LIST(debuglink_t) debuglist_t; 101 102 #define FLARG_PASS , file, line 103 #define FLARG , const char *file, unsigned int line 104 #else /* if ISC_MEM_TRACKLINES */ 105 #define FLARG_PASS 106 #define FLARG 107 #endif /* if ISC_MEM_TRACKLINES */ 108 109 typedef struct element element; 110 struct element { 111 element *next; 112 }; 113 114 #define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C') 115 #define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC) 116 117 /* List of all active memory contexts. */ 118 119 static ISC_LIST(isc_mem_t) contexts; 120 121 static isc_once_t init_once = ISC_ONCE_INIT; 122 static isc_once_t shut_once = ISC_ONCE_INIT; 123 static isc_mutex_t contextslock; 124 125 struct isc_mem { 126 unsigned int magic; 127 unsigned int flags; 128 unsigned int jemalloc_flags; 129 unsigned int jemalloc_arena; 130 unsigned int debugging; 131 isc_mutex_t lock; 132 bool checkfree; 133 isc_refcount_t references; 134 char name[16]; 135 atomic_size_t inuse; 136 atomic_bool hi_called; 137 atomic_size_t hi_water; 138 atomic_size_t lo_water; 139 ISC_LIST(isc_mempool_t) pools; 140 unsigned int poolcnt; 141 142 #if ISC_MEM_TRACKLINES 143 debuglist_t *debuglist; 144 size_t debuglistcnt; 145 #endif /* if ISC_MEM_TRACKLINES */ 146 147 ISC_LINK(isc_mem_t) link; 148 }; 149 150 #define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p') 151 #define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC) 152 153 struct isc_mempool { 154 /* always unlocked */ 155 unsigned int magic; 156 isc_mem_t *mctx; /*%< our memory context */ 157 ISC_LINK(isc_mempool_t) link; /*%< next pool in this mem context */ 158 element *items; /*%< low water item list */ 159 size_t size; /*%< size of each item on this pool */ 160 size_t allocated; /*%< # of items currently given out */ 161 size_t freecount; /*%< # of items on reserved list */ 162 size_t freemax; /*%< # of items allowed on free list */ 163 size_t fillcount; /*%< # of items to fetch on each fill */ 164 /*%< Stats only. */ 165 size_t gets; /*%< # of requests to this pool */ 166 /*%< Debugging only. */ 167 char name[16]; /*%< printed name in stats reports */ 168 }; 169 170 /* 171 * Private Inline-able. 172 */ 173 174 static size_t 175 total_inuse(void) { 176 size_t inuse = 0; 177 LOCK(&contextslock); 178 for (isc_mem_t *ctx = ISC_LIST_HEAD(contexts); ctx != NULL; 179 ctx = ISC_LIST_NEXT(ctx, link)) 180 { 181 inuse += isc_mem_inuse(ctx); 182 } 183 UNLOCK(&contextslock); 184 185 return inuse; 186 } 187 188 static void 189 write_string(int fd, const char *str) { 190 int r = write(fd, str, strlen(str)); 191 if (r == -1) { 192 abort(); 193 } 194 } 195 196 #define STRINGIFY(x) #x 197 #define TOSTRING(x) STRINGIFY(x) 198 static void 199 write_size(int fd, size_t size) { 200 char buf[sizeof(TOSTRING(SIZE_MAX)) + 1] = { 0 }; 201 202 char *str = buf + (sizeof(buf) - 1); 203 204 if (size == 0) { 205 *--str = '0'; 206 } else { 207 while (size) { 208 *--str = '0' + (size % 10); 209 size /= 10; 210 } 211 } 212 213 write_string(fd, str); 214 } 215 #undef TOSTRING 216 #undef STRINGIFY 217 218 static void 219 write_errno(int fd, int errnum) { 220 char buf[BUFSIZ] = { 0 }; 221 int ret = isc_string_strerror_r(errnum, buf, sizeof(buf)); 222 if (ret == 0) { 223 write_string(fd, buf); 224 } 225 } 226 227 static void 228 write_backtrace(int fd) { 229 void *tracebuf[ISC_BACKTRACE_MAXFRAME]; 230 int nframes = isc_backtrace(tracebuf, ISC_BACKTRACE_MAXFRAME); 231 232 if (nframes > 0) { 233 isc_backtrace_symbols_fd(tracebuf, nframes, fd); 234 } 235 } 236 237 #define CHECK_OOM(ptr, size) (void)((ptr != NULL) || (oom(size), false)) 238 239 static void 240 oom(size_t size) { 241 int fd = fileno(stderr); 242 write_string(fd, "Out of memory (trying to allocate "); 243 write_size(fd, size); 244 write_string(fd, ", total "); 245 write_size(fd, total_inuse()); 246 write_string(fd, "): "); 247 write_errno(fd, errno); 248 write_string(fd, "\n"); 249 write_backtrace(fd); 250 251 abort(); 252 } 253 254 #if !ISC_MEM_TRACKLINES 255 #define ADD_TRACE(mctx, ptr, size, file, line) 256 #define DELETE_TRACE(mctx, ptr, size, file, line) 257 #define ISC_MEMFUNC_SCOPE 258 #else /* if !ISC_MEM_TRACKLINES */ 259 #define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE | ISC_MEM_DEBUGRECORD) 260 261 #define SHOULD_TRACE_OR_RECORD(mctx, ptr) \ 262 (((mctx)->debugging & TRACE_OR_RECORD) != 0 && ptr != NULL) 263 264 #define ADD_TRACE(mctx, ptr, size, file, line) \ 265 if (SHOULD_TRACE_OR_RECORD(mctx, ptr)) { \ 266 add_trace_entry(mctx, ptr, size, file, line); \ 267 } 268 269 #define DELETE_TRACE(mctx, ptr, size, file, line) \ 270 if (SHOULD_TRACE_OR_RECORD(mctx, ptr)) { \ 271 delete_trace_entry(mctx, ptr, size, file, line); \ 272 } 273 274 static void 275 print_active(isc_mem_t *ctx, FILE *out); 276 #endif /* ISC_MEM_TRACKLINES */ 277 278 #if ISC_MEM_TRACKLINES 279 /*! 280 * mctx must not be locked. 281 */ 282 static void 283 add_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size FLARG) { 284 debuglink_t *dl = NULL; 285 uint32_t hash; 286 uint32_t idx; 287 288 /* 289 * "file" needs to be copied because it can be part of a dynamically 290 * loaded plugin which would be unloaded at the time the trace is 291 * dumped. Storing "file" pointer then leads to a dangling pointer 292 * dereference and a crash. 293 */ 294 size_t filelen = strlen(file) + 1; 295 size_t dlsize = STRUCT_FLEX_SIZE(dl, file, filelen); 296 297 MCTXLOCK(mctx); 298 299 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 300 fprintf(stderr, "add %p size %zu file %s line %u mctx %p\n", 301 ptr, size, file, line, mctx); 302 } 303 304 if (mctx->debuglist == NULL) { 305 goto unlock; 306 } 307 308 #ifdef __COVERITY__ 309 /* 310 * Use simple conversion from pointer to hash to avoid 311 * tainting 'ptr' due to byte swap in isc_hash32. 312 */ 313 hash = (uintptr_t)ptr >> 3; 314 #else 315 hash = isc_hash32(&ptr, sizeof(ptr), true); 316 #endif 317 idx = hash % DEBUG_TABLE_COUNT; 318 319 dl = mallocx(dlsize, mctx->jemalloc_flags); 320 CHECK_OOM(dl, dlsize); 321 322 ISC_LINK_INIT(dl, link); 323 dl->ptr = ptr; 324 dl->size = size; 325 dl->line = line; 326 dl->dlsize = dlsize; 327 strlcpy((char *)dl->file, file, filelen); 328 329 ISC_LIST_PREPEND(mctx->debuglist[idx], dl, link); 330 mctx->debuglistcnt++; 331 unlock: 332 MCTXUNLOCK(mctx); 333 } 334 335 static void 336 delete_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size, 337 const char *file, unsigned int line) { 338 debuglink_t *dl = NULL; 339 uint32_t hash; 340 uint32_t idx; 341 342 MCTXLOCK(mctx); 343 344 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 345 fprintf(stderr, "del %p size %zu file %s line %u mctx %p\n", 346 ptr, size, file, line, mctx); 347 } 348 349 if (mctx->debuglist == NULL) { 350 goto unlock; 351 } 352 353 #ifdef __COVERITY__ 354 /* 355 * Use simple conversion from pointer to hash to avoid 356 * tainting 'ptr' due to byte swap in isc_hash32. 357 */ 358 hash = (uintptr_t)ptr >> 3; 359 #else 360 hash = isc_hash32(&ptr, sizeof(ptr), true); 361 #endif 362 idx = hash % DEBUG_TABLE_COUNT; 363 364 dl = ISC_LIST_HEAD(mctx->debuglist[idx]); 365 while (dl != NULL) { 366 if (dl->ptr == ptr) { 367 ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link); 368 sdallocx(dl, dl->dlsize, mctx->jemalloc_flags); 369 goto unlock; 370 } 371 dl = ISC_LIST_NEXT(dl, link); 372 } 373 374 /* 375 * If we get here, we didn't find the item on the list. We're 376 * screwed. 377 */ 378 UNREACHABLE(); 379 unlock: 380 MCTXUNLOCK(mctx); 381 } 382 #endif /* ISC_MEM_TRACKLINES */ 383 384 #define ADJUST_ZERO_ALLOCATION_SIZE(s) \ 385 if (s == 0) { \ 386 s = ZERO_ALLOCATION_SIZE; \ 387 } 388 389 /*! 390 * Perform a malloc, doing memory filling and overrun detection as necessary. 391 */ 392 static void * 393 mem_get(isc_mem_t *ctx, size_t size, int flags) { 394 ADJUST_ZERO_ALLOCATION_SIZE(size); 395 396 void *ptr = mallocx(size, flags | ctx->jemalloc_flags); 397 CHECK_OOM(ptr, size); 398 399 if ((flags & ISC__MEM_ZERO) == 0 && 400 (ctx->flags & ISC_MEMFLAG_FILL) != 0) 401 { 402 memset(ptr, 0xbe, size); /* Mnemonic for "beef". */ 403 } 404 405 return ptr; 406 } 407 408 /*! 409 * Perform a free, doing memory filling and overrun detection as necessary. 410 */ 411 /* coverity[+free : arg-1] */ 412 static void 413 mem_put(isc_mem_t *ctx, void *mem, size_t size, int flags) { 414 ADJUST_ZERO_ALLOCATION_SIZE(size); 415 416 if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) { 417 memset(mem, 0xde, size); /* Mnemonic for "dead". */ 418 } 419 sdallocx(mem, size, flags | ctx->jemalloc_flags); 420 } 421 422 static void * 423 mem_realloc(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size, 424 int flags) { 425 void *new_ptr = NULL; 426 427 ADJUST_ZERO_ALLOCATION_SIZE(new_size); 428 429 new_ptr = rallocx(old_ptr, new_size, flags | ctx->jemalloc_flags); 430 CHECK_OOM(new_ptr, new_size); 431 432 if ((flags & ISC__MEM_ZERO) == 0 && 433 (ctx->flags & ISC_MEMFLAG_FILL) != 0) 434 { 435 ssize_t diff_size = new_size - old_size; 436 void *diff_ptr = (uint8_t *)new_ptr + old_size; 437 if (diff_size > 0) { 438 /* Mnemonic for "beef". */ 439 memset(diff_ptr, 0xbe, diff_size); 440 } 441 } 442 443 return new_ptr; 444 } 445 446 /*! 447 * Update internal counters after a memory get. 448 */ 449 static void 450 mem_getstats(isc_mem_t *ctx, size_t size) { 451 atomic_fetch_add_relaxed(&ctx->inuse, size); 452 } 453 454 /*! 455 * Update internal counters after a memory put. 456 */ 457 static void 458 mem_putstats(isc_mem_t *ctx, size_t size) { 459 atomic_size_t s = atomic_fetch_sub_relaxed(&ctx->inuse, size); 460 INSIST(s >= size); 461 } 462 463 /* 464 * Private. 465 */ 466 467 static bool 468 mem_jemalloc_arena_create(unsigned int *pnew_arenano) { 469 REQUIRE(pnew_arenano != NULL); 470 471 #ifdef JEMALLOC_API_SUPPORTED 472 unsigned int arenano = 0; 473 size_t len = sizeof(arenano); 474 int res = 0; 475 476 res = mallctl("arenas.create", &arenano, &len, NULL, 0); 477 if (res != 0) { 478 return false; 479 } 480 481 *pnew_arenano = arenano; 482 483 return true; 484 #else 485 *pnew_arenano = ISC_MEM_ILLEGAL_ARENA; 486 return true; 487 #endif /* JEMALLOC_API_SUPPORTED */ 488 } 489 490 static bool 491 mem_jemalloc_arena_destroy(unsigned int arenano) { 492 #ifdef JEMALLOC_API_SUPPORTED 493 int res = 0; 494 char buf[256] = { 0 }; 495 496 (void)snprintf(buf, sizeof(buf), "arena.%u.destroy", arenano); 497 res = mallctl(buf, NULL, NULL, NULL, 0); 498 if (res != 0) { 499 return false; 500 } 501 502 return true; 503 #else 504 UNUSED(arenano); 505 return true; 506 #endif /* JEMALLOC_API_SUPPORTED */ 507 } 508 509 static void 510 mem_initialize(void) { 511 /* 512 * Check if the values copied from jemalloc still match 513 */ 514 #ifdef JEMALLOC_API_SUPPORTED 515 RUNTIME_CHECK(ISC__MEM_ZERO == MALLOCX_ZERO); 516 #endif /* JEMALLOC_API_SUPPORTED */ 517 518 isc_mutex_init(&contextslock); 519 ISC_LIST_INIT(contexts); 520 } 521 522 void 523 isc__mem_initialize(void) { 524 isc_once_do(&init_once, mem_initialize); 525 } 526 527 static void 528 mem_shutdown(void) { 529 bool empty; 530 531 rcu_barrier(); 532 533 isc__mem_checkdestroyed(); 534 535 LOCK(&contextslock); 536 empty = ISC_LIST_EMPTY(contexts); 537 UNLOCK(&contextslock); 538 539 if (empty) { 540 isc_mutex_destroy(&contextslock); 541 } 542 } 543 544 void 545 isc__mem_shutdown(void) { 546 isc_once_do(&shut_once, mem_shutdown); 547 } 548 549 static void 550 mem_create(isc_mem_t **ctxp, unsigned int debugging, unsigned int flags, 551 unsigned int jemalloc_flags) { 552 isc_mem_t *ctx = NULL; 553 554 REQUIRE(ctxp != NULL && *ctxp == NULL); 555 556 ctx = mallocx(sizeof(*ctx), jemalloc_flags); 557 CHECK_OOM(ctx, sizeof(*ctx)); 558 559 *ctx = (isc_mem_t){ 560 .magic = MEM_MAGIC, 561 .debugging = debugging, 562 .flags = flags, 563 .jemalloc_flags = jemalloc_flags, 564 .jemalloc_arena = ISC_MEM_ILLEGAL_ARENA, 565 .checkfree = true, 566 }; 567 568 isc_mutex_init(&ctx->lock); 569 isc_refcount_init(&ctx->references, 1); 570 571 atomic_init(&ctx->inuse, 0); 572 atomic_init(&ctx->hi_water, 0); 573 atomic_init(&ctx->lo_water, 0); 574 atomic_init(&ctx->hi_called, false); 575 576 ISC_LIST_INIT(ctx->pools); 577 578 #if ISC_MEM_TRACKLINES 579 if ((ctx->debugging & ISC_MEM_DEBUGRECORD) != 0) { 580 unsigned int i; 581 size_t debuglist_size = ISC_CHECKED_MUL(DEBUG_TABLE_COUNT, 582 sizeof(debuglist_t)); 583 584 ctx->debuglist = mallocx(debuglist_size, jemalloc_flags); 585 CHECK_OOM(ctx->debuglist, debuglist_size); 586 587 for (i = 0; i < DEBUG_TABLE_COUNT; i++) { 588 ISC_LIST_INIT(ctx->debuglist[i]); 589 } 590 } 591 #endif /* if ISC_MEM_TRACKLINES */ 592 593 LOCK(&contextslock); 594 ISC_LIST_INITANDAPPEND(contexts, ctx, link); 595 UNLOCK(&contextslock); 596 597 *ctxp = ctx; 598 } 599 600 /* 601 * Public. 602 */ 603 604 static void 605 destroy(isc_mem_t *ctx) { 606 unsigned int arena_no; 607 LOCK(&contextslock); 608 ISC_LIST_UNLINK(contexts, ctx, link); 609 UNLOCK(&contextslock); 610 611 ctx->magic = 0; 612 613 arena_no = ctx->jemalloc_arena; 614 615 INSIST(ISC_LIST_EMPTY(ctx->pools)); 616 617 #if ISC_MEM_TRACKLINES 618 if (ctx->debuglist != NULL) { 619 debuglink_t *dl; 620 for (size_t i = 0; i < DEBUG_TABLE_COUNT; i++) { 621 for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL; 622 dl = ISC_LIST_HEAD(ctx->debuglist[i])) 623 { 624 if (ctx->checkfree && dl->ptr != NULL) { 625 print_active(ctx, stderr); 626 } 627 INSIST(!ctx->checkfree || dl->ptr == NULL); 628 629 ISC_LIST_UNLINK(ctx->debuglist[i], dl, link); 630 sdallocx(dl, sizeof(*dl), ctx->jemalloc_flags); 631 } 632 } 633 634 sdallocx( 635 ctx->debuglist, 636 ISC_CHECKED_MUL(DEBUG_TABLE_COUNT, sizeof(debuglist_t)), 637 ctx->jemalloc_flags); 638 } 639 #endif /* if ISC_MEM_TRACKLINES */ 640 641 isc_mutex_destroy(&ctx->lock); 642 643 if (ctx->checkfree) { 644 INSIST(atomic_load(&ctx->inuse) == 0); 645 } 646 sdallocx(ctx, sizeof(*ctx), ctx->jemalloc_flags); 647 648 if (arena_no != ISC_MEM_ILLEGAL_ARENA) { 649 RUNTIME_CHECK(mem_jemalloc_arena_destroy(arena_no) == true); 650 } 651 } 652 653 void 654 isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) { 655 REQUIRE(VALID_CONTEXT(source)); 656 REQUIRE(targetp != NULL && *targetp == NULL); 657 658 isc_refcount_increment(&source->references); 659 660 *targetp = source; 661 } 662 663 void 664 isc__mem_detach(isc_mem_t **ctxp FLARG) { 665 isc_mem_t *ctx = NULL; 666 667 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); 668 669 ctx = *ctxp; 670 *ctxp = NULL; 671 672 if (isc_refcount_decrement(&ctx->references) == 1) { 673 isc_refcount_destroy(&ctx->references); 674 #if ISC_MEM_TRACKLINES 675 if ((ctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 676 fprintf(stderr, "destroy mctx %p file %s line %u\n", 677 ctx, file, line); 678 } 679 #endif 680 destroy(ctx); 681 } 682 } 683 684 /* 685 * isc_mem_putanddetach() is the equivalent of: 686 * 687 * mctx = NULL; 688 * isc_mem_attach(ptr->mctx, &mctx); 689 * isc_mem_detach(&ptr->mctx); 690 * isc_mem_put(mctx, ptr, sizeof(*ptr); 691 * isc_mem_detach(&mctx); 692 */ 693 694 void 695 isc__mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size, 696 int flags FLARG) { 697 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); 698 REQUIRE(ptr != NULL); 699 REQUIRE(size != 0); 700 701 isc_mem_t *ctx = *ctxp; 702 *ctxp = NULL; 703 704 isc__mem_put(ctx, ptr, size, flags FLARG_PASS); 705 isc__mem_detach(&ctx FLARG_PASS); 706 } 707 708 void 709 isc__mem_destroy(isc_mem_t **ctxp FLARG) { 710 isc_mem_t *ctx = NULL; 711 712 /* 713 * This routine provides legacy support for callers who use mctxs 714 * without attaching/detaching. 715 */ 716 717 REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); 718 719 ctx = *ctxp; 720 *ctxp = NULL; 721 722 rcu_barrier(); 723 724 #if ISC_MEM_TRACKLINES 725 if ((ctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 726 fprintf(stderr, "destroy mctx %p file %s line %u\n", ctx, file, 727 line); 728 } 729 730 if (isc_refcount_decrement(&ctx->references) > 1) { 731 print_active(ctx, stderr); 732 } 733 #else /* if ISC_MEM_TRACKLINES */ 734 isc_refcount_decrementz(&ctx->references); 735 #endif /* if ISC_MEM_TRACKLINES */ 736 isc_refcount_destroy(&ctx->references); 737 destroy(ctx); 738 739 *ctxp = NULL; 740 } 741 742 void * 743 isc__mem_get(isc_mem_t *ctx, size_t size, int flags FLARG) { 744 void *ptr = NULL; 745 746 REQUIRE(VALID_CONTEXT(ctx)); 747 748 ptr = mem_get(ctx, size, flags); 749 750 mem_getstats(ctx, size); 751 ADD_TRACE(ctx, ptr, size, file, line); 752 753 return ptr; 754 } 755 756 void 757 isc__mem_put(isc_mem_t *ctx, void *ptr, size_t size, int flags FLARG) { 758 REQUIRE(VALID_CONTEXT(ctx)); 759 760 DELETE_TRACE(ctx, ptr, size, file, line); 761 762 mem_putstats(ctx, size); 763 mem_put(ctx, ptr, size, flags); 764 } 765 766 #if ISC_MEM_TRACKLINES 767 static void 768 print_active(isc_mem_t *mctx, FILE *out) { 769 if (mctx->debuglist != NULL) { 770 debuglink_t *dl; 771 unsigned int i; 772 bool found; 773 774 fprintf(out, "Dump of all outstanding memory " 775 "allocations:\n"); 776 found = false; 777 for (i = 0; i < DEBUG_TABLE_COUNT; i++) { 778 dl = ISC_LIST_HEAD(mctx->debuglist[i]); 779 780 if (dl != NULL) { 781 found = true; 782 } 783 784 while (dl != NULL) { 785 if (dl->ptr != NULL) { 786 fprintf(out, 787 "\tptr %p size %zu " 788 "file %s " 789 "line %u\n", 790 dl->ptr, dl->size, dl->file, 791 dl->line); 792 } 793 dl = ISC_LIST_NEXT(dl, link); 794 } 795 } 796 797 if (!found) { 798 fprintf(out, "\tNone.\n"); 799 } 800 } 801 } 802 #endif /* if ISC_MEM_TRACKLINES */ 803 804 /* 805 * Print the stats[] on the stream "out" with suitable formatting. 806 */ 807 void 808 isc_mem_stats(isc_mem_t *ctx, FILE *out) { 809 isc_mempool_t *pool = NULL; 810 811 REQUIRE(VALID_CONTEXT(ctx)); 812 813 MCTXLOCK(ctx); 814 815 /* 816 * Note that since a pool can be locked now, these stats might 817 * be somewhat off if the pool is in active use at the time the 818 * stats are dumped. The link fields are protected by the 819 * isc_mem_t's lock, however, so walking this list and 820 * extracting integers from stats fields is always safe. 821 */ 822 pool = ISC_LIST_HEAD(ctx->pools); 823 if (pool != NULL) { 824 fprintf(out, "[Pool statistics]\n"); 825 fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %1s\n", "name", 826 "size", "allocated", "freecount", "freemax", 827 "fillcount", "gets", "L"); 828 } 829 while (pool != NULL) { 830 fprintf(out, 831 "%15s %10zu %10zu %10zu %10zu %10zu %10zu %10zu %s\n", 832 pool->name, pool->size, (size_t)0, pool->allocated, 833 pool->freecount, pool->freemax, pool->fillcount, 834 pool->gets, "N"); 835 pool = ISC_LIST_NEXT(pool, link); 836 } 837 838 #if ISC_MEM_TRACKLINES 839 print_active(ctx, out); 840 #endif /* if ISC_MEM_TRACKLINES */ 841 842 MCTXUNLOCK(ctx); 843 } 844 845 void * 846 isc__mem_allocate(isc_mem_t *ctx, size_t size, int flags FLARG) { 847 void *ptr = NULL; 848 849 REQUIRE(VALID_CONTEXT(ctx)); 850 851 ptr = mem_get(ctx, size, flags); 852 853 /* Recalculate the real allocated size */ 854 size = sallocx(ptr, flags | ctx->jemalloc_flags); 855 856 mem_getstats(ctx, size); 857 ADD_TRACE(ctx, ptr, size, file, line); 858 859 return ptr; 860 } 861 862 void * 863 isc__mem_reget(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size, 864 int flags FLARG) { 865 void *new_ptr = NULL; 866 867 if (old_ptr == NULL) { 868 REQUIRE(old_size == 0); 869 new_ptr = isc__mem_get(ctx, new_size, flags FLARG_PASS); 870 } else if (new_size == 0) { 871 isc__mem_put(ctx, old_ptr, old_size, flags FLARG_PASS); 872 } else { 873 DELETE_TRACE(ctx, old_ptr, old_size, file, line); 874 mem_putstats(ctx, old_size); 875 876 new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, flags); 877 878 mem_getstats(ctx, new_size); 879 ADD_TRACE(ctx, new_ptr, new_size, file, line); 880 881 /* 882 * We want to postpone the call to water in edge case 883 * where the realloc will exactly hit on the boundary of 884 * the water and we would call water twice. 885 */ 886 } 887 888 return new_ptr; 889 } 890 891 void * 892 isc__mem_reallocate(isc_mem_t *ctx, void *old_ptr, size_t new_size, 893 int flags FLARG) { 894 void *new_ptr = NULL; 895 896 REQUIRE(VALID_CONTEXT(ctx)); 897 898 if (old_ptr == NULL) { 899 new_ptr = isc__mem_allocate(ctx, new_size, flags FLARG_PASS); 900 } else if (new_size == 0) { 901 isc__mem_free(ctx, old_ptr, flags FLARG_PASS); 902 } else { 903 size_t old_size = sallocx(old_ptr, flags | ctx->jemalloc_flags); 904 905 DELETE_TRACE(ctx, old_ptr, old_size, file, line); 906 mem_putstats(ctx, old_size); 907 908 new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, flags); 909 910 /* Recalculate the real allocated size */ 911 new_size = sallocx(new_ptr, flags | ctx->jemalloc_flags); 912 913 mem_getstats(ctx, new_size); 914 ADD_TRACE(ctx, new_ptr, new_size, file, line); 915 916 /* 917 * We want to postpone the call to water in edge case 918 * where the realloc will exactly hit on the boundary of 919 * the water and we would call water twice. 920 */ 921 } 922 923 return new_ptr; 924 } 925 926 void 927 isc__mem_free(isc_mem_t *ctx, void *ptr, int flags FLARG) { 928 size_t size = 0; 929 930 REQUIRE(VALID_CONTEXT(ctx)); 931 REQUIRE(ptr != NULL); 932 933 size = sallocx(ptr, flags | ctx->jemalloc_flags); 934 935 DELETE_TRACE(ctx, ptr, size, file, line); 936 937 mem_putstats(ctx, size); 938 mem_put(ctx, ptr, size, flags); 939 } 940 941 /* 942 * Other useful things. 943 */ 944 945 char * 946 isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { 947 size_t len; 948 char *ns = NULL; 949 950 REQUIRE(VALID_CONTEXT(mctx)); 951 REQUIRE(s != NULL); 952 953 len = strlen(s) + 1; 954 955 ns = isc__mem_allocate(mctx, len, 0 FLARG_PASS); 956 957 strlcpy(ns, s, len); 958 959 return ns; 960 } 961 962 char * 963 isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) { 964 size_t len; 965 char *ns = NULL; 966 967 REQUIRE(VALID_CONTEXT(mctx)); 968 REQUIRE(s != NULL); 969 REQUIRE(size != 0); 970 971 len = strlen(s) + 1; 972 if (len > size) { 973 len = size; 974 } 975 976 ns = isc__mem_allocate(mctx, len, 0 FLARG_PASS); 977 978 strlcpy(ns, s, len); 979 980 return ns; 981 } 982 983 void 984 isc_mem_setdestroycheck(isc_mem_t *ctx, bool flag) { 985 REQUIRE(VALID_CONTEXT(ctx)); 986 987 MCTXLOCK(ctx); 988 989 ctx->checkfree = flag; 990 991 MCTXUNLOCK(ctx); 992 } 993 994 size_t 995 isc_mem_inuse(isc_mem_t *ctx) { 996 REQUIRE(VALID_CONTEXT(ctx)); 997 998 return atomic_load_relaxed(&ctx->inuse); 999 } 1000 1001 void 1002 isc_mem_clearwater(isc_mem_t *mctx) { 1003 isc_mem_setwater(mctx, 0, 0); 1004 } 1005 1006 void 1007 isc_mem_setwater(isc_mem_t *ctx, size_t hiwater, size_t lowater) { 1008 REQUIRE(VALID_CONTEXT(ctx)); 1009 REQUIRE(hiwater >= lowater); 1010 1011 atomic_store_release(&ctx->hi_water, hiwater); 1012 atomic_store_release(&ctx->lo_water, lowater); 1013 1014 return; 1015 } 1016 1017 bool 1018 isc_mem_isovermem(isc_mem_t *ctx) { 1019 REQUIRE(VALID_CONTEXT(ctx)); 1020 1021 size_t hiwater = atomic_load_relaxed(&ctx->hi_water); 1022 if (hiwater == 0) { 1023 return false; 1024 } 1025 1026 size_t inuse = atomic_load_relaxed(&ctx->inuse); 1027 if (inuse >= hiwater) { 1028 return true; 1029 } 1030 1031 size_t lowater = atomic_load_relaxed(&ctx->lo_water); 1032 if (inuse <= lowater) { 1033 return false; 1034 } 1035 1036 /* 1037 * Between lo_water and hi_water, return true with a probability 1038 * that ramps linearly from 0 at lo_water to 1 at hi_water. This 1039 * spreads cache cleaning across many inserts instead of triggering 1040 * a thundering herd once the hi_water mark is crossed. 1041 */ 1042 uint32_t prob = (uint32_t)(((uint64_t)(inuse - lowater) * 256) / 1043 (hiwater - lowater)); 1044 return isc_random8() < prob; 1045 } 1046 1047 void 1048 isc_mem_setname(isc_mem_t *ctx, const char *name) { 1049 REQUIRE(VALID_CONTEXT(ctx)); 1050 1051 LOCK(&ctx->lock); 1052 strlcpy(ctx->name, name, sizeof(ctx->name)); 1053 UNLOCK(&ctx->lock); 1054 } 1055 1056 const char * 1057 isc_mem_getname(isc_mem_t *ctx) { 1058 REQUIRE(VALID_CONTEXT(ctx)); 1059 1060 if (ctx->name[0] == 0) { 1061 return ""; 1062 } 1063 1064 return ctx->name; 1065 } 1066 1067 /* 1068 * Memory pool stuff 1069 */ 1070 1071 void 1072 isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size, 1073 isc_mempool_t **restrict mpctxp FLARG) { 1074 isc_mempool_t *restrict mpctx = NULL; 1075 size_t size = element_size; 1076 1077 REQUIRE(VALID_CONTEXT(mctx)); 1078 REQUIRE(size > 0U); 1079 REQUIRE(mpctxp != NULL && *mpctxp == NULL); 1080 1081 /* 1082 * Mempools are stored as a linked list of element. 1083 */ 1084 if (size < sizeof(element)) { 1085 size = sizeof(element); 1086 } 1087 1088 /* 1089 * Allocate space for this pool, initialize values, and if all 1090 * works well, attach to the memory context. 1091 */ 1092 mpctx = isc_mem_get(mctx, sizeof(isc_mempool_t)); 1093 1094 *mpctx = (isc_mempool_t){ 1095 .size = size, 1096 .freemax = 1, 1097 .fillcount = 1, 1098 }; 1099 1100 #if ISC_MEM_TRACKLINES 1101 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 1102 fprintf(stderr, "create pool %p file %s line %u mctx %p\n", 1103 mpctx, file, line, mctx); 1104 } 1105 #endif /* ISC_MEM_TRACKLINES */ 1106 1107 isc_mem_attach(mctx, &mpctx->mctx); 1108 mpctx->magic = MEMPOOL_MAGIC; 1109 1110 *mpctxp = (isc_mempool_t *)mpctx; 1111 1112 MCTXLOCK(mctx); 1113 ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link); 1114 mctx->poolcnt++; 1115 MCTXUNLOCK(mctx); 1116 } 1117 1118 void 1119 isc_mempool_setname(isc_mempool_t *restrict mpctx, const char *name) { 1120 REQUIRE(VALID_MEMPOOL(mpctx)); 1121 REQUIRE(name != NULL); 1122 1123 strlcpy(mpctx->name, name, sizeof(mpctx->name)); 1124 } 1125 1126 void 1127 isc__mempool_destroy(isc_mempool_t **restrict mpctxp FLARG) { 1128 isc_mempool_t *restrict mpctx = NULL; 1129 isc_mem_t *mctx = NULL; 1130 element *restrict item = NULL; 1131 1132 REQUIRE(mpctxp != NULL); 1133 REQUIRE(VALID_MEMPOOL(*mpctxp)); 1134 1135 mpctx = *mpctxp; 1136 *mpctxp = NULL; 1137 1138 mctx = mpctx->mctx; 1139 1140 #if ISC_MEM_TRACKLINES 1141 if ((mctx->debugging & ISC_MEM_DEBUGTRACE) != 0) { 1142 fprintf(stderr, "destroy pool %p file %s line %u mctx %p\n", 1143 mpctx, file, line, mctx); 1144 } 1145 #endif 1146 1147 if (mpctx->allocated > 0) { 1148 UNEXPECTED_ERROR("mempool %s leaked memory", mpctx->name); 1149 } 1150 REQUIRE(mpctx->allocated == 0); 1151 1152 /* 1153 * Return any items on the free list 1154 */ 1155 while (mpctx->items != NULL) { 1156 INSIST(mpctx->freecount > 0); 1157 mpctx->freecount--; 1158 1159 item = mpctx->items; 1160 mpctx->items = item->next; 1161 1162 mem_putstats(mctx, mpctx->size); 1163 mem_put(mctx, item, mpctx->size, 0); 1164 } 1165 1166 /* 1167 * Remove our linked list entry from the memory context. 1168 */ 1169 MCTXLOCK(mctx); 1170 ISC_LIST_UNLINK(mctx->pools, mpctx, link); 1171 mctx->poolcnt--; 1172 MCTXUNLOCK(mctx); 1173 1174 mpctx->magic = 0; 1175 1176 isc_mem_putanddetach(&mpctx->mctx, mpctx, sizeof(isc_mempool_t)); 1177 } 1178 1179 void * 1180 isc__mempool_get(isc_mempool_t *restrict mpctx FLARG) { 1181 element *restrict item = NULL; 1182 1183 REQUIRE(VALID_MEMPOOL(mpctx)); 1184 1185 mpctx->allocated++; 1186 1187 if (mpctx->items == NULL) { 1188 isc_mem_t *mctx = mpctx->mctx; 1189 #if !__SANITIZE_ADDRESS__ 1190 const size_t fillcount = mpctx->fillcount; 1191 #else 1192 const size_t fillcount = 1; 1193 #endif 1194 /* 1195 * We need to dip into the well. Fill up our free list. 1196 */ 1197 for (size_t i = 0; i < fillcount; i++) { 1198 item = mem_get(mctx, mpctx->size, 0); 1199 mem_getstats(mctx, mpctx->size); 1200 item->next = mpctx->items; 1201 mpctx->items = item; 1202 mpctx->freecount++; 1203 } 1204 } 1205 1206 INSIST(mpctx->items != NULL); 1207 item = mpctx->items; 1208 1209 mpctx->items = item->next; 1210 1211 INSIST(mpctx->freecount > 0); 1212 mpctx->freecount--; 1213 mpctx->gets++; 1214 1215 ADD_TRACE(mpctx->mctx, item, mpctx->size, file, line); 1216 1217 return item; 1218 } 1219 1220 /* coverity[+free : arg-1] */ 1221 void 1222 isc__mempool_put(isc_mempool_t *restrict mpctx, void *mem FLARG) { 1223 element *restrict item = NULL; 1224 1225 REQUIRE(VALID_MEMPOOL(mpctx)); 1226 REQUIRE(mem != NULL); 1227 1228 isc_mem_t *mctx = mpctx->mctx; 1229 const size_t freecount = mpctx->freecount; 1230 #if !__SANITIZE_ADDRESS__ 1231 const size_t freemax = mpctx->freemax; 1232 #else 1233 const size_t freemax = 0; 1234 #endif 1235 1236 INSIST(mpctx->allocated > 0); 1237 mpctx->allocated--; 1238 1239 DELETE_TRACE(mctx, mem, mpctx->size, file, line); 1240 1241 /* 1242 * If our free list is full, return this to the mctx directly. 1243 */ 1244 if (freecount >= freemax) { 1245 mem_putstats(mctx, mpctx->size); 1246 mem_put(mctx, mem, mpctx->size, 0); 1247 return; 1248 } 1249 1250 /* 1251 * Otherwise, attach it to our free list and bump the counter. 1252 */ 1253 item = (element *)mem; 1254 item->next = mpctx->items; 1255 mpctx->items = item; 1256 mpctx->freecount++; 1257 } 1258 1259 /* 1260 * Quotas 1261 */ 1262 1263 void 1264 isc_mempool_setfreemax(isc_mempool_t *restrict mpctx, 1265 const unsigned int limit) { 1266 REQUIRE(VALID_MEMPOOL(mpctx)); 1267 mpctx->freemax = limit; 1268 } 1269 1270 unsigned int 1271 isc_mempool_getfreemax(isc_mempool_t *restrict mpctx) { 1272 REQUIRE(VALID_MEMPOOL(mpctx)); 1273 1274 return mpctx->freemax; 1275 } 1276 1277 unsigned int 1278 isc_mempool_getfreecount(isc_mempool_t *restrict mpctx) { 1279 REQUIRE(VALID_MEMPOOL(mpctx)); 1280 1281 return mpctx->freecount; 1282 } 1283 1284 unsigned int 1285 isc_mempool_getallocated(isc_mempool_t *restrict mpctx) { 1286 REQUIRE(VALID_MEMPOOL(mpctx)); 1287 1288 return mpctx->allocated; 1289 } 1290 1291 void 1292 isc_mempool_setfillcount(isc_mempool_t *restrict mpctx, 1293 unsigned int const limit) { 1294 REQUIRE(VALID_MEMPOOL(mpctx)); 1295 REQUIRE(limit > 0); 1296 1297 mpctx->fillcount = limit; 1298 } 1299 1300 unsigned int 1301 isc_mempool_getfillcount(isc_mempool_t *restrict mpctx) { 1302 REQUIRE(VALID_MEMPOOL(mpctx)); 1303 1304 return mpctx->fillcount; 1305 } 1306 1307 /* 1308 * Requires contextslock to be held by caller. 1309 */ 1310 #if ISC_MEM_TRACKLINES 1311 static void 1312 print_contexts(FILE *file) { 1313 isc_mem_t *ctx; 1314 1315 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; 1316 ctx = ISC_LIST_NEXT(ctx, link)) 1317 { 1318 fprintf(file, "context: %p (%s): %" PRIuFAST32 " references\n", 1319 ctx, ctx->name[0] == 0 ? "<unknown>" : ctx->name, 1320 isc_refcount_current(&ctx->references)); 1321 print_active(ctx, file); 1322 } 1323 fflush(file); 1324 } 1325 #endif 1326 1327 static atomic_uintptr_t checkdestroyed = 0; 1328 1329 void 1330 isc_mem_checkdestroyed(FILE *file) { 1331 atomic_store_release(&checkdestroyed, (uintptr_t)file); 1332 } 1333 1334 void 1335 isc__mem_checkdestroyed(void) { 1336 FILE *file = (FILE *)atomic_load_acquire(&checkdestroyed); 1337 1338 if (file == NULL) { 1339 return; 1340 } 1341 1342 LOCK(&contextslock); 1343 if (!ISC_LIST_EMPTY(contexts)) { 1344 #if ISC_MEM_TRACKLINES 1345 if ((isc_mem_debugging & TRACE_OR_RECORD) != 0) { 1346 print_contexts(file); 1347 } 1348 #endif /* if ISC_MEM_TRACKLINES */ 1349 UNREACHABLE(); 1350 } 1351 UNLOCK(&contextslock); 1352 } 1353 1354 unsigned int 1355 isc_mem_references(isc_mem_t *ctx) { 1356 return isc_refcount_current(&ctx->references); 1357 } 1358 1359 #ifdef HAVE_LIBXML2 1360 #define TRY0(a) \ 1361 do { \ 1362 xmlrc = (a); \ 1363 if (xmlrc < 0) \ 1364 goto error; \ 1365 } while (0) 1366 static int 1367 xml_renderctx(isc_mem_t *ctx, size_t *inuse, xmlTextWriterPtr writer) { 1368 REQUIRE(VALID_CONTEXT(ctx)); 1369 1370 int xmlrc; 1371 1372 MCTXLOCK(ctx); 1373 1374 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context")); 1375 1376 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); 1377 TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx)); 1378 TRY0(xmlTextWriterEndElement(writer)); /* id */ 1379 1380 if (ctx->name[0] != 0) { 1381 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name")); 1382 TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name)); 1383 TRY0(xmlTextWriterEndElement(writer)); /* name */ 1384 } 1385 1386 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references")); 1387 TRY0(xmlTextWriterWriteFormatString( 1388 writer, "%" PRIuFAST32, 1389 isc_refcount_current(&ctx->references))); 1390 TRY0(xmlTextWriterEndElement(writer)); /* references */ 1391 1392 *inuse += isc_mem_inuse(ctx); 1393 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse")); 1394 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", 1395 (uint64_t)isc_mem_inuse(ctx))); 1396 TRY0(xmlTextWriterEndElement(writer)); /* inuse */ 1397 1398 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced")); 1399 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", 1400 (uint64_t)isc_mem_inuse(ctx))); 1401 TRY0(xmlTextWriterEndElement(writer)); /* malloced */ 1402 1403 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools")); 1404 TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt)); 1405 TRY0(xmlTextWriterEndElement(writer)); /* pools */ 1406 1407 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater")); 1408 TRY0(xmlTextWriterWriteFormatString( 1409 writer, "%" PRIu64 "", 1410 (uint64_t)atomic_load_relaxed(&ctx->hi_water))); 1411 TRY0(xmlTextWriterEndElement(writer)); /* hiwater */ 1412 1413 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater")); 1414 TRY0(xmlTextWriterWriteFormatString( 1415 writer, "%" PRIu64 "", 1416 (uint64_t)atomic_load_relaxed(&ctx->lo_water))); 1417 TRY0(xmlTextWriterEndElement(writer)); /* lowater */ 1418 1419 TRY0(xmlTextWriterEndElement(writer)); /* context */ 1420 1421 error: 1422 MCTXUNLOCK(ctx); 1423 1424 return xmlrc; 1425 } 1426 1427 int 1428 isc_mem_renderxml(void *writer0) { 1429 isc_mem_t *ctx; 1430 size_t inuse = 0; 1431 int xmlrc; 1432 xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; 1433 1434 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts")); 1435 1436 LOCK(&contextslock); 1437 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; 1438 ctx = ISC_LIST_NEXT(ctx, link)) 1439 { 1440 xmlrc = xml_renderctx(ctx, &inuse, writer); 1441 if (xmlrc < 0) { 1442 UNLOCK(&contextslock); 1443 goto error; 1444 } 1445 } 1446 UNLOCK(&contextslock); 1447 1448 TRY0(xmlTextWriterEndElement(writer)); /* contexts */ 1449 1450 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary")); 1451 1452 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Malloced")); 1453 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", 1454 (uint64_t)inuse)); 1455 TRY0(xmlTextWriterEndElement(writer)); /* malloced */ 1456 1457 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse")); 1458 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", 1459 (uint64_t)inuse)); 1460 TRY0(xmlTextWriterEndElement(writer)); /* InUse */ 1461 1462 TRY0(xmlTextWriterEndElement(writer)); /* summary */ 1463 error: 1464 return xmlrc; 1465 } 1466 1467 #endif /* HAVE_LIBXML2 */ 1468 1469 #ifdef HAVE_JSON_C 1470 #define CHECKMEM(m) RUNTIME_CHECK(m != NULL) 1471 1472 static isc_result_t 1473 json_renderctx(isc_mem_t *ctx, size_t *inuse, json_object *array) { 1474 REQUIRE(VALID_CONTEXT(ctx)); 1475 REQUIRE(array != NULL); 1476 1477 json_object *ctxobj, *obj; 1478 char buf[1024]; 1479 1480 MCTXLOCK(ctx); 1481 1482 *inuse += isc_mem_inuse(ctx); 1483 1484 ctxobj = json_object_new_object(); 1485 CHECKMEM(ctxobj); 1486 1487 snprintf(buf, sizeof(buf), "%p", ctx); 1488 obj = json_object_new_string(buf); 1489 CHECKMEM(obj); 1490 json_object_object_add(ctxobj, "id", obj); 1491 1492 if (ctx->name[0] != 0) { 1493 obj = json_object_new_string(ctx->name); 1494 CHECKMEM(obj); 1495 json_object_object_add(ctxobj, "name", obj); 1496 } 1497 1498 obj = json_object_new_int64(isc_refcount_current(&ctx->references)); 1499 CHECKMEM(obj); 1500 json_object_object_add(ctxobj, "references", obj); 1501 1502 obj = json_object_new_int64(isc_mem_inuse(ctx)); 1503 CHECKMEM(obj); 1504 json_object_object_add(ctxobj, "malloced", obj); 1505 1506 obj = json_object_new_int64(isc_mem_inuse(ctx)); 1507 CHECKMEM(obj); 1508 json_object_object_add(ctxobj, "inuse", obj); 1509 1510 obj = json_object_new_int64(ctx->poolcnt); 1511 CHECKMEM(obj); 1512 json_object_object_add(ctxobj, "pools", obj); 1513 1514 obj = json_object_new_int64(atomic_load_relaxed(&ctx->hi_water)); 1515 CHECKMEM(obj); 1516 json_object_object_add(ctxobj, "hiwater", obj); 1517 1518 obj = json_object_new_int64(atomic_load_relaxed(&ctx->lo_water)); 1519 CHECKMEM(obj); 1520 json_object_object_add(ctxobj, "lowater", obj); 1521 1522 MCTXUNLOCK(ctx); 1523 json_object_array_add(array, ctxobj); 1524 return ISC_R_SUCCESS; 1525 } 1526 1527 isc_result_t 1528 isc_mem_renderjson(void *memobj0) { 1529 isc_result_t result = ISC_R_SUCCESS; 1530 isc_mem_t *ctx; 1531 size_t inuse = 0; 1532 json_object *ctxarray, *obj; 1533 json_object *memobj = (json_object *)memobj0; 1534 1535 ctxarray = json_object_new_array(); 1536 CHECKMEM(ctxarray); 1537 1538 LOCK(&contextslock); 1539 for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; 1540 ctx = ISC_LIST_NEXT(ctx, link)) 1541 { 1542 result = json_renderctx(ctx, &inuse, ctxarray); 1543 if (result != ISC_R_SUCCESS) { 1544 UNLOCK(&contextslock); 1545 goto error; 1546 } 1547 } 1548 UNLOCK(&contextslock); 1549 1550 obj = json_object_new_int64(inuse); 1551 CHECKMEM(obj); 1552 json_object_object_add(memobj, "InUse", obj); 1553 1554 obj = json_object_new_int64(inuse); 1555 CHECKMEM(obj); 1556 json_object_object_add(memobj, "Malloced", obj); 1557 1558 json_object_object_add(memobj, "contexts", ctxarray); 1559 return ISC_R_SUCCESS; 1560 1561 error: 1562 if (ctxarray != NULL) { 1563 json_object_put(ctxarray); 1564 } 1565 return result; 1566 } 1567 #endif /* HAVE_JSON_C */ 1568 1569 void 1570 isc__mem_create(isc_mem_t **mctxp FLARG) { 1571 mem_create(mctxp, isc_mem_debugging, isc_mem_defaultflags, 0); 1572 #if ISC_MEM_TRACKLINES 1573 if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { 1574 fprintf(stderr, "create mctx %p file %s line %u\n", *mctxp, 1575 file, line); 1576 } 1577 #endif /* ISC_MEM_TRACKLINES */ 1578 } 1579 1580 void 1581 isc__mem_create_arena(isc_mem_t **mctxp FLARG) { 1582 unsigned int arena_no = ISC_MEM_ILLEGAL_ARENA; 1583 1584 RUNTIME_CHECK(mem_jemalloc_arena_create(&arena_no)); 1585 1586 /* 1587 * We use MALLOCX_TCACHE_NONE to bypass the tcache and route 1588 * allocations directly to the arena. That is a recommendation 1589 * from jemalloc developers: 1590 * 1591 * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1698173849 1592 */ 1593 mem_create(mctxp, isc_mem_debugging, isc_mem_defaultflags, 1594 arena_no == ISC_MEM_ILLEGAL_ARENA 1595 ? 0 1596 : MALLOCX_ARENA(arena_no) | MALLOCX_TCACHE_NONE); 1597 (*mctxp)->jemalloc_arena = arena_no; 1598 #if ISC_MEM_TRACKLINES 1599 if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { 1600 fprintf(stderr, 1601 "create mctx %p file %s line %u for jemalloc arena " 1602 "%u\n", 1603 *mctxp, file, line, arena_no); 1604 } 1605 #endif /* ISC_MEM_TRACKLINES */ 1606 } 1607 1608 #ifdef JEMALLOC_API_SUPPORTED 1609 static bool 1610 jemalloc_set_ssize_value(const char *valname, ssize_t newval) { 1611 int ret; 1612 1613 ret = mallctl(valname, NULL, NULL, &newval, sizeof(newval)); 1614 return ret == 0; 1615 } 1616 #endif /* JEMALLOC_API_SUPPORTED */ 1617 1618 static isc_result_t 1619 mem_set_arena_ssize_value(isc_mem_t *mctx, const char *arena_valname, 1620 const ssize_t newval) { 1621 REQUIRE(VALID_CONTEXT(mctx)); 1622 #ifdef JEMALLOC_API_SUPPORTED 1623 bool ret; 1624 char buf[256] = { 0 }; 1625 1626 if (mctx->jemalloc_arena == ISC_MEM_ILLEGAL_ARENA) { 1627 return ISC_R_UNEXPECTED; 1628 } 1629 1630 (void)snprintf(buf, sizeof(buf), "arena.%u.%s", mctx->jemalloc_arena, 1631 arena_valname); 1632 1633 ret = jemalloc_set_ssize_value(buf, newval); 1634 1635 if (!ret) { 1636 return ISC_R_FAILURE; 1637 } 1638 1639 return ISC_R_SUCCESS; 1640 #else 1641 UNUSED(arena_valname); 1642 UNUSED(newval); 1643 return ISC_R_NOTIMPLEMENTED; 1644 #endif 1645 } 1646 1647 isc_result_t 1648 isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) { 1649 return mem_set_arena_ssize_value(mctx, "muzzy_decay_ms", decay_ms); 1650 } 1651 1652 isc_result_t 1653 isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) { 1654 return mem_set_arena_ssize_value(mctx, "dirty_decay_ms", decay_ms); 1655 } 1656 1657 void 1658 isc__mem_printactive(isc_mem_t *ctx, FILE *file) { 1659 #if ISC_MEM_TRACKLINES 1660 REQUIRE(VALID_CONTEXT(ctx)); 1661 REQUIRE(file != NULL); 1662 1663 print_active(ctx, file); 1664 #else /* if ISC_MEM_TRACKLINES */ 1665 UNUSED(ctx); 1666 UNUSED(file); 1667 #endif /* if ISC_MEM_TRACKLINES */ 1668 } 1669