]>
Commit | Line | Data |
---|---|---|
1bd5483e | 1 | /* Test allocation function behavior on allocation failure. |
6d7e8eda | 2 | Copyright (C) 2015-2023 Free Software Foundation, Inc. |
1bd5483e FW |
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 License as | |
7 | published by the Free Software Foundation; either version 2.1 of the | |
8 | 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; see the file COPYING.LIB. If | |
5a82c748 | 17 | not, see <https://www.gnu.org/licenses/>. */ |
1bd5483e FW |
18 | |
19 | /* This test case attempts to trigger various unusual conditions | |
20 | related to allocation failures, notably switching to a different | |
21 | arena, and falling back to mmap (via sysmalloc). */ | |
22 | ||
23 | #include <errno.h> | |
24 | #include <malloc.h> | |
25 | #include <pthread.h> | |
26 | #include <stdbool.h> | |
27 | #include <stdint.h> | |
28 | #include <stdio.h> | |
29 | #include <stdlib.h> | |
30 | #include <sys/resource.h> | |
31 | #include <sys/wait.h> | |
32 | #include <unistd.h> | |
33 | ||
34 | /* Wrapper for calloc with an optimization barrier. */ | |
35 | static void * | |
36 | __attribute__ ((noinline, noclone)) | |
37 | allocate_zeroed (size_t a, size_t b) | |
38 | { | |
39 | return calloc (a, b); | |
40 | } | |
41 | ||
42 | /* System page size, as determined by sysconf (_SC_PAGE_SIZE). */ | |
43 | static unsigned long page_size; | |
44 | ||
45 | /* Test parameters. */ | |
46 | static size_t allocation_size; | |
47 | static size_t alignment; | |
48 | static enum { | |
49 | with_malloc, | |
50 | with_realloc, | |
51 | with_aligned_alloc, | |
52 | with_memalign, | |
53 | with_posix_memalign, | |
54 | with_valloc, | |
55 | with_pvalloc, | |
56 | with_calloc, | |
57 | last_allocation_function = with_calloc | |
58 | } allocation_function; | |
59 | ||
60 | /* True if an allocation function uses the alignment test | |
61 | parameter. */ | |
62 | const static bool alignment_sensitive[last_allocation_function + 1] = | |
63 | { | |
64 | [with_aligned_alloc] = true, | |
65 | [with_memalign] = true, | |
66 | [with_posix_memalign] = true, | |
67 | }; | |
68 | ||
69 | /* Combined pointer/expected alignment result of an allocation | |
70 | function. */ | |
71 | struct allocate_result { | |
72 | void *pointer; | |
73 | size_t alignment; | |
74 | }; | |
75 | ||
76 | /* Call the allocation function specified by allocation_function, with | |
77 | allocation_size and alignment (if applicable) as arguments. No | |
78 | alignment check. */ | |
79 | static struct allocate_result | |
80 | allocate_1 (void) | |
81 | { | |
82 | switch (allocation_function) | |
83 | { | |
84 | case with_malloc: | |
85 | return (struct allocate_result) | |
86 | {malloc (allocation_size), _Alignof (max_align_t)}; | |
87 | case with_realloc: | |
88 | { | |
89 | void *p = realloc (NULL, 16); | |
90 | void *q; | |
91 | if (p == NULL) | |
92 | q = NULL; | |
93 | else | |
94 | { | |
95 | q = realloc (p, allocation_size); | |
96 | if (q == NULL) | |
97 | free (p); | |
98 | } | |
99 | return (struct allocate_result) {q, _Alignof (max_align_t)}; | |
100 | } | |
101 | case with_aligned_alloc: | |
102 | { | |
103 | void *p = aligned_alloc (alignment, allocation_size); | |
104 | return (struct allocate_result) {p, alignment}; | |
105 | } | |
106 | case with_memalign: | |
107 | { | |
108 | void *p = memalign (alignment, allocation_size); | |
109 | return (struct allocate_result) {p, alignment}; | |
110 | } | |
111 | case with_posix_memalign: | |
112 | { | |
113 | void *p; | |
114 | if (posix_memalign (&p, alignment, allocation_size)) | |
115 | { | |
116 | if (errno == ENOMEM) | |
117 | p = NULL; | |
118 | else | |
119 | { | |
120 | printf ("error: posix_memalign (p, %zu, %zu): %m\n", | |
121 | alignment, allocation_size); | |
122 | abort (); | |
123 | } | |
124 | } | |
125 | return (struct allocate_result) {p, alignment}; | |
126 | } | |
127 | case with_valloc: | |
128 | { | |
129 | void *p = valloc (allocation_size); | |
130 | return (struct allocate_result) {p, page_size}; | |
131 | } | |
132 | case with_pvalloc: | |
133 | { | |
134 | void *p = pvalloc (allocation_size); | |
135 | return (struct allocate_result) {p, page_size}; | |
136 | } | |
137 | case with_calloc: | |
138 | { | |
139 | char *p = allocate_zeroed (1, allocation_size); | |
140 | /* Check for non-zero bytes. */ | |
141 | if (p != NULL) | |
142 | for (size_t i = 0; i < allocation_size; ++i) | |
143 | if (p[i] != 0) | |
144 | { | |
145 | printf ("error: non-zero byte at offset %zu\n", i); | |
146 | abort (); | |
147 | } | |
148 | return (struct allocate_result) {p, _Alignof (max_align_t)}; | |
149 | } | |
150 | } | |
151 | abort (); | |
152 | } | |
153 | ||
154 | /* Call allocate_1 and perform the alignment check on the result. */ | |
155 | static void * | |
156 | allocate (void) | |
157 | { | |
158 | struct allocate_result r = allocate_1 (); | |
159 | if ((((uintptr_t) r.pointer) & (r.alignment - 1)) != 0) | |
160 | { | |
161 | printf ("error: allocation function %d, size %zu not aligned to %zu\n", | |
162 | (int) allocation_function, allocation_size, r.alignment); | |
163 | abort (); | |
164 | } | |
165 | return r.pointer; | |
166 | } | |
167 | ||
168 | /* Barriers to synchronize thread creation and termination. */ | |
169 | static pthread_barrier_t start_barrier; | |
170 | static pthread_barrier_t end_barrier; | |
171 | ||
172 | /* Thread function which performs the allocation test. Called by | |
173 | pthread_create and from the main thread. */ | |
174 | static void * | |
175 | allocate_thread (void *closure) | |
176 | { | |
177 | /* Wait for the creation of all threads. */ | |
178 | { | |
179 | int ret = pthread_barrier_wait (&start_barrier); | |
180 | if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) | |
181 | { | |
182 | errno = ret; | |
183 | printf ("error: pthread_barrier_wait: %m\n"); | |
184 | abort (); | |
185 | } | |
186 | } | |
187 | ||
188 | /* Allocate until we run out of memory, creating a single-linked | |
189 | list. */ | |
190 | struct list { | |
191 | struct list *next; | |
192 | }; | |
193 | struct list *head = NULL; | |
194 | while (true) | |
195 | { | |
196 | struct list *e = allocate (); | |
197 | if (e == NULL) | |
198 | break; | |
199 | ||
200 | e->next = head; | |
201 | head = e; | |
202 | } | |
203 | ||
204 | /* Wait for the allocation of all available memory. */ | |
205 | { | |
206 | int ret = pthread_barrier_wait (&end_barrier); | |
207 | if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) | |
208 | { | |
209 | errno = ret; | |
210 | printf ("error: pthread_barrier_wait: %m\n"); | |
211 | abort (); | |
212 | } | |
213 | } | |
214 | ||
215 | /* Free the allocated memory. */ | |
216 | while (head != NULL) | |
217 | { | |
218 | struct list *next = head->next; | |
219 | free (head); | |
220 | head = next; | |
221 | } | |
222 | ||
223 | return NULL; | |
224 | } | |
225 | ||
226 | /* Number of threads (plus the main thread. */ | |
227 | enum { thread_count = 8 }; | |
228 | ||
229 | /* Thread attribute to request creation of threads with a non-default | |
230 | stack size which is rather small. This avoids interfering with the | |
231 | configured address space limit. */ | |
232 | static pthread_attr_t small_stack; | |
233 | ||
234 | /* Runs one test in multiple threads, all in a subprocess so that | |
235 | subsequent tests do not interfere with each other. */ | |
236 | static void | |
237 | run_one (void) | |
238 | { | |
239 | /* Isolate the tests in a subprocess, so that we can start over | |
240 | from scratch. */ | |
241 | pid_t pid = fork (); | |
242 | if (pid == 0) | |
243 | { | |
244 | /* In the child process. Create the allocation threads. */ | |
245 | pthread_t threads[thread_count]; | |
246 | ||
247 | for (unsigned i = 0; i < thread_count; ++i) | |
248 | { | |
249 | int ret = pthread_create (threads + i, &small_stack, allocate_thread, NULL); | |
250 | if (ret != 0) | |
251 | { | |
252 | errno = ret; | |
253 | printf ("error: pthread_create: %m\n"); | |
254 | abort (); | |
255 | } | |
256 | } | |
257 | ||
258 | /* Also run the test on the main thread. */ | |
259 | allocate_thread (NULL); | |
260 | ||
261 | for (unsigned i = 0; i < thread_count; ++i) | |
262 | { | |
263 | int ret = pthread_join (threads[i], NULL); | |
264 | if (ret != 0) | |
265 | { | |
266 | errno = ret; | |
267 | printf ("error: pthread_join: %m\n"); | |
268 | abort (); | |
269 | } | |
270 | } | |
271 | _exit (0); | |
272 | } | |
273 | else if (pid < 0) | |
274 | { | |
275 | printf ("error: fork: %m\n"); | |
276 | abort (); | |
277 | } | |
278 | ||
279 | /* In the parent process. Wait for the child process to exit. */ | |
280 | int status; | |
281 | if (waitpid (pid, &status, 0) < 0) | |
282 | { | |
283 | printf ("error: waitpid: %m\n"); | |
284 | abort (); | |
285 | } | |
286 | if (status != 0) | |
287 | { | |
288 | printf ("error: exit status %d from child process\n", status); | |
289 | exit (1); | |
290 | } | |
291 | } | |
292 | ||
293 | /* Run all applicable allocation functions for the current test | |
294 | parameters. */ | |
295 | static void | |
296 | run_allocation_functions (void) | |
297 | { | |
298 | for (int af = 0; af <= last_allocation_function; ++af) | |
299 | { | |
300 | /* Run alignment-sensitive functions for non-default | |
301 | alignments. */ | |
302 | if (alignment_sensitive[af] != (alignment != 0)) | |
303 | continue; | |
304 | allocation_function = af; | |
305 | run_one (); | |
306 | } | |
307 | } | |
308 | ||
309 | int | |
310 | do_test (void) | |
311 | { | |
312 | /* Limit the number of malloc arenas. We use a very low number so | |
313 | that despute the address space limit configured below, all | |
314 | requested arenas a can be created. */ | |
315 | if (mallopt (M_ARENA_MAX, 2) == 0) | |
316 | { | |
317 | printf ("error: mallopt (M_ARENA_MAX) failed\n"); | |
318 | return 1; | |
319 | } | |
320 | ||
321 | /* Determine the page size. */ | |
322 | { | |
323 | long ret = sysconf (_SC_PAGE_SIZE); | |
324 | if (ret < 0) | |
325 | { | |
326 | printf ("error: sysconf (_SC_PAGE_SIZE): %m\n"); | |
327 | return 1; | |
328 | } | |
329 | page_size = ret; | |
330 | } | |
331 | ||
332 | /* Limit the size of the process, so that memory allocation in | |
333 | allocate_thread will eventually fail, without impacting the | |
334 | entire system. */ | |
335 | { | |
336 | struct rlimit limit; | |
337 | if (getrlimit (RLIMIT_AS, &limit) != 0) | |
338 | { | |
339 | printf ("getrlimit (RLIMIT_AS) failed: %m\n"); | |
340 | return 1; | |
341 | } | |
342 | long target = 200 * 1024 * 1024; | |
343 | if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target) | |
344 | { | |
345 | limit.rlim_cur = target; | |
346 | if (setrlimit (RLIMIT_AS, &limit) != 0) | |
347 | { | |
348 | printf ("setrlimit (RLIMIT_AS) failed: %m\n"); | |
349 | return 1; | |
350 | } | |
351 | } | |
352 | } | |
353 | ||
354 | /* Initialize thread attribute with a reduced stack size. */ | |
355 | { | |
356 | int ret = pthread_attr_init (&small_stack); | |
357 | if (ret != 0) | |
358 | { | |
359 | errno = ret; | |
360 | printf ("error: pthread_attr_init: %m\n"); | |
361 | abort (); | |
362 | } | |
363 | unsigned long stack_size = ((256 * 1024) / page_size) * page_size; | |
364 | if (stack_size < 4 * page_size) | |
365 | stack_size = 8 * page_size; | |
366 | ret = pthread_attr_setstacksize (&small_stack, stack_size); | |
367 | if (ret != 0) | |
368 | { | |
369 | errno = ret; | |
370 | printf ("error: pthread_attr_setstacksize: %m\n"); | |
371 | abort (); | |
372 | } | |
373 | } | |
374 | ||
375 | /* Initialize the barriers. We run thread_count threads, plus 1 for | |
376 | the main thread. */ | |
377 | { | |
378 | int ret = pthread_barrier_init (&start_barrier, NULL, thread_count + 1); | |
379 | if (ret != 0) | |
380 | { | |
381 | errno = ret; | |
382 | printf ("error: pthread_barrier_init: %m\n"); | |
383 | abort (); | |
384 | } | |
385 | ||
386 | ret = pthread_barrier_init (&end_barrier, NULL, thread_count + 1); | |
387 | if (ret != 0) | |
388 | { | |
389 | errno = ret; | |
390 | printf ("error: pthread_barrier_init: %m\n"); | |
391 | abort (); | |
392 | } | |
393 | } | |
394 | ||
395 | allocation_size = 144; | |
396 | run_allocation_functions (); | |
397 | allocation_size = page_size; | |
398 | run_allocation_functions (); | |
399 | ||
400 | alignment = 128; | |
401 | allocation_size = 512; | |
402 | run_allocation_functions (); | |
403 | ||
404 | allocation_size = page_size; | |
405 | run_allocation_functions (); | |
406 | ||
407 | allocation_size = 17 * page_size; | |
408 | run_allocation_functions (); | |
409 | ||
410 | /* Deallocation the barriers and the thread attribute. */ | |
411 | { | |
412 | int ret = pthread_barrier_destroy (&end_barrier); | |
413 | if (ret != 0) | |
414 | { | |
415 | errno = ret; | |
416 | printf ("error: pthread_barrier_destroy: %m\n"); | |
417 | return 1; | |
418 | } | |
419 | ret = pthread_barrier_destroy (&start_barrier); | |
420 | if (ret != 0) | |
421 | { | |
422 | errno = ret; | |
423 | printf ("error: pthread_barrier_destroy: %m\n"); | |
424 | return 1; | |
425 | } | |
426 | ret = pthread_attr_destroy (&small_stack); | |
427 | if (ret != 0) | |
428 | { | |
429 | errno = ret; | |
430 | printf ("error: pthread_attr_destroy: %m\n"); | |
431 | return 1; | |
432 | } | |
433 | } | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | /* The repeated allocations take some time on slow machines. */ | |
983a9637 | 439 | #define TIMEOUT 100 |
1bd5483e FW |
440 | |
441 | #define TEST_FUNCTION do_test () | |
442 | #include "../test-skeleton.c" |