/* * Author: Petr Tesarik * * This program tests what happens when the tracer goes away while * the tracee is waiting for it. * * A successful run should produce output like this: * * Started child 4206 * Attached process 4206 * * OK * Parent terminated with exit code 0 * * A failed run looks like this: * * Started child 4318 * Attached process 4318 * Parent terminated with exit code 0 * Child died unexpectedly * * The exit status is: * 0 (TEST_OK) success * 1 (TEST_FAILED) the test failed * 2 (TEST_ABORTED) unexpected failure prevented the test * to be performed */ #include #include #include #include #include #include #include #include #include #define TEST_OK 0 #define TEST_FAILED 1 #define TEST_ABORTED 2 int run_child() { struct timeval tv; if (ptrace(PTRACE_TRACEME, 0, 0, 0)) { perror("PTRACE_TRACEME"); return TEST_ABORTED; } raise(SIGSTOP); gettimeofday(&tv, NULL); puts("\nOK"); return TEST_OK; } int ptrace_wait(pid_t pid, char *who) { int status; if (waitpid(pid, &status, WUNTRACED) == -1) { perror("waitpid for ptrace"); return TEST_ABORTED; } if (WIFEXITED(status)) { printf("%s terminated with code %d\n", who, WEXITSTATUS(status)); return WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { printf("%s got signal %d\n", who, WTERMSIG(status)); return TEST_ABORTED; } else if (!WIFSTOPPED(status)) { printf("%s died with status %d\n", who, status); return TEST_ABORTED; } return TEST_OK; } int run_parent() { pid_t childpid; int ret; if (!(childpid = fork())) return run_child(); printf("Started child %d\n", childpid); if ((ret = ptrace_wait(childpid, "Child"))) return ret; printf("Attached process %d\n", childpid); if (ptrace(PTRACE_SYSCALL, childpid, 0, SIGCONT)) { perror("PTRACE_SYSCALL child"); return TEST_ABORTED; } /* The rest of this function is artificial, but I * could achieve similar effects by sending a SIGKILL * to strace of a heavily multithreaded application. */ /* Give the child some time to get into TASK_TRACED */ sleep(1); /* do not detach from child and exit */ return TEST_OK; } #define BUFSIZE 512 int copy_stdout(int fd) { FILE *f; static char line[BUFSIZE]; int ret = TEST_FAILED; if (! (f = fdopen(fd, "r"))) { perror("fdopen"); return TEST_ABORTED; } while (fgets(line, BUFSIZE, f)) { if (!strcmp(line, "OK\n")) ret = TEST_OK; fputs(line, stdout); } return ret; } int run_checker(pid_t pid, int fd) { int status; int ret; ret = copy_stdout(fd); /* Wait for the parent process to terminate */ if (waitpid(pid, &status, 0) == -1) { perror("waitpid parent"); return TEST_ABORTED; } if (WIFEXITED(status)) { int exitcode = WEXITSTATUS(status); printf("Parent terminated with exit code %d\n", exitcode); if (exitcode == TEST_OK && ret == TEST_FAILED) printf("Child died unexpectedly\n"); if (ret < exitcode) ret = exitcode; } else { if (WIFSIGNALED(status)) printf("Parent got signal %d\n", WTERMSIG(status)); else printf("Parent died with status %d\n", status); if (ret < TEST_ABORTED) ret = TEST_ABORTED; } return ret; } int main() { pid_t pid; int fds[2]; if (pipe(fds)) { perror("pipe"); return TEST_ABORTED; } if ((pid = fork())) { /* If closing the write side of the pipe fails, * copy_stdout() will never reach EOF, so this * is fairly important. */ if (close(fds[1])) { perror("close"); return TEST_ABORTED; } return run_checker(pid, fds[0]); } else { if (dup2(fds[1], 1) == -1) { perror("dup2"); return TEST_ABORTED; } setlinebuf(stdout); /* I don't care if these closes fail... */ close(fds[0]); close(fds[1]); return run_parent(); } }