strace工具的实现原理【转】

转自:http://blog.csdn.net/jasonchen_gbd/article/details/44044539

版权声明:本文为博主原创文章,转载请附上原博链接。

 

目录(?)[-]

  1. ptrace系统调用
    1. 使用ptrace跟踪进程
    2. 常用的request
  2. strace工具的实现原理

 

strace是Linux系统下的一个用来跟踪系统调用的工具,它的实现基础是ptrace系统调用。使用strace工具可以跟踪一个程序执行过程中发生的系统调用。

我这里讲到的内容有一点点和mips体系相关,不过不熟悉mips也不影响阅读。

ptrace系统调用

ptrace系统调用提供了一种方法来跟踪和控制进程的执行,它可以读取和修改进程地址空间中的内容,包括寄存器的值。ptrace主要用于实现断点调试和跟踪系统调用。该系统调用的原型如下:

long ptrace(enum __ptrace_request request, pid_t pid, void *addr,void *data);

ptrace的四个参数的含义为:

1.   request:用于选择一个操作,见下文。

2.   pid:目标进程即被跟踪进程的pid。

3.   addr和data用于修改和拷贝被跟踪进程的进程地址空间的数据。

下面的内容中将用父进程指代跟踪者,用子进程指代被跟踪者。实际上,在一个进程被跟踪之后,跟踪者进程会在某种意义上充当被跟踪进程的父进程(如使用ps命令就可以看到他们的父子关系),而子进程真正的父进程被保存在其task_struct结构的real_parent成员中。

使用ptrace跟踪进程

父进程跟踪一个进程的方式有两种:1.调用fork(),然后子进程打上PTRACE_TRACEME标记,并执行exec。2.父进程可以给自己打上PTRACE_ATTACH标记来跟踪一个已有进程。

一个进程被跟踪后,他只要接收到一个信号(即使这个信号被设置为忽略)就会停止运行(SIGKILL除外),然后父进程会在每次调用wait()时得到子进程停止运行的通知,这时父进程就可以检测和修改子进程了,随后父进程可以让子进程继续运行。

当父进程不想跟踪了,可以通过设置PTRACE_KILL标记来终止子进程的运行。也可以通过设置PTRACE_DETACH标记让子进程解除被跟踪,继续正常运行。

常用的request

PTRACE_TRACEME

进程设置这个request目的是让自己被父进程跟踪。任何发送到该子进程的信号(除了SIGKILL)都会导致他停下来,并在父进程wait()的时候通知到父进程。另外,子进程后续调用exec会导致子进程自己收到一个SIGTRAP信号,这是为了让父进程有机会在exec的新程序开始执行前获得控制权。

除非一个进程知道父进程要跟踪他,一般不会去设置这个request。设置这个请求时,pid,addr和data三个参数都会被忽略。

这个request只供子进程设置,其他的request都是只供父进程使用的。相应的,下面的request中,参数pid为被跟踪的子进程的pid。

PTRACE_ATTACH

将pid指定的进程作为自己要跟踪的进程,并开始跟踪。这和子进程自己调用PTRACE_TRACEME的效果相同。

设置这个request时,子进程会首先收到一个SIGSTOP信号,但并不会停止,只是导致跟踪者进程第一次被中断,从而开始跟踪,否则只能等到子进程接收到第一个信号时才开始跟踪。之后当子进程有待决信号时,进程总是会暂停,这时父进程通过SIGCHLD信号得到通知。在子进程暂停前,父进程可使用wait函数等待。

参数addr和data会被忽略。

PTRACE_CONT

让被停掉的子进程继续运行,而当子进程再次接收到信号时会暂停。

参数data如果被设置为一个非零值并且不是SIGSTOP,那data就是父进程发送给子进程的信号,否则,不给子进程发送信号。这样一来,父进程可以控制是否向子进程发送一个信号。

参数addr会被忽略。

PTRACE_SYSCALL

让停止的子进程继续运行(同PTRACE_CONT),而当子进程再次接收到信号时会暂停,另外,在子进程中发生系统调用时,在系统调用的入口和结束时子进程也会停止,这时父进程认为子进程是因为收到SIGTRAP信号而停止的。

由于子进程在系统调用的入口和结束时都会停止,父进程就可以在系统调用入口处停止后,获得系统调用的参数信息,而在系统调用结束时停止后,获得系统调用的返回值。

参数addr会被忽略。

PTRACE_DETACH

