On Tue, Nov 27, 2018 at 08:58:19AM -0600, Clark Williams wrote: > Joe, > > This looks interesting. Do you have a git repo where I can pull the > source? > > Clark Hi Clark, No I don't, sorry. I am attaching the LAG version, it is a few dozen lines shorter than the version I first sent out to the mailing list. Joe PS: Oh, I forgot to do.... Signed-off-by: Joe Korty <joe.korty@xxxxxxxxxxxxxxxxx> > On Tue, 20 Nov 2018 12:29:00 -0500 > Steven Rostedt <rostedt@xxxxxxxxxxx> wrote: > > > [ Adding Clark and John who manage the rt-tests repo ] > > > > -- Steve > > > > On Mon, 19 Nov 2018 19:46:23 +0000 > > Joe Korty <Joe.Korty@xxxxxxxxxxxxxxxxx> wrote: > > > > > Hi Julia & the RT team, > > > > > > 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: > > > > > > #forks: 10 > > > #steps: 10000 > > > > > > 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. > > > > > > This is what the test output looks like for a failing run: > > > > > > #forks: 10 > > > #steps: 10000 > > > > > > forktest#0/22906: STARTING > > > forktest#1/22907: STARTING > > > forktest#2/22909: STARTING > > > forktest#3/22911: STARTING > > > forktest#4/22912: STARTING > > > forktest#5/22915: STARTING > > > forktest#6/22916: STARTING > > > forktest#7/22919: STARTING > > > forktest#8/22920: STARTING > > > forktest#9/22923: STARTING > > > forktest#2/22909: EXITING, ERROR: wait on PTRACE_SINGLESTEP #9: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#5/22915: EXITING, no error > > > forktest#3/22911: EXITING, ERROR: wait on PTRACE_SINGLESTEP #7953: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#0/22906: EXITING, ERROR: wait on PTRACE_SINGLESTEP #5072: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#9/22923: EXITING, ERROR: wait on PTRACE_SINGLESTEP #7992: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#4/22912: EXITING, ERROR: wait on PTRACE_SINGLESTEP #9923: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#1/22907: EXITING, ERROR: wait on PTRACE_SINGLESTEP #7723: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#7/22919: EXITING, ERROR: wait on PTRACE_SINGLESTEP #5036: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#8/22920: EXITING, ERROR: wait on PTRACE_SINGLESTEP #4943: wanted STATE_STOPPED, saw STATE_SIGNALED instead (and saw signo 5 too) > > > forktest#6/22916: EXITING, no error > > > One or more tests FAILED. > > > > > > Here are the observations from my testing so far: > > > > > > - It has never failed when confined to a single cpu. > > > - It has never failed on !rt kernels. > > > - It has never failed on any kernel prior to 4.13. > > > - More failures than what chance would dictate happen > > > near the end of a test run (ie, if a test of 10,000 > > > steps is run, the failure will be at the 9,xxx'th step, > > > if 100,000 steps are run, the failure will be at > > > the 9x,xxx'th step). > > > > > > The above results are from kernels 4.{4,9,11,13,14,19}-rt > > > and some !rt's of these as well. > > > > > > I have yet to see or hear of this bug, if it is a bug, > > > giving anyone a problem in a debug session. It might not > > > even be a bug but merely expected behaviour. And of course > > > there is the possibility of a misuse of ptrace/waitpid in > > > my test program. Its API, after all, is rather convoluted. > > > > > > Regards, > > > Joe > > > > > > > > > > > > > > > /* > > > * 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 tracee waits on each PTRACE_SINGLESTEP with a waitpid(2) > > > * and checks that waitpid's return values for correctness. > > > */ > > > #include <stdio.h> > > > #include <stdlib.h> > > > #include <stddef.h> > > > #include <unistd.h> > > > #include <string.h> > > > #include <signal.h> > > > #include <errno.h> > > > > > > #include <sys/types.h> > > > #include <sys/wait.h> > > > #include <sys/ptrace.h> > > > > > > /* 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 + 40 = 150 msecs */ > > > for (i = 0; i < 10 && !got_sigchld; i++) > > > usleep(16000); /* 160 + 150 = 310 msecs */ > > > for (i = 0; i < 10 && !got_sigchld; i++) > > > usleep(32000); /* 320 + 310 = 630 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; > > > } > > > > > > /* > > > * We are all done, kill the tracee and wait for it to die. We test > > > * the killing results as much as the above attach and sstep results, > > > * though failure here really isn't the point of this test. > > > */ > > > status = kill(child, SIGKILL); > > > > > > if (status) { > > > printf("forktest#%d/%d: EXITING, ERROR: " > > > "kill of child %d returned %d\n", > > > testid, getpid(), child, errno); > > > exit(1); > > > } > > > > > > status = do_wait(&wait_pid, &ret_sig); > > > if (wait_pid != child) { > > > printf("forktest#%d/%d: EXITING, ERROR: " > > > "kill: Unexpected wait pid %d\n", > > > testid, getpid(), wait_pid); > > > exit(1); > > > } > > > if (status != STATE_SIGNALED) { > > > printf("forktest#%d/%d: EXITING, ERROR: " > > > "kill: Unexpected child, do_wait status %d " > > > "[%s, wanted STATE_SIGNALED]\n", > > > testid, getpid(), status, get_state_name(status)); > > > exit(1); > > > } > > > if (ret_sig != SIGKILL) { > > > printf("forktest#%d/%d: EXITING, ERROR: " > > > "kill: Unexpected child signal %d\n", > > > testid, getpid(), ret_sig); > > > exit(1); > > > } > > > > > > 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); > > > } > > > > > -- > The United States Coast Guard > Ruining Natural Selection since 1790
/* * 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 <stdio.h> #include <stdlib.h> #include <stddef.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ptrace.h> /* 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); }