Linux上程序调试的基石(1)--ptrace
引子:
1.在Linux系统中,进程状态除了我们所熟知的TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_STOPPED等,还有一个TASK_TRACED。这表明这个进程处于什么状态?
2.strace可以方便的帮助我们记录进程所执行的系统调用,它是如何跟踪到进程执行的?
3.gdb是我们调试程序的利器,可以设置断点,单步跟踪程序。它的实现原理又是什么?
所有这一切的背后都隐藏着Linux所提供的一个强大的系统调用ptrace().
1.ptrace系统调用
ptrace系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包 括寄存器)的值。其基本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被 系统标注为TASK_TRACED。而父进程收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。
其原型为:
#include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
ptrace有四个参数:
1). enum __ptrace_request request:指示了ptrace要执行的命令。
2). pid_t pid: 指示ptrace要跟踪的进程。
3). void *addr: 指示要监控的内存地址。
4). void *data: 存放读取出的或者要写入的数据。
ptrace是如此的强大,以至于有很多大家所常用的工具都基于ptrace来实现,如strace和gdb。接下来,我们借由对strace和gdb的实现,来看看ptrace是如何使用的。
2. strace的实现
strace常常被用来拦截和记录进程所执行的系统调用,以及进程所收到的信号。如有这么一段程序:
HelloWorld.c:
#include <stdio.h>
int main(){
printf("Hello World!\n");
return 0;
}
编译后,用strace跟踪: strace ./HelloWorld
可以看到形如:
execve("./HelloWorld", ["./HelloWorld"], [/* 67 vars */]) = 0
brk(0) = 0x804a000
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f18000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/home/supperman/WorkSpace/lib/tls/i686/sse2/libc.so.6", O_RDONLY) = -1 ENOENT (No such file or directory)
...
的一段输出,这就是在执行HelloWorld中,系统所执行的系统调用,以及他们的返回值。
下面我们用ptrace来研究一下它是怎么实现的。
...
switch(pid = fork())
{
case -1:
return -1;
case 0: //子进程
ptrace(PTRACE_TRACEME,0,NULL,NULL);
execl("./HelloWorld", "HelloWorld", NULL);
default: //父进程
wait(&val); //等待并记录execve
if(WIFEXITED(val))
return 0;
syscallID=ptrace(PTRACE_PEEKUSER, pid, ORIG_EAX*4, NULL);
printf("Process executed system call ID = %ld\n",syscallID);
ptrace(PTRACE_SYSCALL,pid,NULL,NULL);
while(1)
{
wait(&val); //等待信号
if(WIFEXITED(val)) //判断子进程是否退出
return 0;
if(flag==0) //第一次(进入系统调用),获取系统调用的参数
{
syscallID=ptrace(PTRACE_PEEKUSER, pid, ORIG_EAX*4, NULL);
printf("Process executed system call ID = %ld ",syscallID);
flag=1;
}
else //第二次(退出系统调用),获取系统调用的返回值
{
returnValue=ptrace(PTRACE_PEEKUSER, pid, EAX*4, NULL);
printf("with return value= %ld\n", returnValue);
flag=0;
}
ptrace(PTRACE_SYSCALL,pid,NULL,NULL);
}
}
...
在上面的程序中,fork出的子进程先调用了ptrace(PTRACE_TRACEME)表示子进程让父进程跟踪自己。然后子进程调用 execl加载执行了HelloWorld。而在父进程中则使用wait系统调用等待子进程的状态改变。子进程因为设置了PTRACE_TRACEME而 在执行系统调用被系统停止(设置为TASK_TRACED),这时父进程被唤醒,使用ptrace(PTRACE_PEEKUSER,pid,...)分 别去读取子进程执行的系统调用ID(放在ORIG_EAX中)以及系统调用返回时的值(放在EAX中)。然后使用ptrace (PTRACE_SYSCALL,pid,...)指示子进程运行到下一次执行系统调用的时候(进入或者退出),直到子进程退出为止。
程序的执行结果如下:
Process executed system call ID = 11
Process executed system call ID = 45 with return value= 134520832
Process executed system call ID = 192 with return value= -1208934400
Process executed system call ID = 33 with return value= -2
Process executed system call ID = 5 with return value= -2
...
其中,11号系统调用就是execve,45号是brk,192是mmap2,33是access,5是open...经过比对可以发现,和 strace的输出结果一样。当然strace进行了更详尽和完善的处理,我们这里只是揭示其原理,感兴趣的同学可以去研究一下strace的实现。
PS:
1). 在系统调用执行的时候,会执行pushl %eax # 保存系统调用号ORIG_EAX在程序用户栈中。
2). 在系统调用返回的时候,会执行movl %eax,EAX(%esp)将系统调用的返回值放入寄存器%eax中。
3). WIFEXITED()宏用来判断子进程是否为正常退出的,如果是,它会返回一个非零值。
4). 被跟踪的程序在进入或者退出某次系统调用的时候都会触发一个SIGTRAP信号,而被父进程捕获。
5). execve()系统调用执行成功的时候并没有返回值,因为它开始执行一段新的程序,并没有"返回"的概念。失败的时候会返回-1。
6). 在父进程进行进行操作的时候,用ps查看,可以看到子进程的状态为T,表示子进程处于TASK_TRACED状态。当然为了更具操作性,你可以在父进程中加入sleep()。
Linux上程序调试的基石(1)--ptrace的更多相关文章
- Linux上程序调试的基石(2)--GDB
3. GDB的实现 GDB是GNU发布的一个强大的程序调试工具,用以调试C/C++程序.可以使程序员在程序运行的时候观察程序在内存/寄存器中的使用情况.它的实现也是基于ptrace系统调用来完成的. ...
- 嵌入式linux应用程序调试方法
嵌入式linux应用程序调试方法 四 内存工具 五 C/C++代码覆盖.性能profiling工具 四 内存工具 您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形. 许多小组花了许许多多时间来 ...
- Linux Shell 程序调试
Linux Shell 程序调试 Shell程序的调试是通过运行程序时加入相关调试选项或在脚本程序中加入相关语句,让shell程序在执行过程中显示出一些可供参考的“调试信息”.当然,用户也可以在she ...
- Linux上程序执行的入口--Main
main()函数,想必大家都不陌生了,从刚开始写程序的时候,大家便开始写main(),我们都知道main是程序的入口.那main作为一个函数,又是谁调用的它,它是怎么被调用的,返回给谁,返回的又是什么 ...
- [转]Linux上程序执行的入口--Main
main()函数,想必大家都不陌生了,从刚开始写程序的时候,大家便开始写main(),我们都知道main是程序的入口.那main作为一个函数,又是谁调用的它,它是怎么被调用的,返回给谁,返回的又是什么 ...
- linux文件或文件夹常见操作,排查部署在linux上程序问题常用操作
创建文件夹 mkdir [-p] DirName [ 在工作目录下,建立一个名为 A 新的子目录 : mkdir A 在工作目录下的 B目录中,建立一个名为 T 的子目录: 若 B 目录不存在,则 ...
- Linux Bash命令关于程序调试详解
转载:http://os.51cto.com/art/201006/207230.htm 参考:<Linux shell 脚本攻略>Page22-23 Linux bash程序在程序员的使 ...
- C#在Linux上的开发指南
本人才疏学浅,在此记录自己用C#在Linux上开发的一点经验,写下这篇指南.(给想要在Linux上开发C#程序的朋友提供建议) 目前在Linux上跑的网站:http://douxiubar.com | ...
- pycharm远程linux开发和调试代码
pycharm是一个非常强大的python开发工具,现在很多代码最终在线上跑的环境都是linux,而开发环境可能还是windows下开发,这就需要经常在linux上进行调试,或者在linux对代码进行 ...
随机推荐
- Java并发编程:JMM(Java内存模型)和volatile
1. 并发编程的3个概念 并发编程时,要想并发程序正确地执行,必须要保证原子性.可见性和有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.1. 原子性 原子性:即一个或多个操作要么全部 ...
- Nginx+uWSGI+Django环境配置
通常项目会部署在虚拟环境,虚拟环境的使用可以参考这里,点击前往 当然你也可以直接部署,这里不多说. 一.安装uWSGI 1.通过pip安装 pip install uwsgi 这里只说明了一种安装方式 ...
- 关于ajax的content-download时间过慢问题的解决方案与思考
前言: 做前端架构很久很久了,经常到我这里都是些棘手的问题,之前没有养成很好的记录问题的习惯,以后会努力成文,积累. 于是今天就有个这篇文章.关于ajax的content-download时间过慢 ...
- Mybatis之一级缓存,二级缓存
一级缓存:Mybatis的一级缓存在session上,只要通过session查过的数据,都会放在session上,下一次再查询相同id的数据,都直接冲缓存中取出来,而不用到数据库里去取了. 二级缓存: ...
- 在腾讯云的ubuntu服务器上面安装git服务器
GitHub是一个免费托管开源代码的远程仓库.但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用.搭建Git服 ...
- Jackson工具
Jackson Jackson包含一个core JAR,和两个依赖core JAR的JAR: jackson-core-2.2.3.jar(核心jar包,下载地址) jackson-annotatio ...
- DDL/DML/DCL区别概述
DDL DDL的概述 DDL(Data Definition Language 数据定义语言)用于操作对象和对象的属性,这种对象包括数据库本身,以及数据库对象,像:表.视图等等,DDL对这些对象和属性 ...
- Kali安装使用文泉驿字体
安装文泉驿字体 Kali选择"简体中文"安装后,在终端等地方发现字体总有重叠,只要安装中文字体即可,这里推荐文泉驿字体.文泉驿是一个以开发开源.免费中文电子资源--如汉字字体.词库 ...
- 字符流之FileReader&FileWriter
package zhang; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; pub ...
- cannot open file "cxcore.lib"
运行例子程序的时候总是出现连接错误:LINK : fatal error LNK1104: cannot open file "cxcore.lib". 在VC选项里把C:\Pro ...