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