6

I am trying to code a program that traces itself for system calls. I am having a difficult time making this work. I tried calling a fork() to create an instance of itself (the code), then monitor the resulting child process.

The goal is for the parent process to return the index of every system call made by the child process and output it to the screen. Somehow it is not working as planned.

Here is the code:

#include <unistd.h>     /* for read(), write(), close(), fork() */
#include <fcntl.h>      /* for open() */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>


int main(int argc, char *argv[]) {
    pid_t child;
    long orig_eax;
    child = fork();

    if (0 == child) 
    {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        if (argc != 3) {
           fprintf(stderr, "Usage: copy <filefrom> <fileto>\n"); 
           return 1;
        }

        int c;
        size_t file1_fd, file2_fd; 
        if ((file1_fd = open(argv[1], O_RDONLY)) < 0) {
           fprintf(stderr, "copy: can't open %s\n", argv[1]);
           return 1;
        }

        if ((file2_fd = open(argv[2], O_WRONLY | O_CREAT)) < 0) {
            fprintf(stderr, "copy: can't open %s\n", argv[2]);
            return 1;
        }

        while (read(file1_fd, &c, 1) > 0) 
        write(file2_fd, &c, 1);
    }
    else
    {
        wait(NULL);
        orig_eax = ptrace (PTRACE_PEEKUSER, child, 4 * ORIG_EAX, NULL);
        printf("copy made a system call %ld\n", orig_eax);
        ptrace(PTRACE_CONT, child, NULL, NULL);
    }           
return 0;
}

This code was based on this code:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>   /* For constants
                               ORIG_EAX etc */
int main()
{   
    pid_t child;
    long orig_eax;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else {
        wait(NULL);
        orig_eax = ptrace(PTRACE_PEEKUSER,
                          child, 4 * ORIG_EAX,
                          NULL);
        printf("The child made a "
               "system call %ld\n", orig_eax);
        ptrace(PTRACE_CONT, child, NULL, NULL);
    }
    return 0;
}

The output of this one is:

The child made a system call 11

which is the index for the exec system call.

According to the man pages for wait():

All of these system calls are used to wait for state changes in a child
of the calling process, and obtain information about  the  child  whose
state  has changed. A state change is considered to be: the child terminated; 
the child was stopped by a signal; or the child was resumed by
a  signal.

The way I understand it is that every time a system call is invoked by a user program, the kernel will first inspect if the process is being traced prior to executing the system call routine and pauses that process with a signal and returns control to the parent. Wouldn't that be a state change already?

1der
  • 185
  • 1
  • 3
  • 12
  • 2
    "Somehow it is not working as planned." Care to elaborate? What do you expect to happen, and what does actually happen? Please edit question to add it, not as a comment. – Some programmer dude Jun 18 '12 at 11:31
  • 7
    Besides, the first thing you do in the parent process, is calling `wait`. This function does exactly that, wait until the child process is finished, which means that the `ptrace` call tries to trace a process that no longer exists. – Some programmer dude Jun 18 '12 at 11:33
  • [Refer this][1] I think, it may help. [1]: http://stackoverflow.com/questions/6468896/why-is-orig-eax-provided-in-addition-to-eax – Manik Sidana Jun 18 '12 at 11:43
  • 2
    @ManikSidana: that is not really relevant. – Asherah Jun 18 '12 at 11:45

4 Answers4

8

The problem is that when the child calls ptrace(TRACEME) it sets itself up for tracing but doesn't actually stop -- it keeps going until it calls exec (in which case it stops with a SIGTRAP), or it gets some other signal. So in order for you to have the parent see what it does WITHOUT an exec call, you need to arrange for the child to receive a signal. The easiest way to do that is probably to have the child call raise(SIGCONT); (or any other signal) immediately after calling ptrace(TRACEME)

Now in the parent you just wait (once) and assume that the child is now stopped at a system call. This won't be the case if it stopped at a signal, so you instead need to call wait(&status) to get the child status and call WIFSTOPPED(status) and WSTOPSIG(status) to see WHY it has stopped. If it has stopped due to a syscall, the signal will be SIGTRAP.

