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

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

 
 

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的值,判断应该返回什么结果,如下:

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

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)结构,定义如下。它保存了当前发生的系统调用的信息。

  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跟踪该程序的系统调用可以看到如下结果:

  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

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

  1. 主机管理+堡垒机系统开发:strace工具的实现原理(七)

    strace是Linux系统下的一个用来跟踪系统调用的工具,它的实现基础是ptrace系统调用.使用strace工具可以跟踪一个程序执行过程中发生的系统调用. 我这里讲到的内容有一点点和mips体系相 ...

  2. 使用strace工具故障排查的5种简单方法

    使用strace工具故障排查的5种简单方法 本文源自5 simple ways to troubleshoot using strace strace 是一个非常简单的工具,用来跟踪可执行程序的系统调 ...

  3. 打包工具的核心原理(转自:https://juejin.im/entry/5b223ebd518825748b569bda)

    打包工具就是负责把一些分散的小模块,按照一定的规则整合成一个大模块的工具.与此同时,打包工具也会处理好模块之间的依赖关系,最终这个大模块将可以被运行在合适的平台中. 打包工具会从一个入口文件开始,分析 ...

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

    只做工作总结备忘之用. 正在烧镜像,稍总结一下进来改bug遇到的问题. 一个项目里要用到L3 switch的nat,vrrp功能,但实地测试中偶然出现write file挂死的情况,但不是必现.交付在 ...

  5. 强大的 strace 工具

    什么是 strace strace是Linux环境下的一款程序调试工具,用来监察一个应用程序所使用的系统调用. Strace是一个简单的跟踪系统调用执行的工具.在其最简单的形式中,它可以从开始到结束跟 ...

  6. cglib、orika、spring等bean copy工具性能测试和原理分析

    简介 在实际项目中,考虑到不同的数据使用者,我们经常要处理 VO.DTO.Entity.DO 等对象的转换,如果手动编写 setter/getter 方法一个个赋值,将非常繁琐且难维护.通常情况下,这 ...

  7. 分享自研实现的多数据源(支持同DB不同表、跨DB表、内存数据、外部系统数据等)分页查询工具类实现原理及使用

    思考: 提起分页查询,想必任何一个开发人员(不论是新手还是老手)都能快速编码实现,实现原理再简单不过,无非就是写一条SELECT查询的SQL语句,ORDER BY分页排序的字段, 再结合limit ( ...

  8. 使用strace 工具跟踪系统调用和信号

    使用strace来执行程序,它会记录程序执行过程中调用,接收到的信号,通过查看记录结果,就可以知道程序打开哪些文件,进行哪些读写,映射哪些内存,向系统申请多少内存等信息 strace 移植 下载str ...

  9. 简述一个javascript简单继承工具的实现原理

    背景 由于本人非常希望能够开发自己的游戏,所以业余时间一直在想着能不能自己一些好玩又有趣的东西出来,最近随着steam上众多独立游戏的爆发,感觉自己又燃烧了起来,所以又拾起了很久以前的一个2d引擎,决 ...

随机推荐

  1. cocos2dx for lua 摄像机移动

    在cocos2dx中,我们想通过移动摄像机来做一些特殊处理,比如将摄像机聚焦在某个物体上,或者摄像机颤抖,摄像机原理观察sprite回收状况等等, 都需要通过相机移动来使用. cocos2dx中的摄像 ...

  2. python入门:UTF-8转换成GBK编码

    #!/usr/bin/env python # -*- coding:utf-8 -*- #UTF-8转换成GBK编码 #temp(临时雇员,译音:泰坡) #decode(编码,译音:迪口德) #en ...

  3. spartan6不能直接把时钟连到IO上

    1.问题的提出:spartan6中不允许时钟信号直接连到IO口上面? 2.解决办法: ODDR2的使用 ODDR2Primitive: Double Data Rate Output D Flip-F ...

  4. C与C++接口相互调用

    转载于:http://blog.csdn.net/lincoln_2012/article/details/50801080 项目中经常使用C和C++混合编程,那么,在调用对方接口时,总是不可避免地出 ...

  5. FSMC原理通俗解释

    所以不用GPIO口直接驱动液晶,是因为这种方法速度太慢,而FSMC是用来外接各种存储芯片的,所以其数据通信速度是比普通GPIO口要快得多的.TFT-LCD 驱动芯片的读写时序和SRAM的差不多,所以就 ...

  6. nrf528xx bootloader 模块介绍(转载)

    转载https://www.cnblogs.com/rfnets/p/8205521.html 1. bootloader 的基本功能: 启动应用 几个应用之间切换 初始化外设 nordic nrf5 ...

  7. Persona5

    65536K   Persona5 is a famous video game. In the game, you are going to build relationship with your ...

  8. TTL与COMS的区别

    1.电平的上限和下限定义不一样,CMOS具有更大的抗噪区域. 同是5伏供电的话,ttl一般是1.7V和3.5V的样子,CMOS一般是  2.2V,2.9V的样子,不准确,仅供参考. 2.电流驱动能力不 ...

  9. 使用MeidaStore.Audio获得手机中的音频文件

    MediaStore是安卓系统自带的多媒体系统数据库,他在每次开机时刷新一次,可以通过Cursor这个类对数据库进行访问与修改,修改之后需用广播强制刷新. 使用Cursor必须通过Context获得C ...

  10. exe4j+Inno_setup打包java桌面应用

    打开exe4j,这里有个注意点,就是欢迎界面下面的License,如果没有请到网上找一个序列号,否则生成的exe打开之后都会先弹出您未激活exe4j的警告!点击下一步 这里有两个选项,第一个是通常编译 ...