主机管理+堡垒机系统开发: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标记让子进程解除被跟踪,继续正常运行。
strace工具是一个用户态的应用程序,用来追踪进程的系统调用。它的基础就是ptrace系统调用。安装strace之后,就可以使用strace命令了。
最简单的strace命令的用法就是:strace PROG,PROG是要执行的程序。strace命令执行的结果就是按照调用顺序打印出所有的系统调用,包括函数名、参数列表以及返回值。
使用strace跟踪一个进程的系统调用的基本流程如图1所示
三、strace实现流程
1、流程图
2、流程文字说明
从图中可以看出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)结构,定义如下。它保存了当前发生的系统调用的信息
3、代码
/* Trace Control Block */
struct tcb {
int flags; /* See below for TCB_ values */
int pid; /* Process Id of this entry */
int qual_flg; /* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW*/
int u_error; /* Error code */
long scno; /* System call number */
long u_arg[MAX_ARGS]; /* System call arguments */
long u_rval; /* Return value */
int curcol; /* Output column for this process */
FILE *outf; /* Output file for this process */
const char *auxstr;/*Auxiliary info from syscall (see RVAL_STR) */
const struct_sysent *s_ent;/* sysent[scno] or dummy struct for bad scno */
struct timeval stime;/*System time usage as of last process wait */
struct timeval dtime; /* Delta for system time usage */
struct timeval etime; /* Syscall entry time */
/* Support fortracing forked processes: */
long inst[2]; /* Saved clone args (badly named) */
};
四、strace中打印系统调用的实现流程
上面已经提到,子进程会在系统调用前后各停止一次,所以打印系统调用信息时分为两个阶段:在系统调用开始时可以获取系统调用号和参数,在系统调用结束时可以获取系统调用的返回结果。通过给tcb结构的flags字段清除和添加TCB_INSYSCALL标志位来区分系统调用的开始和结束。
1、流程图
2、代码
例如编写一个使用printf打印“Hello world”的程序hello.c,使用strace跟踪该程序的系统调用可以看到如下结果:
# ./strace ./hello
execve("./hello ", ["./hello "], [/* 7 vars */])= 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaad000
stat("/etc/ld.so.cache", 0x7faf4ca8) = -1 ENOENT (No such file or directory)
open("/tmp/libgcc_s.so.1", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/libgcc_s.so.1", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1565445, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
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
mmap(NULL, 241664, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aabe000
mmap(0x2aabe000, 169308, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aabe000
mmap(0x2aaf8000, 2400, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x2a000) = 0x2aaf8000
close(3) = 0
munmap(0x2aaae000, 4096) = 0
open("/tmp/libc.so.0", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,-1, 0) = 0x2aaae000
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
mmap(NULL, 471040, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =0x2aaf9000
mmap(0x2aaf9000, 380336, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED,3, 0) = 0x2aaf9000
mmap(0x2ab65000, 8088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED,3, 0x5c000) = 0x2ab65000
mmap(0x2ab67000, 19376, PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x2ab67000
close(3) = 0
munmap(0x2aaae000, 4096) = 0
open("/tmp/libc.so.0", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/libc.so.0", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=431732, ...}) = 0
close(3) = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755,st_size=22604, ...}) = 0
mprotect(0x2ab65000, 4096, PROT_READ) = 0
mprotect(0x2aabc000, 4096, PROT_READ) = 0
ioctl(0, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
ioctl(1, TIOCNXCL, {B115200 opost isig icanon echo ...}) = 0
write(1, "Hello world\n", 12Hello world
) = 12
exit(0) = ?
+++ exited with 0 +++
#
从结果可以看出,执行该程序调用了很多系统调用,并最终通过write系统调用打印出“Hello world”。
跟踪一个正在运行的进程,使用-p选项加上进程的pid。
跟踪某个特定的系统调用,使用-e选项加上系统调用名。
例如,跟踪进程727的epoll_wait系统调用:strace -e epoll_wait -p 727
五、常用的request
1、PTRACE_TRACEME
进程设置这个request目的是让自己被父进程跟踪。任何发送到该子进程的信号(除了SIGKILL)都会导致他停下来,并在父进程wait()的时候通知到父进程。另外,子进程后续调用exec会导致子进程自己收到一个SIGTRAP信号,这是为了让父进程有机会在exec的新程序开始执行前获得控制权。
除非一个进程知道父进程要跟踪他,一般不会去设置这个request。设置这个请求时,pid,addr和data三个参数都会被忽略。
这个request只供子进程设置,其他的request都是只供父进程使用的。相应的,下面的request中,参数pid为被跟踪的子进程的pid。
2、PTRACE_ATTACH
将pid指定的进程作为自己要跟踪的进程,并开始跟踪。这和子进程自己调用PTRACE_TRACEME的效果相同。
设置这个request时,子进程会首先收到一个SIGSTOP信号,但并不会停止,只是导致跟踪者进程第一次被中断,从而开始跟踪,否则只能等到子进程接收到第一个信号时才开始跟踪。之后当子进程有待决信号时,进程总是会暂停,这时父进程通过SIGCHLD信号得到通知。在子进程暂停前,父进程可使用wait函数等待。
参数addr和data会被忽略。
3、PTRACE_CONT
让被停掉的子进程继续运行,而当子进程再次接收到信号时会暂停。
参数data如果被设置为一个非零值并且不是SIGSTOP,那data就是父进程发送给子进程的信号,否则,不给子进程发送信号。这样一来,父进程可以控制是否向子进程发送一个信号。
参数addr会被忽略。
4、PTRACE_SYSCALL
让停止的子进程继续运行(同PTRACE_CONT),而当子进程再次接收到信号时会暂停,另外,在子进程中发生系统调用时,在系统调用的入口和结束时子进程也会停止,这时父进程认为子进程是因为收到SIGTRAP信号而停止的。
由于子进程在系统调用的入口和结束时都会停止,父进程就可以在系统调用入口处停止后,获得系统调用的参数信息,而在系统调用结束时停止后,获得系统调用的返回值。
参数addr会被忽略。
5、PTRACE_DETACH
让停止的子进程继续运行(同PTRACE_CONT),但是在这之前会先与跟踪它的父进程解除PTRACE_ATTACH或PTRACE_TRACEME时的关系。
参数addr会被忽略。
6、PTRACE_KILL
给子进程发送一个SIGKILL信号来终止子进程。addr和data参数会被忽略。
7、PTRACE_PEEKTEXT,PTRACE_PEEKDATA
读取进程地址空间中addr地址处的内容,读出的长度为一个word(4字节),作为ptrace()的返回值(long型)返回。Linux中的代码和数据的地址空间并不是分离的,所以这两个request实际上意义相同。
参数data会被忽略。
8、PTRACE_PEEKUSR
在进程的USER区域读取一个word的长度。参数addr是指相对USER开头的offset,结果作为返回值。参数data会被忽略。
在mips中的进程自身信息和进程地址空间中并没有所谓的USER区域,在内核中通过参数addr的值,判断应该返回什么结果
主机管理+堡垒机系统开发:strace工具的实现原理(七)的更多相关文章
- python 之路,Day27 - 主机管理+堡垒机系统开发
python 之路,Day27 - 主机管理+堡垒机系统开发 本节内容 需求讨论 构架设计 表结构设计 程序开发 1.需求讨论 实现对用户的权限管理,能访问哪些机器,在被访问的机器上有哪些权限 实 ...
- 主机管理+堡垒机系统开发:webssh(十)
一.安装shellinabox 1.安装依赖工具 yum install git openssl-devel pam-devel zlib-devel autoconf automake libtoo ...
- 主机管理+堡垒机系统开发:strace命令用法详解(六)
一.简单介绍 strace是什么? 按照strace官网的描述, strace是一个可用于诊断.调试和教学的Linux用户空间跟踪器.我们用它来监控用户空间进程和内核的交互,比如系统调用.信号传递.进 ...
- 主机管理+堡垒机系统开发:strace命令及日志解析(五)
一.strace命令简介 测试命令截图 第一个窗口执行命令如下 [root@elk ~]# w 16:51:56 up 3 days, 6:01, 3 users, load average: 0.0 ...
- day28 CRM万能权限组件开发 && 主机管理-堡垒机
1,CRM项目实战-万能权限组件开发参考博客:http://www.cnblogs.com/alex3714/articles/6661911.html 参考代码:https://github.com ...
- day29 主机管理-堡垒机2-原生ssh会话记录
day29课堂代码:https://github.com/liyongsan/git_class/tree/master/day29 课堂笔记: 通过原生Ssh 记录会话1. 在我们自己的堡垒机交互脚 ...
- day30 主机管理-堡垒机3-操作记录
课堂代码:https://github.com/liyongsan/git_class/tree/master/day30
- 简单易用的堡垒机系统—Teleport
简单易用的堡垒机系统-Teleport 官方文档:http://teleport.eomsoft.net/doc#!1 一.Teleport介绍 Teleport是触维软件推出的一款简单易用的堡垒机 ...
- strace工具的实现原理【转】
转自:http://blog.csdn.net/jasonchen_gbd/article/details/44044539 版权声明:本文为博主原创文章,转载请附上原博链接. 目录(?)[-] ...
随机推荐
- c/c++ llinux epoll系列4 利用epoll_wait实现非阻塞的connect
llinux epoll系列4 利用epoll_wait实现非阻塞的connect connect函数是阻塞的,而且不能设置connect函数的timeout时间,所以一旦阻塞太长时间,影响用户的体验 ...
- iOS MVVM架构总结
为什么使用MVVM iOS中,我们使用的大部分都是MVC架构.虽然MVC的层次明确,但是由于功能日益的增加.代码的维护,使得更多的代码被写在了Controller中,这样Controller就显得非常 ...
- 自动化测试之路3-selenium3+python3环境搭建
1.首先安装火狐浏览器 有单独文章分享怎么安装 2.搭建python环境 安装python,安装的时候把path选好,就不用自己在配置,安装方法有单独文档分享 安装好以后cmd打开输入python查 ...
- LeetCode算法题-Convert BST to Greater Tree(Java实现)
这是悦乐书的第255次更新,第268篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第122题(顺位题号是538).给定二进制搜索树(BST),将其转换为更大树,使原始BS ...
- jest 自动化测试
概述 jest 是 facebook 开源的,用来进行单元测试的框架,可以测试 javascipt 和 react. 单元测试各种好处已经被说烂了,这里就不多扯了.重点要说的是,使用 jest, 可以 ...
- Interrupt中断线程注意点
首先我们要明确,线程中断并不会使线程立即退出,而是发送一个通知,告知目标线程你该退出了,但是后面如何处理,则完全有目标线程自行决定. 这就是和stop()不一样的地方,stop执行后线程会立即终止,这 ...
- k8s--如何使用Namespaces
Namespaces 使用示例 Viewing namespaces Creating a new namespace Deleting a namespace Subdividing your cl ...
- call()与apply()区别typeof和instanceof的区别
摘自 http://www.cnblogs.com/qzsonline/archive/2013/03/05/2944367.html 一.方法的定义 call方法: 语法:call(thisObj, ...
- mybatis 错误
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.TooManyR ...
- 前端——JavaScript
何谓JavaScript?它与Java有什么关系? JavaScript与HTML.CSS组合使用应用于前端开发,JavaScript是一门独立的语言,浏览器内置了JS的解释器.它除了和Java名字长 ...