playing with ptrace(Part I) 之二 --- 通过ptrace读取系统调用的参数

来源:互联网 发布:淘宝网页模板下载 编辑:程序博客网 时间:2024/05/21 18:40

文章出处:http://www.linuxjournal.com/article/6100?page=0,1

in

读取系统调用的参数

读取系统调用的参数
调用ptrace并将其第一个参数设为PTRACE_PEEKUSER,我们可以读到用户数据区存储的寄存器值和其他一些信息。
下面来看一个例子:

#include <sys/ptrace.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <linux/user.h>#include <sys/syscall.h>   /* For SYS_write etc */int main(){   pid_t child;    long orig_eax, eax;    long params[3];    int status;    int insyscall = 0;    child = fork();    if(child == 0) {        ptrace(PTRACE_TRACEME, 0, NULL, NULL);        execl("/bin/ls", "ls", NULL);    }    else {       while(1) {          wait(&status);          if(WIFEXITED(status))              break;          orig_eax = ptrace(PTRACE_PEEKUSER,                     child, 4 * ORIG_EAX, NULL);          if(orig_eax == SYS_write) {             if(insyscall == 0) {                /* Syscall entry */                insyscall = 1;                params[0] = ptrace(PTRACE_PEEKUSER,                                   child, 4 * EBX,                                   NULL);                params[1] = ptrace(PTRACE_PEEKUSER,                                   child, 4 * ECX,                                   NULL);                params[2] = ptrace(PTRACE_PEEKUSER,                                   child, 4 * EDX,                                   NULL);                printf("Write called with "                       "%ld, %ld, %ld\n",                       params[0], params[1],                       params[2]);                }            else { /* Syscall exit */                eax = ptrace(PTRACE_PEEKUSER,                             child, 4 * EAX, NULL);                    printf("Write returned "                           "with %ld\n", eax);                    insyscall = 0;                }            }            ptrace(PTRACE_SYSCALL,                   child, NULL, NULL);        }    }    return 0;}


程序的输出类似如下:

ppadala@linux:~/ptrace > ls
a.out        dummy.s      ptrace.txt
libgpm.html  registers.c  syscallparams.c
dummy        ptrace.html  simple.c
ppadala@linux:~/ptrace > ./a.out
Write called with 1, 1075154944, 48
a.out        dummy.s      ptrace.txt
Write returned with 48
Write called with 1, 1075154944, 59
libgpm.html  registers.c  syscallparams.c
Write returned with 59
Write called with 1, 1075154944, 30
dummy        ptrace.html  simple.c
Write returned with 30

这里我们跟踪write系统调用,ls命令将会产生3个write系统调用。将ptrace的第一个参数设为PTRACE_SYSCALL,内核会在系统调用的入口或出口处暂停子进程。这等同于使用PTRACE_CONT ,并在下一个系统调用的入口/出口处暂停子进程。

注: 笔者执行这个例子只产生了一次write系统调用)

在上一个例子中,使用了PTRACE_PEEKUSER来查看write系统调用的参数。当系统调用返回时,返回值存放在%eax寄存器中,可以使用例子中的方法进行读取。

wait调用中的status变量值用于检查子进程是否已经退出,这是检测子进程是被ptrace暂停还是退出的典型用法。想知道WIFEXITED宏的细节,请查看man手册中的wait(2)。

读取寄存器的值

如果想在系统调用的入口或出口处读取寄存器的值,上面的方法就显得不那么灵活了。将ptrace的第一个参数设为PTRACE_GETREGS ,就可以在一次调用中获取所有寄存器的值。

获取寄存器值的代码如下:

#include <sys/ptrace.h>#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <linux/user.h>#include <sys/syscall.h>int main(){   pid_t child;    long orig_eax, eax;    long params[3];    int status;    int insyscall = 0;    struct user_regs_struct regs;    child = fork();    if(child == 0) {        ptrace(PTRACE_TRACEME, 0, NULL, NULL);        execl("/bin/ls", "ls", NULL);    }    else {       while(1) {          wait(&status);          if(WIFEXITED(status))              break;          orig_eax = ptrace(PTRACE_PEEKUSER,                            child, 4 * ORIG_EAX,                            NULL);          if(orig_eax == SYS_write) {              if(insyscall == 0) {                 /* Syscall entry */                 insyscall = 1;                 ptrace(PTRACE_GETREGS, child,                        NULL, &regs);                 printf("Write called with "                        "%ld, %ld, %ld\n",                        regs.ebx, regs.ecx,                        regs.edx);             }             else { /* Syscall exit */                 eax = ptrace(PTRACE_PEEKUSER,                              child, 4 * EAX,                              NULL);                 printf("Write returned "                        "with %ld\n", eax);                 insyscall = 0;             }          }          ptrace(PTRACE_SYSCALL, child,                 NULL, NULL);       }   }   return 0;}

 

这段代码与上面的例子相似,不同之处在于使用了PTRACE_GETREGS作为ptrace的第一个参数,这里我们用到了<linux/user.h>中定义的 user_regs_struct 来读取寄存器的值。

 

FAQ:

1. 在系统调用执行的时候,会执行pushl %eax #  保存系统调用号ORIG_EAX在程序用户栈中。x64和x32的区别可以看Here和sys/reg.h;

2. 在系统调用返回的时候,会执行movl %eax, RAX(%esp)将系统调用的返回值放入寄存器%eax中。

3. WIFEXITED()宏用来判断子进程是否为正常退出的,如果是,它会返回一个非零值;

4. 被跟踪的程序在进入或退出某次系统调用的时候都会触发一个SIGTRAP信号,而被父进程捕获。

5. execv系列系统调用执行成功的时候并没有返回值,因为它开始一段新的程序,并没有“返回”的概念。失败时会返回-1;

6. 在父进程进行操作的时候,用ps查看,可以看到子进程的状态为T,表示子进程处于TASK TRACED状态;

当然,为了更具可操作性,你可以在父进程中加入sleep().



原创粉丝点击