10 #include <sys/ptrace.h>
17 * We create a process hierarchy of:
19 * grandparent -> parent -> child
23 * When the debugger calls ptrace(2) on child, it
24 * is temporarily reparented.
26 * We may also create a hierarchy of:
28 * grandparent -> parent/debugger -> child
33 eParentExitAfterWaitpid
= 0,
34 eParentExitAfterWaitpidAndSIGCHLD
,
35 eParentExitBeforeWaitpid
,
36 eParentExitAfterDebuggerAttach
,
37 eParentExitBeforeDebuggerAttach
,
42 eDebuggerExitAfterKillAndWaitpid
= 0,
43 eDebuggerExitAfterKillWithoutWaitpid
,
44 eDebuggerExitAfterDetach
,
45 eDebuggerExitWithoutDetach
48 void do_grandparent(pid_t parent
, pid_t child
, pid_t debugger
, debugger_exit_t debugger_exit_time
) __attribute__((noreturn
));
49 void do_parent(pid_t child
, pid_t debugger
, parent_exit_t parent_exit_time
, debugger_exit_t debugger_exit_time
) __attribute__((noreturn
));
50 void do_child(void) __attribute__((noreturn
));
51 void do_debugger(pid_t child
, debugger_exit_t debugger_exit_time
) __attribute__((noreturn
));
53 bool iszombie(pid_t p
);
55 char *str_kev_filter(int filter
);
56 char *str_kev_flags(int filter
, uint16_t flags
);
57 char *str_kev_fflags(int filter
, uint32_t fflags
);
58 char *str_kev_data(int filter
, uint32_t fflags
, int64_t data
, uint64_t udata
);
59 char *print_exit(pid_t p
, int stat_loc
);
61 void logline(const char *format
, ...);
64 int test_all_permutations(void);
65 void test(parent_exit_t parent_exit_time
, debugger_exit_t debugger_exit_time
) __attribute__((noreturn
));
67 int main(int argc
, char *argv
[]) {
70 int parent_exit_time
= -1;
71 int debugger_exit_time
= -1;
73 while ((ch
= getopt(argc
, argv
, "p:w:")) != -1) {
76 parent_exit_time
= atoi(optarg
);
79 debugger_exit_time
= atoi(optarg
);
87 /* no explicit options, loop through them all */
88 if (parent_exit_time
== -1 &&
89 debugger_exit_time
== -1) {
90 return test_all_permutations();
93 if (parent_exit_time
== -1 ||
94 debugger_exit_time
== -1) {
98 test((parent_exit_t
)parent_exit_time
,
99 (debugger_exit_t
)debugger_exit_time
);
101 return 0; /* never reached */
104 void test(parent_exit_t parent_exit_time
, debugger_exit_t debugger_exit_time
)
106 pid_t parent
, child
, debugger
;
110 /* pipe for parent to send child pid to grandparent */
113 err(1, "failed to create pipe");
118 /* parent sub-branch */
122 err(1, "close read end of pipe");
130 err(1, "close write end of pipe");
134 } else if (child
== -1) {
135 err(1, "parent failed to fork child");
138 if (-1 == write(fds
[1], &child
, sizeof(child
))) {
139 err(1, "writing child pid to grandparent");
142 if (parent_exit_time
== eParentIsDebugger
) {
145 if (-1 == write(fds
[1], &debugger
, sizeof(debugger
))) {
146 err(1, "writing debugger pid to grandparent");
150 err(1, "close write end of pipe");
153 do_debugger(child
, debugger_exit_time
);
160 err(1, "close write end of pipe");
163 do_debugger(child
, debugger_exit_time
);
164 } else if (debugger
== -1) {
165 err(1, "parent failed to fork debugger");
168 if (-1 == write(fds
[1], &debugger
, sizeof(debugger
))) {
169 err(1, "writing debugger pid to grandparent");
173 err(1, "close write end of pipe");
176 do_parent(child
, debugger
, parent_exit_time
, debugger_exit_time
);
180 } else if (parent
== -1) {
181 err(1, "grandparent failed to fork parent");
185 err(1, "close write end of pipe");
188 if (-1 == read(fds
[0], &child
, sizeof(child
))) {
189 err(1, "could not read child pid");
192 if (-1 == read(fds
[0], &debugger
, sizeof(debugger
))) {
193 err(1, "could not read debugger pid");
198 err(1, "close read end of pipe");
201 do_grandparent(parent
, child
, debugger
, debugger_exit_time
);
207 errx(1, "Usage: %s [-p <parent_exit_time> -w <debugger_exit_time>]", getprogname());
210 int test_all_permutations(void)
213 bool has_failure
= false;
215 for (p
= 0; p
<= 5; p
++) {
216 for (w
= 0; w
<= 3; w
++) {
222 logline("-------------------------------------------------------");
223 logline("*** Executing self-test: %s -p %d -w %d",
224 getprogname(), p
, w
);
225 test((parent_exit_t
)p
,
227 _exit(1); /* never reached */
228 } else if (testpid
== -1) {
229 err(1, "failed to fork test pid");
233 ret
= waitpid(testpid
, &stat_loc
, 0);
235 err(1, "waitpid(%d) by test harness failed", testpid
);
237 logline("test process: %s", print_exit(testpid
, stat_loc
));
238 if (!WIFEXITED(stat_loc
) || (0 != WEXITSTATUS(stat_loc
))) {
239 logline("FAILED TEST");
247 logline("test failures found");
254 void do_grandparent(pid_t parent
, pid_t child
, pid_t debugger
, debugger_exit_t debugger_exit_time
)
261 struct kevent64_s kev
;
262 int neededdeathcount
= (debugger
!= -1) ? 3 : 2;
264 setprogname("GRANDPARENT");
266 logline("grandparent pid %d has parent pid %d and child pid %d. waiting for parent process exit...", getpid(), parent
, child
);
268 /* make sure we can at least observe real child's exit */
273 EV_SET64(&kev
, child
, EVFILT_PROC
, EV_ADD
|EV_ENABLE
,
274 NOTE_EXIT
, 0, child
, 0, 0);
275 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
277 err(1, "kevent64 EVFILT_PROC");
279 EV_SET64(&kev
, parent
, EVFILT_PROC
, EV_ADD
|EV_ENABLE
,
280 NOTE_EXIT
, 0, parent
, 0, 0);
281 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
283 err(1, "kevent64 EVFILT_PROC");
285 if (debugger
!= -1) {
286 EV_SET64(&kev
, debugger
, EVFILT_PROC
, EV_ADD
|EV_ENABLE
,
287 NOTE_EXIT
, 0, debugger
, 0, 0);
288 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
290 err(1, "kevent64 EVFILT_PROC");
293 EV_SET64(&kev
, 5, EVFILT_TIMER
, EV_ADD
|EV_ENABLE
|EV_ONESHOT
,
294 NOTE_SECONDS
, 5, 0, 0, 0);
295 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
297 err(1, "kevent64 EVFILT_TIMER");
301 ret
= kevent64(kq
, NULL
, 0, &kev
, 1, 0, NULL
);
306 } else if (ret
== 0) {
310 logline("kevent64 returned ident %llu filter %s fflags %s data %s",
311 kev
.ident
, str_kev_filter(kev
.filter
),
312 str_kev_fflags(kev
.filter
, kev
.fflags
),
313 str_kev_data(kev
.filter
, kev
.fflags
, kev
.data
, kev
.udata
));
314 if (kev
.filter
== EVFILT_PROC
) {
315 if (child
== kev
.udata
) {
317 } else if (parent
== kev
.udata
) {
319 } else if ((debugger
!= -1) && (debugger
== kev
.udata
)) {
322 } else if (kev
.filter
== EVFILT_TIMER
) {
323 logline("timed out waiting for NOTE_EXIT");
328 if (neededdeathcount
== 0) {
333 result
= waitpid(parent
, &stat_loc
, 0);
335 err(1, "waitpid(%d) by grandparent failed", parent
);
338 logline("parent process: %s", print_exit(parent
, stat_loc
));
339 if (!WIFEXITED(stat_loc
) || (0 != WEXITSTATUS(stat_loc
))) {
343 if (iszombie(parent
)) {
344 logline("parent %d is now a zombie", parent
);
348 if (iszombie(child
)) {
349 logline("child %d is now a zombie", child
);
353 if ((debugger
!= -1) && iszombie(debugger
)) {
354 logline("debugger %d is now a zombie", debugger
);
362 * debugger will register kevents, wait for quorum on events, then exit
364 void do_parent(pid_t child
, pid_t debugger
, parent_exit_t parent_exit_time
, debugger_exit_t debugger_exit_time
)
368 struct kevent64_s kev
;
370 int childsignalcount
= 0;
373 setprogname("PARENT");
375 logline("parent pid %d has child pid %d and debugger pid %d. waiting for processes to exit...", getpid(), child
, debugger
);
381 EV_SET64(&kev
, child
, EVFILT_PROC
, EV_ADD
|EV_ENABLE
,
382 NOTE_EXIT
|NOTE_EXITSTATUS
|NOTE_EXIT_DETAIL
|NOTE_FORK
|NOTE_EXEC
|NOTE_SIGNAL
,
384 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
386 err(1, "kevent64 EVFILT_PROC");
388 EV_SET64(&kev
, SIGCHLD
, EVFILT_SIGNAL
, EV_ADD
|EV_ENABLE
,
390 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
392 err(1, "kevent64 EVFILT_SIGNAL");
394 EV_SET64(&kev
, 7, EVFILT_TIMER
, EV_ADD
|EV_ENABLE
|EV_ONESHOT
,
395 NOTE_SECONDS
, 7, 0, 0, 0);
396 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
398 err(1, "kevent64 EVFILT_TIMER");
401 ret
= kevent64(kq
, NULL
, 0, &kev
, 1, 0, NULL
);
406 } else if (ret
== 0) {
410 logline("kevent64 returned ident %llu filter %s fflags %s data %s",
411 kev
.ident
, str_kev_filter(kev
.filter
),
412 str_kev_fflags(kev
.filter
, kev
.fflags
),
413 str_kev_data(kev
.filter
, kev
.fflags
, kev
.data
, kev
.udata
));
414 if (kev
.filter
== EVFILT_SIGNAL
) {
415 /* must be SIGCHLD */
417 } else if (kev
.filter
== EVFILT_PROC
) {
418 if (child
== kev
.udata
) {
419 if ((kev
.fflags
& (NOTE_EXIT
|NOTE_EXITSTATUS
)) == (NOTE_EXIT
|NOTE_EXITSTATUS
)) {
421 } else if (kev
.fflags
& NOTE_SIGNAL
) {
423 if ((parent_exit_time
== eParentExitAfterDebuggerAttach
) && (childsignalcount
>= 2)) {
424 /* second signal is attach */
425 logline("exiting because of eParentExitAfterDebuggerAttach");
428 } else if (kev
.fflags
& NOTE_FORK
) {
429 if (parent_exit_time
== eParentExitBeforeDebuggerAttach
) {
430 logline("exiting because of eParentExitBeforeDebuggerAttach");
435 } else if (kev
.filter
== EVFILT_TIMER
) {
436 errx(1, "timed out waiting for NOTE_EXIT");
439 if (deathcount
>= (parent_exit_time
== eParentExitAfterWaitpidAndSIGCHLD
? 2 : 1)) {
444 if (parent_exit_time
== eParentExitBeforeWaitpid
) {
445 logline("exiting because of eParentExitBeforeWaitpid");
449 ret
= waitpid(child
, &stat_loc
, 0);
451 err(1, "waitpid(%d) by parent failed", child
);
453 logline("child process: %s", print_exit(child
, stat_loc
));
454 if (!WIFSIGNALED(stat_loc
) || (SIGKILL
!= WTERMSIG(stat_loc
)))
455 errx(1, "child did not exit as expected");
457 ret
= waitpid(debugger
, &stat_loc
, 0);
459 err(1, "waitpid(%d) by parent failed", debugger
);
461 logline("debugger process: %s", print_exit(debugger
, stat_loc
));
462 if (!WIFEXITED(stat_loc
) || (0 != WEXITSTATUS(stat_loc
)))
463 errx(1, "debugger did not exit as expected");
465 /* Received both SIGCHLD and NOTE_EXIT, as needed */
466 logline("exiting beacuse of eParentExitAfterWaitpid/eParentExitAfterWaitpidAndSIGCHLD");
470 /* child will spin waiting to be killed by debugger or parent or someone */
475 setprogname("CHILD");
477 logline("child pid %d. waiting for external termination...", getpid());
481 doublechild
= fork();
482 if (doublechild
== 0) {
484 } else if (doublechild
== -1) {
485 err(1, "doublechild");
487 ret
= waitpid(doublechild
, NULL
, 0);
489 err(1, "waitpid(%d) by parent failed", doublechild
);
498 * debugger will register kevents, attach+kill child, wait for quorum on events,
501 void do_debugger(pid_t child
, debugger_exit_t debugger_exit_time
)
505 struct kevent64_s kev
;
509 setprogname("DEBUGGER");
511 logline("debugger pid %d has child pid %d. waiting for process exit...", getpid(), child
);
514 fprintf(stderr
, "\n");
515 ret
= ptrace(PT_ATTACH
, child
, 0, 0);
517 err(1, "ptrace(PT_ATTACH)");
519 ret
= waitpid(child
, &stat_loc
, WUNTRACED
);
521 err(1, "waitpid(child, WUNTRACED)");
523 logline("child process stopped: %s", print_exit(child
, stat_loc
));
525 if (debugger_exit_time
== eDebuggerExitWithoutDetach
) {
526 logline("exiting because of eDebuggerExitWithoutDetach");
528 } else if (debugger_exit_time
== eDebuggerExitAfterDetach
) {
529 ret
= ptrace(PT_DETACH
, child
, 0, 0);
531 err(1, "ptrace(PT_DETACH)");
533 ret
= kill(child
, SIGKILL
);
535 err(1, "kill(SIGKILL)");
537 logline("exiting because of eDebuggerExitAfterDetach");
545 EV_SET64(&kev
, child
, EVFILT_PROC
, EV_ADD
|EV_ENABLE
,
546 NOTE_EXIT
|NOTE_EXITSTATUS
|NOTE_EXIT_DETAIL
|NOTE_FORK
|NOTE_EXEC
|NOTE_SIGNAL
,
548 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
550 err(1, "kevent64 EVFILT_PROC");
552 EV_SET64(&kev
, SIGCHLD
, EVFILT_SIGNAL
, EV_ADD
|EV_ENABLE
,
554 ret
= kevent64(kq
, &kev
, 1, NULL
, 0, 0, NULL
);
556 err(1, "kevent64 EVFILT_SIGNAL");
559 fprintf(stderr
, "\n");
560 ret
= ptrace(PT_KILL
, child
, 0, 0);
562 err(1, "ptrace(PT_KILL)");
565 ret
= kevent64(kq
, NULL
, 0, &kev
, 1, 0, NULL
);
570 } else if (ret
== 0) {
574 logline("kevent64 returned ident %llu filter %s fflags %s data %s",
575 kev
.ident
, str_kev_filter(kev
.filter
),
576 str_kev_fflags(kev
.filter
, kev
.fflags
),
577 str_kev_data(kev
.filter
, kev
.fflags
, kev
.data
, kev
.udata
));
578 if (kev
.filter
== EVFILT_SIGNAL
) {
579 /* must be SIGCHLD */
581 } else if (kev
.filter
== EVFILT_PROC
) {
582 if ((kev
.fflags
& (NOTE_EXIT
|NOTE_EXITSTATUS
)) == (NOTE_EXIT
|NOTE_EXITSTATUS
)) {
587 if (deathcount
>= 2) {
592 if (debugger_exit_time
== eDebuggerExitAfterKillWithoutWaitpid
) {
593 logline("exiting because of eDebuggerExitAfterKillWithoutWaitpid");
598 fprintf(stderr
, "\n");
599 ret
= waitpid(child
, &stat_loc
, 0);
601 err(1, "waitpid(%d) by debugger failed", child
);
603 logline("child process: %s", print_exit(child
, stat_loc
));
605 /* Received both SIGCHLD and NOTE_EXIT */
609 void logline(const char *format
, ...)
612 char newformat
[1024];
614 snprintf(newformat
, sizeof(newformat
), "%s: %s\n", getprogname(), format
);
618 va_start(va
, format
);
619 vasprintf(&line
, newformat
, va
);
623 write(STDOUT_FILENO
, line
, strlen(line
));
626 write(STDOUT_FILENO
, "error\n", 6);
631 char *str_kev_filter(int filter
)
633 static char filter_string
[32];
634 if (filter
== EVFILT_PROC
)
635 strlcpy(filter_string
, "EVFILT_PROC", sizeof(filter_string
));
636 else if (filter
== EVFILT_SIGNAL
)
637 strlcpy(filter_string
, "EVFILT_SIGNAL", sizeof(filter_string
));
638 else if (filter
== EVFILT_TIMER
)
639 strlcpy(filter_string
, "EVFILT_TIMER", sizeof(filter_string
));
641 strlcpy(filter_string
, "EVFILT_UNKNOWN", sizeof(filter_string
));
643 return filter_string
;
646 char *str_kev_flags(int filter
, uint16_t flags
)
648 static char flags_string
[128];
650 flags_string
[0] = '\0';
651 if (filter
& EV_ADD
) strlcat(flags_string
, "|EV_ADD", sizeof(flags_string
));
652 if (filter
& EV_DELETE
) strlcat(flags_string
, "|EV_DELETE", sizeof(flags_string
));
653 if (filter
& EV_ENABLE
) strlcat(flags_string
, "|EV_ENABLE", sizeof(flags_string
));
654 if (filter
& EV_DISABLE
) strlcat(flags_string
, "|EV_DISABLE", sizeof(flags_string
));
655 if (filter
& EV_RECEIPT
) strlcat(flags_string
, "|EV_RECEIPT", sizeof(flags_string
));
656 if (filter
& EV_ONESHOT
) strlcat(flags_string
, "|EV_ONESHOT", sizeof(flags_string
));
657 if (filter
& EV_CLEAR
) strlcat(flags_string
, "|EV_CLEAR", sizeof(flags_string
));
658 if (filter
& EV_DISPATCH
) strlcat(flags_string
, "|EV_DISPATCH", sizeof(flags_string
));
659 if (filter
& EV_EOF
) strlcat(flags_string
, "|EV_EOF", sizeof(flags_string
));
660 if (filter
& EV_ERROR
) strlcat(flags_string
, "|EV_ERROR", sizeof(flags_string
));
662 if (flags_string
[0] == '|')
663 return &flags_string
[1];
668 char *str_kev_fflags(int filter
, uint32_t fflags
)
670 static char fflags_string
[128];
672 fflags_string
[0] = '\0';
674 if (filter
== EVFILT_SIGNAL
) {
675 if (fflags
& NOTE_SIGNAL
) strlcat(fflags_string
, "|NOTE_SIGNAL", sizeof(fflags_string
));
676 } else if (filter
== EVFILT_PROC
) {
677 if (fflags
& NOTE_EXIT
) strlcat(fflags_string
, "|NOTE_EXIT", sizeof(fflags_string
));
678 if (fflags
& NOTE_FORK
) strlcat(fflags_string
, "|NOTE_FORK", sizeof(fflags_string
));
679 if (fflags
& NOTE_EXEC
) strlcat(fflags_string
, "|NOTE_EXEC", sizeof(fflags_string
));
680 if (fflags
& NOTE_SIGNAL
) strlcat(fflags_string
, "|NOTE_SIGNAL", sizeof(fflags_string
));
681 if (fflags
& NOTE_EXITSTATUS
) strlcat(fflags_string
, "|NOTE_EXITSTATUS", sizeof(fflags_string
));
682 if (fflags
& NOTE_EXIT_DETAIL
) strlcat(fflags_string
, "|NOTE_EXIT_DETAIL", sizeof(fflags_string
));
683 if (fflags
& NOTE_EXIT_DECRYPTFAIL
) strlcat(fflags_string
, "|NOTE_EXIT_DECRYPTFAIL", sizeof(fflags_string
));
684 if (fflags
& NOTE_EXIT_MEMORY
) strlcat(fflags_string
, "|NOTE_EXIT_MEMORY", sizeof(fflags_string
));
685 #ifdef NOTE_EXIT_CSERROR
686 if (fflags
& NOTE_EXIT_CSERROR
) strlcat(fflags_string
, "|NOTE_EXIT_CSERROR", sizeof(fflags_string
));
688 } else if (filter
== EVFILT_TIMER
) {
689 if (fflags
& NOTE_SECONDS
) strlcat(fflags_string
, "|NOTE_SECONDS", sizeof(fflags_string
));
691 strlcat(fflags_string
, "UNKNOWN", sizeof(fflags_string
));
694 if (fflags_string
[0] == '|')
695 return &fflags_string
[1];
697 return fflags_string
;
700 char *str_kev_data(int filter
, uint32_t fflags
, int64_t data
, uint64_t udata
)
702 static char data_string
[128];
704 if (filter
== EVFILT_PROC
) {
705 if ((fflags
& (NOTE_EXIT
|NOTE_EXITSTATUS
)) == (NOTE_EXIT
|NOTE_EXITSTATUS
)) {
706 if (WIFEXITED(data
)) {
707 snprintf(data_string
, sizeof(data_string
), "pid %llu exited with status %d", udata
, WEXITSTATUS(data
));
708 } else if (WIFSIGNALED(data
)) {
709 snprintf(data_string
, sizeof(data_string
), "pid %llu received signal %d%s", udata
, WTERMSIG(data
), WCOREDUMP(data
) ? " (core dumped)" : "");
710 } else if (WIFSTOPPED(data
)) {
711 snprintf(data_string
, sizeof(data_string
), "pid %llu stopped with signal %d", udata
, WSTOPSIG(data
));
713 snprintf(data_string
, sizeof(data_string
), "pid %llu unknown exit status 0x%08llx", udata
, data
);
715 } else if (fflags
& NOTE_EXIT
) {
716 snprintf(data_string
, sizeof(data_string
), "pid %llu exited", udata
);
718 data_string
[0] = '\0';
720 } else if (filter
== EVFILT_TIMER
) {
721 snprintf(data_string
, sizeof(data_string
), "timer fired %lld time(s)", data
);
723 data_string
[0] = '\0';
729 char *print_exit(pid_t p
, int stat_loc
)
731 return str_kev_data(EVFILT_PROC
, NOTE_EXIT
|NOTE_EXITSTATUS
, stat_loc
, p
);
734 bool iszombie(pid_t p
)
737 struct proc_bsdshortinfo bsdinfo
;
739 ret
= proc_pidinfo(p
, PROC_PIDT_SHORTBSDINFO
, 1, &bsdinfo
, sizeof(bsdinfo
));
740 if (ret
!= sizeof(bsdinfo
)) {
744 if (bsdinfo
.pbsi_status
== SZOMB
) {