限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
strace
是 Linux
下一款诊断、调试、追踪
工具,它可以用来监控和获取
内核的系统调用、信号投递、进程状态变更
等信息。其基本实现机制是通过 ptrace()
系统调用,来获取内核相关信息。对 strace
实现原理感兴趣的读者,可直接阅读 strace
源码,或参考博文 Linux:系统调用追踪原理简析 。
在程序启动时,可通过 strace
跟踪程序运行期间的所有系统调用。如:
# strace ls
execve("/bin/ls", ["ls"], 0xbeb73e50 /* 12 vars */) = 0
brk(NULL) = 0xc3000
uname({sysname="Linux", nodename="(none)", ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0e000
...
stat64(".", {st_mode=S_IFDIR|0755, st_size=232, ...}) = 0
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
getdents64(3, 0xc30b0 /* 3 entries */, 32768) = 80
lstat64("./tcp_server", {st_mode=S_IFREG|0755, st_size=11192, ...}) = 0
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3)
...
exit_group(0) = ?
+++ exited with 0 +++
上面的输出,每一行显示一个系统调用,包含系统调用名、调用参数、以及返回值
(=
后的内容),譬如输出:
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
表示调用了 open()
,打开的是当前目录 "."
,传递的 flags 参数为 O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY
,返回值为 3
。
首先找到目标进程的 PID:
# ps -ef | grep -v grep | grep InfiniteDsp
160 root ./InfiniteDsp
然后进行追踪:
# strace -p 160
strace: Process 160 attached
read(27, "\1\0\0\0", 4) = 4
[......]
对于调用 fork()
或 clone()
等系列接口的程序,在 3.1.1,3.1.2
基础上,需再加上 -f
或 --follow-forks
命令行参数来进程追踪:
strace -f <program>
strace -fp <PID>
如有一个 PID 为 2622 的多线程程序
,我们用 strace
来追踪它:
# strace -fp 2622
strace: Process 2622 attached with 4 threads
[pid 2627] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2625] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2622] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] <... restart_syscall resumed> ) = 0
从输出看到,PID 为 2622 的进程有 4 个线程
,同时输出每行开头都带上了线程的 PID
,这样就能知道每个系统调用是由哪个线程发起的。
通过 -c
或 -C
命令行参数,统计系统调用消耗的总时间、调用总次数、每次消耗的平均时间、出错总次数
等信息,并在结束追踪时输出这些信息。-c
和 -C
的差别在于:-c
不会在追踪期间输出系统调用情况,而 -C
则相反。看一个简单的例子:
# strace -C -fp 2622
strace: Process 2622 attached with 4 threads
[pid 2626] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2627] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2625] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
[pid 2622] restart_syscall(<... resuming interrupted poll ...> <unfinished ...>
[pid 2626] <... restart_syscall resumed> ) = 0
[......]
^Cstrace: Process 2622 detached
<detached ...>
strace: Process 2625 detached
strace: Process 2626 detached
strace: Process 2627 detached
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
84.95 0.000948 474 2 restart_syscall
8.42 0.000094 47 2 2 inotify_add_watch
6.63 0.000074 25 3 2 recvmsg
------ ----------- ----------- --------- --------- ----------------
100.00 0.001116 7 4 total
有时候并不想追踪程序所有的系统调用,通过下列选项
-e trace=syscall_set
-e t=syscall_set
--trace=syscall_set
指定想追踪
或不想追踪
的系统调用集合。如只想追踪程序的 open()
调用:
# strace -e t=open /bin/ls
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/proc/filesystems", O_RDONLY) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[......]
+++ exited with 0 +++
strace
只输出程序运行过程中所有 open()
调用。如果想追踪多个系统调用,可以用 ,
分隔,如:
# strace -e t=open,read /bin/ls
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260Z\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000\25\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\r\0\0\0\0\0\0"..., 832) = 832
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260`\0\0\0\0\0\0"..., 832) = 832
open("/proc/filesystems", O_RDONLY) = 3
read(3, "nodev\tsysfs\nnodev\trootfs\nnodev\tr"..., 1024) = 429
read(3, "", 1024) = 0
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[......]
+++ exited with 0 +++
通过下列选项:
-e trace-fd=set
-e trace-fds=set
-e fd=set
-e fds=set
追踪对指定文件句柄集合操作的系统调用。如追踪所有对文件句柄 3
的操作:
# strace -e fd=3 /bin/ls
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\235h\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0777, st_size=898752, ...}) = 0
mmap2(NULL, 968040, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6e01000
mmap2(0xb6ee8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xd7000) = 0xb6ee8000
close(3) = 0
getdents64(3, 0xc30b0 /* 14 entries */, 32768) = 456
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3) = 0
[......]
+++ exited with 0 +++
通过下列选项:
-P path
--trace-path=path
追踪指定访问目录的系统调用。
# strace --trace-path=/var /bin/ls /var
stat64("/var", {st_mode=S_IFDIR|0777, st_size=672, ...}) = 0
openat(AT_FDCWD, "/var", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_CLOEXEC|O_DIRECTORY) = 3
getdents64(3, 0xc30b0 /* 10 entries */, 32768) = 256
getdents64(3, 0xc30b0 /* 0 entries */, 32768) = 0
close(3) = 0
[......]
+++ exited with 0 +++
通过下列选项:
-z
--successful-only
只追踪返回成功的系统调用。
通过下列选项:
-Z
--failed-only
只追踪返回错误码的系统调用。
从前面知道,下列选项
-e trace=syscall_set
-e t=syscall_set
--trace=syscall_set
可以指定要追踪的系统调用集合。前面已经示范了通过具体的名字来指定集合,这里介绍通过类别指定系统调用集合
的方式,即 syscall_set
还可以通过如下方式指定:
/regex : 正则表达式
%file, file: 追踪文件操作相关的系统调用(open,close,access,...)
%process, process: 追踪进程操作相关的系统调用(exec,...)
%net, %network, network: 追踪网络操作相关的系统调用(exec,...)
......
选项
-r
--relative-timestamps[=precision]
输出相对时间(相对于程序启动时间):
# strace -r /bin/ls
0.000000 execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 0
0.000589 brk(NULL) = 0x188c000
0.000323 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
0.000285 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000209 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
0.000335 fstat(3, {st_mode=S_IFREG|0644, st_size=100481, ...}) = 0
0.000566 mmap(NULL, 100481, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9a94f30000
0.000304 close(3) = 0
[......]
0.000096 exit_group(0) = ?
0.000101 +++ exited with 0 +++
选项
-t
--absolute-timestamps
输出绝对时间:
# strace -t /bin/ls
11:18:35 execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 0
11:18:35 brk(NULL) = 0x14f0000
11:18:35 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
11:18:35 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
[......]
选项
-T
--syscall-times[=precision]
输出每个系统调用消耗的时间:
# strace -T /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 26 vars */]) = 0 <0.000381>
brk(NULL) = 0x1701000 <0.000126>
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000135>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000094>
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000084>
[......]
选项
-o filename
--output=filename
将追踪结果写入文件。
strace
的更多使用方法可参考其手册 https://www.man7.org/linux/man-pages/man1/strace.1.html 。
先获取 strace
源码(当前版本为 6.6
):
git clone https://gitlab.com/strace/strace.git
交叉编译 strace
源码(假定目标平台为 ARM32
,交叉编译器为 arm-linux-gnueabihf-gcc
):
./bootstrap
CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld RANLIB=arm-linux-gnueabihf-ranlib \
./configure --prefix=$(pwd)/out --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf
make && make install
将在 out
目录下生成 strace
程序:
$ tree out
out
├── bin
│ ├── strace
│ └── strace-log-merge
└── share
└── man
└── man1
├── strace.1
└── strace-log-merge.1
[1] https://strace.io/
[2] https://www.man7.org/linux/man-pages/man1/strace.1.html