]>
Commit | Line | Data |
---|---|---|
2d21ac55 A |
1 | /* |
2 | * memory_tests.c.c | |
3 | * xnu_quick_test | |
4 | * | |
5 | * Created by Jerry Cottingham on 4/12/05. | |
6 | * Copyright 2005 Apple Computer Inc. All rights reserved. | |
7 | * | |
8 | */ | |
9 | ||
10 | #include "tests.h" | |
b0d623f7 | 11 | #include <mach/mach.h> |
6d2010ae | 12 | #include <dirent.h> /* crashcount() */ |
2d21ac55 A |
13 | |
14 | extern char g_target_path[ PATH_MAX ]; | |
15 | ||
6d2010ae A |
16 | /* |
17 | * static to localize to this compilation unit; volatile to avoid register | |
18 | * optimization which would prevent modification by a signal handler. | |
19 | */ | |
20 | static volatile int my_err; | |
21 | ||
22 | /* | |
23 | * Handler; used by memory_tests() child to reset my_err so that it will | |
24 | * exit normally following a SIGBUS, rather than triggering a crash report; | |
25 | * this depends on setting the error non-zero before triggering the condition | |
26 | * that would trigger a SIGBUS. To avoid confusion, this is most easily done | |
27 | * right before the test in question, and if there are subsequent tests, then | |
28 | * undone immediately after to avoid false test negatives. | |
29 | */ | |
30 | void | |
31 | bus_handler(int sig, siginfo_t *si, void *mcontext) | |
32 | { | |
33 | /* Reset global error value when we see a SIGBUS */ | |
34 | if (sig == SIGBUS) | |
35 | my_err = 0; | |
36 | } | |
37 | ||
38 | /* | |
39 | * Count the number of crashes for us in /Library/Logs/CrashReporter/ | |
40 | * | |
41 | * XXX Assumes that CrashReporter uses our name as a prefix | |
42 | * XXX Assumes no one lese has the same prefix as our name | |
43 | */ | |
44 | int | |
45 | crashcount(char *namebuf1, char *namebuf2) | |
46 | { | |
47 | char *crashdir1 = "/Library/Logs/CrashReporter"; | |
48 | char *crashdir2 = "/Library/Logs/DiagnosticReports"; | |
49 | char *crash_file_pfx = "xnu_quick_test"; | |
50 | int crash_file_pfxlen = strlen(crash_file_pfx); | |
51 | struct stat sb; | |
316670eb | 52 | DIR *dirp1 = NULL, *dirp2 = NULL; |
6d2010ae A |
53 | struct dirent *dep1, *dep2; |
54 | int count = 0; | |
55 | ||
316670eb A |
56 | /* If we can't open the directory, dirp1 will be NULL */ |
57 | dirp1 = opendir(crashdir1); | |
6d2010ae | 58 | |
316670eb | 59 | while(dirp1 != NULL && ((dep1 = readdir(dirp1)) != NULL)) { |
6d2010ae A |
60 | if (strncmp(crash_file_pfx, dep1->d_name, crash_file_pfxlen)) |
61 | continue; | |
62 | /* record each one to get the last one */ | |
63 | if (namebuf1) { | |
64 | strcpy(namebuf1, crashdir1); | |
65 | strcat(namebuf1, "/"); | |
66 | strcat(namebuf1, dep1->d_name); | |
67 | } | |
68 | count++; | |
69 | } | |
70 | ||
316670eb A |
71 | if (dirp1 != NULL) |
72 | closedir(dirp1); | |
6d2010ae | 73 | |
316670eb A |
74 | #if !TARGET_OS_EMBEDDED |
75 | /* If we can't open the directory, dirp2 will be NULL */ | |
76 | dirp2 = opendir(crashdir2); | |
6d2010ae | 77 | |
316670eb | 78 | while(dirp2 != NULL && (dep2 = readdir(dirp2)) != NULL) { |
6d2010ae A |
79 | if (strncmp(crash_file_pfx, dep2->d_name, crash_file_pfxlen)) |
80 | continue; | |
81 | /* record each one to get the last one */ | |
82 | if (namebuf2) { | |
83 | strcpy(namebuf2, crashdir2); | |
84 | strcat(namebuf2, "/"); | |
85 | strcat(namebuf2, dep2->d_name); | |
86 | } | |
87 | count++; | |
88 | } | |
316670eb A |
89 | if (dirp2 != NULL) |
90 | closedir(dirp2); | |
91 | #endif | |
92 | return( count ); | |
6d2010ae A |
93 | } |
94 | ||
95 | ||
2d21ac55 A |
96 | /* ************************************************************************************************************** |
97 | * Test madvise, mincore, minherit, mlock, mlock, mmap, mprotect, msync, munmap system calls. | |
98 | * todo - see if Francois has better versions of these tests... | |
99 | * ************************************************************************************************************** | |
100 | */ | |
101 | int memory_tests( void * the_argp ) | |
102 | { | |
2d21ac55 A |
103 | int my_page_size, my_status; |
104 | int my_fd = -1; | |
105 | char * my_pathp = NULL; | |
106 | char * my_bufp = NULL; | |
107 | char * my_addr = NULL; | |
108 | char * my_test_page_p = NULL; | |
109 | ssize_t my_result; | |
110 | pid_t my_pid, my_wait_pid; | |
b0d623f7 | 111 | kern_return_t my_kr; |
6d2010ae A |
112 | struct sigaction my_sa; |
113 | static int my_crashcount; | |
114 | static char my_namebuf1[256]; /* XXX big enough */ | |
115 | static char my_namebuf2[256]; | |
2d21ac55 | 116 | |
b0d623f7 A |
117 | my_kr = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t*)&my_pathp, PATH_MAX, VM_FLAGS_ANYWHERE); |
118 | if(my_kr != KERN_SUCCESS){ | |
119 | printf( "vm_allocate failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
120 | goto test_failed_exit; | |
121 | } | |
122 | ||
2d21ac55 A |
123 | *my_pathp = 0x00; |
124 | strcat( my_pathp, &g_target_path[0] ); | |
125 | strcat( my_pathp, "/" ); | |
126 | ||
127 | /* create a test file */ | |
128 | my_err = create_random_name( my_pathp, 1 ); | |
129 | if ( my_err != 0 ) { | |
130 | goto test_failed_exit; | |
131 | } | |
132 | ||
133 | my_page_size = getpagesize( ); | |
b0d623f7 A |
134 | my_kr = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t*)&my_test_page_p, my_page_size, VM_FLAGS_ANYWHERE); |
135 | if(my_kr != KERN_SUCCESS){ | |
136 | printf( "vm_allocate failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
137 | goto test_failed_exit; | |
138 | } | |
139 | ||
2d21ac55 A |
140 | *my_test_page_p = 0x00; |
141 | strcat( my_test_page_p, "parent data" ); | |
142 | ||
143 | /* test minherit - share a page with child, add to the string in child then | |
144 | * check for modification after child terminates. | |
145 | */ | |
146 | my_err = minherit( my_test_page_p, my_page_size, VM_INHERIT_SHARE ); | |
147 | if ( my_err == -1 ) { | |
148 | printf( "minherit failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
149 | goto test_failed_exit; | |
150 | } | |
151 | ||
6d2010ae A |
152 | /* |
153 | * Find out how many crashes there have already been; if it's not | |
154 | * zero, then don't even attempt this test. | |
155 | */ | |
316670eb A |
156 | my_namebuf1[0] = '\0'; |
157 | my_namebuf2[0] = '\0'; | |
6d2010ae A |
158 | if ((my_crashcount = crashcount(my_namebuf1, my_namebuf2)) != 0) { |
159 | printf( "memtest aborted: can not distinguish our expected crash from \n"); | |
160 | printf( "%d existing crashes including %s \n", my_crashcount, my_namebuf2); | |
161 | goto test_failed_exit; | |
162 | } | |
163 | ||
2d21ac55 A |
164 | /* |
165 | * spin off a child process that we will use for testing. | |
166 | */ | |
167 | my_pid = fork( ); | |
168 | if ( my_pid == -1 ) { | |
169 | printf( "fork failed with errno %d - %s \n", errno, strerror( errno ) ); | |
170 | goto test_failed_exit; | |
171 | } | |
172 | if ( my_pid == 0 ) { | |
173 | /* | |
174 | * child process... | |
175 | */ | |
176 | strcat( my_test_page_p, " child data" ); | |
177 | ||
178 | /* create a test file in page size chunks */ | |
b0d623f7 A |
179 | my_kr = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t*)&my_bufp, (my_page_size * 10), VM_FLAGS_ANYWHERE); |
180 | if(my_kr != KERN_SUCCESS){ | |
181 | printf( "vm_allocate failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
182 | my_err = -1; | |
183 | goto exit_child; | |
184 | } | |
185 | ||
186 | /* test madvise on anonymous memory */ | |
187 | my_err = madvise(my_bufp, (my_page_size * 10), MADV_WILLNEED); | |
188 | if ( my_err == -1 ) { | |
189 | printf("madvise WILLNEED on anon memory failed with error %d - \"%s\" \n", errno, strerror( errno ) ); | |
2d21ac55 A |
190 | my_err = -1; |
191 | goto exit_child; | |
192 | } | |
193 | ||
194 | memset( my_bufp, 'j', (my_page_size * 10) ); | |
195 | my_fd = open( my_pathp, O_RDWR, 0 ); | |
196 | if ( my_fd == -1 ) { | |
197 | printf( "open call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
198 | my_err = -1; | |
199 | goto exit_child; | |
200 | } | |
201 | ||
b0d623f7 A |
202 | /* test madvise on anonymous memory */ |
203 | my_err = madvise(my_bufp, (my_page_size * 10), MADV_DONTNEED); | |
204 | if ( my_err == -1 ) { | |
205 | printf("madvise DONTNEED on anon memory failed with error %d - \"%s\" \n", errno, strerror( errno ) ); | |
206 | my_err = -1; | |
207 | goto exit_child; | |
208 | } | |
209 | ||
2d21ac55 A |
210 | my_result = write( my_fd, my_bufp, (my_page_size * 10) ); |
211 | if ( my_result == -1 ) { | |
212 | printf( "write call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
213 | my_err = -1; | |
214 | goto exit_child; | |
215 | } | |
216 | ||
217 | /* map the file into memory */ | |
218 | my_addr = (char *) mmap( NULL, (my_page_size * 2), (PROT_READ | PROT_WRITE), (MAP_FILE | MAP_SHARED), my_fd, 0 ); | |
219 | if ( my_addr == (char *) -1 ) { | |
220 | printf( "mmap call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
221 | my_err = -1; | |
222 | goto exit_child; | |
223 | } | |
224 | ||
225 | /* make sure we got the right data mapped */ | |
226 | if ( *my_addr != 'j' || *(my_addr + my_page_size) != 'j' ) { | |
227 | printf( "did not map in correct data \n" ); | |
228 | my_err = -1; | |
229 | goto exit_child; | |
230 | } | |
231 | ||
232 | /* test madvise */ | |
233 | my_err = madvise( my_addr, (my_page_size * 2), MADV_WILLNEED ); | |
234 | if ( my_err == -1 ) { | |
b0d623f7 A |
235 | printf( "madvise WILLNEED call failed with error %d - \"%s\" \n", errno, strerror( errno) ); |
236 | my_err = -1; | |
237 | goto exit_child; | |
238 | } | |
239 | ||
240 | my_err = madvise( my_addr, (my_page_size * 2), MADV_DONTNEED ); | |
241 | if ( my_err == -1 ) { | |
242 | printf( "madvise DONTNEED call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
2d21ac55 A |
243 | my_err = -1; |
244 | goto exit_child; | |
245 | } | |
246 | ||
247 | /* test mincore, mlock, mlock */ | |
248 | my_err = mlock( my_addr, my_page_size ); | |
249 | if ( my_err == -1 ) { | |
250 | printf( "mlock call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
251 | my_err = -1; | |
252 | goto exit_child; | |
253 | } | |
254 | ||
b0d623f7 A |
255 | /* mybufp is about to be reused, so test madvise on anonymous memory */ |
256 | my_err = madvise(my_bufp, (my_page_size * 10), MADV_FREE); | |
257 | if ( my_err == -1 ) { | |
258 | printf("madvise FREE on anon memory failed with error %d - \"%s\" \n", errno, strerror( errno ) ); | |
259 | my_err = -1; | |
260 | goto exit_child; | |
261 | } | |
262 | ||
2d21ac55 A |
263 | my_err = mincore( my_addr, 1, my_bufp ); |
264 | if ( my_err == -1 ) { | |
265 | printf( "mincore call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
266 | my_err = -1; | |
267 | goto exit_child; | |
268 | } | |
269 | /* page my_addr is in should be resident after mlock */ | |
270 | if ( (*my_bufp & MINCORE_INCORE) == 0 ) { | |
271 | printf( "mincore call failed to find resident page \n" ); | |
272 | my_err = -1; | |
273 | goto exit_child; | |
274 | } | |
275 | ||
276 | my_err = munlock( my_addr, my_page_size ); | |
277 | if ( my_err == -1 ) { | |
278 | printf( "munlock call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
279 | my_err = -1; | |
280 | goto exit_child; | |
281 | } | |
282 | ||
283 | /* modify first page then use msync to push data out */ | |
284 | memset( my_addr, 'x', my_page_size ); | |
285 | my_err = msync( my_addr, my_page_size, (MS_SYNC | MS_INVALIDATE) ); | |
286 | if ( my_err == -1 ) { | |
287 | printf( "msync call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
288 | my_err = -1; | |
289 | goto exit_child; | |
290 | } | |
291 | ||
b0d623f7 A |
292 | /* test madvise */ |
293 | my_err = madvise( my_addr, (my_page_size * 2), MADV_DONTNEED ); | |
294 | if ( my_err == -1 ) { | |
295 | printf( "madvise DONTNEED call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
296 | my_err = -1; | |
297 | goto exit_child; | |
298 | } | |
299 | ||
300 | /* test madvise */ | |
301 | my_err = madvise( my_addr, (my_page_size * 2), MADV_FREE ); | |
302 | if ( my_err == -1 ) { | |
303 | printf( "madvise FREE call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
304 | my_err = -1; | |
305 | goto exit_child; | |
306 | } | |
307 | ||
2d21ac55 A |
308 | /* verify that the file was updated */ |
309 | lseek( my_fd, 0, SEEK_SET ); | |
310 | bzero( (void *)my_bufp, my_page_size ); | |
311 | my_result = read( my_fd, my_bufp, my_page_size ); | |
312 | if ( my_result == -1 ) { | |
313 | printf( "read call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
314 | my_err = -1; | |
315 | goto exit_child; | |
316 | } | |
317 | if ( *my_bufp != 'x' ) { | |
318 | printf( "msync did not flush correct data \n" ); | |
319 | my_err = -1; | |
320 | goto exit_child; | |
321 | } | |
322 | ||
323 | /* unmap our test page */ | |
324 | my_err = munmap( my_addr, (my_page_size * 2) ); | |
325 | if ( my_err == -1 ) { | |
326 | printf( "munmap call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
327 | my_err = -1; | |
328 | goto exit_child; | |
329 | } | |
330 | my_addr = NULL; | |
331 | ||
332 | /* map the file into memory again for mprotect test */ | |
333 | my_addr = (char *) mmap( NULL, (my_page_size * 2), (PROT_READ | PROT_WRITE), (MAP_FILE | MAP_SHARED), my_fd, 0 ); | |
334 | if ( my_addr == (char *) -1 ) { | |
335 | printf( "mmap call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
336 | my_err = -1; | |
337 | goto exit_child; | |
338 | } | |
339 | *my_addr = 'a'; | |
340 | ||
341 | ||
342 | ||
343 | /* test mprotect - change protection to only PROT_READ */ | |
344 | my_err = mprotect( my_addr, my_page_size, PROT_READ ); | |
345 | if ( my_err == -1 ) { | |
346 | printf( "mprotect call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
347 | my_err = -1; | |
348 | goto exit_child; | |
349 | } | |
350 | ||
6d2010ae A |
351 | /* |
352 | * Establish SIGBUS handler; will reset (disable itself) if it fires; | |
353 | * we would need how to recover from the exceptional condition that | |
354 | * raised the SIGBUS by modifying the contents of the (opaque to us) | |
355 | * mcontext in order to prevent this from being terminal, so we let | |
356 | * it be terminal. This is enough to avoid triggering crash reporter. | |
357 | */ | |
358 | my_sa.sa_sigaction = bus_handler; | |
359 | my_sa.sa_flags = SA_SIGINFO | SA_RESETHAND; | |
360 | if ((my_err = sigaction(SIGBUS, &my_sa, NULL)) != 0) { | |
361 | printf("sigaction call failed with error %d - \"%s\" \n", errno, strerror( errno) ); | |
362 | my_err = -1; | |
363 | goto exit_child; | |
364 | } | |
365 | ||
366 | my_err = -1; /* default to error out if we do NOT trigger a SIGBUS */ | |
367 | ||
2d21ac55 | 368 | *my_addr = 'z'; /* should cause SIGBUS signal (we look for this at child termination within the parent) */ |
2d21ac55 | 369 | |
6d2010ae A |
370 | /* NOTREACHED */ |
371 | ||
372 | printf("Expected SIGBUS signal, got nothing!\n"); | |
373 | my_err = -1; | |
2d21ac55 A |
374 | exit_child: |
375 | exit( my_err ); | |
376 | } | |
377 | ||
2d21ac55 | 378 | /* parent process - |
6d2010ae | 379 | * we should get no error if the child has completed all tests successfully |
2d21ac55 A |
380 | */ |
381 | my_wait_pid = wait4( my_pid, &my_status, 0, NULL ); | |
382 | if ( my_wait_pid == -1 ) { | |
383 | printf( "wait4 failed with errno %d - %s \n", errno, strerror( errno ) ); | |
384 | goto test_failed_exit; | |
385 | } | |
386 | ||
387 | /* wait4 should return our child's pid when it exits */ | |
388 | if ( my_wait_pid != my_pid ) { | |
389 | printf( "wait4 did not return child pid - returned %d should be %d \n", my_wait_pid, my_pid ); | |
390 | goto test_failed_exit; | |
391 | } | |
392 | ||
6d2010ae A |
393 | /* If we were not signalled, or we died from an unexpected signal, report it. |
394 | */ | |
395 | if ( !WIFSIGNALED( my_status ) || WTERMSIG( my_status ) != SIGBUS) { | |
396 | printf( "wait4 returned child died of status - 0x%02X \n", my_status ); | |
2d21ac55 A |
397 | goto test_failed_exit; |
398 | } | |
399 | ||
6d2010ae A |
400 | /* |
401 | * Wait long enough that CrashReporter has finished. | |
402 | */ | |
403 | sleep(5); | |
404 | ||
405 | /* | |
406 | * Find out how many crashes there have already been; if it's not | |
407 | * one, then don't even attempt this test. | |
408 | */ | |
316670eb A |
409 | my_namebuf1[0] = '\0'; |
410 | my_namebuf2[0] = '\0'; | |
411 | my_crashcount = crashcount(my_namebuf1, my_namebuf2); | |
412 | if (!(my_crashcount == 1 || my_crashcount == 2)) { | |
6d2010ae | 413 | printf( "child did not crash as expected \n"); |
316670eb | 414 | printf( "saw %d crashes including %s \n", my_crashcount, my_namebuf1); |
6d2010ae A |
415 | goto test_failed_exit; |
416 | } | |
417 | ||
418 | /* post-remove the expected crash report */ | |
316670eb | 419 | if (unlink(my_namebuf1) && !(errno == ENOENT || errno == ENOTDIR)) { |
6d2010ae A |
420 | printf("unlink of expected crash report '%s' failed \n", my_namebuf1); |
421 | goto test_failed_exit; | |
422 | } | |
316670eb A |
423 | #if !TARGET_OS_EMBEDDED |
424 | /* /Library/Logs/DiagnosticReports/ does not exist on embedded targets. */ | |
425 | if (unlink(my_namebuf2) && !(errno == ENOENT || errno == ENOTDIR)) { | |
6d2010ae A |
426 | printf("unlink of expected crash report '%s' failed \n", my_namebuf2); |
427 | goto test_failed_exit; | |
428 | } | |
316670eb | 429 | #endif |
2d21ac55 A |
430 | /* make sure shared page got modified in child */ |
431 | if ( strcmp( my_test_page_p, "parent data child data" ) != 0 ) { | |
432 | printf( "minherit did not work correctly - shared page looks wrong \n" ); | |
433 | goto test_failed_exit; | |
434 | } | |
435 | my_err = 0; | |
436 | goto test_passed_exit; | |
437 | ||
438 | test_failed_exit: | |
439 | my_err = -1; | |
440 | ||
441 | test_passed_exit: | |
442 | if ( my_pathp != NULL ) { | |
443 | remove( my_pathp ); | |
b0d623f7 | 444 | vm_deallocate(mach_task_self(), (vm_address_t)my_pathp, PATH_MAX); |
2d21ac55 A |
445 | } |
446 | if ( my_test_page_p != NULL ) { | |
b0d623f7 | 447 | vm_deallocate(mach_task_self(), (vm_address_t)my_test_page_p, my_page_size); |
2d21ac55 A |
448 | } |
449 | return( my_err ); | |
450 | } | |
451 |