1#include <darwintest.h> 2#include <darwintest_utils.h> 3#include <sys/kern_memorystatus.h> 4#include <kern/debug.h> 5#include <mach-o/dyld.h> 6#include <sys/stackshot.h> 7#include <kdd.h> 8#include <signal.h> 9#include "test_utils.h" 10 11#define RECURSIONS 25 12#define FIRST_RECURSIVE_FRAME 3 13 14T_GLOBAL_META( 15 T_META_NAMESPACE("xnu.stackshot.accuracy"), 16 T_META_RADAR_COMPONENT_NAME("xnu"), 17 T_META_RADAR_COMPONENT_VERSION("stackshot"), 18 T_META_OWNER("jonathan_w_adams"), 19 T_META_CHECK_LEAKS(false), 20 T_META_ASROOT(true), 21 XNU_T_META_SOC_SPECIFIC 22 ); 23 24 25void child_init(void); 26void parent_helper_singleproc(int); 27 28#define CHECK_FOR_FAULT_STATS (1 << 0) 29#define WRITE_STACKSHOT_BUFFER_TO_TMP (1 << 1) 30#define CHECK_FOR_KERNEL_THREADS (1 << 2) 31int check_stackshot(void *, int); 32 33/* used for WRITE_STACKSHOT_BUFFER_TO_TMP */ 34static char const *current_scenario_name; 35static pid_t child_pid; 36 37/* helpers */ 38 39static void __attribute__((noinline)) 40child_recurse(int r, int spin, void (^cb)(void)) 41{ 42 if (r > 0) { 43 child_recurse(r - 1, spin, cb); 44 } 45 46 cb(); 47 48 /* wait forever */ 49 if (spin == 0) { 50 sleep(100000); 51 } else if (spin == 2) { 52 int v = 1; 53 /* ssh won't let the session die if we still have file handles open to its output. */ 54 close(STDERR_FILENO); 55 close(STDOUT_FILENO); 56 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.wedge_thread", NULL, NULL, &v, sizeof(v)), 57 "wedged thread in the kernel"); 58 } else { 59 while (1) { 60 __asm__ volatile("" : : : "memory"); 61 } 62 } 63} 64 65T_HELPER_DECL(simple_child_process, "child process that will be frozen and others") 66{ 67 child_init(); 68} 69 70T_HELPER_DECL(sid_child_process, "child process that setsid()s") 71{ 72 pid_t ppid = getppid(); 73 74 T_ASSERT_POSIX_SUCCESS(setsid(), "session id set"); 75 76 child_recurse(RECURSIONS, 2, ^{ 77 kill(ppid, SIGUSR1); 78 }); 79 80 T_ASSERT_FAIL("child_init returned!"); 81} 82 83static void 84kill_children(void) 85{ 86 kill(child_pid, SIGKILL); 87} 88 89static void * 90take_stackshot(pid_t target_pid, uint64_t extra_flags, uint64_t since_timestamp) 91{ 92 void *stackshot_config; 93 int err, retries = 5; 94 uint64_t stackshot_flags = STACKSHOT_KCDATA_FORMAT | 95 STACKSHOT_THREAD_WAITINFO | 96 STACKSHOT_GET_DQ; 97 98 /* we should be able to verify delta stackshots */ 99 if (since_timestamp != 0) { 100 stackshot_flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT; 101 } 102 103 stackshot_flags |= extra_flags; 104 105 stackshot_config = stackshot_config_create(); 106 T_ASSERT_NOTNULL(stackshot_config, "allocate stackshot config"); 107 108 err = stackshot_config_set_flags(stackshot_config, stackshot_flags); 109 T_ASSERT_EQ(err, 0, "set flags on stackshot config"); 110 111 err = stackshot_config_set_pid(stackshot_config, target_pid); 112 T_ASSERT_EQ(err, 0, "set target pid on stackshot config"); 113 114 if (since_timestamp != 0) { 115 err = stackshot_config_set_delta_timestamp(stackshot_config, since_timestamp); 116 T_ASSERT_EQ(err, 0, "set prev snapshot time on stackshot config"); 117 } 118 119 while (retries > 0) { 120 err = stackshot_capture_with_config(stackshot_config); 121 if (err == 0) { 122 break; 123 } else if (err == EBUSY || err == ETIMEDOUT) { 124 T_LOG("stackshot capture returned %d (%s)\n", err, strerror(err)); 125 if (retries == 0) { 126 T_ASSERT_FAIL("failed to take stackshot with error after retries: %d: %s\n", err, strerror(err)); 127 } 128 129 retries--; 130 continue; 131 } else { 132 T_ASSERT_FAIL("failed to take stackshot with error: %d: %s\n", err, strerror(err)); 133 } 134 } 135 136 return stackshot_config; 137} 138 139int 140check_stackshot(void *stackshot_config, int flags) 141{ 142 void *buf; 143 uint32_t buflen, kcdata_type; 144 kcdata_iter_t iter; 145 NSError *nserror = nil; 146 pid_t target_pid; 147 int ret = 0; 148 uint64_t expected_return_addr = 0; 149 bool found_fault_stats = false; 150 struct stackshot_fault_stats fault_stats = {0}; 151 152 buf = stackshot_config_get_stackshot_buffer(stackshot_config); 153 T_ASSERT_NOTNULL(buf, "stackshot buffer is not null"); 154 buflen = stackshot_config_get_stackshot_size(stackshot_config); 155 T_ASSERT_GT(buflen, 0, "valid stackshot buffer length"); 156 target_pid = ((struct stackshot_config*)stackshot_config)->sc_pid; 157 T_ASSERT_GT(target_pid, 0, "valid target_pid"); 158 159 /* if need to write it to fs, do it now */ 160 if (flags & WRITE_STACKSHOT_BUFFER_TO_TMP) { 161 char sspath[MAXPATHLEN]; 162 strlcpy(sspath, current_scenario_name, sizeof(sspath)); 163 strlcat(sspath, ".kcdata", sizeof(sspath)); 164 T_QUIET; T_ASSERT_POSIX_ZERO(dt_resultfile(sspath, sizeof(sspath)), 165 "create result file path"); 166 167 FILE *f = fopen(sspath, "w"); 168 T_WITH_ERRNO; T_QUIET; T_ASSERT_NOTNULL(f, 169 "open stackshot output file"); 170 171 size_t written = fwrite(buf, buflen, 1, f); 172 T_QUIET; T_ASSERT_POSIX_SUCCESS(written, "wrote stackshot to file"); 173 174 fclose(f); 175 } 176 177 /* begin iterating */ 178 iter = kcdata_iter(buf, buflen); 179 T_ASSERT_EQ(kcdata_iter_type(iter), KCDATA_BUFFER_BEGIN_STACKSHOT, "buffer is a stackshot"); 180 181 /* time to iterate */ 182 iter = kcdata_iter_next(iter); 183 KCDATA_ITER_FOREACH(iter) { 184 kcdata_type = kcdata_iter_type(iter); 185 NSNumber *parsedPid; 186 NSMutableDictionary *parsedContainer, *parsedThreads; 187 188 if ((flags & CHECK_FOR_FAULT_STATS) != 0 && 189 kcdata_type == STACKSHOT_KCTYPE_STACKSHOT_FAULT_STATS) { 190 memcpy(&fault_stats, kcdata_iter_payload(iter), sizeof(fault_stats)); 191 found_fault_stats = true; 192 } 193 194 if (kcdata_type != KCDATA_TYPE_CONTAINER_BEGIN) { 195 continue; 196 } 197 198 if (kcdata_iter_container_type(iter) != STACKSHOT_KCCONTAINER_TASK) { 199 continue; 200 } 201 202 parsedContainer = parseKCDataContainer(&iter, &nserror); 203 T_ASSERT_NOTNULL(parsedContainer, "parsedContainer is not null"); 204 T_ASSERT_NULL(nserror, "no NSError occured while parsing the kcdata container"); 205 206 /* 207 * given that we've targetted the pid, we can be sure that this 208 * ts_pid will be the pid we expect 209 */ 210 parsedPid = parsedContainer[@"task_snapshots"][@"task_snapshot"][@"ts_pid"]; 211 T_ASSERT_EQ([parsedPid intValue], target_pid, "found correct pid"); 212 213 /* start parsing the threads */ 214 parsedThreads = parsedContainer[@"task_snapshots"][@"thread_snapshots"]; 215 for (id th_key in parsedThreads) { 216 uint32_t frame_index = 0; 217 218 if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) { 219 /* skip threads that don't have enough frames */ 220 if ([parsedThreads[th_key][@"user_stack_frames"] count] < RECURSIONS) { 221 continue; 222 } 223 224 for (id frame in parsedThreads[th_key][@"user_stack_frames"]) { 225 if ((frame_index >= FIRST_RECURSIVE_FRAME) && (frame_index < (RECURSIONS - FIRST_RECURSIVE_FRAME))) { 226 if (expected_return_addr == 0ull) { 227 expected_return_addr = [frame[@"lr"] unsignedLongLongValue]; 228 } else { 229 T_QUIET; 230 T_ASSERT_EQ(expected_return_addr, [frame[@"lr"] unsignedLongLongValue], "expected return address found"); 231 } 232 } 233 frame_index ++; 234 } 235 } else { 236 T_ASSERT_NOTNULL(parsedThreads[th_key][@"kernel_stack_frames"], 237 "found kernel stack frames"); 238 } 239 240 } 241 } 242 243 if (found_fault_stats) { 244 T_LOG("number of pages faulted in: %d", fault_stats.sfs_pages_faulted_in); 245 T_LOG("MATUs spent faulting: %lld", fault_stats.sfs_time_spent_faulting); 246 T_LOG("MATUS fault time limit: %lld", fault_stats.sfs_system_max_fault_time); 247 T_LOG("did we stop because of the limit?: %s", fault_stats.sfs_stopped_faulting ? "yes" : "no"); 248 if (expected_return_addr != 0ull) { 249 T_ASSERT_GT(fault_stats.sfs_pages_faulted_in, 0, "faulted at least one page in"); 250 T_LOG("NOTE: successfully faulted in the pages"); 251 } else { 252 T_LOG("NOTE: We were not able to fault the stack's pages back in"); 253 254 /* if we couldn't fault the pages back in, then at least verify that we tried */ 255 T_ASSERT_GT(fault_stats.sfs_time_spent_faulting, 0ull, "spent time trying to fault"); 256 } 257 } else if ((flags & CHECK_FOR_KERNEL_THREADS) == 0) { 258 T_ASSERT_NE(expected_return_addr, 0ull, "found child thread with recursions"); 259 } 260 261 if (flags & CHECK_FOR_FAULT_STATS) { 262 T_ASSERT_EQ(found_fault_stats, true, "found fault stats"); 263 } 264 265 return ret; 266} 267 268void 269child_init(void) 270{ 271#if !TARGET_OS_OSX 272 int freeze_state; 273#endif /* !TARGET_OS_OSX */ 274 pid_t pid = getpid(); 275 char padding[16 * 1024]; 276 __asm__ volatile(""::"r"(padding)); 277 278 T_LOG("child pid: %d\n", pid); 279 280#if !TARGET_OS_OSX 281 /* allow us to be frozen */ 282 freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0); 283 if (freeze_state == 0) { 284 T_LOG("CHILD was found to be UNFREEZABLE, enabling freezing."); 285 memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, pid, 1, NULL, 0); 286 freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, pid, 0, NULL, 0); 287 T_ASSERT_EQ(freeze_state, 1, "successfully set freezeability"); 288 } 289#else 290 T_LOG("Cannot change freezeability as freezing is only available on embedded devices"); 291#endif /* !TARGET_OS_OSX */ 292 293 /* 294 * recurse a bunch of times to generate predictable data in the stackshot, 295 * then send SIGUSR1 to the parent to let it know that we are done. 296 */ 297 child_recurse(RECURSIONS, 0, ^{ 298 kill(getppid(), SIGUSR1); 299 }); 300 301 T_ASSERT_FAIL("child_recurse returned, but it must not?"); 302} 303 304void 305parent_helper_singleproc(int spin) 306{ 307 dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0); 308 dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic_sp", NULL); 309 void *stackshot_config; 310 311 dispatch_async(dq, ^{ 312 char padding[16 * 1024]; 313 __asm__ volatile(""::"r"(padding)); 314 315 child_recurse(RECURSIONS, spin, ^{ 316 dispatch_semaphore_signal(child_done_sema); 317 }); 318 }); 319 320 dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER); 321 T_LOG("done waiting for child"); 322 323 /* take the stackshot and parse it */ 324 stackshot_config = take_stackshot(getpid(), 0, 0); 325 326 /* check that the stackshot has the stack frames */ 327 check_stackshot(stackshot_config, 0); 328 329 T_LOG("done!"); 330} 331 332T_DECL(basic, "test that no-fault stackshot works correctly", T_META_TAG_VM_PREFERRED) 333{ 334 char path[PATH_MAX]; 335 uint32_t path_size = sizeof(path); 336 char *args[] = { path, "-n", "simple_child_process", NULL }; 337 dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.basic", NULL); 338 dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0); 339 dispatch_source_t child_sig_src; 340 void *stackshot_config; 341 342 current_scenario_name = __func__; 343 344 T_LOG("parent pid: %d\n", getpid()); 345 T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); 346 347 /* check if we can run the child successfully */ 348#if !TARGET_OS_OSX 349 int freeze_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0); 350 if (freeze_state == -1) { 351 T_SKIP("This device doesn't have CONFIG_FREEZE enabled."); 352 } 353#endif 354 355 /* setup signal handling */ 356 signal(SIGUSR1, SIG_IGN); 357 child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq); 358 dispatch_source_set_event_handler(child_sig_src, ^{ 359 dispatch_semaphore_signal(child_done_sema); 360 }); 361 dispatch_activate(child_sig_src); 362 363 /* create the child process */ 364 T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched"); 365 T_ATEND(kill_children); 366 367 /* wait until the child has recursed enough */ 368 dispatch_semaphore_wait(child_done_sema, dispatch_time(DISPATCH_TIME_NOW, 10 /*seconds*/ * 1000000000ULL)); 369 370 T_LOG("child finished, parent executing"); 371 372 /* take the stackshot and parse it */ 373 stackshot_config = take_stackshot(child_pid, 0, 0); 374 375 /* check that the stackshot has the stack frames */ 376 check_stackshot(stackshot_config, 0); 377 378 T_LOG("all done, killing child"); 379 380 /* tell the child to quit */ 381 T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child"); 382} 383 384T_DECL(basic_singleproc, "test that no-fault stackshot works correctly in single process setting", T_META_TAG_VM_PREFERRED) 385{ 386 current_scenario_name = __func__; 387 parent_helper_singleproc(0); 388} 389 390T_DECL(basic_singleproc_spin, "test that no-fault stackshot works correctly in single process setting with spinning", T_META_TAG_VM_PREFERRED) 391{ 392 current_scenario_name = __func__; 393 parent_helper_singleproc(1); 394} 395 396T_DECL(fault, "test that faulting stackshots work correctly", T_META_TAG_VM_PREFERRED) 397{ 398 dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_fault_accuracy", NULL); 399 dispatch_source_t child_sig_src; 400 dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0); 401 void *stackshot_config; 402 int oldftm, newval = 1, freeze_enabled, oldratio, newratio = 0; 403 size_t oldlen = sizeof(oldftm), fe_len = sizeof(freeze_enabled), ratiolen = sizeof(oldratio); 404 char path[PATH_MAX]; 405 uint32_t path_size = sizeof(path); 406 char *args[] = { path, "-n", "simple_child_process", NULL }; 407 408 current_scenario_name = __func__; 409 T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); 410 411#if TARGET_OS_OSX 412 T_SKIP("freezing is not available on macOS"); 413#endif /* TARGET_OS_OSX */ 414 415 /* Try checking if freezing is enabled at all */ 416 if (sysctlbyname("vm.freeze_enabled", &freeze_enabled, &fe_len, NULL, 0) == -1) { 417 if (errno == ENOENT) { 418 T_SKIP("This device doesn't have CONFIG_FREEZE enabled."); 419 } else { 420 T_FAIL("failed to query vm.freeze_enabled, errno: %d", errno); 421 } 422 } 423 424 if (!freeze_enabled) { 425 T_SKIP("Freeze is not enabled, skipping test."); 426 } 427 428 /* signal handling */ 429 signal(SIGUSR1, SIG_IGN); 430 child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq); 431 dispatch_source_set_event_handler(child_sig_src, ^{ 432 dispatch_semaphore_signal(child_done_sema); 433 }); 434 dispatch_activate(child_sig_src); 435 436 T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched"); 437 T_ATEND(kill_children); 438 439 dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER); 440 441 /* keep processes in memory */ 442 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", &oldftm, &oldlen, &newval, sizeof(newval)), 443 "disabled freezing to disk"); 444 445 /* set the ratio to zero */ 446 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", &oldratio, &ratiolen, &newratio, sizeof(newratio)), "disabled private:shared ratio checking"); 447 448 /* freeze the child */ 449 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze", NULL, 0, &child_pid, sizeof(child_pid)), 450 "froze child"); 451 452 /* Sleep to allow the compressor to finish compressing the child */ 453 sleep(5); 454 455 /* take the stackshot and parse it */ 456 stackshot_config = take_stackshot(child_pid, STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0); 457 458 /* check that the stackshot has the stack frames */ 459 check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS); 460 461 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_to_memory", NULL, 0, &oldftm, sizeof(oldftm)), 462 "reset freezing to disk"); 463 464 /* reset the private:shared ratio */ 465 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_private_shared_pages_ratio", NULL, 0, &oldratio, sizeof(oldratio)), "reset private:shared ratio"); 466 467 T_LOG("all done, killing child"); 468 469 /* tell the child to quit */ 470 T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child"); 471} 472 473T_DECL(fault_singleproc, "test that faulting stackshots work correctly in a single process setting", T_META_TAG_VM_PREFERRED) 474{ 475 dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0); 476 dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.fault_sp", NULL); 477 void *stackshot_config; 478 __block pthread_t child_thread; 479 char *child_stack; 480 size_t child_stacklen; 481 482#if !TARGET_OS_OSX 483 T_SKIP("madvise(..., ..., MADV_PAGEOUT) is not available on embedded platforms"); 484#endif /* !TARGET_OS_OSX */ 485 486 dispatch_async(dq, ^{ 487 char padding[16 * 1024]; 488 __asm__ volatile(""::"r"(padding)); 489 490 child_recurse(RECURSIONS, 0, ^{ 491 child_thread = pthread_self(); 492 dispatch_semaphore_signal(child_done_sema); 493 }); 494 }); 495 496 dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER); 497 T_LOG("done waiting for child"); 498 499 child_stack = pthread_get_stackaddr_np(child_thread); 500 child_stacklen = pthread_get_stacksize_np(child_thread); 501 child_stack -= child_stacklen; 502 T_LOG("child stack: [0x%p - 0x%p]: 0x%zu bytes", (void *)child_stack, 503 (void *)(child_stack + child_stacklen), child_stacklen); 504 505 /* paging out the child */ 506 T_ASSERT_POSIX_SUCCESS(madvise(child_stack, child_stacklen, MADV_PAGEOUT), "paged out via madvise(2) the child stack"); 507 508 /* take the stackshot and parse it */ 509 stackshot_config = take_stackshot(getpid(), STACKSHOT_ENABLE_BT_FAULTING | STACKSHOT_ENABLE_UUID_FAULTING, 0); 510 511 /* check that the stackshot has the stack frames */ 512 check_stackshot(stackshot_config, CHECK_FOR_FAULT_STATS); 513 514 T_LOG("done!"); 515} 516 517T_DECL(zombie, "test that threads wedged in the kernel can be stackshot'd", T_META_TAG_VM_PREFERRED) 518{ 519 dispatch_queue_t dq = dispatch_queue_create("com.apple.stackshot_accuracy.zombie", NULL); 520 dispatch_semaphore_t child_done_sema = dispatch_semaphore_create(0); 521 dispatch_source_t child_sig_src; 522 void *stackshot_config; 523 char path[PATH_MAX]; 524 uint32_t path_size = sizeof(path); 525 char *args[] = { path, "-n", "sid_child_process", NULL }; 526 527 current_scenario_name = __func__; 528 T_QUIET; T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath"); 529 530 T_LOG("parent pid: %d\n", getpid()); 531 532 /* setup signal handling */ 533 signal(SIGUSR1, SIG_IGN); 534 child_sig_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dq); 535 dispatch_source_set_event_handler(child_sig_src, ^{ 536 dispatch_semaphore_signal(child_done_sema); 537 }); 538 dispatch_activate(child_sig_src); 539 540 /* create the child process */ 541 T_ASSERT_POSIX_SUCCESS(dt_launch_tool(&child_pid, args, false, NULL, NULL), "child launched"); 542 T_ATEND(kill_children); 543 544 /* wait until the child has recursed enough */ 545 dispatch_semaphore_wait(child_done_sema, DISPATCH_TIME_FOREVER); 546 547 T_LOG("child finished, parent executing. invoking jetsam"); 548 549 T_ASSERT_POSIX_SUCCESS(memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0), 550 "jetsam'd the child"); 551 552 /* Sleep to allow the target process to become zombified */ 553 sleep(1); 554 555 /* take the stackshot and parse it */ 556 stackshot_config = take_stackshot(child_pid, 0, 0); 557 558 /* check that the stackshot has the stack frames */ 559 check_stackshot(stackshot_config, CHECK_FOR_KERNEL_THREADS); 560 561 T_LOG("all done, unwedging and killing child"); 562 563 int v = 1; 564 T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.unwedge_thread", NULL, NULL, &v, sizeof(v)), 565 "unwedged child"); 566 567 /* tell the child to quit */ 568 T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGTERM), "killed child"); 569} 570