If you want to see multiple system calls in the client, you'll need to do all of this in a loop; something like:

while(1) {
    wait(&status);
    if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
        // stopped before or after a system call -- query the child and print out info
    }
    if (WIFEXITED(status) || WIFSIGNALED(status)) {
        // child has exited or terminated
        break;
    }
    ptrace(PTRACE_SYSCALL, childpid, 0, 0);  // ignore any signal and continue the child
}

Note that it will stop TWICE for each system call -- once before the system call and a second time just after the system call completes.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
2

you are basically trying to write strace binary in linux, which traces the system calls of the process. Linux provides ptrace(2) system call for this. ptrace system call takes 4 arguement and the first arguement tells what you need to do. OS communicates with the parent process with signals and child process is stopped by sending SIGSTOP. broadly you need to follow below steps.

if(fork() == 0 )

{
    //child process

    ptrace(PTRACE_TRACEME, 0,0, 0);
    exec(...); 
}
else
{

 start:

    wait4(...);

    if (WIFSIGNALED(status)) {
        //done
    }
    if (WIFEXITED(status)) {
       //done
    }
    if(flag == startup)
    {
        flag = startupdone;

        ptrace(PTRACE_SYSCALL, pid,0, 0) ;
        goto start;
    }
    if (if (WSTOPSIG(status) == SIGTRAP) {) {
          //extract the register
          ptrace(PTRACE_GETREGS,pid,(char *)&regs,0) 

    }

Note the register reading and interpretation will depend on your architecture. The above code is just an example to get it right you need to dig deeper. have a look at strace code for further understanding.

ecbrodie
  • 11,246
  • 21
  • 71
  • 120
Yusuf Khan
  • 409
  • 3
  • 9
  • According to the man pages, only the first argument of a PTRACEME is used and the rest ignored. What's with the (char *)1? – 1der Jun 18 '12 at 20:58
1

In your parent how many calls do you want to monitor? If you want more than one you're going to need some kind of loop.

Note the line in the example, it's important:

ptrace(PTRACE_TRACEME, 0, NULL, NULL);

Looking at the man page the child needs to either do a PTRACE_TRACEME and an exec, or the parent needs to trace using PTRACE_ATTACH. I don't see either in your code:

The parent can initiate a trace by calling fork(2) and having the resulting child do a PTRACE_TRACEME, followed (typically) by an exec(3). Alternatively, the parent may commence trace of an existing process using PTRACE_ATTACH.

evandrix
  • 6,041
  • 4
  • 27
  • 38
Paul Rubel
  • 26,632
  • 7
  • 60
  • 80
0

Just putting together what Chris Dodd said:

#include <unistd.h>     /* for read(), write(), close(), fork() */
#include <fcntl.h>      /* for open() */
#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(int argc, char *argv[]) {
pid_t child;
int status;
long orig_eax;
child = fork();

if (0 == child) 
{
    ptrace(PTRACE_TRACEME, 0, NULL, NULL);
    raise(SIGCONT);
    if (argc != 3) {
       fprintf(stderr, "Usage: copy <filefrom> <fileto>\n"); 
       return 1;
    }

    int c;
    size_t file1_fd, file2_fd; 
    if ((file1_fd = open(argv[1], O_RDONLY)) < 0) {
       fprintf(stderr, "copy: can't open %s\n", argv[1]);
       return 1;
    }

    if ((file2_fd = open(argv[2], O_WRONLY | O_CREAT)) < 0) {
        fprintf(stderr, "copy: can't open %s\n", argv[2]);
        return 1;
    }

    while (read(file1_fd, &c, 1) > 0)
        write(file2_fd, &c, 1);
}
else
{
    while(1){
        wait(&status);
        if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP){
            orig_eax = ptrace(PTRACE_PEEKUSER, child, sizeof(long) * ORIG_EAX, NULL);
            printf("copy made a system call %ld\n", orig_eax);
        }
        if(WIFEXITED(status) || WIFSIGNALED(status)){
            break;
        }

        ptrace(PTRACE_SYSCALL, child, 0, 0);
    }           
}
return 0;
}
Marcos Valle
  • 77
  • 2
  • 11