1 /* Emulate Emacs heap dumping to test malloc_set_state.
2 Copyright (C) 2001-2022 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, shrinked. */
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 *
dumped_heap_alloc(size_t length)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
rand_next(unsigned long long * seed)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
randomize_buffer(unsigned char * buffer,size_t length,unsigned long long seed)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);
161 }
162
163 /* Dumps the buffer to standard output, in hexadecimal. */
164 static void
dump_hex(unsigned char * buffer,size_t length)165 dump_hex (unsigned char *buffer, size_t length)
166 {
167 for (int i = 0; i < length; ++i)
168 printf (" %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
check_allocation(const struct allocation * alloc,int index)185 check_allocation (const struct allocation *alloc, int index)
186 {
187 size_t size = alloc->size;
188 if (alloc->data == NULL)
189 {
190 printf ("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 ("error: invalid allocation size %zu at %d, seed %u\n",
200 size, index, alloc->seed);
201 errors = true;
202 return;
203 }
204 randomize_buffer (expected, size, alloc->seed);
205 if (memcmp (alloc->data, expected, size) != 0)
206 {
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);
211 putc ('\n', stdout);
212 printf (" actual:");
213 dump_hex (alloc->data, size);
214 putc ('\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
shuffle_allocation_tasks(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 (&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
initial_allocations(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 (task->allocation.size);
272 randomize_buffer (task->allocation.data, task->allocation.size,
273 task->allocation.seed);
274 }
275
276 for (int i = 0; i < allocation_task_count; ++i)
277 check_allocation (&allocation_tasks[i].allocation, 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
init_heap(void)288 init_heap (void)
289 {
290 if (test_verbose)
291 printf ("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
heap_activity(void)321 heap_activity (void)
322 {
323 /* Only do this from time to time. */
324 if ((rand_next (&global_seed) % 4) == 0)
325 {
326 int slot = rand_next (&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 (&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);
335 }
336 else
337 {
338 check_allocation (alloc, 1000 + slot);
339 free (alloc->data);
340 alloc->data = NULL;
341 }
342 }
343 }
344
345 static void
heap_activity_deallocate(void)346 heap_activity_deallocate (void)
347 {
348 for (int i = 0; i < heap_activity_allocations_count; ++i)
349 free (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
full_heap_check(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 (&allocation_tasks[i].allocation, 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 (heap_activity_allocations + i, i);
366 }
367
368 /* Used as an optimization barrier to force a heap allocation. */
369 __attribute__ ((noinline, noclone))
370 static void
my_free(void * ptr)371 my_free (void *ptr)
372 {
373 free (ptr);
374 }
375
376 static int
do_test(void)377 do_test (void)
378 {
379 my_free (malloc (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 ("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 (&task->allocation, i);
399 free (task->allocation.data);
400 task->allocation.data = NULL;
401 break;
402
403 case action_realloc:
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);
408 break;
409
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);
415 break;
416
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);
421 if (new_size == 0)
422 {
423 if (task->allocation.data != NULL)
424 {
425 printf ("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 (&task->allocation, 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 ("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 (&task->allocation, i);
464 free (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 ("error: malloc_get_state succeeded\n");
482 errors = true;
483 }
484 if (errno != ENOSYS)
485 {
486 printf ("error: malloc_get_state: %m\n");
487 errors = true;
488 }
489
490 return errors;
491 }
492
493 #include <support/test-driver.c>
494