ptrace源代码分析

来源:互联网 发布:淘宝国际转运 日本 编辑:程序博客网 时间:2024/05/20 05:10

 ptrace作为应用程序调试的基石,要想对其有深入的了解,最好的方法是分析它的源代码。选取linux2.6.8,更高版本的内容基本相同。实现ptrace系统调用功能的主要是sys_ptrace函数,当然还包括一些读写寄存器的辅助函数。该函数的基本结构比较简单:

(1)判断该进程是否被跟踪,即request==PTRACE_TRACEME,如果是,对其进行处理。

(2)根据被跟踪子进程的pid找到其task结构体 

(3)判断是否为init进程(pid==1)或者是自身进程current,init进程是计算机上电启动后执行的第一个进程,也是所有进程的父进程,它不能被跟踪。

(4)如果request==PTRACE_ATTACH,则将父进程附着在子进程上,并检查是否扶着成功。该命令实现的功能是父进程监视一个已经在运行的子进程。

(5)上述步骤完成后,就可以根据request的命令对子进程进行各种操作。

      该函数有个关键词asmlinkage是指明该函数用堆栈来传递参数。是汇编程序向相应的C语言程序传递参数的一种方式。其源代码如下:(linux/arch/i386/kernel/ptrace.c)

233 asmlinkage int sys_ptrace(long request, long pid, long addr, long data)234 {235         struct task_struct *child;236         struct user * dummy = NULL;237         int i, ret;238         unsigned long __user *datap = (unsigned long __user *)data;239 240         lock_kernel();241         ret = -EPERM;242         if (request == PTRACE_TRACEME) {243                 /* are we already being traced? */244                 if (current->ptrace & PT_PTRACED)245                         goto out;246                 ret = security_ptrace(current->parent, current);247                 if (ret)248                         goto out;249                 /* set the ptrace bit in the process flags. */250                 current->ptrace |= PT_PTRACED;251                 ret = 0;252                 goto out;253         }254         ret = -ESRCH;255         read_lock(&tasklist_lock);256         child = find_task_by_pid(pid);257         if (child)258                 get_task_struct(child);259         read_unlock(&tasklist_lock);260         if (!child)261                 goto out;262 263         ret = -EPERM;264         if (pid == 1)           /* you may not mess with init */265                 goto out_tsk;266 267         if (request == PTRACE_ATTACH) {268                 ret = ptrace_attach(child);269                 goto out_tsk;270         }271 272         ret = ptrace_check_attach(child, request == PTRACE_KILL);273         if (ret < 0)274                 goto out_tsk;275 276         switch (request) {277         /* when I and D space are separate, these will need to be fixed. */278         case PTRACE_PEEKTEXT: /* read word at location addr. */ 279         case PTRACE_PEEKDATA: {280                 unsigned long tmp;281                 int copied;282 283                 copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);284                 ret = -EIO;285                 if (copied != sizeof(tmp))286                         break;287                 ret = put_user(tmp, datap);288                 break;289         }290 291         /* read the word at location addr in the USER area. */292         case PTRACE_PEEKUSR: {293                 unsigned long tmp;294 295                 ret = -EIO;296                 if ((addr & 3) || addr < 0 || 297                     addr > sizeof(struct user) - 3)298                         break;299 300                 tmp = 0;  /* Default return condition */301                 if(addr < FRAME_SIZE*sizeof(long))302                         tmp = getreg(child, addr);303                 if(addr >= (long) &dummy->u_debugreg[0] &&304                    addr <= (long) &dummy->u_debugreg[7]){305                         addr -= (long) &dummy->u_debugreg[0];306                         addr = addr >> 2;307                         tmp = child->thread.debugreg[addr];308                 }309                 ret = put_user(tmp, datap);310                 break;311         }312 313         /* when I and D space are separate, this will have to be fixed. */314         case PTRACE_POKETEXT: /* write the word at location addr. */315         case PTRACE_POKEDATA:316                 ret = 0;317                 if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data))318                         break;319                 ret = -EIO;320                 break;321 322         case PTRACE_POKEUSR: /* write the word at location addr in the USER area */323                 ret = -EIO;324                 if ((addr & 3) || addr < 0 || 325                     addr > sizeof(struct user) - 3)326                         break;327 328                 if (addr < FRAME_SIZE*sizeof(long)) {329                         ret = putreg(child, addr, data);330                         break;331                 }332                 /* We need to be very careful here.  We implicitly333                    want to modify a portion of the task_struct, and we334                    have to be selective about what portions we allow someone335                    to modify. */336 337                   ret = -EIO;338                   if(addr >= (long) &dummy->u_debugreg[0] &&339                      addr <= (long) &dummy->u_debugreg[7]){340 341                           if(addr == (long) &dummy->u_debugreg[4]) break;342                           if(addr == (long) &dummy->u_debugreg[5]) break;343                           if(addr < (long) &dummy->u_debugreg[4] &&344                              ((unsigned long) data) >= TASK_SIZE-3) break;345                           346                           if(addr == (long) &dummy->u_debugreg[7]) {347                                   data &= ~DR_CONTROL_RESERVED;348                                   for(i=0; i<4; i++)349                                           if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1)350                                                   goto out_tsk;351                           }352 353                           addr -= (long) &dummy->u_debugreg;354                           addr = addr >> 2;355                           child->thread.debugreg[addr] = data;356                           ret = 0;357                   }358                   break;359 360         case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */361         case PTRACE_CONT: { /* restart after signal. */362                 long tmp;363 364                 ret = -EIO;365                 if ((unsigned long) data > _NSIG)366                         break;367                 if (request == PTRACE_SYSCALL) {368                         set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);369                 }370                 else {371                         clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);372                 }373                 child->exit_code = data;374         /* make sure the single step bit is not set. */375                 tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG;376                 put_stack_long(child, EFL_OFFSET,tmp);377                 wake_up_process(child);378                 ret = 0;379                 break;380         }381 382 /*383  * make the child exit.  Best I can do is send it a sigkill. 384  * perhaps it should be put in the status that it wants to 385  * exit.386  */387         case PTRACE_KILL: {388                 long tmp;389 390                 ret = 0;391                 if (child->state == TASK_ZOMBIE)        /* already dead */392                         break;393                 child->exit_code = SIGKILL;394                 /* make sure the single step bit is not set. */395                 tmp = get_stack_long(child, EFL_OFFSET) & ~TRAP_FLAG;396                 put_stack_long(child, EFL_OFFSET, tmp);397                 wake_up_process(child);398                 break;399         }400 401         case PTRACE_SINGLESTEP: {  /* set the trap flag. */402                 long tmp;403 404                 ret = -EIO;405                 if ((unsigned long) data > _NSIG)406                         break;407                 clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);408                 if ((child->ptrace & PT_DTRACE) == 0) {409                         /* Spurious delayed TF traps may occur */410                         child->ptrace |= PT_DTRACE;411                 }412                 tmp = get_stack_long(child, EFL_OFFSET) | TRAP_FLAG;413                 put_stack_long(child, EFL_OFFSET, tmp);414                 child->exit_code = data;415                 /* give it a chance to run. */416                 wake_up_process(child);417                 ret = 0;418                 break;419         }420 421         case PTRACE_DETACH:422                 /* detach a process that was attached. */423                 ret = ptrace_detach(child, data);424                 break;425 426         case PTRACE_GETREGS: { /* Get all gp regs from the child. */427                 if (!access_ok(VERIFY_WRITE, datap, FRAME_SIZE*sizeof(long))) {428                         ret = -EIO;429                         break;430                 }431                 for ( i = 0; i < FRAME_SIZE*sizeof(long); i += sizeof(long) ) {432                         __put_user(getreg(child, i), datap);433                         datap++;434                 }435                 ret = 0;436                 break;437         }438 439         case PTRACE_SETREGS: { /* Set all gp regs in the child. */440                 unsigned long tmp;441                 if (!access_ok(VERIFY_READ, datap, FRAME_SIZE*sizeof(long))) {442                         ret = -EIO;443                         break;444                 }445                 for ( i = 0; i < FRAME_SIZE*sizeof(long); i += sizeof(long) ) {446                         __get_user(tmp, datap);447                         putreg(child, i, tmp);448                         datap++;449                 }450                 ret = 0;451                 break;452         }453 454         case PTRACE_GETFPREGS: { /* Get the child FPU state. */455                 if (!access_ok(VERIFY_WRITE, datap,456                                sizeof(struct user_i387_struct))) {457                         ret = -EIO;458                         break;459                 }460                 ret = 0;461                 if (!child->used_math)462                         init_fpu(child);463                 get_fpregs((struct user_i387_struct __user *)data, child);464                 break;465         }466 467         case PTRACE_SETFPREGS: { /* Set the child FPU state. */468                 if (!access_ok(VERIFY_READ, datap,469                                sizeof(struct user_i387_struct))) {470                         ret = -EIO;471                         break;472                 }473                 child->used_math = 1;474                 set_fpregs(child, (struct user_i387_struct __user *)data);475                 ret = 0;476                 break;477         }478 479         case PTRACE_GETFPXREGS: { /* Get the child extended FPU state. */480                 if (!access_ok(VERIFY_WRITE, datap,481                                sizeof(struct user_fxsr_struct))) {482                         ret = -EIO;483                         break;484                 }485                 if (!child->used_math)486                         init_fpu(child);487                 ret = get_fpxregs((struct user_fxsr_struct __user *)data, child);488                 break;489         }490 491         case PTRACE_SETFPXREGS: { /* Set the child extended FPU state. */492                 if (!access_ok(VERIFY_READ, datap,493                                sizeof(struct user_fxsr_struct))) {494                         ret = -EIO;495                         break;496                 }497                 child->used_math = 1;498                 ret = set_fpxregs(child, (struct user_fxsr_struct __user *)data);499                 break;500         }501 502         case PTRACE_GET_THREAD_AREA:503                 ret = ptrace_get_thread_area(child, addr,504                                         (struct user_desc __user *) data);505                 break;506 507         case PTRACE_SET_THREAD_AREA:508                 ret = ptrace_set_thread_area(child, addr,509                                         (struct user_desc __user *) data);510                 break;511 512         default:513                 ret = ptrace_request(child, request, addr, data);514                 break;515         }516 out_tsk:517         put_task_struct(child);518 out:519         unlock_kernel();520         return ret;521 }

