Today I wanted a way to run a program to completion but stop it just before it is destroyed, therefore allowing me to inspect some /proc variables I was interested in.
As it happens the ptrace option PTRACE_O_TRACEEXIT is designed for just that. To use it, you first either attach to the process, or if forking a new process call PTRACE_TRACEME and exec as usual.
Then you use the ptrace call PTRACE_SETOPTIONS with the PTRACE_O_TRACEEXIT option in the data pointer, and wait for the traced process as usual (unfortunately due to a bug you will have to clag the #defines from the kernel header for now). There are now two conditions to handle:
The child received a SIGTRAP with the PTRACE_EVENT_EXIT status set, signalling to us that it has exited. This is a terminal state; you can inspect the registers, etc, but the process can not come back to life now. This status is "overloaded" in the status returned by wait, e.g.
/* wait for signal from child */ waitpid(pid, &status, 0); /* any signal stops the child, so check that the incoming signal is a SIGTRAP and the event_exit flag is set */ if ((WSTOPSIG(status) == SIGTRAP) && (status & (PTRACE_EVENT_EXIT << 8))) ...
Any other signal will have also caused the tracing parent to be woken, so we need to pass that signal through to the child process with a PTRACE_CONT call specifing the signal in the data pointer; e.g.
/* if not, pass the original signal onto the child */ if (ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status))) { perror("stopper: ptrace(PTRACE_CONT, ...)"); exit(1); }
A straight forward example is stopper.c. The program allows you to run a program (or attach to a running program) and effecitvley pause it just before it exits, allowing you to inspect it and then manually dispose of it once you have the data you need.