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"
11#include <mach/mach.h>
12#include <dirent.h>		/* crashcount() */
13
14extern char  g_target_path[ PATH_MAX ];
15
16/*
17 * static to localize to this compilation unit; volatile to avoid register
18 * optimization which would prevent modification by a signal handler.
19 */
20static 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 */
30void
31bus_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 */
44int
45crashcount(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;
52	DIR		*dirp1 = NULL, *dirp2 = NULL;
53	struct dirent	*dep1, *dep2;
54	int		count = 0;
55
56	/* If we can't open the directory, dirp1 will be NULL */
57	dirp1 = opendir(crashdir1);
58
59	while(dirp1 != NULL && ((dep1 = readdir(dirp1)) != NULL)) {
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
71	if (dirp1 != NULL)
72		closedir(dirp1);
73
74#if !TARGET_OS_EMBEDDED
75	/* If we can't open the directory, dirp2 will be NULL */
76        dirp2 = opendir(crashdir2);
77
78        while(dirp2 != NULL && (dep2 = readdir(dirp2)) != NULL) {
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        }
89	if (dirp2 != NULL)
90	        closedir(dirp2);
91#endif
92	return( count );
93}
94
95
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 */
101int memory_tests( void * the_argp )
102{
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;
111	kern_return_t   my_kr;
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];
116
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
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( );
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
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
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	 */
156	 my_namebuf1[0] = '\0';
157	 my_namebuf2[0] = '\0';
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
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 */
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 ) );
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
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
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 ) {
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) );
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
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
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
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
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
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
368		*my_addr = 'z'; /* should cause SIGBUS signal (we look for this at child termination within the parent) */
369
370		/* NOTREACHED */
371
372		printf("Expected SIGBUS signal, got nothing!\n");
373		my_err = -1;
374exit_child:
375		exit( my_err );
376	}
377
378	/* parent process -
379	 * we should get no error if the child has completed all tests successfully
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
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 );
397		goto test_failed_exit;
398	}
399
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	 */
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)) {
413		printf( "child did not crash as expected \n");
414		printf( "saw %d crashes including %s \n", my_crashcount, my_namebuf1);
415		goto test_failed_exit;
416	}
417
418	/* post-remove the expected crash report */
419	if (unlink(my_namebuf1) && !(errno == ENOENT || errno == ENOTDIR)) {
420		printf("unlink of expected crash report '%s' failed \n", my_namebuf1);
421		goto test_failed_exit;
422	}
423#if !TARGET_OS_EMBEDDED
424	/* /Library/Logs/DiagnosticReports/ does not exist on embedded targets. */
425        if (unlink(my_namebuf2) && !(errno == ENOENT || errno == ENOTDIR)) {
426                printf("unlink of expected crash report '%s' failed \n", my_namebuf2);
427                goto test_failed_exit;
428        }
429#endif
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
438test_failed_exit:
439	my_err = -1;
440
441test_passed_exit:
442	if ( my_pathp != NULL ) {
443		remove( my_pathp );
444		vm_deallocate(mach_task_self(), (vm_address_t)my_pathp, PATH_MAX);
445	 }
446	 if ( my_test_page_p != NULL ) {
447		vm_deallocate(mach_task_self(), (vm_address_t)my_test_page_p, my_page_size);
448	 }
449	return( my_err );
450}
451
452