]>
Commit | Line | Data |
---|---|---|
39236c6e A |
1 | #include <stdio.h> |
2 | #include <stdlib.h> | |
3 | #include <unistd.h> | |
4 | #include <signal.h> | |
5 | #include <string.h> | |
6 | #include <err.h> | |
7 | #include <sys/types.h> | |
8 | #include <sys/time.h> | |
9 | #include <sys/event.h> | |
10 | #include <sys/ptrace.h> | |
11 | #include <errno.h> | |
12 | #include <sys/proc.h> | |
13 | #include <libproc.h> | |
14 | #include <stdarg.h> | |
15 | ||
16 | /* | |
17 | * We create a process hierarchy of: | |
18 | * | |
19 | * grandparent -> parent -> child | |
20 | * \ | |
21 | * \--> debugger | |
22 | * | |
23 | * When the debugger calls ptrace(2) on child, it | |
24 | * is temporarily reparented. | |
25 | * | |
26 | * We may also create a hierarchy of: | |
27 | * | |
28 | * grandparent -> parent/debugger -> child | |
29 | * | |
30 | */ | |
31 | ||
32 | typedef enum { | |
33 | eParentExitAfterWaitpid = 0, | |
34 | eParentExitAfterWaitpidAndSIGCHLD, | |
35 | eParentExitBeforeWaitpid, | |
36 | eParentExitAfterDebuggerAttach, | |
37 | eParentExitBeforeDebuggerAttach, | |
38 | eParentIsDebugger | |
39 | } parent_exit_t; | |
40 | ||
41 | typedef enum { | |
42 | eDebuggerExitAfterKillAndWaitpid = 0, | |
43 | eDebuggerExitAfterKillWithoutWaitpid, | |
44 | eDebuggerExitAfterDetach, | |
45 | eDebuggerExitWithoutDetach | |
46 | } debugger_exit_t; | |
47 | ||
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)); | |
52 | ||
53 | bool iszombie(pid_t p); | |
54 | ||
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); | |
60 | ||
61 | void logline(const char *format, ...); | |
62 | ||
63 | void usage(void); | |
64 | int test_all_permutations(void); | |
65 | void test(parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time) __attribute__((noreturn)); | |
66 | ||
67 | int main(int argc, char *argv[]) { | |
68 | int ch; | |
69 | ||
70 | int parent_exit_time = -1; | |
71 | int debugger_exit_time = -1; | |
72 | ||
73 | while ((ch = getopt(argc, argv, "p:w:")) != -1) { | |
74 | switch (ch) { | |
75 | case 'p': | |
76 | parent_exit_time = atoi(optarg); | |
77 | break; | |
78 | case 'w': | |
79 | debugger_exit_time = atoi(optarg); | |
80 | break; | |
81 | case '?': | |
82 | default: | |
83 | usage(); | |
84 | } | |
85 | } | |
86 | ||
87 | /* no explicit options, loop through them all */ | |
88 | if (parent_exit_time == -1 && | |
89 | debugger_exit_time == -1) { | |
90 | return test_all_permutations(); | |
91 | } | |
92 | ||
93 | if (parent_exit_time == -1 || | |
94 | debugger_exit_time == -1) { | |
95 | usage(); | |
96 | } | |
97 | ||
98 | test((parent_exit_t)parent_exit_time, | |
99 | (debugger_exit_t)debugger_exit_time); | |
100 | ||
101 | return 0; /* never reached */ | |
102 | } | |
103 | ||
104 | void test(parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time) | |
105 | { | |
106 | pid_t parent, child, debugger; | |
107 | int ret; | |
108 | int fds[2]; | |
109 | ||
110 | /* pipe for parent to send child pid to grandparent */ | |
111 | ret = pipe(fds); | |
112 | if (-1 == ret) { | |
113 | err(1, "failed to create pipe"); | |
114 | } | |
115 | ||
116 | parent = fork(); | |
117 | if (parent == 0) { | |
118 | /* parent sub-branch */ | |
119 | ||
120 | ret = close(fds[0]); | |
121 | if (ret == -1) { | |
122 | err(1, "close read end of pipe"); | |
123 | } | |
124 | ||
125 | child = fork(); | |
126 | if (child == 0) { | |
127 | /* child */ | |
128 | ret = close(fds[1]); | |
129 | if (ret == -1) { | |
130 | err(1, "close write end of pipe"); | |
131 | } | |
132 | ||
133 | do_child(); | |
134 | } else if (child == -1) { | |
135 | err(1, "parent failed to fork child"); | |
136 | } else { | |
137 | /* parent */ | |
138 | if (-1 == write(fds[1], &child, sizeof(child))) { | |
139 | err(1, "writing child pid to grandparent"); | |
140 | } | |
141 | ||
142 | if (parent_exit_time == eParentIsDebugger) { | |
143 | debugger = -1; | |
144 | ||
145 | if (-1 == write(fds[1], &debugger, sizeof(debugger))) { | |
146 | err(1, "writing debugger pid to grandparent"); | |
147 | } | |
148 | ret = close(fds[1]); | |
149 | if (ret == -1) { | |
150 | err(1, "close write end of pipe"); | |
151 | } | |
152 | ||
153 | do_debugger(child, debugger_exit_time); | |
154 | } else { | |
155 | debugger = fork(); | |
156 | if (debugger == 0) { | |
157 | /* debugger */ | |
158 | ret = close(fds[1]); | |
159 | if (ret == -1) { | |
160 | err(1, "close write end of pipe"); | |
161 | } | |
162 | ||
163 | do_debugger(child, debugger_exit_time); | |
164 | } else if (debugger == -1) { | |
165 | err(1, "parent failed to fork debugger"); | |
166 | } else { | |
167 | /* still parent */ | |
168 | if (-1 == write(fds[1], &debugger, sizeof(debugger))) { | |
169 | err(1, "writing debugger pid to grandparent"); | |
170 | } | |
171 | ret = close(fds[1]); | |
172 | if (ret == -1) { | |
173 | err(1, "close write end of pipe"); | |
174 | } | |
175 | ||
176 | do_parent(child, debugger, parent_exit_time, debugger_exit_time); | |
177 | } | |
178 | } | |
179 | } | |
180 | } else if (parent == -1) { | |
181 | err(1, "grandparent failed to fork parent"); | |
182 | } else { | |
183 | ret = close(fds[1]); | |
184 | if (ret == -1) { | |
185 | err(1, "close write end of pipe"); | |
186 | } | |
187 | ||
188 | if (-1 == read(fds[0], &child, sizeof(child))) { | |
189 | err(1, "could not read child pid"); | |
190 | } | |
191 | ||
192 | if (-1 == read(fds[0], &debugger, sizeof(debugger))) { | |
193 | err(1, "could not read debugger pid"); | |
194 | } | |
195 | ||
196 | ret = close(fds[0]); | |
197 | if (ret == -1) { | |
198 | err(1, "close read end of pipe"); | |
199 | } | |
200 | ||
201 | do_grandparent(parent, child, debugger, debugger_exit_time); | |
202 | } | |
203 | } | |
204 | ||
205 | void usage(void) | |
206 | { | |
207 | errx(1, "Usage: %s [-p <parent_exit_time> -w <debugger_exit_time>]", getprogname()); | |
208 | } | |
209 | ||
210 | int test_all_permutations(void) | |
211 | { | |
212 | int p, w; | |
213 | bool has_failure = false; | |
214 | ||
215 | for (p = 0; p <= 5; p++) { | |
216 | for (w = 0; w <= 3; w++) { | |
217 | int testpid; | |
218 | int ret; | |
219 | ||
220 | testpid = fork(); | |
221 | if (testpid == 0) { | |
222 | logline("-------------------------------------------------------"); | |
223 | logline("*** Executing self-test: %s -p %d -w %d", | |
224 | getprogname(), p, w); | |
225 | test((parent_exit_t)p, | |
226 | (debugger_exit_t)w); | |
227 | _exit(1); /* never reached */ | |
228 | } else if (testpid == -1) { | |
229 | err(1, "failed to fork test pid"); | |
230 | } else { | |
231 | int stat_loc; | |
232 | ||
233 | ret = waitpid(testpid, &stat_loc, 0); | |
234 | if (ret == -1) | |
235 | err(1, "waitpid(%d) by test harness failed", testpid); | |
236 | ||
237 | logline("test process: %s", print_exit(testpid, stat_loc)); | |
238 | if (!WIFEXITED(stat_loc) || (0 != WEXITSTATUS(stat_loc))) { | |
239 | logline("FAILED TEST"); | |
240 | has_failure = true; | |
241 | } | |
242 | } | |
243 | } | |
244 | } | |
245 | ||
246 | if (has_failure) { | |
247 | logline("test failures found"); | |
248 | return 1; | |
249 | } | |
250 | ||
251 | return 0; | |
252 | } | |
253 | ||
254 | void do_grandparent(pid_t parent, pid_t child, pid_t debugger, debugger_exit_t debugger_exit_time) | |
255 | { | |
256 | pid_t result; | |
257 | int stat_loc; | |
258 | int exit_code = 0; | |
259 | int kq; | |
260 | int ret; | |
261 | struct kevent64_s kev; | |
262 | int neededdeathcount = (debugger != -1) ? 3 : 2; | |
263 | ||
264 | setprogname("GRANDPARENT"); | |
265 | ||
266 | logline("grandparent pid %d has parent pid %d and child pid %d. waiting for parent process exit...", getpid(), parent, child); | |
267 | ||
268 | /* make sure we can at least observe real child's exit */ | |
269 | kq = kqueue(); | |
270 | if (kq < 0) | |
271 | err(1, "kqueue"); | |
272 | ||
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); | |
276 | if (ret == -1) | |
277 | err(1, "kevent64 EVFILT_PROC"); | |
278 | ||
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); | |
282 | if (ret == -1) | |
283 | err(1, "kevent64 EVFILT_PROC"); | |
284 | ||
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); | |
289 | if (ret == -1) | |
290 | err(1, "kevent64 EVFILT_PROC"); | |
291 | } | |
292 | ||
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); | |
296 | if (ret == -1) | |
297 | err(1, "kevent64 EVFILT_TIMER"); | |
298 | ||
299 | while(1) { | |
300 | ||
301 | ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL); | |
302 | if (ret == -1) { | |
303 | if (errno == EINTR) | |
304 | continue; | |
305 | err(1, "kevent64"); | |
306 | } else if (ret == 0) { | |
307 | break; | |
308 | } | |
309 | ||
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) { | |
316 | neededdeathcount--; | |
317 | } else if (parent == kev.udata) { | |
318 | neededdeathcount--; | |
319 | } else if ((debugger != -1) && (debugger == kev.udata)) { | |
320 | neededdeathcount--; | |
321 | } | |
322 | } else if (kev.filter == EVFILT_TIMER) { | |
323 | logline("timed out waiting for NOTE_EXIT"); | |
324 | exit_code = 1; | |
325 | break; | |
326 | } | |
327 | ||
328 | if (neededdeathcount == 0) { | |
329 | break; | |
330 | } | |
331 | } | |
332 | ||
333 | result = waitpid(parent, &stat_loc, 0); | |
334 | if (result == -1) | |
335 | err(1, "waitpid(%d) by grandparent failed", parent); | |
336 | ||
337 | ||
338 | logline("parent process: %s", print_exit(parent, stat_loc)); | |
339 | if (!WIFEXITED(stat_loc) || (0 != WEXITSTATUS(stat_loc))) { | |
340 | exit_code = 1; | |
341 | } | |
342 | ||
343 | if (iszombie(parent)) { | |
344 | logline("parent %d is now a zombie", parent); | |
345 | exit_code = 1; | |
346 | } | |
347 | ||
348 | if (iszombie(child)) { | |
349 | logline("child %d is now a zombie", child); | |
350 | exit_code = 1; | |
351 | } | |
352 | ||
353 | if ((debugger != -1) && iszombie(debugger)) { | |
354 | logline("debugger %d is now a zombie", debugger); | |
355 | exit_code = 1; | |
356 | } | |
357 | ||
358 | exit(exit_code); | |
359 | } | |
360 | ||
361 | /* | |
362 | * debugger will register kevents, wait for quorum on events, then exit | |
363 | */ | |
364 | void do_parent(pid_t child, pid_t debugger, parent_exit_t parent_exit_time, debugger_exit_t debugger_exit_time) | |
365 | { | |
366 | int kq; | |
367 | int ret; | |
368 | struct kevent64_s kev; | |
369 | int deathcount = 0; | |
370 | int childsignalcount = 0; | |
371 | int stat_loc; | |
372 | ||
373 | setprogname("PARENT"); | |
374 | ||
375 | logline("parent pid %d has child pid %d and debugger pid %d. waiting for processes to exit...", getpid(), child, debugger); | |
376 | ||
377 | kq = kqueue(); | |
378 | if (kq < 0) | |
379 | err(1, "kqueue"); | |
380 | ||
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, | |
383 | 0, child, 0, 0); | |
384 | ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL); | |
385 | if (ret == -1) | |
386 | err(1, "kevent64 EVFILT_PROC"); | |
387 | ||
388 | EV_SET64(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, | |
389 | 0, 0, child, 0, 0); | |
390 | ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL); | |
391 | if (ret == -1) | |
392 | err(1, "kevent64 EVFILT_SIGNAL"); | |
393 | ||
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); | |
397 | if (ret == -1) | |
398 | err(1, "kevent64 EVFILT_TIMER"); | |
399 | ||
400 | while(1) { | |
401 | ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL); | |
402 | if (ret == -1) { | |
403 | if (errno == EINTR) | |
404 | continue; | |
405 | err(1, "kevent64"); | |
406 | } else if (ret == 0) { | |
407 | break; | |
408 | } | |
409 | ||
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 */ | |
416 | deathcount++; | |
417 | } else if (kev.filter == EVFILT_PROC) { | |
418 | if (child == kev.udata) { | |
419 | if ((kev.fflags & (NOTE_EXIT|NOTE_EXITSTATUS)) == (NOTE_EXIT|NOTE_EXITSTATUS)) { | |
420 | deathcount++; | |
421 | } else if (kev.fflags & NOTE_SIGNAL) { | |
422 | childsignalcount++; | |
423 | if ((parent_exit_time == eParentExitAfterDebuggerAttach) && (childsignalcount >= 2)) { | |
424 | /* second signal is attach */ | |
425 | logline("exiting because of eParentExitAfterDebuggerAttach"); | |
426 | exit(0); | |
427 | } | |
428 | } else if (kev.fflags & NOTE_FORK) { | |
429 | if (parent_exit_time == eParentExitBeforeDebuggerAttach) { | |
430 | logline("exiting because of eParentExitBeforeDebuggerAttach"); | |
431 | exit(0); | |
432 | } | |
433 | } | |
434 | } | |
435 | } else if (kev.filter == EVFILT_TIMER) { | |
436 | errx(1, "timed out waiting for NOTE_EXIT"); | |
437 | } | |
438 | ||
439 | if (deathcount >= (parent_exit_time == eParentExitAfterWaitpidAndSIGCHLD ? 2 : 1)) { | |
440 | break; | |
441 | } | |
442 | } | |
443 | ||
444 | if (parent_exit_time == eParentExitBeforeWaitpid) { | |
445 | logline("exiting because of eParentExitBeforeWaitpid"); | |
446 | exit(0); | |
447 | } | |
448 | ||
449 | ret = waitpid(child, &stat_loc, 0); | |
450 | if (ret == -1) | |
451 | err(1, "waitpid(%d) by parent failed", child); | |
452 | ||
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"); | |
456 | ||
457 | ret = waitpid(debugger, &stat_loc, 0); | |
458 | if (ret == -1) | |
459 | err(1, "waitpid(%d) by parent failed", debugger); | |
460 | ||
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"); | |
464 | ||
465 | /* Received both SIGCHLD and NOTE_EXIT, as needed */ | |
466 | logline("exiting beacuse of eParentExitAfterWaitpid/eParentExitAfterWaitpidAndSIGCHLD"); | |
467 | exit(0); | |
468 | } | |
469 | ||
470 | /* child will spin waiting to be killed by debugger or parent or someone */ | |
471 | void do_child(void) | |
472 | { | |
473 | pid_t doublechild; | |
474 | int ret; | |
475 | setprogname("CHILD"); | |
476 | ||
477 | logline("child pid %d. waiting for external termination...", getpid()); | |
478 | ||
479 | usleep(500000); | |
480 | ||
481 | doublechild = fork(); | |
482 | if (doublechild == 0) { | |
483 | exit(0); | |
484 | } else if (doublechild == -1) { | |
485 | err(1, "doublechild"); | |
486 | } else { | |
487 | ret = waitpid(doublechild, NULL, 0); | |
488 | if (ret == -1) | |
489 | err(1, "waitpid(%d) by parent failed", doublechild); | |
490 | } | |
491 | ||
492 | while (1) { | |
493 | sleep(60); | |
494 | } | |
495 | } | |
496 | ||
497 | /* | |
498 | * debugger will register kevents, attach+kill child, wait for quorum on events, | |
499 | * then exit. | |
500 | */ | |
501 | void do_debugger(pid_t child, debugger_exit_t debugger_exit_time) | |
502 | { | |
503 | int kq; | |
504 | int ret; | |
505 | struct kevent64_s kev; | |
506 | int deathcount = 0; | |
507 | int stat_loc; | |
508 | ||
509 | setprogname("DEBUGGER"); | |
510 | ||
511 | logline("debugger pid %d has child pid %d. waiting for process exit...", getpid(), child); | |
512 | ||
513 | sleep(1); | |
514 | fprintf(stderr, "\n"); | |
515 | ret = ptrace(PT_ATTACH, child, 0, 0); | |
516 | if (ret == -1) | |
517 | err(1, "ptrace(PT_ATTACH)"); | |
518 | ||
519 | ret = waitpid(child, &stat_loc, WUNTRACED); | |
520 | if (ret == -1) | |
521 | err(1, "waitpid(child, WUNTRACED)"); | |
522 | ||
523 | logline("child process stopped: %s", print_exit(child, stat_loc)); | |
524 | ||
525 | if (debugger_exit_time == eDebuggerExitWithoutDetach) { | |
526 | logline("exiting because of eDebuggerExitWithoutDetach"); | |
527 | exit(0); | |
528 | } else if (debugger_exit_time == eDebuggerExitAfterDetach) { | |
529 | ret = ptrace(PT_DETACH, child, 0, 0); | |
530 | if (ret == -1) | |
531 | err(1, "ptrace(PT_DETACH)"); | |
532 | ||
533 | ret = kill(child, SIGKILL); | |
534 | if (ret == -1) | |
535 | err(1, "kill(SIGKILL)"); | |
536 | ||
537 | logline("exiting because of eDebuggerExitAfterDetach"); | |
538 | exit(0); | |
539 | } | |
540 | ||
541 | kq = kqueue(); | |
542 | if (kq < 0) | |
543 | err(1, "kqueue"); | |
544 | ||
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, | |
547 | 0, child, 0, 0); | |
548 | ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL); | |
549 | if (ret == -1) | |
550 | err(1, "kevent64 EVFILT_PROC"); | |
551 | ||
552 | EV_SET64(&kev, SIGCHLD, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, | |
553 | 0, 0, child, 0, 0); | |
554 | ret = kevent64(kq, &kev, 1, NULL, 0, 0, NULL); | |
555 | if (ret == -1) | |
556 | err(1, "kevent64 EVFILT_SIGNAL"); | |
557 | ||
558 | sleep(1); | |
559 | fprintf(stderr, "\n"); | |
560 | ret = ptrace(PT_KILL, child, 0, 0); | |
561 | if (ret == -1) | |
562 | err(1, "ptrace(PT_KILL)"); | |
563 | ||
564 | while(1) { | |
565 | ret = kevent64(kq, NULL, 0, &kev, 1, 0, NULL); | |
566 | if (ret == -1) { | |
567 | if (errno == EINTR) | |
568 | continue; | |
569 | err(1, "kevent64"); | |
570 | } else if (ret == 0) { | |
571 | continue; | |
572 | } | |
573 | ||
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 */ | |
580 | deathcount++; | |
581 | } else if (kev.filter == EVFILT_PROC) { | |
582 | if ((kev.fflags & (NOTE_EXIT|NOTE_EXITSTATUS)) == (NOTE_EXIT|NOTE_EXITSTATUS)) { | |
583 | deathcount++; | |
584 | } | |
585 | } | |
586 | ||
587 | if (deathcount >= 2) { | |
588 | break; | |
589 | } | |
590 | } | |
591 | ||
592 | if (debugger_exit_time == eDebuggerExitAfterKillWithoutWaitpid) { | |
593 | logline("exiting because of eDebuggerExitAfterKillWithoutWaitpid"); | |
594 | exit(0); | |
595 | } | |
596 | ||
597 | sleep(1); | |
598 | fprintf(stderr, "\n"); | |
599 | ret = waitpid(child, &stat_loc, 0); | |
600 | if (ret == -1) | |
601 | err(1, "waitpid(%d) by debugger failed", child); | |
602 | ||
603 | logline("child process: %s", print_exit(child, stat_loc)); | |
604 | ||
605 | /* Received both SIGCHLD and NOTE_EXIT */ | |
606 | exit(0); | |
607 | } | |
608 | ||
609 | void logline(const char *format, ...) | |
610 | { | |
611 | char *line = NULL; | |
612 | char newformat[1024]; | |
613 | ||
614 | snprintf(newformat, sizeof(newformat), "%s: %s\n", getprogname(), format); | |
615 | ||
616 | va_list va; | |
617 | ||
618 | va_start(va, format); | |
619 | vasprintf(&line, newformat, va); | |
620 | va_end(va); | |
621 | ||
622 | if (line) { | |
623 | write(STDOUT_FILENO, line, strlen(line)); | |
624 | free(line); | |
625 | } else { | |
626 | write(STDOUT_FILENO, "error\n", 6); | |
627 | } | |
628 | } | |
629 | ||
630 | ||
631 | char *str_kev_filter(int filter) | |
632 | { | |
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)); | |
640 | else | |
641 | strlcpy(filter_string, "EVFILT_UNKNOWN", sizeof(filter_string)); | |
642 | ||
643 | return filter_string; | |
644 | } | |
645 | ||
646 | char *str_kev_flags(int filter, uint16_t flags) | |
647 | { | |
648 | static char flags_string[128]; | |
649 | ||
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)); | |
661 | ||
662 | if (flags_string[0] == '|') | |
663 | return &flags_string[1]; | |
664 | else | |
665 | return flags_string; | |
666 | } | |
667 | ||
668 | char *str_kev_fflags(int filter, uint32_t fflags) | |
669 | { | |
670 | static char fflags_string[128]; | |
671 | ||
672 | fflags_string[0] = '\0'; | |
673 | ||
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)); | |
687 | #endif | |
688 | } else if (filter == EVFILT_TIMER) { | |
689 | if (fflags & NOTE_SECONDS) strlcat(fflags_string, "|NOTE_SECONDS", sizeof(fflags_string)); | |
690 | } else { | |
691 | strlcat(fflags_string, "UNKNOWN", sizeof(fflags_string)); | |
692 | } | |
693 | ||
694 | if (fflags_string[0] == '|') | |
695 | return &fflags_string[1]; | |
696 | else | |
697 | return fflags_string; | |
698 | } | |
699 | ||
700 | char *str_kev_data(int filter, uint32_t fflags, int64_t data, uint64_t udata) | |
701 | { | |
702 | static char data_string[128]; | |
703 | ||
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)); | |
712 | } else { | |
713 | snprintf(data_string, sizeof(data_string), "pid %llu unknown exit status 0x%08llx", udata, data); | |
714 | } | |
715 | } else if (fflags & NOTE_EXIT) { | |
716 | snprintf(data_string, sizeof(data_string), "pid %llu exited", udata); | |
717 | } else { | |
718 | data_string[0] = '\0'; | |
719 | } | |
720 | } else if (filter == EVFILT_TIMER) { | |
721 | snprintf(data_string, sizeof(data_string), "timer fired %lld time(s)", data); | |
722 | } else { | |
723 | data_string[0] = '\0'; | |
724 | } | |
725 | ||
726 | return data_string; | |
727 | } | |
728 | ||
729 | char *print_exit(pid_t p, int stat_loc) | |
730 | { | |
731 | return str_kev_data(EVFILT_PROC, NOTE_EXIT|NOTE_EXITSTATUS, stat_loc, p); | |
732 | } | |
733 | ||
734 | bool iszombie(pid_t p) | |
735 | { | |
736 | int ret; | |
737 | struct proc_bsdshortinfo bsdinfo; | |
738 | ||
739 | ret = proc_pidinfo(p, PROC_PIDT_SHORTBSDINFO, 1, &bsdinfo, sizeof(bsdinfo)); | |
740 | if (ret != sizeof(bsdinfo)) { | |
741 | return false; | |
742 | } | |
743 | ||
744 | if (bsdinfo.pbsi_status == SZOMB) { | |
745 | return true; | |
746 | } else { | |
747 | return false; | |
748 | } | |
749 | } |