让停止的子进程继续运行(同PTRACE_CONT),但是在这之前会先与跟踪它的父进程解除PTRACE_ATTACH或PTRACE_TRACEME时的关系。

参数addr会被忽略。

PTRACE_KILL

给子进程发送一个SIGKILL信号来终止子进程。addr和data参数会被忽略。

PTRACE_PEEKTEXT,PTRACE_PEEKDATA

读取进程地址空间中addr地址处的内容,读出的长度为一个word(4字节),作为ptrace()的返回值(long型)返回。Linux中的代码和数据的地址空间并不是分离的,所以这两个request实际上意义相同。

参数data会被忽略。

PTRACE_PEEKUSR

在进程的USER区域读取一个word的长度。参数addr是指相对USER开头的offset,结果作为返回值。参数data会被忽略。

在mips中的进程自身信息和进程地址空间中并没有所谓的USER区域,在内核中通过参数addr的值,判断应该返回什么结果,如下:

 

[cpp] view plain copy

 

  1. /* Read the word at location addr in theUSER area. */  
  2.     case PTRACE_PEEKUSR: {  
  3.        struct pt_regs *regs;  
  4.        unsigned long tmp = 0;  
  5.    
  6.        /* 获得进程地址空间的pt_regs区域的内容。 */  
  7.        regs =task_pt_regs(child);  
  8.        ret = 0;  /* Default return value. */  
  9.    
  10.        switch (addr) {  
  11.        case 0 ... 31:    /* 通用寄存器 */  
  12.            tmp =regs->regs[addr];  
  13.            break;  
  14.        case FPR_BASE ...FPR_BASE + 31:  
  15.            ......  
  16.            break;  
  17.        case PC:  
  18.            tmp =regs->cp0_epc;  
  19.            break;  
  20.        case CAUSE:  
  21.            tmp =regs->cp0_cause;  
  22.            break;  
  23.        case BADVADDR:  
  24.            tmp =regs->cp0_badvaddr;  
  25.            break;  
  26.        case MMHI:  
  27.            tmp = regs->hi;  
  28.            break;  
  29.        case MMLO:  
  30.            tmp = regs->lo;  
  31.            break;  
  32.        case FPC_CSR:  
  33.            tmp =child->thread.fpu.fcr31;  
  34.            break;  
  35.        case FPC_EIR: {   /* implementation / version register */  
  36.            ......  
  37.            break;  
  38.        }  
  39.        case DSP_BASE ...DSP_BASE + 5: {  
  40.            ......  
  41.            break;  
  42.        }  
  43.        case DSP_CONTROL:  
  44.            ......  
  45.            break;  
  46.        default:  
  47.            tmp = 0;  
  48.            ret = -EIO;  
  49.            goto out;  
  50.        }  
  51.        ret = put_user(tmp,(unsigned long __user *) data);  
  52.        break;  
  53.     }  

 

PTRACE_POKETEXT,PTRACE_POKEDATA

将data的内容拷贝到进程地址空间中addr指向的地址。

PTRACE_POKEUSR

将data的内容拷贝到进程USER区域中偏移为addr的地方,一般addr要求是word-aligned的。由于要修改USER区域,内核为保证完整健全,会禁止某些域被修改。

strace工具的实现原理

strace工具是一个用户态的应用程序,用来追踪进程的系统调用。它的基础就是ptrace系统调用。安装strace之后,就可以使用strace命令了。

最简单的strace命令的用法就是:strace PROG,PROG是要执行的程序。strace命令执行的结果就是按照调用顺序打印出所有的系统调用,包括函数名、参数列表以及返回值。

使用strace跟踪一个进程的系统调用的基本流程如图1所示。

图1 strace实现流程

从图中可以看出strace做了以下几件事情:

1.   设置SIGCHLD 信号的处理函数,这个处理函数只要不是SIG_IGN即可。由于子进程停止后是通过SIGCHLD信号通知父进程的,所以这里要防止SIGCHLD信号被忽略。

2.   创建子进程,在子进程中调用ptrace(PTRACE_TRACEME,0L, 0L, 0L)使其被父进程跟踪,并通过execv函数执行被跟踪的程序。

3.   通过wait()等待子进程停止,并获得子进程停止时的状态status。

4.   通过子进程的状态查看子进程是否已正常退出,如果是,则不再跟踪,随后调用ptrace发送PTRACE_DETACH请求解除跟踪关系。

