strace 常用操作

前言

strace 可以用来查看/记录程序运行过程中调用的 系统调用 以及接收到的进程信号(signal), 对于我们日常 debug 疑难杂症非常的有帮助,是一个非常好的 debug 工具。本文简单记录一下 strace 的常用功能和操作。

输出的含义

我们通过一个简单的命令来查看 strace 程序的典型输出:

$ strace ls /tmp/trace
execve("/bin/ls", ["ls", "/tmp/trace"], [/* 41 vars */]) = 0
brk(0)                                  = 0x11af000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0738c16000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=35759, ...}) = 0
mmap(NULL, 35759, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0738c0a000
close(3)                                = 0
open("/lib64/libselinux.so.1", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0PX\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=122040, ...}) = 0
....
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb97a060000
write(1, "test_dir  test.txt\n", 19test_dir  test.txt
)    = 19
close(1)                                = 0
munmap(0x7fb97a060000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

比如下面的这个输出:

execve("/bin/ls", ["ls", "/tmp/trace"], [/* 41 vars */]) = 0

的含义如下:

  • execve: 系统调用的名称。
  • ("/bin/ls", ["ls", "/tmp/trace"], [/* 41 vars */]): 这个括号里是系统调用的参数。
  • = 0 : 这个 0 是系统调用的返回值,不一定是数字看具体的系统调用返回啥结果就是啥值。

上面有些系统调用的返回值比较特殊,比如:

access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)

后面这个 ENOENT (No such file or directory) 中的 ENOENT 是错误码, (No such file or directory) 是错误码的解释。

上面是系统调用相关的输出格式和含义,下面再看一个进程信号(signal)的输出格式:

$ strace -p 15718
Process 15718 attached
select(1, [0], NULL, NULL, NULL)        = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=15757, si_uid=500} ---
+++ killed by SIGTERM +++

其中:

--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=15757, si_uid=500} ---

这一句就是进程接收到的具体 signal 的信息。

常用命令行参数

常用的参数组合:

strace -f -s 1024 -tt -T -yy -p <pid>
strace -f -s 1024 -tt -T -yy -p <pid> -o <filename>
strace -c -p <pid>
strace -f -s 1024 -tt -T -yy -C -p <pid>
  • -p <pid>: 附加到进程中,记录某个进程的系统调用和进程信号信息。多个 -p <pid> 可以实现同时追踪多个进程。

  • -f: 同时追踪子进程的系统调用情况(如果是多线程程序的话,会同时追踪所有线程)。如果不加 -f 参数的话默认只追踪指定的单个进程。

  • -o <filename>: 把输出结果保存到文件中(默认是输出到标准错误)。

  • -ff: 与 -o <filename> 一起使用,会把每个 pid (进程 pid 或线程 id) 的 strace 数据保存到 <filename>.<pid> 的文件中。

  • -e <expr>: 过滤事件,只输出符合规则的事件,可以用来指定只看某些系统调用的情况,比如: -e open,read 。 或者不看某些系统调用: -e '!open,read' ,更强大的过滤功能可以查看 strace(1)

  • -s <size>: 指定字符串数据的大小,默认 32。可以通过 -s 来显示更详细的信息或精简信息(比如很多系统调用的参数的值会被截断,可以通过 -s 配置更大的 size 来查看更详细的参数值)。

  • -tt: 显示系统调用是在哪个时刻调用的,包含微秒( -t 参数一样的效果就是时间不包含微秒):

    $ strace -e open,read -s 2 -tt ls
    14:32:18.877470 read(3, "\177E"..., 832) = 832
    14:32:18.878249 read(3, "\177E"..., 832) = 832
    14:32:18.878867 read(3, "\177E"..., 832) = 832
    14:32:18.879386 read(3, "\177E"..., 832) = 832
    14:32:18.879871 read(3, "\177E"..., 832) = 832
    14:32:18.880923 read(3, "no"..., 1024)  = 420
    14:32:18.881255 read(3, "", 1024)       = 0
    
    上面的第一列时间信息即为 -tt 的效果。
    
  • -T: 显示执行系统调用所花费的时间,单位是秒

    $ strace -e read -s 2 -T pwd
    read(3, "\177E"..., 832)                = 832 <0.000012>
    
    上面行末的 <0.000012> 即为 -T 的效果。
    
  • -y: 输出文件描述符所对应的文件路径

    $ strace -e read -s 10  cat test.txt
    read(3, "\177ELF\2\1\1\3\0\0"..., 832)  = 832
    read(3, "hello\n", 131072)              = 6
    hello
    read(3, "", 131072)                     = 0
    
    $ strace -e read -s 10 -y  cat test.txt
    read(3</lib/x86_64-linux-gnu/libc-2.27.so>, "\177ELF\2\1\1\3\0\0"..., 832) = 832
    read(3</path/to/test.txt>, "hello\n", 131072) = 6
    hello
    read(3</path/to/test.txt>, "", 131072) = 0
    
    可以看到加了 -y 后文件描述符后面会跟它所对应的文件的路径
    
  • -yy: 输出文件描述更详细的信息,比如 socket 文件描述符输出所对应的协议信息。

    $ strace -e connect -s 3  nc baidu.com 80
    ...
    connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
    ...
    
    $ strace -e connect -s 3 -y  nc baidu.com 80
    ...
    connect(3<socket:[22180]>, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
    
    $ strace -e connect -s 3 -yy  nc baidu.com 80
    ...
    connect(3<UDP:[22202]>, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
    
    可以看到 -yy 会显示上面的 UDP 这个协议信息。
    
  • -c: 统计系统调用的次数、种类以及错误信息,会隐藏详细的追踪信息(前面的 -e 过滤参数也可以用于这个):

    $ strace -c cat test.txt
    hello
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     18.45    0.000019           3         7           mmap
     18.45    0.000019           5         4           mprotect
     17.48    0.000018           9         2           munmap
     13.59    0.000014           4         4           openat
      9.71    0.000010          10         1           write
      6.80    0.000007           2         3           read
      5.83    0.000006           2         3         3 access
      3.88    0.000004           1         5           fstat
      2.91    0.000003           1         6           close
      1.94    0.000002           1         3           brk
      0.97    0.000001           1         1           execve
      0.00    0.000000           0         1           arch_prctl
      0.00    0.000000           0         1           fadvise64
    ------ ----------- ----------- --------- --------- ----------------
    100.00    0.000103                    41         3 total
    
  • -C: 跟 -c 类似,只是增加了会输出详细的追踪信息

    $ strace -e read -C -s 3 cat test.txt
    read(3, "\177EL"..., 832)               = 832
    read(3, "hel"..., 131072)               = 6
    hello
    read(3, "", 131072)                     = 0
    +++ exited with 0 +++
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
      0.00    0.000000           0         3           read
    ------ ----------- ----------- --------- --------- ----------------
    100.00    0.000000                     3           total
    
  • -v: 显示系统调用时传入的参数变量的具体内容,而不是默认显示为 /* 8 vars */

常用过滤表达式

-e <expr> 这个参数可以用来指定需要 trace 哪些事件,格式如下:

[qualifier=][!][?]value1[,[?]value2]...

其中 qualifier 的值是 trace, abbrev, verbose, raw,
signal, read, write, fault, inject, or kvm 。默认是 trace

value 就是各种过滤条件了。
  • -e trace=<set> : <set> 是系统调用名称(默认是 trace=all ),比如 trace=open,close,read,write
  • -e trace=/<regex>: 可以通过这种方式来用正则表达式指定系统调用名称,支持的正则语法可以参考 regex(7)
  • -e trace=%file: 文件相关系统调用。
  • -e trace=%desc: 文件描述符相关。
  • -e trace=%process: 进程管理相关系统调用。
  • -e trace=%network: 网络相关。
  • -e trace=%signal: 信号相关。
  • -e trace=%ipc: IPC 相关。
  • -e trace=%memory: 内存 mapping 相关。
  • -e signal=<set>: <set> 是进程信号的名称(默认是 signal=all ),比如 -e signal=SIGTERM
  • -e read=<set>: 追踪指定 fd 上的数据并打印 hex 和 ascii 格式的数据, <set> 是 fd 比如:read=3,5

常见系统调用

更多关于某个系统调用的含义/用途可以通过 man 2 <syscall> (把 <syscall> 替换为实际的系统调用名称,比如 man 2 open )命令行查看或者访问 http://man7.org/linux/man-pages/man2/<syscall>.2.html 这个网址(把 <syscall> 替换为实际的系统调用名称, 比如 http://man7.org/linux/man-pages/man2/open.2.html)亦或是 Google 一下。

总结

更多关于 strace 的信息可以从参考资料的 strace(1) 中获取,如果对输出中系统调用不了解的话可以参考参考资料中的 syscalls(2) 中的信息,同时也别忘了搜索引擎是你的好朋友,有啥不明白的记得 Google 一下。


Comments