| 1 | /* Emulate Emacs heap dumping to test malloc_set_state. |
| 2 | Copyright (C) 2001-2024 Free Software Foundation, Inc. |
| 3 | This file is part of the GNU C Library. |
| 4 | |
| 5 | The GNU C Library is free software; you can redistribute it and/or |
| 6 | modify it under the terms of the GNU Lesser General Public |
| 7 | License as published by the Free Software Foundation; either |
| 8 | version 2.1 of the License, or (at your option) any later version. |
| 9 | |
| 10 | The GNU C Library is distributed in the hope that it will be useful, |
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 | Lesser General Public License for more details. |
| 14 | |
| 15 | You should have received a copy of the GNU Lesser General Public |
| 16 | License along with the GNU C Library; if not, see |
| 17 | <https://www.gnu.org/licenses/>. */ |
| 18 | |
| 19 | #include <errno.h> |
| 20 | #include <stdbool.h> |
| 21 | #include <stdio.h> |
| 22 | #include <string.h> |
| 23 | #include <libc-symbols.h> |
| 24 | #include <shlib-compat.h> |
| 25 | #include <support/check.h> |
| 26 | #include <support/support.h> |
| 27 | #include <support/test-driver.h> |
| 28 | |
| 29 | #include "malloc.h" |
| 30 | |
| 31 | /* Make the compatibility symbols availabile to this test case. */ |
| 32 | void *malloc_get_state (void); |
| 33 | compat_symbol_reference (libc, malloc_get_state, malloc_get_state, GLIBC_2_0); |
| 34 | int malloc_set_state (void *); |
| 35 | compat_symbol_reference (libc, malloc_set_state, malloc_set_state, GLIBC_2_0); |
| 36 | |
| 37 | /* Maximum object size in the fake heap. */ |
| 38 | enum { max_size = 64 }; |
| 39 | |
| 40 | /* Allocation actions. These are randomized actions executed on the |
| 41 | dumped heap (see allocation_tasks below). They are interspersed |
| 42 | with operations on the new heap (see heap_activity). */ |
| 43 | enum allocation_action |
| 44 | { |
| 45 | action_free, /* Dumped and freed. */ |
| 46 | action_realloc, /* Dumped and realloc'ed. */ |
| 47 | action_realloc_same, /* Dumped and realloc'ed, same size. */ |
| 48 | action_realloc_smaller, /* Dumped and realloc'ed, shrunk. */ |
| 49 | action_count |
| 50 | }; |
| 51 | |
| 52 | /* Dumped heap. Initialize it, so that the object is placed into the |
| 53 | .data section, for increased realism. The size is an upper bound; |
| 54 | we use about half of the space. */ |
| 55 | static size_t dumped_heap[action_count * max_size * max_size |
| 56 | / sizeof (size_t)] = {1}; |
| 57 | |
| 58 | /* Next free space in the dumped heap. Also top of the heap at the |
| 59 | end of the initialization procedure. */ |
| 60 | static size_t *next_heap_chunk; |
| 61 | |
| 62 | /* Copied from malloc.c and hooks.c. The version is deliberately |
| 63 | lower than the final version of malloc_set_state. */ |
| 64 | # define NBINS 128 |
| 65 | # define MALLOC_STATE_MAGIC 0x444c4541l |
| 66 | # define MALLOC_STATE_VERSION (0 * 0x100l + 4l) |
| 67 | static struct |
| 68 | { |
| 69 | long magic; |
| 70 | long version; |
| 71 | void *av[NBINS * 2 + 2]; |
| 72 | char *sbrk_base; |
| 73 | int sbrked_mem_bytes; |
| 74 | unsigned long trim_threshold; |
| 75 | unsigned long top_pad; |
| 76 | unsigned int n_mmaps_max; |
| 77 | unsigned long mmap_threshold; |
| 78 | int check_action; |
| 79 | unsigned long max_sbrked_mem; |
| 80 | unsigned long max_total_mem; |
| 81 | unsigned int n_mmaps; |
| 82 | unsigned int max_n_mmaps; |
| 83 | unsigned long mmapped_mem; |
| 84 | unsigned long max_mmapped_mem; |
| 85 | int using_malloc_checking; |
| 86 | unsigned long max_fast; |
| 87 | unsigned long arena_test; |
| 88 | unsigned long arena_max; |
| 89 | unsigned long narenas; |
| 90 | } save_state = |
| 91 | { |
| 92 | .magic = MALLOC_STATE_MAGIC, |
| 93 | .version = MALLOC_STATE_VERSION, |
| 94 | }; |
| 95 | |
| 96 | /* Allocate a blob in the fake heap. */ |
| 97 | static void * |
| 98 | dumped_heap_alloc (size_t length) |
| 99 | { |
| 100 | /* malloc needs three state bits in the size field, so the minimum |
| 101 | alignment is 8 even on 32-bit architectures. malloc_set_state |
| 102 | should be compatible with such heaps even if it currently |
| 103 | provides more alignment to applications. */ |
| 104 | enum |
| 105 | { |
| 106 | heap_alignment = 8, |
| 107 | heap_alignment_mask = heap_alignment - 1 |
| 108 | }; |
| 109 | _Static_assert (sizeof (size_t) <= heap_alignment, |
| 110 | "size_t compatible with heap alignment" ); |
| 111 | |
| 112 | /* Need at least this many bytes for metadata and application |
| 113 | data. */ |
| 114 | size_t chunk_size = sizeof (size_t) + length; |
| 115 | /* Round up the allocation size to the heap alignment. */ |
| 116 | chunk_size += heap_alignment_mask; |
| 117 | chunk_size &= ~heap_alignment_mask; |
| 118 | TEST_VERIFY_EXIT ((chunk_size & 3) == 0); |
| 119 | if (next_heap_chunk == NULL) |
| 120 | /* Initialize the top of the heap. Add one word of zero padding, |
| 121 | to match existing practice. */ |
| 122 | { |
| 123 | dumped_heap[0] = 0; |
| 124 | next_heap_chunk = dumped_heap + 1; |
| 125 | } |
| 126 | else |
| 127 | /* The previous chunk is allocated. */ |
| 128 | chunk_size |= 1; |
| 129 | *next_heap_chunk = chunk_size; |
| 130 | |
| 131 | /* User data starts after the chunk header. */ |
| 132 | void *result = next_heap_chunk + 1; |
| 133 | next_heap_chunk += chunk_size / sizeof (size_t); |
| 134 | |
| 135 | /* Mark the previous chunk as used. */ |
| 136 | *next_heap_chunk = 1; |
| 137 | return result; |
| 138 | } |
| 139 | |
| 140 | /* Global seed variable for the random number generator. */ |
| 141 | static unsigned long long global_seed; |
| 142 | |
| 143 | /* Simple random number generator. The numbers are in the range from |
| 144 | 0 to UINT_MAX (inclusive). */ |
| 145 | static unsigned int |
| 146 | rand_next (unsigned long long *seed) |
| 147 | { |
| 148 | /* Linear congruential generated as used for MMIX. */ |
| 149 | *seed = *seed * 6364136223846793005ULL + 1442695040888963407ULL; |
| 150 | return *seed >> 32; |
| 151 | } |
| 152 | |
| 153 | /* Fill LENGTH bytes at BUFFER with random contents, as determined by |
| 154 | SEED. */ |
| 155 | static void |
| 156 | randomize_buffer (unsigned char *buffer, size_t length, |
| 157 | unsigned long long seed) |
| 158 | { |
| 159 | for (size_t i = 0; i < length; ++i) |
| 160 | buffer[i] = rand_next (seed: &seed); |
| 161 | } |
| 162 | |
| 163 | /* Dumps the buffer to standard output, in hexadecimal. */ |
| 164 | static void |
| 165 | dump_hex (unsigned char *buffer, size_t length) |
| 166 | { |
| 167 | for (int i = 0; i < length; ++i) |
| 168 | printf (format: " %02X" , buffer[i]); |
| 169 | } |
| 170 | |
| 171 | /* Set to true if an error is encountered. */ |
| 172 | static bool errors = false; |
| 173 | |
| 174 | /* Keep track of object allocations. */ |
| 175 | struct allocation |
| 176 | { |
| 177 | unsigned char *data; |
| 178 | unsigned int size; |
| 179 | unsigned int seed; |
| 180 | }; |
| 181 | |
| 182 | /* Check that the allocation task allocation has the expected |
| 183 | contents. */ |
| 184 | static void |
| 185 | check_allocation (const struct allocation *alloc, int index) |
| 186 | { |
| 187 | size_t size = alloc->size; |
| 188 | if (alloc->data == NULL) |
| 189 | { |
| 190 | printf (format: "error: NULL pointer for allocation of size %zu at %d, seed %u\n" , |
| 191 | size, index, alloc->seed); |
| 192 | errors = true; |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | unsigned char expected[4096]; |
| 197 | if (size > sizeof (expected)) |
| 198 | { |
| 199 | printf (format: "error: invalid allocation size %zu at %d, seed %u\n" , |
| 200 | size, index, alloc->seed); |
| 201 | errors = true; |
| 202 | return; |
| 203 | } |
| 204 | randomize_buffer (buffer: expected, length: size, seed: alloc->seed); |
| 205 | if (memcmp (alloc->data, expected, size) != 0) |
| 206 | { |
| 207 | printf (format: "error: allocation %d data mismatch, size %zu, seed %u\n" , |
| 208 | index, size, alloc->seed); |
| 209 | printf (format: " expected:" ); |
| 210 | dump_hex (buffer: expected, length: size); |
| 211 | putc (c: '\n', stdout); |
| 212 | printf (format: " actual:" ); |
| 213 | dump_hex (buffer: alloc->data, length: size); |
| 214 | putc (c: '\n', stdout); |
| 215 | errors = true; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /* A heap allocation combined with pending actions on it. */ |
| 220 | struct allocation_task |
| 221 | { |
| 222 | struct allocation allocation; |
| 223 | enum allocation_action action; |
| 224 | }; |
| 225 | |
| 226 | /* Allocation tasks. Initialized by init_allocation_tasks and used by |
| 227 | perform_allocations. */ |
| 228 | enum { allocation_task_count = action_count * max_size }; |
| 229 | static struct allocation_task allocation_tasks[allocation_task_count]; |
| 230 | |
| 231 | /* Fisher-Yates shuffle of allocation_tasks. */ |
| 232 | static void |
| 233 | shuffle_allocation_tasks (void) |
| 234 | { |
| 235 | for (int i = 0; i < allocation_task_count - 1; ++i) |
| 236 | { |
| 237 | /* Pick pair in the tail of the array. */ |
| 238 | int j = i + (rand_next (seed: &global_seed) |
| 239 | % ((unsigned) (allocation_task_count - i))); |
| 240 | TEST_VERIFY_EXIT (j >= 0 && j < allocation_task_count); |
| 241 | /* Exchange. */ |
| 242 | struct allocation_task tmp = allocation_tasks[i]; |
| 243 | allocation_tasks[i] = allocation_tasks[j]; |
| 244 | allocation_tasks[j] = tmp; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | /* Set up the allocation tasks and the dumped heap. */ |
| 249 | static void |
| 250 | initial_allocations (void) |
| 251 | { |
| 252 | /* Initialize in a position-dependent way. */ |
| 253 | for (int i = 0; i < allocation_task_count; ++i) |
| 254 | allocation_tasks[i] = (struct allocation_task) |
| 255 | { |
| 256 | .allocation = |
| 257 | { |
| 258 | .size = 1 + (i / action_count), |
| 259 | .seed = i, |
| 260 | }, |
| 261 | .action = i % action_count |
| 262 | }; |
| 263 | |
| 264 | /* Execute the tasks in a random order. */ |
| 265 | shuffle_allocation_tasks (); |
| 266 | |
| 267 | /* Initialize the contents of the dumped heap. */ |
| 268 | for (int i = 0; i < allocation_task_count; ++i) |
| 269 | { |
| 270 | struct allocation_task *task = allocation_tasks + i; |
| 271 | task->allocation.data = dumped_heap_alloc (length: task->allocation.size); |
| 272 | randomize_buffer (buffer: task->allocation.data, length: task->allocation.size, |
| 273 | seed: task->allocation.seed); |
| 274 | } |
| 275 | |
| 276 | for (int i = 0; i < allocation_task_count; ++i) |
| 277 | check_allocation (alloc: &allocation_tasks[i].allocation, index: i); |
| 278 | } |
| 279 | |
| 280 | /* Indicates whether init_heap has run. This variable needs to be |
| 281 | volatile because malloc is declared __THROW, which implies it is a |
| 282 | leaf function, but we expect it to run our hooks. */ |
| 283 | static volatile bool heap_initialized; |
| 284 | |
| 285 | /* Executed by glibc malloc, through __malloc_initialize_hook |
| 286 | below. */ |
| 287 | static void |
| 288 | init_heap (void) |
| 289 | { |
| 290 | if (test_verbose) |
| 291 | printf (format: "info: performing heap initialization\n" ); |
| 292 | heap_initialized = true; |
| 293 | |
| 294 | /* Populate the dumped heap. */ |
| 295 | initial_allocations (); |
| 296 | |
| 297 | /* Complete initialization of the saved heap data structure. */ |
| 298 | save_state.sbrk_base = (void *) dumped_heap; |
| 299 | save_state.sbrked_mem_bytes = sizeof (dumped_heap); |
| 300 | /* Top pointer. Adjust so that it points to the start of struct |
| 301 | malloc_chunk. */ |
| 302 | save_state.av[2] = (void *) (next_heap_chunk - 1); |
| 303 | |
| 304 | /* Integrate the dumped heap into the process heap. */ |
| 305 | TEST_VERIFY_EXIT (malloc_set_state (&save_state) == 0); |
| 306 | } |
| 307 | |
| 308 | /* Interpose the initialization callback. */ |
| 309 | void (*volatile __malloc_initialize_hook) (void) = init_heap; |
| 310 | compat_symbol_reference (libc, __malloc_initialize_hook, |
| 311 | __malloc_initialize_hook, GLIBC_2_0); |
| 312 | |
| 313 | /* Simulate occasional unrelated heap activity in the non-dumped |
| 314 | heap. */ |
| 315 | enum { heap_activity_allocations_count = 32 }; |
| 316 | static struct allocation heap_activity_allocations |
| 317 | [heap_activity_allocations_count] = {}; |
| 318 | static int heap_activity_seed_counter = 1000 * 1000; |
| 319 | |
| 320 | static void |
| 321 | heap_activity (void) |
| 322 | { |
| 323 | /* Only do this from time to time. */ |
| 324 | if ((rand_next (seed: &global_seed) % 4) == 0) |
| 325 | { |
| 326 | int slot = rand_next (seed: &global_seed) % heap_activity_allocations_count; |
| 327 | struct allocation *alloc = heap_activity_allocations + slot; |
| 328 | if (alloc->data == NULL) |
| 329 | { |
| 330 | alloc->size = rand_next (seed: &global_seed) % (4096U + 1); |
| 331 | alloc->data = xmalloc (n: alloc->size); |
| 332 | alloc->seed = heap_activity_seed_counter++; |
| 333 | randomize_buffer (buffer: alloc->data, length: alloc->size, seed: alloc->seed); |
| 334 | check_allocation (alloc, index: 1000 + slot); |
| 335 | } |
| 336 | else |
| 337 | { |
| 338 | check_allocation (alloc, index: 1000 + slot); |
| 339 | free (ptr: alloc->data); |
| 340 | alloc->data = NULL; |
| 341 | } |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | static void |
| 346 | heap_activity_deallocate (void) |
| 347 | { |
| 348 | for (int i = 0; i < heap_activity_allocations_count; ++i) |
| 349 | free (ptr: heap_activity_allocations[i].data); |
| 350 | } |
| 351 | |
| 352 | /* Perform a full heap check across the dumped heap allocation tasks, |
| 353 | and the simulated heap activity directly above. */ |
| 354 | static void |
| 355 | full_heap_check (void) |
| 356 | { |
| 357 | /* Dumped heap. */ |
| 358 | for (int i = 0; i < allocation_task_count; ++i) |
| 359 | if (allocation_tasks[i].allocation.data != NULL) |
| 360 | check_allocation (alloc: &allocation_tasks[i].allocation, index: i); |
| 361 | |
| 362 | /* Heap activity allocations. */ |
| 363 | for (int i = 0; i < heap_activity_allocations_count; ++i) |
| 364 | if (heap_activity_allocations[i].data != NULL) |
| 365 | check_allocation (alloc: heap_activity_allocations + i, index: i); |
| 366 | } |
| 367 | |
| 368 | /* Used as an optimization barrier to force a heap allocation. */ |
| 369 | __attribute__ ((noinline, noclone)) |
| 370 | static void |
| 371 | my_free (void *ptr) |
| 372 | { |
| 373 | free (ptr: ptr); |
| 374 | } |
| 375 | |
| 376 | static int |
| 377 | do_test (void) |
| 378 | { |
| 379 | my_free (ptr: malloc (size: 1)); |
| 380 | TEST_VERIFY_EXIT (heap_initialized); |
| 381 | |
| 382 | /* The first pass performs the randomly generated allocation |
| 383 | tasks. */ |
| 384 | if (test_verbose) |
| 385 | printf (format: "info: first pass through allocation tasks\n" ); |
| 386 | full_heap_check (); |
| 387 | |
| 388 | /* Execute the post-undump tasks in a random order. */ |
| 389 | shuffle_allocation_tasks (); |
| 390 | |
| 391 | for (int i = 0; i < allocation_task_count; ++i) |
| 392 | { |
| 393 | heap_activity (); |
| 394 | struct allocation_task *task = allocation_tasks + i; |
| 395 | switch (task->action) |
| 396 | { |
| 397 | case action_free: |
| 398 | check_allocation (alloc: &task->allocation, index: i); |
| 399 | free (ptr: task->allocation.data); |
| 400 | task->allocation.data = NULL; |
| 401 | break; |
| 402 | |
| 403 | case action_realloc: |
| 404 | check_allocation (alloc: &task->allocation, index: i); |
| 405 | task->allocation.data = xrealloc |
| 406 | (o: task->allocation.data, n: task->allocation.size + max_size); |
| 407 | check_allocation (alloc: &task->allocation, index: i); |
| 408 | break; |
| 409 | |
| 410 | case action_realloc_same: |
| 411 | check_allocation (alloc: &task->allocation, index: i); |
| 412 | task->allocation.data = xrealloc |
| 413 | (o: task->allocation.data, n: task->allocation.size); |
| 414 | check_allocation (alloc: &task->allocation, index: i); |
| 415 | break; |
| 416 | |
| 417 | case action_realloc_smaller: |
| 418 | check_allocation (alloc: &task->allocation, index: i); |
| 419 | size_t new_size = task->allocation.size - 1; |
| 420 | task->allocation.data = xrealloc (o: task->allocation.data, n: new_size); |
| 421 | if (new_size == 0) |
| 422 | { |
| 423 | if (task->allocation.data != NULL) |
| 424 | { |
| 425 | printf (format: "error: realloc with size zero did not deallocate\n" ); |
| 426 | errors = true; |
| 427 | } |
| 428 | /* No further action on this task. */ |
| 429 | task->action = action_free; |
| 430 | } |
| 431 | else |
| 432 | { |
| 433 | task->allocation.size = new_size; |
| 434 | check_allocation (alloc: &task->allocation, index: i); |
| 435 | } |
| 436 | break; |
| 437 | |
| 438 | case action_count: |
| 439 | FAIL_EXIT1 ("task->action should never be action_count" ); |
| 440 | } |
| 441 | full_heap_check (); |
| 442 | } |
| 443 | |
| 444 | /* The second pass frees the objects which were allocated during the |
| 445 | first pass. */ |
| 446 | if (test_verbose) |
| 447 | printf (format: "info: second pass through allocation tasks\n" ); |
| 448 | |
| 449 | shuffle_allocation_tasks (); |
| 450 | for (int i = 0; i < allocation_task_count; ++i) |
| 451 | { |
| 452 | heap_activity (); |
| 453 | struct allocation_task *task = allocation_tasks + i; |
| 454 | switch (task->action) |
| 455 | { |
| 456 | case action_free: |
| 457 | /* Already freed, nothing to do. */ |
| 458 | break; |
| 459 | |
| 460 | case action_realloc: |
| 461 | case action_realloc_same: |
| 462 | case action_realloc_smaller: |
| 463 | check_allocation (alloc: &task->allocation, index: i); |
| 464 | free (ptr: task->allocation.data); |
| 465 | task->allocation.data = NULL; |
| 466 | break; |
| 467 | |
| 468 | case action_count: |
| 469 | FAIL_EXIT1 ("task->action should never be action_count" ); |
| 470 | } |
| 471 | full_heap_check (); |
| 472 | } |
| 473 | |
| 474 | heap_activity_deallocate (); |
| 475 | |
| 476 | /* Check that the malloc_get_state stub behaves in the intended |
| 477 | way. */ |
| 478 | errno = 0; |
| 479 | if (malloc_get_state () != NULL) |
| 480 | { |
| 481 | printf (format: "error: malloc_get_state succeeded\n" ); |
| 482 | errors = true; |
| 483 | } |
| 484 | if (errno != ENOSYS) |
| 485 | { |
| 486 | printf (format: "error: malloc_get_state: %m\n" ); |
| 487 | errors = true; |
| 488 | } |
| 489 | |
| 490 | return errors; |
| 491 | } |
| 492 | |
| 493 | #include <support/test-driver.c> |
| 494 | |