5.   子进程停止后,打印系统调用的函数名、参数和返回值。具体流程见图2。

6.   通过PTRACE_SYSCALL让子进程继续运行,由于这个请求会让子进程在系统调用的入口处和系统调用完成时都会停止并通知父进程,这样,父进程就可以在系统调用开始之前获得参数,结束之后获得返回值。

在系统调用的入口和结束时子进程停止运行时,这时父进程认为子进程是因为收到SIGTRAP信号而停止的。所以父进程在wait()后可以通过SIGTRAP来与其他信号区分开。

Strace中为每个要跟踪的进程维护了一个TCB(Trace Control Block)结构,定义如下。它保存了当前发生的系统调用的信息。

 

[cpp] view plain copy

 

  1. /* Trace Control Block */  
  2. struct tcb {  
  3.     int flags;    /* See below for TCB_ values */  
  4.     int pid;      /* Process Id of this entry */  
  5.     int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW*/  
  6.     int u_error;  /* Error code */  
  7.     long scno;    /* System call number */  
  8.     long u_arg[MAX_ARGS];    /* System call arguments */  
  9.     long u_rval;      /* Return value */  
  10.     int curcol;       /* Output column for this process */  
  11.     FILE *outf;       /* Output file for this process */  
  12.     const char *auxstr;/*Auxiliary info from syscall (see RVAL_STR) */  
  13.     const struct_sysent *s_ent;/* sysent[scno] or dummy struct for bad scno */  
  14.     struct timeval stime;/*System time usage as of last process wait */  
  15.     struct timeval dtime;    /* Delta for system time usage */  
  16.     struct timeval etime;    /* Syscall entry time */  
  17.               /* Support fortracing forked processes: */  
  18.     long inst[2];     /* Saved clone args (badly named) */  
  19. };  

 

上面已经提到,子进程会在系统调用前后各停止一次,所以打印系统调用信息时分为两个阶段:在系统调用开始时可以获取系统调用号和参数,在系统调用结束时可以获取系统调用的返回结果。通过给tcb结构的flags字段清除和添加TCB_INSYSCALL标志位来区分系统调用的开始和结束。

图2 strace中打印系统调用的实现流程

例如编写一个使用printf打印“Hello world”的程序hello.c,使用strace跟踪该程序的系统调用可以看到如下结果:

 

