]>
Commit | Line | Data |
---|---|---|
ef4f9764 | 1 | /* Minimal malloc implementation for interposition tests. |
04277e02 | 2 | Copyright (C) 2016-2019 Free Software Foundation, Inc. |
ef4f9764 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/>. */ |
ef4f9764 FW |
18 | |
19 | #include "tst-interpose-aux.h" | |
20 | ||
21 | #include <errno.h> | |
22 | #include <stdarg.h> | |
23 | #include <stddef.h> | |
ceaa9889 | 24 | #include <stdint.h> |
ef4f9764 FW |
25 | #include <stdio.h> |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <sys/mman.h> | |
29 | #include <sys/uio.h> | |
30 | #include <unistd.h> | |
31 | ||
32 | #if INTERPOSE_THREADS | |
33 | #include <pthread.h> | |
34 | #endif | |
35 | ||
36 | /* Print the error message and terminate the process with status 1. */ | |
37 | __attribute__ ((noreturn)) | |
38 | __attribute__ ((format (printf, 1, 2))) | |
39 | static void * | |
40 | fail (const char *format, ...) | |
41 | { | |
42 | /* This assumes that vsnprintf will not call malloc. It does not do | |
43 | so for the format strings we use. */ | |
44 | char message[4096]; | |
45 | va_list ap; | |
46 | va_start (ap, format); | |
47 | vsnprintf (message, sizeof (message), format, ap); | |
48 | va_end (ap); | |
49 | ||
50 | enum { count = 3 }; | |
51 | struct iovec iov[count]; | |
52 | ||
53 | iov[0].iov_base = (char *) "error: "; | |
54 | iov[1].iov_base = (char *) message; | |
55 | iov[2].iov_base = (char *) "\n"; | |
56 | ||
57 | for (int i = 0; i < count; ++i) | |
58 | iov[i].iov_len = strlen (iov[i].iov_base); | |
59 | ||
60 | int unused __attribute__ ((unused)); | |
61 | unused = writev (STDOUT_FILENO, iov, count); | |
62 | _exit (1); | |
63 | } | |
64 | ||
65 | #if INTERPOSE_THREADS | |
66 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | |
67 | #endif | |
68 | ||
69 | static void | |
70 | lock (void) | |
71 | { | |
72 | #if INTERPOSE_THREADS | |
73 | int ret = pthread_mutex_lock (&mutex); | |
74 | if (ret != 0) | |
75 | { | |
76 | errno = ret; | |
77 | fail ("pthread_mutex_lock: %m"); | |
78 | } | |
79 | #endif | |
80 | } | |
81 | ||
82 | static void | |
83 | unlock (void) | |
84 | { | |
85 | #if INTERPOSE_THREADS | |
86 | int ret = pthread_mutex_unlock (&mutex); | |
87 | if (ret != 0) | |
88 | { | |
89 | errno = ret; | |
90 | fail ("pthread_mutex_unlock: %m"); | |
91 | } | |
92 | #endif | |
93 | } | |
94 | ||
95 | struct __attribute__ ((aligned (__alignof__ (max_align_t)))) allocation_header | |
96 | { | |
97 | size_t allocation_index; | |
98 | size_t allocation_size; | |
99 | }; | |
100 | ||
101 | /* Array of known allocations, to track invalid frees. */ | |
102 | enum { max_allocations = 65536 }; | |
103 | static struct allocation_header *allocations[max_allocations]; | |
104 | static size_t allocation_index; | |
105 | static size_t deallocation_count; | |
106 | ||
107 | /* Sanity check for successful malloc interposition. */ | |
108 | __attribute__ ((destructor)) | |
109 | static void | |
110 | check_for_allocations (void) | |
111 | { | |
112 | if (allocation_index == 0) | |
113 | { | |
114 | /* Make sure that malloc is called at least once from libc. */ | |
115 | void *volatile ptr = strdup ("ptr"); | |
ef4f9764 FW |
116 | /* Compiler barrier. The strdup function calls malloc, which |
117 | updates allocation_index, but strdup is marked __THROW, so | |
118 | the compiler could optimize away the reload. */ | |
119 | __asm__ volatile ("" ::: "memory"); | |
e4e26210 | 120 | free (ptr); |
ef4f9764 FW |
121 | /* If the allocation count is still zero, it means we did not |
122 | interpose malloc successfully. */ | |
123 | if (allocation_index == 0) | |
124 | fail ("malloc does not seem to have been interposed"); | |
125 | } | |
126 | } | |
127 | ||
128 | static struct allocation_header *get_header (const char *op, void *ptr) | |
129 | { | |
130 | struct allocation_header *header = ((struct allocation_header *) ptr) - 1; | |
131 | if (header->allocation_index >= allocation_index) | |
132 | fail ("%s: %p: invalid allocation index: %zu (not less than %zu)", | |
133 | op, ptr, header->allocation_index, allocation_index); | |
134 | if (allocations[header->allocation_index] != header) | |
135 | fail ("%s: %p: allocation pointer does not point to header, but %p", | |
136 | op, ptr, allocations[header->allocation_index]); | |
137 | return header; | |
138 | } | |
139 | ||
140 | /* Internal helper functions. Those must be called while the lock is | |
141 | acquired. */ | |
142 | ||
143 | static void * | |
144 | malloc_internal (size_t size) | |
145 | { | |
146 | if (allocation_index == max_allocations) | |
147 | { | |
148 | errno = ENOMEM; | |
149 | return NULL; | |
150 | } | |
151 | size_t allocation_size = size + sizeof (struct allocation_header); | |
152 | if (allocation_size < size) | |
153 | { | |
154 | errno = ENOMEM; | |
155 | return NULL; | |
156 | } | |
157 | ||
158 | size_t index = allocation_index++; | |
159 | void *result = mmap (NULL, allocation_size, PROT_READ | PROT_WRITE, | |
160 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
161 | if (result == MAP_FAILED) | |
162 | return NULL; | |
163 | allocations[index] = result; | |
164 | *allocations[index] = (struct allocation_header) | |
165 | { | |
166 | .allocation_index = index, | |
167 | .allocation_size = allocation_size | |
168 | }; | |
169 | return allocations[index] + 1; | |
170 | } | |
171 | ||
172 | static void | |
173 | free_internal (const char *op, struct allocation_header *header) | |
174 | { | |
175 | size_t index = header->allocation_index; | |
176 | int result = mprotect (header, header->allocation_size, PROT_NONE); | |
177 | if (result != 0) | |
178 | fail ("%s: mprotect (%p, %zu): %m", op, header, header->allocation_size); | |
179 | /* Catch double-free issues. */ | |
180 | allocations[index] = NULL; | |
181 | ++deallocation_count; | |
182 | } | |
183 | ||
184 | static void * | |
185 | realloc_internal (void *ptr, size_t new_size) | |
186 | { | |
187 | struct allocation_header *header = get_header ("realloc", ptr); | |
188 | size_t old_size = header->allocation_size - sizeof (struct allocation_header); | |
189 | if (old_size >= new_size) | |
190 | return ptr; | |
191 | ||
192 | void *newptr = malloc_internal (new_size); | |
193 | if (newptr == NULL) | |
194 | return NULL; | |
195 | memcpy (newptr, ptr, old_size); | |
196 | free_internal ("realloc", header); | |
197 | return newptr; | |
198 | } | |
199 | ||
200 | /* Public interfaces. These functions must perform locking. */ | |
201 | ||
202 | size_t | |
203 | malloc_allocation_count (void) | |
204 | { | |
205 | lock (); | |
206 | size_t count = allocation_index; | |
207 | unlock (); | |
208 | return count; | |
209 | } | |
210 | ||
211 | size_t | |
212 | malloc_deallocation_count (void) | |
213 | { | |
214 | lock (); | |
215 | size_t count = deallocation_count; | |
216 | unlock (); | |
217 | return count; | |
218 | } | |
219 | void * | |
220 | malloc (size_t size) | |
221 | { | |
222 | lock (); | |
223 | void *result = malloc_internal (size); | |
224 | unlock (); | |
225 | return result; | |
226 | } | |
227 | ||
228 | void | |
229 | free (void *ptr) | |
230 | { | |
231 | if (ptr == NULL) | |
232 | return; | |
233 | lock (); | |
234 | struct allocation_header *header = get_header ("free", ptr); | |
235 | free_internal ("free", header); | |
236 | unlock (); | |
237 | } | |
238 | ||
239 | void * | |
240 | calloc (size_t a, size_t b) | |
241 | { | |
242 | if (b > 0 && a > SIZE_MAX / b) | |
243 | { | |
244 | errno = ENOMEM; | |
245 | return NULL; | |
246 | } | |
247 | lock (); | |
248 | /* malloc_internal uses mmap, so the memory is zeroed. */ | |
249 | void *result = malloc_internal (a * b); | |
250 | unlock (); | |
251 | return result; | |
252 | } | |
253 | ||
254 | void * | |
255 | realloc (void *ptr, size_t n) | |
256 | { | |
257 | if (n ==0) | |
258 | { | |
259 | free (ptr); | |
260 | return NULL; | |
261 | } | |
262 | else if (ptr == NULL) | |
263 | return malloc (n); | |
264 | else | |
265 | { | |
266 | lock (); | |
267 | void *result = realloc_internal (ptr, n); | |
268 | unlock (); | |
269 | return result; | |
270 | } | |
271 | } |