Linux进程照妖镜strace命令
一、工具简介
strace
是个功能强大的Linux调试分析诊断工具,可用于跟踪程序执行时进程系统调用(system call)和所接收的信号,尤其是针对源码不可读或源码无法再编译的程序。
在Linux系统中,用户进程不能直接访问计算机硬件设备。当进程需要访问硬件设备(如读取磁盘文件或接收网络数据等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。
strace
可跟踪进程产生的系统调用,包括参数、返回值和执行所消耗的时间。若strace
没有任何输出,并不代表此时进程发生阻塞;也可能程序进程正在执行某些不需要与系统其它部分发生通信的事情。strace
从内核接收信息,且无需以任何特殊方式来构建内核。
二、strace工具编译
1. 下载源码包
2. 配置工具链
./configure CC=*** LD=***
3. 编译
make LDFLAGS+='-static -pthread'
三、strace命令的使用
1.我们先写一个简单的测试程序,源码如下
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
char buffer[256];
getcwd(buffer, 255);
printf(buffer);
return 0;
}
2.将上面源码保存为test.c
文件,然后执行gcc test.c
命令进行编译,编译后生成a.out
可执行文件
gcc test.c
3.执行./a.out
命令输出如下,测试程序仅仅获取当前路径进行输出
/home/devl
4.执行strace ./a.out
命令输出的最后几行如下
getcwd("/home/devl", 255) = 11
fstat(1, {st_mode=S_IFCHR|0620, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, ...) = 0x7f7f0a8b2000
write(1, "/home/devl", 10/home/devl) = 10
exit_group(0) = ?
+++ exited with 0 +++
可以看到a.out
调用了getcwd
函数,传入可用长度为255的缓冲区,将11字节(包含字符串结束标志)的/home/devl
内容复制到缓冲区中,然后调用write
函数将10个字符的/home/devl
写入编号为1
的文件中(编号为文件实际上就是标准输出设备stdout
)。从上面输出内容中可以看出C语言库函数printf
实际上会调用以下三个系统调用:
fstat
检查文件状态mmap
将文件地址映射到内存空间write
写入文件内容
我们将上面的代码最后多加几个printf
函数,调整后的代码如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
char buffer[256];
getcwd(buffer, 255);
printf(buffer);
printf(buffer);
printf(buffer);
return 0;
}
保存调整后的代码,依次执行gcc test.c
与strace ./a.out
输出内容如下:
munmap(0x7f3dcf146000, 47893) = 0
getcwd("/home/devl", 255) = 11
fstat(1, {st_mode=S_IFCHR|0620, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, ...) = 0x7f3dcf151000
write(1, "/home/devl/home/devl/home/devl", 30/home/devl/home/devl/home/devl) = 30
exit_group(0) = ?
+++ exited with 0 +++
很明显程序中系统调用没有任何变化,1个pirntf
函数与3个printf
函数的系统调用完全相同,说明printf
函数在对连续打印进行了优化。这个优化是依赖mmap
函数的,可以断定这种情况下调用printf
函数只是将内存拷贝到输出缓冲区中,最后一次性调用write
函数输出全部缓冲区的内容。
我们不防再做一个实验,在上面的buffer
后加上一个换行符,调整后的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char** argv)
{
char buffer[256];
getcwd(buffer, 255);
strcat(buffer, "\n");
printf(buffer);
printf(buffer);
printf(buffer);
return 0;
}
保存调整后的代码,依次执行gcc test.c
与strace ./a.out
输出内容如下:
getcwd("/home/devl", 255) = 11
fstat(1, {st_mode=S_IFCHR|0620, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, ...)= 0 x7f947d400000
write(1, "/home/devl\n", 11/home/devl
) = 11
write(1, "/home/devl\n", 11/home/devl
) = 11
write(1, "/home/devl\n", 11/home/devl
) = 11
exit_group(0) = ?
+++ exited with 0 +++
结果非常有意思,这次竟然调用3次write
函数。实际上printf
函数在遇到换行符\n
时会刷新输出缓冲区,我们可以大概猜到刷新输出缓冲区实际上就是执行write
函数将缓冲区内容写入到具体文件或设备。
四、常用命令
strace command
执行名称为command的命令或程序并跟踪系统调用
strace -p procid
跟踪ID为的procid的进程系统调用情况
strace -c -p procid
统计ID为的procid的进程系统调用次数与用时,按CTRL+C
结束统计,执行结果如下:
[work@xungen ~]$ strace -c -p 27637
strace: Process 27637 attached
strace: Process 27637 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.26 0.091733 30578 3 accept
0.73 0.000671 224 3 epoll_ctl
0.01 0.000010 2 6 setsockopt
------ ----------- ----------- --------- --------- ----------------
100.00 0.092414 12 total