现在的位置: 首页 > 综合 > 正文

ptrace 应用之一基础知识

2013年08月09日 ⁄ 综合 ⁄ 共 4968字 ⁄ 字号 评论关闭

      Linux提供了ptrace系统函数,使得父进程得以控制和监视其它进程。当使用ptrace跟踪子进程后,所有发给子进程的信号(除了SIGKILL)外,都会被父进程截获,而此时子进程阻塞,并且被标记为TASK_TRACED。父进程收到信号后,可以查看和修改子进程的内核映像和寄存器。父进程完成所要做的工作之后可以选择让子进程继续执行还是终止。

     在i386体系中,应用程序的系统调用基本过程:将系统调用号放在eax寄存器,其他的函数形参依次放在ebx,ecx,edx,esi,edi中,

注意: stdin =0为标注输入流。stdout=1标注输出流(默认为屏幕)       stderr=2标注错误输出流(默认为屏幕)

write系统调用   write(2,“hello”,5)  

汇编以后:

movl  $4,%eax

movl   $2,%ebx

movl   $hello,%ecx

movl  $5,%edx

int  $0x80

其中4是write的系统调用号。在/usr/include/asm/unistd.h中声明和定义。

下面的这个程序是利用ptrace跟踪系统调用和查看寄存器的值。

 

      1 #include
      2 #include
      3 #include
      4 #include
      5 #include
      6 #include
      7
      8 int main()
      9 {
     10         pid_t  child;
     11         long orig_eax,eax;      
     12         long params[3];
     13         int status;
     14         int insyscall=0;
     15         struct user_regs_struct regs;
     16         child=fork();
     17         if(-1==child)  printf("fork error/n");
     18         else if(0==child)
     19         {
     20           ptrace(PTRACE_TRACEME,child,NULL,NULL);
     21           execl("/bin/pwd","pwd",NULL);
     22         }
     23         else
     24         {
     25                   wait(&status);
     26                   if(WIFEXITED(status))  return 0;
     27                   orig_eax=ptrace(PTRACE_PEEKUSER,child,4*ORIG_EAX,NULL);
     28                   printf("process executed syscall id=%ld/n",orig_eax);
     29                   ptrace(PTRACE_SYSCALL,child,NULL,NULL);
     30            while(1)
     31                 {
     32                   wait(&status);
     33                   if(WIFEXITED(status))  break;
     34                   orig_eax=ptrace(PTRACE_PEEKUSER,child,4*ORIG_EAX,NULL);
     35                   if(0==insyscall)
     36                           {
     37                              insyscall=1;
     38                              orig_eax=ptrace(PTRACE_PEEKUSER,child,4*ORIG_EAX,NULL);
     39                              printf("process executed syscall id=%ld/n",orig_eax);
     40                              ptrace(PTRACE_GETREGS,child,NULL,&regs);
     41                              printf("write called with ebx=%ld,ecx=%ld,edx=%ld/n",regs.ebx,regs.ecx,regs.edx);
     42                           }
     43                   else
     44                           {
     45                              eax=ptrace(PTRACE_PEEKUSER,child,4*EAX,NULL);
     46                              printf("syscall with return value = %ld/n",eax);
     47                              insyscall=0;
     48                           }
     49
     50                 ptrace(PTRACE_SYSCALL,child,NULL,NULL);
     51                 }
     52         }
     53 }
     54

在头文件/usr/include/asm/usr.h中,有关于struct user_regs_struct regs的原型声明如下:

struct user_regs_struct {

        long ebx, ecx, edx, esi, edi, ebp, eax;

        unsigned short ds, __ds, es, __es;

        unsigned short fs, __fs, gs, __gs;

        long orig_eax, eip;

        unsigned short cs, __cs;

        long eflags, esp;

