| /* Emulate Emacs heap dumping to test malloc_set_state. |
| Copyright (C) 2001-2018 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| Contributed by Wolfram Gloger <wg@malloc.de>, 2001. |
| |
| The GNU C Library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with the GNU C Library; if not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <libc-symbols.h> |
| #include <shlib-compat.h> |
| #include <support/check.h> |
| #include <support/support.h> |
| #include <support/test-driver.h> |
| |
| #include "malloc.h" |
| |
| #if TEST_COMPAT (libc, GLIBC_2_0, GLIBC_2_25) |
| |
| /* Make the compatibility symbols availabile to this test case. */ |
| void *malloc_get_state (void); |
| compat_symbol_reference (libc, malloc_get_state, malloc_get_state, GLIBC_2_0); |
| int malloc_set_state (void *); |
| compat_symbol_reference (libc, malloc_set_state, malloc_set_state, GLIBC_2_0); |
| |
| /* Maximum object size in the fake heap. */ |
| enum { max_size = 64 }; |
| |
| /* Allocation actions. These are randomized actions executed on the |
| dumped heap (see allocation_tasks below). They are interspersed |
| with operations on the new heap (see heap_activity). */ |
| enum allocation_action |
| { |
| action_free, /* Dumped and freed. */ |
| action_realloc, /* Dumped and realloc'ed. */ |
| action_realloc_same, /* Dumped and realloc'ed, same size. */ |
| action_realloc_smaller, /* Dumped and realloc'ed, shrinked. */ |
| action_count |
| }; |
| |
| /* Dumped heap. Initialize it, so that the object is placed into the |
| .data section, for increased realism. The size is an upper bound; |
| we use about half of the space. */ |
| static size_t dumped_heap[action_count * max_size * max_size |
| / sizeof (size_t)] = {1}; |
| |
| /* Next free space in the dumped heap. Also top of the heap at the |
| end of the initialization procedure. */ |
| static size_t *next_heap_chunk; |
| |
| /* Copied from malloc.c and hooks.c. The version is deliberately |
| lower than the final version of malloc_set_state. */ |
| # define NBINS 128 |
| # define MALLOC_STATE_MAGIC 0x444c4541l |
| # define MALLOC_STATE_VERSION (0 * 0x100l + 4l) |
| static struct |
| { |
| long magic; |
| long version; |
| void *av[NBINS * 2 + 2]; |
| char *sbrk_base; |
| int sbrked_mem_bytes; |
| unsigned long trim_threshold; |
| unsigned long top_pad; |
| unsigned int n_mmaps_max; |
| unsigned long mmap_threshold; |
| int check_action; |
| unsigned long max_sbrked_mem; |
| unsigned long max_total_mem; |
| unsigned int n_mmaps; |
| unsigned int max_n_mmaps; |
| unsigned long mmapped_mem; |
| unsigned long max_mmapped_mem; |
| int using_malloc_checking; |
| unsigned long max_fast; |
| unsigned long arena_test; |
| unsigned long arena_max; |
| unsigned long narenas; |
| } save_state = |
| { |
| .magic = MALLOC_STATE_MAGIC, |
| .version = MALLOC_STATE_VERSION, |
| }; |
| |
| /* Allocate a blob in the fake heap. */ |
| static void * |
| dumped_heap_alloc (size_t length) |
| { |
| /* malloc needs three state bits in the size field, so the minimum |
| alignment is 8 even on 32-bit architectures. malloc_set_state |
| should be compatible with such heaps even if it currently |
| provides more alignment to applications. */ |
| enum |
| { |
| heap_alignment = 8, |
| heap_alignment_mask = heap_alignment - 1 |
| }; |
| _Static_assert (sizeof (size_t) <= heap_alignment, |
| "size_t compatible with heap alignment"); |
| |
| /* Need at least this many bytes for metadata and application |
| data. */ |
| size_t chunk_size = sizeof (size_t) + length; |
| /* Round up the allocation size to the heap alignment. */ |
| chunk_size += heap_alignment_mask; |
| chunk_size &= ~heap_alignment_mask; |
| TEST_VERIFY_EXIT ((chunk_size & 3) == 0); |
| if (next_heap_chunk == NULL) |
| /* Initialize the top of the heap. Add one word of zero padding, |
| to match existing practice. */ |
| { |
| dumped_heap[0] = 0; |
| next_heap_chunk = dumped_heap + 1; |
| } |
| else |
| /* The previous chunk is allocated. */ |
| chunk_size |= 1; |
| *next_heap_chunk = chunk_size; |
| |
| /* User data starts after the chunk header. */ |
| void *result = next_heap_chunk + 1; |
| next_heap_chunk += chunk_size / sizeof (size_t); |
| |
| /* Mark the previous chunk as used. */ |
| *next_heap_chunk = 1; |
| return result; |
| } |
| |
| /* Global seed variable for the random number generator. */ |
| static unsigned long long global_seed; |
| |
| /* Simple random number generator. The numbers are in the range from |
| 0 to UINT_MAX (inclusive). */ |
| static unsigned int |
| rand_next (unsigned long long *seed) |
| { |
| /* Linear congruential generated as used for MMIX. */ |
| *seed = *seed * 6364136223846793005ULL + 1442695040888963407ULL; |
| return *seed >> 32; |
| } |
| |
| /* Fill LENGTH bytes at BUFFER with random contents, as determined by |
| SEED. */ |
| static void |
| randomize_buffer (unsigned char *buffer, size_t length, |
| unsigned long long seed) |
| { |
| for (size_t i = 0; i < length; ++i) |
| buffer[i] = rand_next (&seed); |
| } |
| |
| /* Dumps the buffer to standard output, in hexadecimal. */ |
| static void |
| dump_hex (unsigned char *buffer, size_t length) |
| { |
| for (int i = 0; i < length; ++i) |
| printf (" %02X", buffer[i]); |
| } |
| |
| /* Set to true if an error is encountered. */ |
| static bool errors = false; |
| |
| /* Keep track of object allocations. */ |
| struct allocation |
| { |
| unsigned char *data; |
| unsigned int size; |
| unsigned int seed; |
| }; |
| |
| /* Check that the allocation task allocation has the expected |
| contents. */ |
| static void |
| check_allocation (const struct allocation *alloc, int index) |
| { |
| size_t size = alloc->size; |
| if (alloc->data == NULL) |
| { |
| printf ("error: NULL pointer for allocation of size %zu at %d, seed %u\n", |
| size, index, alloc->seed); |
| errors = true; |
| return; |
| } |
| |
| unsigned char expected[4096]; |
| if (size > sizeof (expected)) |
| { |
| printf ("error: invalid allocation size %zu at %d, seed %u\n", |
| size, index, alloc->seed); |
| errors = true; |
| return; |
| } |
| randomize_buffer (expected, size, alloc->seed); |
| if (memcmp (alloc->data, expected, size) != 0) |
| { |
| printf ("error: allocation %d data mismatch, size %zu, seed %u\n", |
| index, size, alloc->seed); |
| printf (" expected:"); |
| dump_hex (expected, size); |
| putc ('\n', stdout); |
| printf (" actual:"); |
| dump_hex (alloc->data, size); |
| putc ('\n', stdout); |
| errors = true; |
| } |
| } |
| |
| /* A heap allocation combined with pending actions on it. */ |
| struct allocation_task |
| { |
| struct allocation allocation; |
| enum allocation_action action; |
| }; |
| |
| /* Allocation tasks. Initialized by init_allocation_tasks and used by |
| perform_allocations. */ |
| enum { allocation_task_count = action_count * max_size }; |
| static struct allocation_task allocation_tasks[allocation_task_count]; |
| |
| /* Fisher-Yates shuffle of allocation_tasks. */ |
| static void |
| shuffle_allocation_tasks (void) |
| { |
| for (int i = 0; i < allocation_task_count - 1; ++i) |
| { |
| /* Pick pair in the tail of the array. */ |
| int j = i + (rand_next (&global_seed) |
| % ((unsigned) (allocation_task_count - i))); |
| TEST_VERIFY_EXIT (j >= 0 && j < allocation_task_count); |
| /* Exchange. */ |
| struct allocation_task tmp = allocation_tasks[i]; |
| allocation_tasks[i] = allocation_tasks[j]; |
| allocation_tasks[j] = tmp; |
| } |
| } |
| |
| /* Set up the allocation tasks and the dumped heap. */ |
| static void |
| initial_allocations (void) |
| { |
| /* Initialize in a position-dependent way. */ |
| for (int i = 0; i < allocation_task_count; ++i) |
| allocation_tasks[i] = (struct allocation_task) |
| { |
| .allocation = |
| { |
| .size = 1 + (i / action_count), |
| .seed = i, |
| }, |
| .action = i % action_count |
| }; |
| |
| /* Execute the tasks in a random order. */ |
| shuffle_allocation_tasks (); |
| |
| /* Initialize the contents of the dumped heap. */ |
| for (int i = 0; i < allocation_task_count; ++i) |
| { |
| struct allocation_task *task = allocation_tasks + i; |
| task->allocation.data = dumped_heap_alloc (task->allocation.size); |
| randomize_buffer (task->allocation.data, task->allocation.size, |
| task->allocation.seed); |
| } |
| |
| for (int i = 0; i < allocation_task_count; ++i) |
| check_allocation (&allocation_tasks[i].allocation, i); |
| } |
| |
| /* Indicates whether init_heap has run. This variable needs to be |
| volatile because malloc is declared __THROW, which implies it is a |
| leaf function, but we expect it to run our hooks. */ |
| static volatile bool heap_initialized; |
| |
| /* Executed by glibc malloc, through __malloc_initialize_hook |
| below. */ |
| static void |
| init_heap (void) |
| { |
| if (test_verbose) |
| printf ("info: performing heap initialization\n"); |
| heap_initialized = true; |
| |
| /* Populate the dumped heap. */ |
| initial_allocations (); |
| |
| /* Complete initialization of the saved heap data structure. */ |
| save_state.sbrk_base = (void *) dumped_heap; |
| save_state.sbrked_mem_bytes = sizeof (dumped_heap); |
| /* Top pointer. Adjust so that it points to the start of struct |
| malloc_chunk. */ |
| save_state.av[2] = (void *) (next_heap_chunk - 1); |
| |
| /* Integrate the dumped heap into the process heap. */ |
| TEST_VERIFY_EXIT (malloc_set_state (&save_state) == 0); |
| } |
| |
| /* Interpose the initialization callback. */ |
| void (*volatile __malloc_initialize_hook) (void) = init_heap; |
| |
| /* Simulate occasional unrelated heap activity in the non-dumped |
| heap. */ |
| enum { heap_activity_allocations_count = 32 }; |
| static struct allocation heap_activity_allocations |
| [heap_activity_allocations_count] = {}; |
| static int heap_activity_seed_counter = 1000 * 1000; |
| |
| static void |
| heap_activity (void) |
| { |
| /* Only do this from time to time. */ |
| if ((rand_next (&global_seed) % 4) == 0) |
| { |
| int slot = rand_next (&global_seed) % heap_activity_allocations_count; |
| struct allocation *alloc = heap_activity_allocations + slot; |
| if (alloc->data == NULL) |
| { |
| alloc->size = rand_next (&global_seed) % (4096U + 1); |
| alloc->data = xmalloc (alloc->size); |
| alloc->seed = heap_activity_seed_counter++; |
| randomize_buffer (alloc->data, alloc->size, alloc->seed); |
| check_allocation (alloc, 1000 + slot); |
| } |
| else |
| { |
| check_allocation (alloc, 1000 + slot); |
| free (alloc->data); |
| alloc->data = NULL; |
| } |
| } |
| } |
| |
| static void |
| heap_activity_deallocate (void) |
| { |
| for (int i = 0; i < heap_activity_allocations_count; ++i) |
| free (heap_activity_allocations[i].data); |
| } |
| |
| /* Perform a full heap check across the dumped heap allocation tasks, |
| and the simulated heap activity directly above. */ |
| static void |
| full_heap_check (void) |
| { |
| /* Dumped heap. */ |
| for (int i = 0; i < allocation_task_count; ++i) |
| if (allocation_tasks[i].allocation.data != NULL) |
| check_allocation (&allocation_tasks[i].allocation, i); |
| |
| /* Heap activity allocations. */ |
| for (int i = 0; i < heap_activity_allocations_count; ++i) |
| if (heap_activity_allocations[i].data != NULL) |
| check_allocation (heap_activity_allocations + i, i); |
| } |
| |
| /* Used as an optimization barrier to force a heap allocation. */ |
| __attribute__ ((noinline, noclone)) |
| static void |
| my_free (void *ptr) |
| { |
| free (ptr); |
| } |
| |
| static int |
| do_test (void) |
| { |
| my_free (malloc (1)); |
| TEST_VERIFY_EXIT (heap_initialized); |
| |
| /* The first pass performs the randomly generated allocation |
| tasks. */ |
| if (test_verbose) |
| printf ("info: first pass through allocation tasks\n"); |
| full_heap_check (); |
| |
| /* Execute the post-undump tasks in a random order. */ |
| shuffle_allocation_tasks (); |
| |
| for (int i = 0; i < allocation_task_count; ++i) |
| { |
| heap_activity (); |
| struct allocation_task *task = allocation_tasks + i; |
| switch (task->action) |
| { |
| case action_free: |
| check_allocation (&task->allocation, i); |
| free (task->allocation.data); |
| task->allocation.data = NULL; |
| break; |
| |
| case action_realloc: |
| check_allocation (&task->allocation, i); |
| task->allocation.data = xrealloc |
| (task->allocation.data, task->allocation.size + max_size); |
| check_allocation (&task->allocation, i); |
| break; |
| |
| case action_realloc_same: |
| check_allocation (&task->allocation, i); |
| task->allocation.data = xrealloc |
| (task->allocation.data, task->allocation.size); |
| check_allocation (&task->allocation, i); |
| break; |
| |
| case action_realloc_smaller: |
| check_allocation (&task->allocation, i); |
| size_t new_size = task->allocation.size - 1; |
| task->allocation.data = xrealloc (task->allocation.data, new_size); |
| if (new_size == 0) |
| { |
| if (task->allocation.data != NULL) |
| { |
| printf ("error: realloc with size zero did not deallocate\n"); |
| errors = true; |
| } |
| /* No further action on this task. */ |
| task->action = action_free; |
| } |
| else |
| { |
| task->allocation.size = new_size; |
| check_allocation (&task->allocation, i); |
| } |
| break; |
| |
| case action_count: |
| FAIL_EXIT1 ("task->action should never be action_count"); |
| } |
| full_heap_check (); |
| } |
| |
| /* The second pass frees the objects which were allocated during the |
| first pass. */ |
| if (test_verbose) |
| printf ("info: second pass through allocation tasks\n"); |
| |
| shuffle_allocation_tasks (); |
| for (int i = 0; i < allocation_task_count; ++i) |
| { |
| heap_activity (); |
| struct allocation_task *task = allocation_tasks + i; |
| switch (task->action) |
| { |
| case action_free: |
| /* Already freed, nothing to do. */ |
| break; |
| |
| case action_realloc: |
| case action_realloc_same: |
| case action_realloc_smaller: |
| check_allocation (&task->allocation, i); |
| free (task->allocation.data); |
| task->allocation.data = NULL; |
| break; |
| |
| case action_count: |
| FAIL_EXIT1 ("task->action should never be action_count"); |
| } |
| full_heap_check (); |
| } |
| |
| heap_activity_deallocate (); |
| |
| /* Check that the malloc_get_state stub behaves in the intended |
| way. */ |
| errno = 0; |
| if (malloc_get_state () != NULL) |
| { |
| printf ("error: malloc_get_state succeeded\n"); |
| errors = true; |
| } |
| if (errno != ENOSYS) |
| { |
| printf ("error: malloc_get_state: %m\n"); |
| errors = true; |
| } |
| |
| return errors; |
| } |
| #else |
| static int |
| do_test (void) |
| { |
| return 77; |
| } |
| #endif |
| |
| #include <support/test-driver.c> |