使用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的原型如下:

view
source
print?

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. 小例子

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

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

view
source
print?

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

时间: 2024-10-01 13:42:13

使用ptrace跟踪进程的相关文章

strace工具的实现原理【转】

转自:http://blog.csdn.net/jasonchen_gbd/article/details/44044539 版权声明:本文为博主原创文章,转载请附上原博链接.   目录(?)[-] ptrace系统调用 使用ptrace跟踪进程 常用的request strace工具的实现原理   strace是Linux系统下的一个用来跟踪系统调用的工具,它的实现基础是ptrace系统调用.使用strace工具可以跟踪一个程序执行过程中发生的系统调用. 我这里讲到的内容有一点点和mips体系

无需Ptrace就能实现Linux进程间代码注入

本文讲的是无需Ptrace就能实现Linux进程间代码注入, ptrace系统调用 ptrace系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值.其基本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被系统标注为TASK_TRACED.而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进

使用Linux Strace跟踪调试Oracle程序进程

  所谓操作系统,是应用程序与服务器硬件进行沟通的中间层.应用程序的所有操作,都是和操作系统进行沟通交互.操作系统负责将所有交互转化为设备语言,进行硬件交互. 我们在进行Oracle故障调试和内核原理工作的时候,经常需要了解后台运行的动作和细节.一些故障场景,如程序进程hang住.无法登陆等问题,就需要操作系统级别监控,检查定位问题. Oracle自身已经提供了很多这类型的工具,如oradebug.各种等待事件和跟踪方式.此外,各类型的操作系统提供出很多系统级别工具,帮助我们进行监控.本篇主要介

Linux内核剖析 之 进程简介

1.概念    1.1  什么是进程?     进程是程序执行的一个实例,可以看作充分描述程序已经执行到何种程度的数据结构的汇集.     从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体.     我们熟悉的fork()库函数,它有两种用法:     (1).一个父进程希望复制自己,使父子进程执行不同的代码段,常用于网络服务程序.     (2).一个进程要执行一个不同的程序,fork()后立即exec(),如shell. 1.2  什么是线程?     有时候,一个进

关于用strace工具定位vrrpd进程有时会挂死的bug

只做工作总结备忘之用. 正在烧镜像,稍总结一下进来改bug遇到的问题.     一个项目里要用到L3 switch的nat,vrrp功能,但实地测试中偶然出现write file挂死的情况,但不是必现.交付在即,于是加调试信息,反复跑配置的脚本,定位bug.   一,期初怀疑是vtysh与vrrpd进程通信出现阻塞(现象即是系统挂死). (1)因为在跑配置脚本时,出现了enable命令也挂死的情况,所以这么怀疑: (2)在vrrpd与vtysh命令传输的关键点加打印信息(注意vrrpd不要-d

linux跟踪线程的方法:LWP和strace命令

摘要:在使用多线程程序时,有时会遇到程序功能异常的情况,而这种异常情况并不是每次都发生,很难模拟出来.这时就需要运用在程序运行时跟踪线程的手段,而linux系统的LWP和strace命令正是这种技术手段.本文对LWP和strace命令做了简明扼要的介绍,并通过一个实例来说明如何运用.总而言之,LWP和strace的使用可以提高多线程程序的可维护性. 问题描述: 我们来看一个问题:程序tcp_client同时创建多个线程向同一个服务器发送数据,每个线程发送不同类型的数据,服务器接收数据后,可以通过

PHP-FPM进程CPU 100% 问题解决办法

一.进程跟踪  代码如下 复制代码 # top //找出CPU使用率高的进程PID # strace -p PID //跟踪进程 # ll /proc/PID/fd //查看该进程在处理哪些文件 将有可疑的PHP代码修改之,如:file_get_contents没有设置超时时间. 二.内存分配 如果进程跟踪无法找到问题所在,再从系统方面找原因,会不会有可能内存不够用?据说一个较为干净的PHP-CGI打开大概20M-30M左右的内存,决定于PHP模块开启多少. 通过pmap指令查看PHP-CGI进

linux查看进程所占内存 proc pid status

命令:cat /proc/9744/status   //9744为进程id Name: gedit /*进程的程序名*/ State: S (sleeping) /*进程的状态信息*/ Tgid: 9744 /*线程组号*/ Pid: 9744 /*进程pid*/ PPid: 7672 /*父进程的pid*/ TracerPid: 0 /*跟踪进程的pid*/ Uid: 1000    1000    1000    1000 /*uid euid suid fsuid*/ Gid: 1000

使用 Linux 的 strace 命令跟踪/调试程序的常用选项

使用 Linux 的 strace 命令跟踪/调试程序的常用选项 在调试的时候,strace能帮助你追踪到一个程序所执行的系统调用.当你想知道程序和操作系统如何交互的时候,这是极其方便的,比如你想知道执行了哪些系统调用,并且以何种顺序执行. 这个简单而又强大的工具几乎在所有的Linux操作系统上可用,并且可被用来调试大量的程序. 命令用法 让我们看看strace命令如何追踪一个程序的执行情况. 最简单的形式,strace后面可以跟任何命令.它将列出许许多多的系统调用.一开始,我们并不能理解所有的