[plain] view plain copy

 

  1. # ./strace ./hello  
  2. execve("./hello ", ["./hello "], [/* 7 vars */])= 0  
  3. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaad000  
  4. stat("/etc/ld.so.cache", 0x7faf4ca8)    = -1 ENOENT (No such file or directory)  
  5. open("/tmp/libgcc_s.so.1", O_RDONLY)    = -1 ENOENT (No such file or directory)  
  6. open("/lib/libgcc_s.so.1", O_RDONLY)    = 3  
  7. fstat(3, {st_mode=S_IFREG|0644, st_size=1565445, ...}) = 0  
  8. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000  
  9. read(3,"\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\263\200\0\0\0004"...,4096) = 4096  
  10. mmap(NULL, 241664, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aabe000  
  11. mmap(0x2aabe000, 169308, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aabe000  
  12. mmap(0x2aaf8000, 2400, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x2a000) = 0x2aaf8000  
  13. close(3)                                = 0  
  14. munmap(0x2aaae000, 4096)                = 0  
  15. open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)  
  16. open("/lib/libc.so.0", O_RDONLY)        = 3  
  17. fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0  
  18. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000  
  19. read(3, "\177ELF\1\2\1\0\0\0\0\0\0\0\0\0\0\3\0\10\0\0\0\1\0\0\252\200\0\0\0004"...,4096) = 4096  
  20. mmap(NULL, 471040, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aaf9000  
  21. mmap(0x2aaf9000, 380336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aaf9000  
  22. mmap(0x2ab65000, 8088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x5c000) = 0x2ab65000  
  23. mmap(0x2ab67000, 19376, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2ab67000  
  24. close(3)                                = 0  
  25. munmap(0x2aaae000, 4096)                = 0  
  26. open("/tmp/libc.so.0", O_RDONLY)        = -1 ENOENT (No such file or directory)  
  27. open("/lib/libc.so.0", O_RDONLY)        = 3  
  28. fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0  
  29. close(3)                                = 0  
  30. stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755,st_size=22604, ...}) = 0  
  31. mprotect(0x2ab65000, 4096, PROT_READ)   = 0  
  32. mprotect(0x2aabc000, 4096, PROT_READ)   = 0  
  33. ioctl(0, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0  
  34. ioctl(1, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0  
  35. write(1, "Hello world\n", 12Hello world  
  36. )           = 12  
  37. exit(0)                                 = ?  
  38. +++ exited with 0 +++  
  39. #  

 

从结果可以看出,执行该程序调用了很多系统调用,并最终通过write系统调用打印出“Hello world”。

跟踪一个正在运行的进程,使用-p选项加上进程的pid。

跟踪某个特定的系统调用,使用-e选项加上系统调用名。

例如,跟踪进程727的epoll_wait系统调用:strace -e epoll_wait -p 727

时间: 2024-10-16 02:59:29

strace工具的实现原理【转】的相关文章

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

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

刷微博粉丝工具发布及原理解说

自上文:网站SEO优化.IIS日志分析工具 IISLogViewer V2.0 发布 以来,没少在折腾网站SEO优化. 经过大大小小的SEO优化,从N个方面将秋色园QBlog给处理了一下. 如上基本上都处理的差不多了,近几天顺手也写了个:粉丝精灵工具,暂时仅支持新浪微博.   随说几句:   曾经,有熟人找过,让我写一个刷粉丝的工具. 我当时不怎么玩微博,就问,要假的粉丝有啥用呢? 人家回答,这个有人需要,自然有用. 然后上网看了一下,竟然发现N多的刷粉丝工具的业务,1000粉丝/4元. 于是折

Linux系统小技巧(6):刀锋组合-strace和wireshark工具

首先声明下,此处wireshark,可以替换为tcpdump.同样,strace偶尔也可以替换为ltrace,只要熟悉库函数就好. wireshark和strace,对于黑客而言,都是工具箱中的必备工具.有过排查和诊断经历的工程师,谁没有抓包和分析包的经历呢? 相对而言,strace的名气要小一些,毕竟有意愿.有能力追踪并且能够分析进程执行路径的不多.而且常见的系统调用也有二三十个. 系统调用如此强力,why? 让我们先明确下为什么strace工具颇具威力,看图 虽然Linux已经足够复杂,而且

自助Linux之问题诊断工具strace

引言 "Oops,系统挂死了..." "Oops,程序崩溃了..." "Oops,命令执行报错..."   对于维护人员来说,这样的悲剧每天都在上演.理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问题发生的原因.但现实情况,许多错误日志打印模凌两可,更多地描述了出错时的现象(比如"could not open file","connect to XXX time

自助Linux之问题诊断工具strace【转】

转自:http://www.cnblogs.com/bangerlee/archive/2012/02/20/2356818.html   引言 "Oops,系统挂死了..." "Oops,程序崩溃了..." "Oops,命令执行报错..."   对于维护人员来说,这样的悲剧每天都在上演.理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问题发生的原因.但现实情况,许多错误日志打印模凌两可,更多地

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

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

FW MX 2004教程(2):绘图工具

教程 和许多主流的图像处理软件一样,Fireworks的绘图工具主要都集中在"工具条"上.利用这些工具可以绘制出各种图形,并可为其设置相应的属性,如颜色.大小.位置等等. Fireworks MX 2004的"工具条"中增加了不少新工具,并与原有的工具在一起被编排为六个类别:选择区.位图.矢量.Web.颜色和视图区.有些工具按钮的右下角有一个小三角,说明这个工具包含有其它几种不同的工具,按住这个工具按钮不放就能显示其它的工具. 我们先来介绍位图区的绘图工具. 1.选

Fireworks MX 2004 图像修改工具

3.图像修改工具 "橡皮擦" 工具:用于擦除位图的颜色.和使用画笔一样,在选中橡皮工具后,按住鼠标左键在图像上拖动即可,如图2-10. 图2-10 "模糊" 工具:该工具的工作原理就是降低像素之间的反差,使图像产生模糊效果,如图2-11. 图2-11 "锐化" 工具:与"模糊"工具正好相反,"锐化"工具是用于加深像素之间的反差,使图像更加锐化,效果如图2-12. 图2-12 "模糊"与&

VC内存泄露检查工具:VisualLeakDetector

初识VisualLeakDetector灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题.当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问题.内存泄漏是最常见的内存问题之一.内存泄漏如果不是很严重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现.然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行.另外内存问题的一个共同特点是,内存问题本身并不会有很明