背景:最近在看Linux手册,记录一下strace的相关参数,strace用于诊断Linux进程的挂起、异常退出、长时间未完成、未期待的行为,可以输出这个进程的每一次的系统调用syscall或信号。系统调用会从用户模式到内核模式,在linux系统中使用glibc库。
如果你想知道哪些glibc库的函数被调用了,可以使用ltrace命令进行查看,而strace用于查看的是系统调用,本篇文章只关注于系统调用。
命令行工具 -> glibc库 -> 系统调用
什么是系统调用?
系统调用(system call或syscall)是一个程序有计划的向操作系统请求服务的一种方式,可能包括了硬件相关的一些服务,例如:硬盘驱动或硬件镜头,以及新进程的创建和执行、与进程调度进行通信。系统调用在程序和操作系统之间提供了必不可少的接口。
Linux系统一般有超过300个系统调用,例如open,read,write,close,wait,exec,fork,exit,kill等。
通过strace来监视程序在做什么或调用什么系统调用函数,例如:程序被阻塞可能在等待一些资源,而这些资源暂时没准备好(1、试着去打开TCP socket,等待服务器响应;2、已经打开了pipe或TCP socket,但是没有数据可以读取;3、程序试着去锁一个资源,而这个资源已经被锁,程序在等待释放),即 死锁或无响应; 另外一个是程序进入无线循环导致CPU一直运行,即运行变慢,可以指定 -T来查看哪一个系统调用是最耗时的。
$ sudo apt install strace
其他略
strace的一些选项:
-o? ? 将输出记录重定向到文件中,strace默认是输出到屏幕即stdout
? ? ? ? -o? /home/ubuntu/trace.log
-v? ? ?显示冗余的信息(默认情况是strace不会包括所有的系统调用的信息,使用-v来显示冗余信
? ? ? ? 息),并将所有的系统调用参数显示出来。
-f? ? ?可以追踪这个程序运行进程的子进程
-c? ? ?统计各种系统调用的时间占比、某个系统调用的时间和、平均调用的时间、次数
-e? ? ?选择某个系统调用或某几个系统调用进行输出
? ? ? ? ?-e trace=open,close,read,write 或? -e trace=all
? ? ? ? ?-e write,getdents
-p? ? ?选择现有的进程进行追踪
-t? ? ? 在每一行的头部,输出各种系统调用的执行时间,即clock时间,例如:在14:24:47 execve(...)
-tt? ? ?同上,只是多了显示微妙,时间显示更加精确。
-r? ? ? 在每一行的头部,按照系统调用执行的顺序,相对的启动时间戳relative timestamp
? ? ? ? 其实和上面的-t类似,一个显示绝对时间,另一个显示相对时间。
-i? ? ? ?打印指令的指针地址
-T? ? ? 在每一行的尾部,显示每一个系统调用所耗的时间
-s num 每一行的可打印出的字符串大小,默认是32个字符,如果想输出256,则 -s 256
-yy? ? 打印文件描述符相关的所有信息
系统调用主要分为四种类型:
1. 进程管理类的系统调用
2. 文件管理类的系统调用
3. 目录和文件系统管理类的系统调用
4. 其他系统调用
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
其实并不需要记住所有的系统调用的功能,可以去查询手册去参考,可以使用man手册
# man man
分为四类:
1. 可执行程序或shell命令
2. 系统调用(由内核提供的函数)
3. 库函数调用(由库函数提供)
4. 特殊的文件(一般是在/dev)
# man 2 execve? ? ? ? // 去查看execve相关的文档
# man 2 stat? ? ? ? ? ? ? // 去查看 stat相关的文档
# man 2 openat? ? ? ? ?// 去查看 openat相关的文档
# man 2 getdents? ? ? ?// 去查看getdents相关的文档,get directory entries
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
这个openat返回的3是一个文件描述符3,是一个指针或句柄。
write系统调用:
write(1, "file1 file2\n", 13) = 13
这里的1代表 标准输出.
0? -? standard input
1? -? standard output
2? -? standard error
当一个程序运行时候,这3个描述符是默认打开的。
现在就明白了如何追踪一个命令是如何执行的,使用了什么系统调用,我们可以使用以上的知识来分析某个程序的执行过程和使用的系统调用。
我们使用两个命令行窗口,一个执行cat命令,另一个通过 strace -p PID去查看相关的调用
第一步:在第一个命令行窗口输入cat
在命令行输入cat并回车,程序等待输入文字。
第二步,在第二个命令窗口查询cat进程号并进行跟踪:
我们查看cat的进程号128249,直接使用strace进行追踪。
第三步:在第一个窗口输入字符串 China并回车
第四步:在第二个窗口查看追踪的信息,如下图所示。
我们可以看到,通过read标准输入(标准输入standard input为0)的字符串China,并在标准输出(标准输出standard output 1)进行输出,并等待下一次的输入,我们在第一个窗口使用<C-c>退出输入。
代码为
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello, world\n");
return 0;
}
# gcc -o test.out test.c
# strace ./test.out
系统调用write会在显示出来,而write函数的原型为:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd代表文件描述符,buf代表某种类型的指针或字符串指针,count代表输出的字符数。
但是为什么这块会换行呢? 并且为什么在13后面多余一个 Hello,world呢?
因为strace首先会的输出被调用的系统调用的函数名称和相关参数,然后系统调用真实的执行。此时strace的输出和程序的输出混在了一起在同一个终端进行了显示,先显示 write及其函数参数,然后执行,程序的输出Hello,world\n,此时正好换行,并返回13即ssize_t。
参考文献:
https://opensource.com/article/19/10/strace