        unsigned short ss, __ss;

};

          在该函数中,insyscall作为系统调用的标志。父进程fork了一个子进程,在子进程中用PTRACE_TRACEME作为第一个参数调用了ptrace函数,告知内核跟踪自己。在执行完execl函数后,父进程使用wait函数等待来自内核的通知。一旦得到这个通知,就开始跟踪子进程。

WIFEXITED(status)这个宏用来指出子进程是否正常退出,如果是,则返回非零值。

PTARCE_PEEKUSR作为ptrace的第一个参数,其含义为:ptrace(PTARCE_PEEKUSR,child,4*ORIG_EAX,NULL)从由child子进程的用户区addr=4*ORIG_EAX处读出数据并返回该数据。addr必须是字对齐的。

PTRACE_SYSCALL的含义是让子进程重新执行,但在下一次子进程进入系统调用和退出系统调用时停止。

这个程序编译输出的部分结果如下:

/home/estate66/ptrace

process executed syscall id=11

process executed syscall id=122

write called with ebx=-1074571892,ecx=-1074571492,edx=11595732

syscall with return value = 0

process executed syscall id=45

write called with ebx=0,ecx=11595732,edx=-1074571776

syscall with return value = 166211584

process executed syscall id=33

write called with ebx=11579169,ecx=4,edx=11595732

syscall with return value = -2

process executed syscall id=5

write called with ebx=11582367,ecx=0,edx=0

syscall with return value = 3

process executed syscall id=197

write called with ebx=3,ecx=-1074574172,edx=11595732

syscall with return value = 0

process executed syscall id=90

write called with ebx=-1074574204,ecx=2,edx=11595732

syscall with return value = -1208287232

process executed syscall id=6

write called with ebx=3,ecx=2,edx=11595732

syscall with return value = 0

process executed syscall id=5

write called with ebx=-1208195482,ecx=0,edx=0

      第一行是我的当前工作目录,即在终端输入pwd看到的结果,后面的是显示该命令的系统调用过程。我们在终端输入strace  pwd得到如下:

execve("/bin/pwd", ["pwd"], [/* 35 vars */]) = 0

uname({sys="Linux", node="localhost.localdomain", ...}) = 0

brk(0)                                  = 0x97ad000

access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)

open("/etc/ld.so.cache", O_RDONLY)      = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=103838, ...}) = 0

old_mmap(NULL, 103838, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f86000

close(3)                                = 0

open("/lib/tls/libc.so.6", O_RDONLY)    = 3

read(3, "/177ELF/1/1/1/0/0/0/0/0/0/0/0/0/3/0/3/0/1/0/0/0/320n/262/0004/0/0/0"..., 512) = 512

fstat64(3, {st_mode=S_IFREG|0755, st_size=1529136, ...}) = 0

old_mmap(0xb12000, 1227964, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb12000

old_mmap(0xc38000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x125000) = 0xc38000

old_mmap(0xc3c000, 7356, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xc3c000

close(3)                                = 0

old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f85000

mprotect(0xc38000, 8192, PROT_READ)     = 0

mprotect(0xb0e000, 4096, PROT_READ)     = 0

set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f85aa0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0

munmap(0xb7f86000, 103838)              = 0

       通过在系统调用头文件中对比发现:/usr/include/asm/unistd.h部分内容摘录如下:

#define __NR_execve              11                   

#define __NR_access              33

#define __NR_brk                 45

#define __NR_open                 5

#define __NR_mmap                90

#define __NR_fstat64            197

#define __NR_uname              122

#define __NR_close                6

      上面只列出了部分的内容。  通过比对我们可以发现,ptrace返回的系统调用号和strace跟踪的shell命令pwd执行过程中所进行的系统调用是一致的。

execl函数调用号是11,执行该函数后就开始新的进程,因此该函数没有返回值。uname函数调用号是122,返回值为0,brk函数调用号      45,实现向内核中动态的扩展进程数据段的大小。包括余下的函数调用都是很吻合的。

抱歉!评论已关闭.