From 88b17cb724c653391a92be00b48378432a1036a5 Mon Sep 17 00:00:00 2001 From: Joe Korty Date: Mon, 14 Jan 2019 17:29:26 +0100 Subject: [PATCH 1/5] Add ssdd test to the rt-tests suite The following program might make a good addition to the rt test suite. It tests the reliability of PTRACE_SINGLESTEP. It does by default 10,000 ssteps against a simple, spinner tracee. Also by default, it spins off ten of these tracer/tracee pairs, all of which are to run concurrently. Starting with 4.13-rt, this test occasionally encounters a sstep whose waitpid returns a WIFSIGNALED (signal SIGTRAP) rather than a WIFSTOPPED. This usually happens after thousands of ssteps have executed. Having multiple tracer/tracee pairs running dramatically increases the chances of failure. The is what the test output looks like for a good run: forktest#0/22872: STARTING forktest#7/22879: STARTING forktest#8/22880: STARTING forktest#6/22878: STARTING forktest#5/22877: STARTING forktest#3/22875: STARTING forktest#4/22876: STARTING forktest#9/22882: STARTING forktest#2/22874: STARTING forktest#1/22873: STARTING forktest#0/22872: EXITING, no error forktest#8/22880: EXITING, no error forktest#3/22875: EXITING, no error forktest#7/22879: EXITING, no error forktest#6/22878: EXITING, no error forktest#5/22877: EXITING, no error forktest#2/22874: EXITING, no error forktest#4/22876: EXITING, no error forktest#9/22882: EXITING, no error forktest#1/22873: EXITING, no error All tests PASSED. Signed-off-by: Joe Korty Signed-off-by: John Kacur --- src/ssdd/ssdd.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/ssdd/ssdd.c diff --git a/src/ssdd/ssdd.c b/src/ssdd/ssdd.c new file mode 100644 index 000000000000..6d09d54e34e1 --- /dev/null +++ b/src/ssdd/ssdd.c @@ -0,0 +1,315 @@ +/* + * Have a tracer do a bunch of PTRACE_SINGLESTEPs against + * a tracee as fast as possible. Create several of these + * tracer/tracee pairs and see if they can be made to + * interfere with each other. + * + * Usage: + * ssdd nforks niters + * Where: + * nforks - number of tracer/tracee pairs to fork off. + * default 10. + * niters - number of PTRACE_SINGLESTEP iterations to + * do before declaring success, for each tracer/ + * tracee pair set up. Default 10,000. + * + * The tracer waits on each PTRACE_SINGLESTEP with a waitpid(2) + * and checks that waitpid's return values for correctness. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* do_wait return values */ +#define STATE_EXITED 1 +#define STATE_STOPPED 2 +#define STATE_SIGNALED 3 +#define STATE_UNKNOWN 4 +#define STATE_ECHILD 5 +#define STATE_EXITED_TSIG 6 /* exited with termination signal */ +#define STATE_EXITED_ERRSTAT 7 /* exited with non-zero status */ + +char *state_name[] = { + [STATE_EXITED] = "STATE_EXITED", + [STATE_STOPPED] = "STATE_STOPPED", + [STATE_SIGNALED] = "STATE_SIGNALED", + [STATE_UNKNOWN] = "STATE_UNKNOWN", + [STATE_ECHILD] = "STATE_ECHILD", + [STATE_EXITED_TSIG] = "STATE_EXITED_TSIG", + [STATE_EXITED_ERRSTAT] = "STATE_EXITED_ERRSTAT" +}; + +const char *get_state_name(int state) +{ + if (state < STATE_EXITED || state > STATE_EXITED_ERRSTAT) + return "?"; + return state_name[state]; +} + +#define unused __attribute__((unused)) + +static int got_sigchld; + +static int do_wait(pid_t *wait_pid, int *ret_sig) +{ + int status, child_status; + + *ret_sig = -1; /* initially mark 'nothing returned' */ + + while (1) { + status = waitpid(-1, &child_status, WUNTRACED | __WALL); + if (status == -1) { + if (errno == EINTR) + continue; + if (errno == ECHILD) { + *wait_pid = (pid_t)0; + return STATE_ECHILD; + } + printf("do_wait/%d: EXITING, ERROR: " + "waitpid() returned errno %d\n", + getpid(), errno); + exit(1); + } + break; + } + *wait_pid = (pid_t)status; + + if (WIFEXITED(child_status)) { + if (WIFSIGNALED(child_status)) + return STATE_EXITED_TSIG; + if (WEXITSTATUS(child_status)) + return STATE_EXITED_ERRSTAT; + return STATE_EXITED; + } + if (WIFSTOPPED(child_status)) { + *ret_sig = WSTOPSIG(child_status); + return STATE_STOPPED; + } + if (WIFSIGNALED(child_status)) { + *ret_sig = WTERMSIG(child_status); + return STATE_SIGNALED; + } + return STATE_UNKNOWN; +} + +int check_sigchld(void) +{ + int i; + /* + * The signal is asynchronous so give it some + * time to arrive. + */ + for (i = 0; i < 10 && !got_sigchld; i++) + usleep(1000); /* 10 msecs */ + for (i = 0; i < 10 && !got_sigchld; i++) + usleep(2000); /* 20 + 10 = 30 msecs */ + for (i = 0; i < 10 && !got_sigchld; i++) + usleep(4000); /* 40 + 30 = 70 msecs */ + for (i = 0; i < 10 && !got_sigchld; i++) + usleep(8000); /* 80 + 70 = 150 msecs */ + for (i = 0; i < 10 && !got_sigchld; i++) + usleep(16000); /* 160 + 150 = 310 msecs */ + + return got_sigchld; +} + +pid_t parent; +int nforks = 10; +int nsteps = 10000; + +static void sigchld(int sig, unused siginfo_t * info, unused void *arg) +{ + got_sigchld = 1; +} + +static void child_process(void) +{ + unused volatile int i; + + /* wait for ptrace attach */ + usleep(100000); + while (1) + i = 0; +} + +static int forktests(int testid) +{ + int i, status, ret_sig; + long pstatus; + pid_t child, wait_pid; + struct sigaction act, oact; + + parent = getpid(); + printf("forktest#%d/%d: STARTING\n", testid, parent); + + child = fork(); + if (child == -1) { + printf("forktest#%d/%d: EXITING, ERROR: " + "fork returned errno %d\n", testid, parent, errno); + exit(1); + } + if (!child) + child_process(); + + act.sa_sigaction = sigchld; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + status = sigaction(SIGCHLD, &act, &oact); + if (status) { + printf("forktest#%d/%d: EXITING, ERROR: " + "sigaction returned %d, errno %d\n", + testid, parent, status, errno); + exit(1); + } + + /* give both our child and parent time to set things up */ + usleep(125000); + + /* + * Attach to the child. + */ + pstatus = ptrace(PTRACE_ATTACH, child, NULL, NULL); + if (pstatus == ~0l) { + printf("forktest#%d/%d: EXITING, ERROR: " + "attach failed. errno %d\n", + testid, getpid(), errno); + exit(1); + } + + /* + * The attach should cause the child to receive a signal. + */ + status = do_wait(&wait_pid, &ret_sig); + if (wait_pid != child) { + printf("forktest#%d/%d: EXITING, ERROR: " + "attach: Unexpected wait pid %d\n", + testid, getpid(), wait_pid); + exit(1); + } + if (status != STATE_STOPPED) { + printf("forktest#%d/%d: EXITING, ERROR: " + "attach: wait on PTRACE_ATTACH returned %d " + "[%s, wanted STATE_STOPPED], signo %d\n", + testid, getpid(), status, get_state_name(status), + ret_sig); + exit(1); + } + else if (!check_sigchld()) { + printf("forktest#%d/%d: EXITING, ERROR: " + "wait on PTRACE_ATTACH saw a SIGCHLD count of %d, should be 1\n", + testid, getpid(), got_sigchld); + exit(1); + } + got_sigchld = 0; + + + /* + * Generate 'nsteps' PTRACE_SINGLESTEPs, make sure they all actually + * step the tracee. + */ + for (i = 0; i < nsteps; i++) { + pstatus = ptrace(PTRACE_SINGLESTEP, child, NULL, NULL); + + if (pstatus) { + printf("forktest#%d/%d: EXITING, ERROR: " + "PTRACE_SINGLESTEP #%d: returned status %ld, " + "errno %d, signo %d\n", + testid, getpid(), i, pstatus, errno, ret_sig); + exit(1); + } + + status = do_wait(&wait_pid, &ret_sig); + if (wait_pid != child) { + printf("forktest#%d/%d: EXITING, ERROR: " + "wait on PTRACE_SINGLESTEP #%d: returned wrong pid %d, " + "expected %d\n", + testid, getpid(), i, wait_pid, child); + exit(1); + } + if (status != STATE_STOPPED) { + printf("forktest#%d/%d: EXITING, ERROR: " + "wait on PTRACE_SINGLESTEP #%d: wanted STATE_STOPPED, " + "saw %s instead (and saw signo %d too)\n", + testid, getpid(), i, + get_state_name(status), ret_sig); + exit(1); + } + if (ret_sig != SIGTRAP) { + printf("forktest#%d/%d: EXITING, ERROR: " + "wait on PTRACE_SINGLESTEP #%d: returned signal %d, " + "wanted SIGTRAP\n", + testid, getpid(), i, ret_sig); + exit(1); + } + if (!check_sigchld()) { + printf("forktest#%d/%d: EXITING, ERROR: " + "wait on PTRACE_SINGLESTEP #%d: no SIGCHLD seen " + "(signal count == 0), signo %d\n", + testid, getpid(), i, ret_sig); + exit(1); + } + got_sigchld = 0; + } + + /* There is no need for the tracer to kill the tracee. It will + * automatically exit when its owner, ie, us, exits. + */ + + printf("forktest#%d/%d: EXITING, no error\n", testid, parent); + exit(0); +} + +int main(int argc, char **argv) +{ + int i, ret_sig, status; + pid_t child = 0, wait_pid; + int error = 0; + + setbuf(stdout, NULL); + + argc--, argv++; + if (argc) { + nforks = atoi(*argv); + argc--, argv++; + if (argc) + nsteps = atoi(*argv); + } + printf("#forks: %d\n", nforks); + printf("#steps: %d\n", nsteps); + printf("\n"); + + for (i = 0; i < nforks; i++) { + child = fork(); + if (child == -1) { + printf("main: fork returned errno %d\n", errno); + exit(1); + } + if (!child) + forktests(i); + } + + for (i = 0; i < nforks; i++) { + status = do_wait(&wait_pid, &ret_sig); + if (status != STATE_EXITED) { + if (0) printf("main/%d: ERROR: " + "forktest#%d unexpected do_wait status %d " + "[%s, wanted STATE_EXITED]\n", + getpid(), wait_pid, status, + get_state_name(status)); + error = 1; + } + } + + printf("%s.\n", error ? + "One or more tests FAILED" : + "All tests PASSED"); + exit(error); +} -- 2.20.1