Search code examples
cforkparent-childcpusystem-calls

Systemcall child-parent execution problem (fork execve) Linux


Im new to systemcall. Im using execve inside a child so the parent should run as normal and not be ovewritten by execve. The problem is that the child executes and stops the whole thing after execve. My goal here is to count the number of clone executed in the bash command(argument) executed by execve.

Ive read the man, Im still a bit confused. I can only use ptrace, fork, wait/waitpid.

argument:

/bin/bash -c "echo 'first test' | wc -c"
int main(int argc, char *argv[]) {
    pid_t child_pid = fork();
    int status;
    int counter = 0;
    wait(&status);
    if (child_pid == -1) {
        exit(1);
    } else {
        while(status != child_pid){
            if (child_pid == 0) {
                ptrace(PTRACE_TRACEME, child_pid, 0, 0);
                raise(SIGSTOP);
                execve(argv[1], &argv[1], NULL);
                ptrace(PTRACE_SETOPTIONS, child_pid, 0, PTRACE_O_TRACECLONE);
                ptrace(PTRACE_CONT, child_pid, 0L, 0L);
                if(status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8)))
                    counter++;
            }
        }
    }
    printf("# of clone executions: %d", counter);
    return 0;
}

Solution

  • execve will overwrite the child process, so any instruction after execve will not be executed, unless the call to execve fails. You should instead run the ptrace tracing that appears after the execve in the parent process

    Edit: here is a commented solution to count the number of clone syscalls:

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/ptrace.h>
    #include <sys/syscall.h>
    #include <sys/reg.h> // ORIG_RAX
    
    int main(int argc, char *argv[]) {
      pid_t child_pid = fork();
      int counter = 0;
      int entering_syscall = 1;
    
      if(child_pid == -1) {
        perror("fork failed");
        exit(1);
      } else if(child_pid != 0) {
        // parent
        u_int64_t rax;
        int status;
    
        while(1) {
          // wait for the next signal by the child
          if(wait(&status) != child_pid)
            continue;
    
          if(WIFEXITED(status)) {
            puts("child exited");
            break;
          }
    
          // read the USER area, defined in sys/user.h, which contains the registers information
          // in linux, rax contains the syscall number
          rax = ptrace(PTRACE_PEEKUSER, child_pid, 8 * ORIG_RAX, NULL);
    
          if(rax == SYS_clone /* 56 */) {
            // PTRACE_SYSCALL generates a signal both when entering and exiting a syscall
            // only count the syscall during enter
            if(entering_syscall)
              counter++;
    
            entering_syscall = !entering_syscall;
          }
    
          //printf("syscall %ld\n", rax);
    
          // continue the child process until the next syscall enter/exit
          ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL);
        }
      } else {
        // child
    
        // turns the calling thread into a tracee
        ptrace(PTRACE_TRACEME, child_pid, 0, 0);
    
        // signal and wait for the parent. This ensures that PTRACE_SYSCALL
        // will not miss any child syscall
        raise(SIGSTOP);
    
        execve(argv[1], &argv[1], NULL);
    
        // should be never reached
        perror("execve failed");
        exit(1);
      }
    
      printf("Num clone: %d\n", counter);
    }
    

    You can cross-check it against strace strace 2>&1 your_command | grep clone | wc -l