使用ptrace跟踪进程

简介:

转自http://godorz.info/2011/02/process-tracing-using-ptrace/

原文链接:http://linuxgazette.net/81/sandeep.html

系统调用ptrace对gdb这种调试器来说是非常重要的,杯具的是,相关的文档却残缺不详–除非你觉得最好的文档就是内核源码!!下面,我会试着向大家展示ptrace在gdb这类工具中的作用.

1. 介绍

ptrace()是一个系统调用,它允许一个进程控制另外一个进程的执行.不仅如此,我们还可以借助于ptrace修改某个进程的空间(内存或寄存器),任何传递给一个进程(即被跟踪进程)的信号(除了会直接杀死进程的SIGKILL信号)都会使得这个进程进入暂停状态,这时系统通过wait()通知跟踪进程,这样,跟踪进程就可以修改被跟踪进程的行为了.

如果跟踪进程在被跟踪进程的内存中设置了相关的事件标志位,那么运行中被跟踪进程也可能因为特殊的事件而暂停.跟踪结束后,跟踪进程甚至可以通过设置被跟踪进程的退出码(exit code)来杀死它,当然也可以让它继续运行.

注意: ptrace()是高度依赖于底层硬件的.使用ptrace的程序通常不容易在个钟体系结构间移植.

2. 细节

ptrace的原型如下:

1. #include <sys/ptrace.h>
2. long int ptrace(enum __ptrace_request request, pid_t pid, void * addr, void * data)

我们可以看到,ptrace有4个参数,其中,request决定ptrace做什么,pid是被跟踪进程的ID,data存储从进程空间偏移量为addr的地方开始将被读取/写入的数据.

父进程可以通过将request设置为PTRACE_TRACEME,以此跟踪被fork出来的子进程,它同样可以通过使用PTRACE_ATTACH来跟踪一个已经在运行的进程.

2.1 ptrace如何工作

不管ptrace是什么时候被调用的,它首先做的就是锁住内核.在ptrace返回前,内核会被解锁.在这个过程中,ptrace是如何工作的呢,我们看看request值(译注: request的可选值值是定义在/usr/include/sys/ptrace.h中的宏)代表的含义吧.

PTRACE_TRACEME:

PTRACE_TRACEME是被父进程用来跟踪子进程的.正如前面所说的,任何信号(除了SIGKILL),不管是从外来的还是由exec系统调用产生的,都将使得子进程被暂停,由父进程决定子进程的行为.在request为PTRACE_TRACEME情况下,ptrace()只干一件事,它检查当前进程的ptrace标志是否已经被设置,没有的话就设置ptrace标志,除了request的任何参数(pid,addr,data)都将被忽略.

PTRACE_ATTACH:

request为PTRACE_ATTACH也就意味着,一个进程想要控制另外一个进程.需要注意的是,任何进程都不能跟踪控制起始进程init,一个进程也不能跟踪自己.某种意义上,调用ptrace的进程就成为了ID为pid的进程的’父’进程.但是,被跟踪进程的真正父进程是ID为getpid()的进程.

PTRACE_DETACH:

用来停止跟踪一个进程.跟踪进程决定被跟踪进程的生死.PTRACE_DETACH会恢复PTRACE_ATTACH和PTRACE_TRACEME的所有改变.父进程通过data参数设置子进程的退出状态(exit code).子进程的ptrace标志就被复位,然后子进程被移到它原来所在的任务队列中.这时候,子进程的父进程的ID被重新写回子进程的父进程标志位.可能被修改了的single-step标志位也会被复位.最后,子进程被唤醒,貌似神马都没有发生过;参数addr会被忽略.

PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER:

这些宏用来读取子进程的内存和用户态空间(user space).PTRACE_PEEKTEXT和PTRACE_PEEKDATA从子进程内存读取数据,两者功能是相同的.PTRACE_PEEKUSER从子进程的user space读取数据.它们读一个字节的数据,保存在临时的数据结构中,然后使用put_user()(它从内核态空间读一个字符串到用户态空间)将需要的数据写入参数data,返回0表示成功.

