1 /* Emulate Emacs heap dumping to test malloc_set_state.
2 Copyright (C) 2001-2023 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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.
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.
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/>. */
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>
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
);
37 /* Maximum object size in the fake heap. */
38 enum { max_size
= 64 };
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
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. */
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};
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
;
62 /* Copied from malloc.c and hooks.c. The version is deliberately
63 lower than the final version of malloc_set_state. */
65 # define MALLOC_STATE_MAGIC 0x444c4541l
66 # define MALLOC_STATE_VERSION (0 * 0x100l + 4l)
71 void *av
[NBINS
* 2 + 2];
74 unsigned long trim_threshold
;
75 unsigned long top_pad
;
76 unsigned int n_mmaps_max
;
77 unsigned long mmap_threshold
;
79 unsigned long max_sbrked_mem
;
80 unsigned long max_total_mem
;
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
;
92 .magic
= MALLOC_STATE_MAGIC
,
93 .version
= MALLOC_STATE_VERSION
,
96 /* Allocate a blob in the fake heap. */
98 dumped_heap_alloc (size_t length
)
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. */
107 heap_alignment_mask
= heap_alignment
- 1
109 _Static_assert (sizeof (size_t) <= heap_alignment
,
110 "size_t compatible with heap alignment");
112 /* Need at least this many bytes for metadata and application
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. */
124 next_heap_chunk
= dumped_heap
+ 1;
127 /* The previous chunk is allocated. */
129 *next_heap_chunk
= chunk_size
;
131 /* User data starts after the chunk header. */
132 void *result
= next_heap_chunk
+ 1;
133 next_heap_chunk
+= chunk_size
/ sizeof (size_t);
135 /* Mark the previous chunk as used. */
136 *next_heap_chunk
= 1;
140 /* Global seed variable for the random number generator. */
141 static unsigned long long global_seed
;
143 /* Simple random number generator. The numbers are in the range from
144 0 to UINT_MAX (inclusive). */
146 rand_next (unsigned long long *seed
)
148 /* Linear congruential generated as used for MMIX. */
149 *seed
= *seed
* 6364136223846793005ULL + 1442695040888963407ULL;
153 /* Fill LENGTH bytes at BUFFER with random contents, as determined by
156 randomize_buffer (unsigned char *buffer
, size_t length
,
157 unsigned long long seed
)
159 for (size_t i
= 0; i
< length
; ++i
)
160 buffer
[i
] = rand_next (&seed
);
163 /* Dumps the buffer to standard output, in hexadecimal. */
165 dump_hex (unsigned char *buffer
, size_t length
)
167 for (int i
= 0; i
< length
; ++i
)
168 printf (" %02X", buffer
[i
]);
171 /* Set to true if an error is encountered. */
172 static bool errors
= false;
174 /* Keep track of object allocations. */
182 /* Check that the allocation task allocation has the expected
185 check_allocation (const struct allocation
*alloc
, int index
)
187 size_t size
= alloc
->size
;
188 if (alloc
->data
== NULL
)
190 printf ("error: NULL pointer for allocation of size %zu at %d, seed %u\n",
191 size
, index
, alloc
->seed
);
196 unsigned char expected
[4096];
197 if (size
> sizeof (expected
))
199 printf ("error: invalid allocation size %zu at %d, seed %u\n",
200 size
, index
, alloc
->seed
);
204 randomize_buffer (expected
, size
, alloc
->seed
);
205 if (memcmp (alloc
->data
, expected
, size
) != 0)
207 printf ("error: allocation %d data mismatch, size %zu, seed %u\n",
208 index
, size
, alloc
->seed
);
209 printf (" expected:");
210 dump_hex (expected
, size
);
213 dump_hex (alloc
->data
, size
);
219 /* A heap allocation combined with pending actions on it. */
220 struct allocation_task
222 struct allocation allocation
;
223 enum allocation_action action
;
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
];
231 /* Fisher-Yates shuffle of allocation_tasks. */
233 shuffle_allocation_tasks (void)
235 for (int i
= 0; i
< allocation_task_count
- 1; ++i
)
237 /* Pick pair in the tail of the array. */
238 int j
= i
+ (rand_next (&global_seed
)
239 % ((unsigned) (allocation_task_count
- i
)));
240 TEST_VERIFY_EXIT (j
>= 0 && j
< allocation_task_count
);
242 struct allocation_task tmp
= allocation_tasks
[i
];
243 allocation_tasks
[i
] = allocation_tasks
[j
];
244 allocation_tasks
[j
] = tmp
;
248 /* Set up the allocation tasks and the dumped heap. */
250 initial_allocations (void)
252 /* Initialize in a position-dependent way. */
253 for (int i
= 0; i
< allocation_task_count
; ++i
)
254 allocation_tasks
[i
] = (struct allocation_task
)
258 .size
= 1 + (i
/ action_count
),
261 .action
= i
% action_count
264 /* Execute the tasks in a random order. */
265 shuffle_allocation_tasks ();
267 /* Initialize the contents of the dumped heap. */
268 for (int i
= 0; i
< allocation_task_count
; ++i
)
270 struct allocation_task
*task
= allocation_tasks
+ i
;
271 task
->allocation
.data
= dumped_heap_alloc (task
->allocation
.size
);
272 randomize_buffer (task
->allocation
.data
, task
->allocation
.size
,
273 task
->allocation
.seed
);
276 for (int i
= 0; i
< allocation_task_count
; ++i
)
277 check_allocation (&allocation_tasks
[i
].allocation
, i
);
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
;
285 /* Executed by glibc malloc, through __malloc_initialize_hook
291 printf ("info: performing heap initialization\n");
292 heap_initialized
= true;
294 /* Populate the dumped heap. */
295 initial_allocations ();
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
302 save_state
.av
[2] = (void *) (next_heap_chunk
- 1);
304 /* Integrate the dumped heap into the process heap. */
305 TEST_VERIFY_EXIT (malloc_set_state (&save_state
) == 0);
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
);
313 /* Simulate occasional unrelated heap activity in the non-dumped
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;
323 /* Only do this from time to time. */
324 if ((rand_next (&global_seed
) % 4) == 0)
326 int slot
= rand_next (&global_seed
) % heap_activity_allocations_count
;
327 struct allocation
*alloc
= heap_activity_allocations
+ slot
;
328 if (alloc
->data
== NULL
)
330 alloc
->size
= rand_next (&global_seed
) % (4096U + 1);
331 alloc
->data
= xmalloc (alloc
->size
);
332 alloc
->seed
= heap_activity_seed_counter
++;
333 randomize_buffer (alloc
->data
, alloc
->size
, alloc
->seed
);
334 check_allocation (alloc
, 1000 + slot
);
338 check_allocation (alloc
, 1000 + slot
);
346 heap_activity_deallocate (void)
348 for (int i
= 0; i
< heap_activity_allocations_count
; ++i
)
349 free (heap_activity_allocations
[i
].data
);
352 /* Perform a full heap check across the dumped heap allocation tasks,
353 and the simulated heap activity directly above. */
355 full_heap_check (void)
358 for (int i
= 0; i
< allocation_task_count
; ++i
)
359 if (allocation_tasks
[i
].allocation
.data
!= NULL
)
360 check_allocation (&allocation_tasks
[i
].allocation
, i
);
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 (heap_activity_allocations
+ i
, i
);
368 /* Used as an optimization barrier to force a heap allocation. */
369 __attribute__ ((noinline
, noclone
))
379 my_free (malloc (1));
380 TEST_VERIFY_EXIT (heap_initialized
);
382 /* The first pass performs the randomly generated allocation
385 printf ("info: first pass through allocation tasks\n");
388 /* Execute the post-undump tasks in a random order. */
389 shuffle_allocation_tasks ();
391 for (int i
= 0; i
< allocation_task_count
; ++i
)
394 struct allocation_task
*task
= allocation_tasks
+ i
;
395 switch (task
->action
)
398 check_allocation (&task
->allocation
, i
);
399 free (task
->allocation
.data
);
400 task
->allocation
.data
= NULL
;
404 check_allocation (&task
->allocation
, i
);
405 task
->allocation
.data
= xrealloc
406 (task
->allocation
.data
, task
->allocation
.size
+ max_size
);
407 check_allocation (&task
->allocation
, i
);
410 case action_realloc_same
:
411 check_allocation (&task
->allocation
, i
);
412 task
->allocation
.data
= xrealloc
413 (task
->allocation
.data
, task
->allocation
.size
);
414 check_allocation (&task
->allocation
, i
);
417 case action_realloc_smaller
:
418 check_allocation (&task
->allocation
, i
);
419 size_t new_size
= task
->allocation
.size
- 1;
420 task
->allocation
.data
= xrealloc (task
->allocation
.data
, new_size
);
423 if (task
->allocation
.data
!= NULL
)
425 printf ("error: realloc with size zero did not deallocate\n");
428 /* No further action on this task. */
429 task
->action
= action_free
;
433 task
->allocation
.size
= new_size
;
434 check_allocation (&task
->allocation
, i
);
439 FAIL_EXIT1 ("task->action should never be action_count");
444 /* The second pass frees the objects which were allocated during the
447 printf ("info: second pass through allocation tasks\n");
449 shuffle_allocation_tasks ();
450 for (int i
= 0; i
< allocation_task_count
; ++i
)
453 struct allocation_task
*task
= allocation_tasks
+ i
;
454 switch (task
->action
)
457 /* Already freed, nothing to do. */
461 case action_realloc_same
:
462 case action_realloc_smaller
:
463 check_allocation (&task
->allocation
, i
);
464 free (task
->allocation
.data
);
465 task
->allocation
.data
= NULL
;
469 FAIL_EXIT1 ("task->action should never be action_count");
474 heap_activity_deallocate ();
476 /* Check that the malloc_get_state stub behaves in the intended
479 if (malloc_get_state () != NULL
)
481 printf ("error: malloc_get_state succeeded\n");
486 printf ("error: malloc_get_state: %m\n");
493 #include <support/test-driver.c>