Notes for playing with ptrace on 64 bits Ubuntu 12.10

This blog is the notes during I learning the “Playing with ptrace”(http://www.linuxjournal.com/article/6100).

The original examples was using 32 bits machine, which doesn’t work on my 64 bits Ubuntu 12.10.

Let’s start from the first ptrace example:

#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 %ldn", orig_eax);
        ptrace(PTRACE_CONT, child, NULL, NULL);
    }
    return 0;
}

The compiler shows the following error:

fatal error: 'linux/user.h' file not found
#include <linux/user.h>

Something need to change because of:

  1. The ‘linux/user.h’ no longer exists
  2. The 64 bits register is R*X, so EAX changed to RAX

There are two solutions to fix this:
1. change ‘linux/user.h’ to ‘sys/reg.h’, and use:

long original_rax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);

The addr changed from ‘4 * ORIG_EAX’ to ‘8 * ORIG_RAX’ because it’s the address to read in the user area, and the orig_rax member in user_regs_struct is the 15th member(start from 0). The definition of ORIG_RAX in file ‘sys/reg.h’ specify it’s position: # define ORIG_RAX 15. Because of the other members has size 8 on 64 bits machine, so the addr is: 8 * ORIG_RAX.

The definition of struct user_regs_struct and user in file ‘sys/user.h’:

struct user_regs_struct
{
  unsigned long int r15;
  unsigned long int r14;
  unsigned long int r13;
  unsigned long int r12;
  unsigned long int rbp;
  unsigned long int rbx;
  unsigned long int r11;
  unsigned long int r10;
  unsigned long int r9;
  unsigned long int r8;
  unsigned long int rax;
  unsigned long int rcx;
  unsigned long int rdx;
  unsigned long int rsi;
  unsigned long int rdi;
  unsigned long int orig_rax;
  unsigned long int rip;
  unsigned long int cs;
  unsigned long int eflags;
  unsigned long int rsp;
  unsigned long int ss;
  unsigned long int fs_base;
  unsigned long int gs_base;
  unsigned long int ds;
  unsigned long int es;
  unsigned long int fs;
  unsigned long int gs;
};

struct user
{
  struct user_regs_struct   regs;
  int               u_fpvalid;
  struct user_fpregs_struct i387;
  unsigned long int     u_tsize;
  unsigned long int     u_dsize;
  unsigned long int     u_ssize;
  unsigned long int     start_code;
  unsigned long int     start_stack;
  long int          signal;
  int               reserved;
  struct user_regs_struct*  u_ar0;
  struct user_fpregs_struct*    u_fpstate;
  unsigned long int     magic;
  char              u_comm [32];
  unsigned long int     u_debugreg [8];
};
  1. change ‘linux/user.h’ to ‘sys/user.h’, and use
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, child, NULL, &regs);
printf("The child made a system call %ldn", regs.orig_rax);

The second one is simpler because it doesn’t need to calculate the position, but it read more data than the first one.

I think it would be more clear and easier to understand if we use the address of the orig_rax field directly:

struct user* user_space = (struct user*)0;
long original_rax = ptrace(PTRACE_PEEKUSER, child, &user_space->regs.orig_rax, NULL);

We can compile and run it now, but we got: ‘The child made a system call 59‘, which is different with ‘11‘ from the original post, is there anything wrong? From the file sys/syscall.h, it included file ‘asm/unistd.h’ and the comment says that file list the system calls:

/* This file should list the numbers of the system calls the system knows.
   But instead of duplicating this we use the information available
   from the kernel sources.  */
#include <asm/unistd.h>

The file ‘asm/unistd.h’ include different files based on i386 and ILP32:

# ifdef __i386__
#  include <asm/unistd_32.h>
# elif defined(__ILP32__)
#  include <asm/unistd_x32.h>
# else
#  include <asm/unistd_64.h>
# endif

From the file ‘asm/unistd_64.h’ which contains the system call names for 64 bits machine, we can found that:

#define __NR_execve 59

Ok, that’s all for the first example, and after understand it, it’s easy to understand the rest parts in part I(http://www.linuxjournal.com/article/6100) and part II(http://www.linuxjournal.com/article/6210).

7 thoughts on “Notes for playing with ptrace on 64 bits Ubuntu 12.10”

  1. This article was really helpful. In 64 bit systems function calling convention has been changed(arguments passed to the registers). This should also be addressed in the article.

Leave a Reply

Your email address will not be published. Required fields are marked *