对PTRACE_PEEKTEXT和PTRACE_PEEKDATA而言,参数addr是子进程内存中将被读取的数据的地址.对PTRACE_PEEKUSER来说,参数addr是子进程用户态空间的偏移量,此时data被无视.

PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER:

这些宏行为与上面的几个是类似的.唯一的不同是它们用来写入data.(译注: 这段文字与上面的描述差不多,为免繁复,不译.)

PTRACE_SYSCALL, PTRACE_CONT:

这些宏用来唤醒暂停的子进程.在每次系统调用之后,PTRACE_SYSCALL使子进程暂停,而PTRACE_CONT让子进程继续运行.子进程的返回状态都是由ptrace()参数data设置的.但是,这只限于返回状态是有效的情况.ptrace()重置子进程的single-step位,设置/复位syscall-trace位,然后唤醒子进程;参数addr被无视.

PTRACE_SINGLESTEP:

PTRACE_SINGLESTEP的行为与PTRACE_SYSCALL无异,除了子进程在每次机器指令后都被暂停(PTRACE_SYSCALL是使子进程每次在系统调用后被暂停).single-step会被设置,跟PTRACE_SYSCALL一样,参数data包含返回状态,参数addr被无视.

PTRACE_KILL:

PTRACE_KILL被用来终止子进程.”谋杀”是这样进行的: 首先ptrace() 查看子进程是不是已经死了.如果不是, 子进程的返回码被设置为sigkill. single-step位被复位.然后子进程被唤醒,运行到返回码时子进程就死掉了.

2.2 更加依赖于硬件的调用.

上面讨论的request可选值是依赖于操作系统所在的体系结构和实现的.下面讨论的request可选值可以用来get/set子进程的寄存器,这更加依赖于系统架构.对寄存器的设置包括通用寄存器,浮点寄存器和扩展的浮点寄存器.

PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_GETFPXREGS:

这些宏用来读子进程的寄存器.寄存器的值通过getreg()和__put_user()被读入data中;参数addr被无视.

PTRACE_SETREGS, PTRACE_SETFPREGS, PTRACE_SETFPXREGS:

跟上面的描述相反,这些宏被用来设置寄存器.

2.3 ptrace()的返回值

成功的ptrace()调用返回0.如果出错,将返回-1,errno也将被设置.PEEKDATA/PEEKTEXT,即使ptrace()调用成功,返回值也可能是-1,所以我们最好检查一下errno,它的可能值如下:

EPERM: 权限错误,进程无法被跟踪.

ESRCH: 目标进程不存在或者已经被跟踪.

EIO: 参数request的值无效,或者从非法的内存读/写数据.

EFAULT: 需要读/写数据的内存未被映射.

EIO和EFAULT真的很难区分,它们代表很严重的错误.

3. 小例子

如果你觉得上面的说明太枯燥了,好吧,我保证再也不这么干了.下面举个小例子,演示一下吧.

这是第一个,父进程对子进程中发生的每一次机器指令计数.

01. #include <stdio.h>
02. #include <stdlib.h>
03. #include <signal.h>
04. #include <syscall.h>
05. #include <sys/ptrace.h>
06. #include <sys/types.h>
07. #include <sys/wait.h>
08. #include <unistd.h>
09. #include <errno.h>
10.  
11. int main(void)
12. {
13. long long counter = 0;  /*  machine instruction counter */
14. int wait_val;           /*  child's return value        */
15. int pid;                /*  child's process id          */
16.  
17. puts("Please wait");
18.  
19. switch (pid = fork()) {
20. case -1:
21. perror("fork");
22. break;
23. case 0: /*  child process starts        */
24. ptrace(PTRACE_TRACEME, 0, 0, 0);
25. /*
26. *  must be called in order to allow the
27. *  control over the child process
28. */
29. execl("/bin/ls""ls", NULL);
30. /*
31. *  executes the program and causes
32. *  the child to stop and send a signal
33. *  to the parent, the parent can now
34. *  switch to PTRACE_SINGLESTEP  
35. */
36. break;
37. /*  child process ends  */
38. default:/*  parent process starts       */
39. wait(&wait_val);
40. /*  
41. *   parent waits for child to stop at next
42. *   instruction (execl())
43. */
44. while (wait_val == 1407 ) {
45. counter++;
46. if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0) != 0)
47. perror("ptrace");
48. /*
49. *   switch to singlestep tracing and
50. *   release child
51. *   if unable call error.
52. */
53. wait(&wait_val);
54. /*   wait for next instruction to complete  */
55. }
56. /*
57. * continue to stop, wait and release until
58. * the child is finished; wait_val != 1407
59. * Low=0177L and High=05 (SIGTRAP)
60. */
61. }
62. printf("Number of machine instructions : %lld\n", counter);
63. return 0;
64. }

