END_TEST START_TEST(t_trace_me_execve) { int status; pid_t pid; char *const myargv[] = { "/bin/true", NULL }; if ((pid = fork()) < 0) fail("fork: %d(%s)", errno, strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } execvp("/bin/true", myargv); _exit(EXIT_FAILURE); } else { /* parent */ /* FIXME: Why do we need to attach to the process here? */ if (!pink_trace_attach(pid)) fail("pink_trace_attach: %d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); kill(pid, SIGKILL); } }
END_TEST START_TEST(t_trace_syscall_exit_signal) { int status; pid_t pid; if ((pid = fork()) < 0) fail("fork failed: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } kill(getpid(), SIGSTOP); _exit(13); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_syscall_exit(pid, SIGTERM), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSIGNALED(status), "%#x", status); fail_unless(WTERMSIG(status) == SIGTERM, "%#x", status); } }
END_TEST START_TEST(t_trace_cont_basic) { int status; pid_t pid; if ((pid = fork()) < 0) fail("fork: %d(%s)", errno, strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } kill(getpid(), SIGSTOP); _exit(13); } else { fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_resume(pid, 0), "%d(%s)", errno, strerror(errno)); waitpid(pid, &status, 0); fail_unless(WIFEXITED(status), "%#x", status); fail_unless(WEXITSTATUS(status) == 13, "%#x", status); } }
/* * Document-method: PinkTrace::Trace.me * call-seq: * PinkTrace::Trace.me() -> nil * * Indicates that this process is to be traced by its parent. Any signal * (except SIGKILL) delivered to this process will cause it to stop and its * parent to be notified via Process.wait. Also, all subsequent calls to * execve(2) by this process will cause a SIGTRAP to be sent to it, giving the * parent a chance to gain control before the new program begins execution. * * Note: This function is used only by the child process; the rest are used * only by the parent. */ VALUE pinkrb_trace_me(VALUE mod) { if (!pink_trace_me()) rb_sys_fail("pink_trace_me()"); return Qnil; }
END_TEST START_TEST(t_event_fork) { int status; pid_t pid, cpid; pink_event_t event; if ((pid = fork()) < 0) fail("fork: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(-1); } kill(getpid(), SIGSTOP); if ((cpid = fork()) < 0) { perror("fork"); _exit(-1); } _exit(0); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_setup(pid, PINK_TRACE_OPTION_SYSGOOD | PINK_TRACE_OPTION_FORK), "%d(%s)", errno, strerror(errno)); /* Resume the child, it will stop at the next fork(2) call. */ fail_unless(pink_trace_resume(pid, 0), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", strerror(errno)); event = pink_event_decide(status); fail_unless(event == PINK_EVENT_FORK, "%d != %d", PINK_EVENT_FORK, event); pink_trace_kill(pid); } }
END_TEST START_TEST(t_event_exec) { int status; pid_t pid; pink_event_t event; char *myargv[] = { "/bin/true", NULL }; char *myenviron[] = { NULL }; if ((pid = fork()) < 0) fail("fork: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(-1); } kill(getpid(), SIGSTOP); execve(myargv[0], myargv, myenviron); perror("execve"); _exit(-1); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_setup(pid, PINK_TRACE_OPTION_SYSGOOD | PINK_TRACE_OPTION_EXEC), "%d(%s)", errno, strerror(errno)); /* Resume the child, it will stop at the next execve(2) call. */ fail_unless(pink_trace_resume(pid, 0), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); event = pink_event_decide(status); fail_unless(event == PINK_EVENT_EXEC, "%d != %d", PINK_EVENT_EXEC, event); pink_trace_kill(pid); } }
END_TEST START_TEST(t_event_syscall) { int status; pid_t pid; pink_event_t event; if ((pid = fork()) < 0) fail("fork: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(-1); } kill(getpid(), SIGSTOP); /* At this point Glibc may have cached getpid() so we call it * using syscall(2). */ syscall(SYS_getpid); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_setup(pid, PINK_TRACE_OPTION_SYSGOOD), "%d(%s)", errno, strerror(errno)); /* Resume the child and arrange it to be stopped at the next * system call. */ fail_unless(pink_trace_syscall(pid, 0), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); event = pink_event_decide(status); fail_unless(event == PINK_EVENT_SYSCALL, "%d != %d", PINK_EVENT_SYSCALL, event); pink_trace_kill(pid); } }
END_TEST START_TEST(t_event_exit_signal) { int status; pid_t pid; pink_event_t event; if ((pid = fork()) < 0) fail("fork: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(-1); } kill(getpid(), SIGSTOP); kill(getpid(), SIGKILL); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); /* Do NOT set EXIT option, we want the exit(2) to be genuine. */ fail_unless(pink_trace_setup(pid, PINK_TRACE_OPTION_SYSGOOD), "%d(%s)", errno, strerror(errno)); /* Resume the child, it will exit with a signal. */ fail_unless(pink_trace_resume(pid, 0), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); event = pink_event_decide(status); fail_unless(event == PINK_EVENT_EXIT_SIGNAL, "%d != %d", PINK_EVENT_EXIT_SIGNAL, event); pink_trace_kill(pid); } }
END_TEST START_TEST(t_trace_me_signal) { int status; pid_t pid; if ((pid = fork()) < 0) fail("fork %d(%s)", errno, strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } kill(getpid(), SIGTTIN); _exit(EXIT_FAILURE); } else { /* parent */ waitpid(pid, &status, 0); fail_unless(WIFSTOPPED(status), "%#x", status); kill(pid, SIGKILL); } }
END_TEST START_TEST(t_event_genuine) { int status; pid_t pid; pink_event_t event; if ((pid = fork()) < 0) fail("fork: %s", strerror(errno)); else if (!pid) { /* child */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(-1); } kill(getpid(), SIGSTOP); kill(getpid(), SIGINT); } else { /* parent */ fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); fail_unless(WIFSTOPPED(status), "%#x", status); fail_unless(WSTOPSIG(status) == SIGSTOP, "%#x", status); fail_unless(pink_trace_setup(pid, PINK_TRACE_OPTION_SYSGOOD), "%d(%s)", errno, strerror(errno)); /* Resume the child, it will send itself a signal. */ fail_unless(pink_trace_resume(pid, 0), "%d(%s)", errno, strerror(errno)); fail_if(waitpid(pid, &status, 0) < 0, "%d(%s)", errno, strerror(errno)); event = pink_event_decide(status); fail_unless(event == PINK_EVENT_GENUINE, "%d != %d", PINK_EVENT_GENUINE, event); pink_trace_kill(pid); } }
int pink_easy_call(pink_easy_context_t *ctx, pink_easy_child_func_t func, void *userdata) { pink_easy_process_t *proc; assert(ctx != NULL); assert(func != NULL); proc = calloc(1, sizeof(pink_easy_process_t)); if (!proc) { ctx->error = PINK_EASY_ERROR_ALLOC_ELDEST; if (ctx->callback_table.error) ctx->callback_table.error(ctx); return -ctx->error; } if ((proc->pid = fork()) < 0) { ctx->error = PINK_EASY_ERROR_FORK; if (ctx->callback_table.error) ctx->callback_table.error(ctx); goto fail; } else if (!proc->pid) { /* child */ if (!pink_trace_me()) _exit(ctx->callback_table.cerror ? ctx->callback_table.cerror(PINK_EASY_CHILD_ERROR_SETUP) : EXIT_FAILURE); kill(getpid(), SIGTRAP); _exit(func(userdata)); } /* parent */ if (!_pink_easy_init(ctx, proc)) return 0; fail: free(proc); return -ctx->error; }
int main(int argc, char **argv) { int sig, status, exit_code; pink_event_t event; struct child son; /* Parse arguments */ if (argc < 2) { fprintf(stderr, "Usage: %s program [argument...]\n", argv[0]); return EXIT_FAILURE; } /* Fork */ if ((son.pid = fork()) < 0) { perror("fork"); return EXIT_FAILURE; } else if (!son.pid) { /* child */ /* Set up for tracing */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } /* Stop and let the parent continue the execution. */ kill(getpid(), SIGSTOP); ++argv; execvp(argv[0], argv); perror("execvp"); _exit(-1); } else { waitpid(son.pid, &status, 0); event = pink_event_decide(status); assert(event == PINK_EVENT_STOP); /* Set up the tracing options. */ if (!pink_trace_setup(son.pid, PINK_TRACE_OPTION_SYSGOOD | PINK_TRACE_OPTION_EXEC)) { perror("pink_trace_setup"); pink_trace_kill(son.pid); return EXIT_FAILURE; } /* Figure out the bitness of the traced child. */ son.bitness = pink_bitness_get(son.pid); if (son.bitness == PINK_BITNESS_UNKNOWN) err(1, "pink_bitness_get"); printf("Child %i runs in %s mode\n", son.pid, pink_bitness_name(son.bitness)); son.dead = son.insyscall = false; sig = exit_code = 0; for (;;) { /* At this point the traced child is stopped and needs * to be resumed. */ if (!pink_trace_syscall(son.pid, sig)) { perror("pink_trace_syscall"); return (errno == ESRCH) ? 0 : 1; } sig = 0; /* Wait for the child */ if ((son.pid = waitpid(son.pid, &status, 0)) < 0) { perror("waitpid"); return (errno == ECHILD) ? 0 : 1; } /* Check the event. */ event = pink_event_decide(status); switch (event) { case PINK_EVENT_SYSCALL: handle_syscall(&son); break; break; case PINK_EVENT_EXEC: /* Update bitness */ son.bitness = pink_bitness_get(son.pid); if (son.bitness == PINK_BITNESS_UNKNOWN) err(1, "pink_bitness_get"); else printf(" (Updating the bitness of child %i to %s mode)\n", son.pid, pink_bitness_name(son.bitness)); break; case PINK_EVENT_GENUINE: case PINK_EVENT_UNKNOWN: /* Send the signal to the traced child as it * was a genuine signal. */ sig = WSTOPSIG(status); break; case PINK_EVENT_EXIT_GENUINE: exit_code = WEXITSTATUS(status); printf("Child %i exited normally with return code %d\n", son.pid, exit_code); son.dead = true; break; case PINK_EVENT_EXIT_SIGNAL: exit_code = 128 + WTERMSIG(status); printf("Child %i exited with signal %d\n", son.pid, WTERMSIG(status)); son.dead = true; break; default: /* Nothing */ ; } if (son.dead) break; } return exit_code; } }
int main(int argc, char **argv) { int sig, status, exit_code; struct child son; /* Parse arguments */ if (argc < 2) { fprintf(stderr, "Usage: %s program [argument...]\n", argv[0]); return EXIT_FAILURE; } /* Fork */ if ((son.pid = fork()) < 0) { perror("fork"); return EXIT_FAILURE; } else if (!son.pid) { /* child */ /* Set up for tracing */ if (!pink_trace_me()) { perror("pink_trace_me"); _exit(EXIT_FAILURE); } /* Stop and let the parent continue the execution. */ kill(getpid(), SIGSTOP); ++argv; execvp(argv[0], argv); perror("execvp"); _exit(-1); } else { waitpid(son.pid, &status, 0); assert(WIFSTOPPED(status)); assert(WSTOPSIG(status) == SIGSTOP); /* Figure out the bitness of the traced child. */ son.bitness = pink_bitness_get(son.pid); if (son.bitness == PINK_BITNESS_UNKNOWN) err(1, "pink_bitness_get"); printf("Child %i runs in %s mode\n", son.pid, pink_bitness_name(son.bitness)); son.inexecve = son.insyscall = false; son.printret = true; sig = exit_code = 0; for (;;) { /* At this point the traced child is stopped and needs * to be resumed. */ if (!pink_trace_syscall(son.pid, sig)) { perror("pink_trace_syscall"); return (errno == ESRCH) ? 0 : 1; } sig = 0; /* Wait for the child */ if ((son.pid = waitpid(son.pid, &status, 0)) < 0) { perror("waitpid"); return (errno == ECHILD) ? 0 : 1; } /* Check the event */ if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) handle_sigtrap(&son); else { /* Child received a genuine signal. * Send it to the child. */ sig = WSTOPSIG(status); } } else if (WIFEXITED(status)) { exit_code = WEXITSTATUS(status); printf("Child %i exited normally with return code %d\n", son.pid, exit_code); break; } else if (WIFSIGNALED(status)) { exit_code = 128 + WTERMSIG(status); printf("Child %i exited with signal %d\n", son.pid, WTERMSIG(status)); break; } } return exit_code; } }