I'm trying to follow the syscalls of a program using ptrace, but it does not work when the traced program has a fork() and just ignores it, supposedly you just need to set the line below and it should follow the process originating from fork() as well.
ptrace(PTRACE_SETOPTIONS, child, NULL, PTRACE_O_TRACEFORK); I'm posting my code below, for tracer.c:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/user.h>
int main(int argc, char* argv[]){
if (argc < 2) {
printf("Usage: %s <program> [args...]\n", argv[0]);
return 1;
}
pid_t child;
struct user_regs_struct regs;
int status;
int notPrinted = 1;
child = fork();
if (child == 0){
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0){
perror("ptrace");
return 1;
}
execvp(argv[1], &argv[1]);
}
else if (child > 0){
wait(&status);
ptrace(PTRACE_SETOPTIONS, child, NULL, PTRACE_SETOPTIONS | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD);
while (WIFSTOPPED(status)){
ptrace(PTRACE_GETREGS, child, NULL, ®s);
if(notPrinted){
if (regs.orig_rax != -1){
notPrinted = 0;
printf("FROM: %d, Syscall %ld: rdi=%ld, rsi=%ld, rdx=%ld, r10=%ld\n",
child, regs.orig_rax, regs.rbx, regs.rcx, regs.rdx, regs.r10);
}
}
else{
notPrinted = 1;
}
if (ptrace(PTRACE_SYSCALL, child, NULL, NULL) < 0){
perror("ptrace");
return 1;
}
wait(&status);
}
}
else{
perror("fork");
return 1;
}
return 0;
}
As well as a simple program I've been using to test, random.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
if(fork() == 0){
sleep(1);
}
srand(time(NULL));
for (int i = 0; i < 30; i++) {
printf("%d ", rand() & 0xf);
}
printf("\n");
sleep(5);
return 0;
}
Previously I had the following line with just set option to "PTRACE_O_TRACEFORK"; So i believe the issue is still a wrong configuration of PTRACE_SETOPTIONS; my goal is to have the program follow a program that uses fork() correctly, like strace -f option does
ptrace(PTRACE_SETOPTIONS, child, NULL, PTRACE_SETOPTIONS | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD);
EDIT:
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <unistd.h>
#include <errno.h>
int main(int argc, char* argv[]){
if (argc < 2){
printf("Usage: %s <program> [args...]\n", argv[0]);
return 1;
}
pid_t child;
struct user_regs_struct regs;
int status;
child = fork();
if (child == 0){
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) {
perror("ptrace");
return 1;
}
execvp(argv[1], &argv[1]);
} else if (child > 0){
child = wait(&status);
ptrace(PTRACE_SETOPTIONS, child, NULL, PTRACE_SETOPTIONS | PTRACE_O_TRACEFORK | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE);
while (WIFSTOPPED(status)){
ptrace(PTRACE_GETREGS, child, NULL, ®s);
if (regs.orig_rax != -1){
printf("FROM: %d, Syscall %ld: rdi=%ld, rsi=%ld, rdx=%ld, r10=%ld\n",
child, regs.orig_rax, regs.rbx, regs.rcx, regs.rdx, regs.r10);
}
if (ptrace(PTRACE_SYSCALL, child, NULL, NULL) < 0){
perror("ptrace");
return 1;
}
child = wait(&status);
}
} else{
perror("fork");
return 1;
}
return 0;
}
This is the new code with PTRACE_O_TRACECLONE option included, removed notPrinted just for code readability, and with updated child by doing
child = wait(&status);
ptrace(PTRACE_GETREGS, child, NULL, ®s);
to get updated child, and then to read the registers from the process; But I'm still only getting one process as child, any idea why so?
A few issues ...
You need to [also] add
PTRACE_O_TRACECLONEwhen you set options. You're doingforkbut that's the libc function. (e.g. Under linux) it uses theclonesyscall).You have to do
ptrace(PTRACE_CONT, pid, 0, 0);to resume the child after the tracer processes the stop.If you're going to trace an app that calls
forkand you want to also trace any children it created, you have to use the correct pid. You never change the value ofchild, so you never get thepidof any grandchildren.You have to do:
child = wait(&status);When you're tracing multiple children, you have to know which pid caused the return fromwait.For some additional info, you could look at some of my
ptraceanswers:UPDATE:
Yes,
PTRACE_SYSCALLis sufficient.I reworked your latest source. In the process, I discovered the bug.
Your
PTRACE_SETOPTIONScall is incorrect. You are doing:You are ORing in
PTRACE_SETOPTIONSinto the last argument. This argument should only havePTRACE_O_*options in it.This [somehow] breaks things. The correct way is:
I added some extra code to have a per-process control struct to keep track of the state of each process being traced. While not strictly necessary for your example, it is a common thing to do.
I also added a log file for tracing.
And, I refactored your
randoma bit to be slightly more trace friendly (not necessary but helped spot the issue).Here is the tracer:
Here is the test program:
Here is the
LOGfile for the "buggy" case (e.g.-b). Notice that there is only one process traced and that there are noclonesyscalls.Here is the
LOGfor the "fixed" case: