You can use the PTRACE_SYSCALL request: it restarts the child process (just like PTRACE_CONT) but arranges for it to stop at the next entry to or exit from a system call. For example (assume a kernel built for x86):
#include <sys/ptrace.h>
#include <signal.h>
#include <linux/user.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
int status = 0, pid, r;
struct user_regs_struct uregs;
if ((pid = fork()) == 0) {
printf("pid = %d, ppid = %d\n", getpid(), getppid());
ptrace(PTRACE_TRACEME, 0, 0, 0);
kill(getpid(), SIGINT);
r = getpid();
printf("%d\n", r);
} else {
wait(&status);
ptrace(PTRACE_SYSCALL, pid, 0, 0);
wait(&status);
ptrace(PTRACE_GETREGS, pid, 0, &uregs);
/* this prints the syscall number of getpid */
printf("syscall nr: %d\n", uregs.orig_eax);
/* 64 is syscall number of getppid */
uregs.orig_eax = 64;
ptrace(PTRACE_SETREGS, pid, 0, &uregs);
ptrace(PTRACE_CONT, pid, 0, 0);
wait(&status);
if(WIFEXITED(status))
printf("we're done\n");
}
}
The child prints its PID and delivers a signal to itself. Because of the prior call to ptrace()
this means it will be stopped.
The parent waits for this to happen and restarts the child with PTRACE_SYSCALL, then waits. Next, the child invokes the getpid
system call and it is stopped once again. The parent process uses the PTRACE_GETREGS invocation to peek into the child's registers, where eax
holds the system call number. The parent changes this to the system call number of getppid
, then once again allows the child to continue. Because the system call number has changed prior to the invocation of the system call, the child will now invoke getppid
instead of getpid
.
Using ptrace
for this purpose may be portable, but I have not tested it. In gdb, you can also use the catch syscall
command.