主要分析一下PEEKUSER命令实现的部分:其他的requset命令实现类似。

292         case PTRACE_PEEKUSR: {293                 unsigned long tmp;294 295                 ret = -EIO;296                 if ((addr & 3) || addr < 0 || 297                     addr > sizeof(struct user) - 3)298                         break;299 300                 tmp = 0;  /* Default return condition */301                 if(addr < FRAME_SIZE*sizeof(long))302                         tmp = getreg(child, addr);303                 if(addr >= (long) &dummy->u_debugreg[0] &&304                    addr <= (long) &dummy->u_debugreg[7]){305                         addr -= (long) &dummy->u_debugreg[0];306                         addr = addr >> 2;307                         tmp = child->thread.debugreg[addr];308                 }309                 ret = put_user(tmp, datap);310                 break;311         }

        PEEKUSER实现的功能是读取用户user的寄存器值包括调试寄存器的值。第296行判断地址是否对齐,越界,合法。第301行宏定义FRAME_SIZE=17,是通用寄存器的个数。它们分别是EBX、ECX、EDX、ESI、EDI、EBP、EAX、DS,  ES、FS、GS、ORIG_EAX、EIP、CS、EFLAGS、ESP、SS。用getreg来读取这些寄存器的值.getreg函数原型如下:

114 static unsigned long getreg(struct task_struct *child,115         unsigned long regno)116 {117         unsigned long retval = ~0UL;118 119         switch (regno >> 2) {120                 case FS:121                         retval = child->thread.fs;122                         break;123                 case GS:124                         retval = child->thread.gs;125                         break;126                 case DS:127                 case ES:128                 case SS:129                 case CS:130                         retval = 0xffff;131                         /* fall through */132                 default:133                         if (regno > GS*4)134                                 regno -= 2*4;135                         regno = regno - sizeof(struct pt_regs);136                         retval &= get_stack_long(child, regno);137         }138         return retval;139 }140 
函数中的形参regno表示寄存器的编号,在该文件中

linux/include/asm-i386/ptrace.h定义

 4 #define EBX 0  5 #define ECX 1  6 #define EDX 2  7 #define ESI 3  8 #define EDI 4  9 #define EBP 5 10 #define EAX 6 11 #define DS 7 12 #define ES 8 13 #define FS 9 14 #define GS 10 15 #define ORIG_EAX 11 16 #define EIP 12 17 #define CS  13 18 #define EFL 14 19 #define UESP 15 20 #define SS   16 21 #define FRAME_SIZE 17
     进程结构体TSS中存有所有寄存器的值,但在子进程被调试时处于核心态,不能够直接读取寄存器的值,所以getreg只能读取用户堆栈中的寄存器的值。不过fs,gs寄存器的值需要从TSS中读取。ds,ss,cs es均为16位,故高16位值不管。
     第136行即利用get_stack_long函数从堆栈中读出其他寄存器的值。该函数定义如下:
51 static inline int get_stack_long(struct task_struct *task, int offset) 52 { 53         unsigned char *stack; 54  55         stack = (unsigned char *)task->thread.esp0; 56         stack += offset; 57         return (*((int *)stack)); 58 }

      esp0是堆栈指针,通用的寄存器在堆栈中按顺序排放,通过偏移量0ffset便可以依次读取。第303行到308行是读取调试寄存器的值。

      因此,总的来说,ptrace系统调用最主要的是核心函数是sys_ptarce函数,并在该函数中调用了寄存器的辅助读写函数,内存辅助读写函数,通过传入各种request命令,实现了强大的调试功能。

原创粉丝点击