运行一下代码吧(可能程序会很慢,哈哈.).

译注: 小小的解释下吧,子进程开始运行,调用exec后移花栽木,这时子进程的原进程(未调用exec之前的进程)因为要死了,会向父进程发送SIGTRAP信号.父进程一直阻塞等待(第一条wait(&wait_val);语句).父进程捕获到SIGTRAP信号,由此知道子进程已经结束了.接下来发生的就是最有趣的事情了,父进程通过request值为PTRACE_SINGLESTEP的ptrace调用,告诉操作系统,重新唤醒子进程,但是在每条机器指令运行之后暂停.再一次的,父进程阻塞等待子进程暂停(wait_val == 1407等价于WIFSTOPPED(wait_val) (个人看法,我还没有查到子进程状态的编码资料,求~))并计数,子进程结束(非暂停,对应的是WIFEXITED)后,父进程跳出loop循环.

4. 结论

ptrace()在调试器中是被用得很多的,它也可以被用来跟踪系统调用.调试器fork一个子进程并跟踪它,然后子进程exec调用要被调试的目标程序,在目标程序的每一次机器指令之后父进程都可以查看它的寄存器的值.在下一篇文章中,我会尽情展示ptrace的牛逼之处,春节快乐.

作者: Sandeep S


目录
相关文章
|
3月前
|
Linux 调度
Linux进程状态
Linux进程状态
|
6月前
|
Windows
「渗透技巧」利用Fork进程来Dump内存
「渗透技巧」利用Fork进程来Dump内存
|
5月前
|
Linux
linux进程信息
在linux中 每个执行的程序都称为一个进程,每个进程都分配一个id号 每个进程都有可能以两种方式存在的,前台与后台。 显示系统那些进程在执行 ps -a 显示当前终端的所有进程信息 ps -u 以用户的格式显示进程信息 ps -x显示后台进程运行的参数 %men 占用物理内存 vsz 占用虚拟内存 rss 占用物理内存 tty 终端信息 stat 运行状态 s睡眠 r运行时间 n 比普通进程优先级低 r正在运行 d短期等待 z 僵死进程 t 被跟踪或停止 command 进程名 ps -ef 是以全格式显示当前所有的进程 -e 显示所有进程 -f全格式 uid 用户id pid 进程i
28 0
|
3月前
|
算法 Unix Linux
进程原理及系统调用
进程原理及系统调用
|
5月前
|
程序员 Linux Shell
【CSAPP】进程控制 | 系统调用错误处理 | 进程状态 | 终止进程 | 进程创建 | 回收子进程 | 与子进程同步(wait/waitpid) | execve 接口
【CSAPP】进程控制 | 系统调用错误处理 | 进程状态 | 终止进程 | 进程创建 | 回收子进程 | 与子进程同步(wait/waitpid) | execve 接口
52 0
|
9月前
|
缓存 Linux 调度
Linux进程控制【进程创建终止和等待】
Linux进程的创建、终止和等待,包含丰富系统接口介绍,详细讲解,干货满满!
4895 7
Linux进程控制【进程创建终止和等待】
|
9月前
|
Unix Shell Linux
进程系统调用
进程系统调用
62 0
|
9月前
|
存储 缓存 安全
Linux进程理解【进程认识】
Linux进程概念理解与创建操作详细讲解,干货满满!
4342 2
Linux进程理解【进程认识】
|
9月前
|
算法 Linux 调度
进程原理及其系统调用(上)
进程原理及其系统调用
90 0
|
10月前
|
Unix Linux 调度
Linux之创建进程、查看进程、进程的状态以及进程的优先级
Linux之创建进程、查看进程、进程的状态以及进程的优先级
156 0