ptrace源代码分析

 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 implicitly
333                    want to modify a portion of the task_struct, and we
334                    have to be selective about what portions we allow someone
335                    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命令,实现了强大的调试功能。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shim ptrace是一个用于操作进程的系统调用接口。它允许一个进程跟踪、控制和检查另一个进程的执行情况。通过shim ptrace,一个进程可以检查另一个进程的寄存器、内存和系统调用,并有能力修改它们的执行过程。 shim ptrace通常用于调试、监视和诊断进程。调试器可以使用shim ptrace来实现断点、单步执行和修改变量等调试功能。另外,shim ptrace还可以用于分析和检查进程的运行,例如查找进程中的内存泄漏、跟踪系统调用和信号处理。 在使用shim ptrace时,一个进程可以作为被跟踪进程,另一个进程则作为跟踪进程。跟踪进程使用ptrace系统调用来发送指令,而被跟踪进程则接收并执行指令。通过这种方式,跟踪进程可以获取被跟踪进程的状态信息,并对其进行操作。 对于被跟踪进程,它会在指令执行之前接收到跟踪进程发送的指令,并根据指令的要求进行操作。例如,跟踪进程可以用ptrace(PTRACE_PEEKDATA, pid, addr, data)来读取被跟踪进程中地址为addr的内存数据,并将结果保存在data中。类似地,跟踪进程也可以使用ptrace(PTRACE_POKEDATA, pid, addr, data)来修改被跟踪进程的内存值。 总之,shim ptrace是一个强大的工具,允许进程间相互跟踪、控制和修改执行过程。它在调试、监视和诊断进程方面扮演着重要角色,为开发人员提供了有效的方法来分析和改进程序的执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值