本文介绍一下Linux中strace命令的使用。

1. strace跟踪进程中的系统调用

strace常用来跟踪进程执行时的系统调用和所接收的信号。在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数、返回值,执行消耗的时间。

1.1 输出参数含义

每一行都是一条系统调用,等号左边是系统调用的函数名及参数,右边是该调用的返回值。strace显示这些调用的参数并返回符号形式的值。strace从内核接收信息,而且不需要以任何特殊的方式来构建内核。

# strace cat /dev/null
execve("/bin/cat", ["cat", "/dev/null"], [/* 22 vars */]) = 0
brk(0)                                  = 0xab1000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
...

1.2 参数

1) 与输出格式相关的参数

-a column
    设置返回值的输出位置.默认 为40.

-i 输出系统调用的入口指针.

-k 跟踪每一个系统调用的执行栈

-o filename
    将strace的输出写入文件filename

-q 禁止输出关于attach/detach消息.

-r 打印出执行每个系统调用时的相对时间戳

-s strsize
    指定输出的字符串的最大长度(默认为32)。文件名一直全部输出.

-t 时:分:秒
-tt 时:分:秒 . 微秒
-ttt 计算机纪元以来的秒数 . 微秒

-T 显示每一调用所耗的时间,精确到微秒

-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.

2)与统计信息相关的参数

-c 统计每一系统调用的所执行的时间,次数和出错的次数等.

3) 与Filtering相关的参数

-e expr
   一个限定表达式,用于指定跟踪哪些events或者指定如何进行跟踪。格式如下:
   [qualifier=][!][?]value1[,[?]value2]...

   qualifier只能是 trace, abbrev, verbose, raw, signal, read, write, fault, inject, kvm其中之一。
   value是用来限定的符号或数字。默认的 qualifier是 trace。感叹号是否定符号。

   例如: -e open等价于 -e trace=open,表示只跟踪open调用。而-e trace=!open表示跟踪除了open以外的其他调用。

   对于value前面的问号(?),其表示当没有匹配的系统调用时,抑制相应的错误信息输出。

   另外,还有两个特殊的value符号 all 和 none.

  (注意有些shell使用!来执行历史记录里的命令,所以有时要使用\!)

-e trace=set
    只跟踪指定的系统 调用.例如: -e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.

-e trace=%file
-e trace=file (deprecated)
    只跟踪有关文件操作的系统调用。我们可以将此命令看成是-e strace=open,stat,chmod,unlink...等操作的简写形式,
    这有助于我们找出相应进程当前正在引用(reference)哪些文件。

-e trace=%process
-e trace=process (deprecated)
    只跟踪有关进程控制的系统调用。这可用于跟踪fork, wait, exec等系统调用


-e trace=%network
-e trace=network (deprecated)
    跟踪与网络有关的所有系统调用.

-e trace=%signal
-e trace=signal (deprecated)
    跟踪所有与系统信号有关的 系统调用

-e trace=%ipc
-e trace=ipc   (deprecated)
    跟踪所有与进程通讯有关的系统调用

-e trace=%desc
-e trace=desc (deprecated)
    跟踪所有与文件描述符相关的系统调用。


-e trace=%memory
-e trace=memory (deprecated)
    跟踪所有与memory mapping相关的系统调用

-e trace=%stat
    Trace stat syscall variants

-e trace=%lstat
    Trace lstat syscall variants.

-e trace=%fstat
    Trace fstat and fstatat syscall variants.

-e trace=%%stat
    Trace syscalls used for requesting file status (stat, lstat, fstat, fstatat, statx, and their variants).



-e abbrev=set
    设定strace输出的系统调用的结果集,指定哪些系统调用中的大型数组或结构体内容缩减显示,默认为abbrev=all,abbrev=none等价于-v选项。
    如strace -e abbrev=execve ./test仅显示execve调用中argv[]和envp[]的部分内容。

-e raw=set
    指定哪些系统调用中的参数以原始未解码的形式(即16进制)显示。当用户不信任strace解码或需要了解参数实际数值时有用

-e signal=set
    指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.

-e read=set
    以16进制和ASCII码对照形式显示从指定文件描述符中读出的所有数据,如-e read=3,5可观察文件描述符3和5上的输入动作。
    该选项独立于系统调用read的常规跟踪(由-e trace=read选项控制)

-e write=set
    以16进制和ASCII码对照形式显示写入指定文件描述符的所有数据,如-e write=3,5可观察文件描述符3和5上的输出动作。
    该选项独立于系统调用write的常规跟踪(由-e trace=write选项控制)

-P path
   跟踪访问指定path的系统调用。可以使用多个-P选项以指定多个路径

-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.

4) Tracing相关参数

-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.

5) Startup相关参数

-p pid
    跟踪指定的进程pid.

-u username
    以username的UID和GID执行被跟踪的命令

6) Miscellaneous参数

-d 输出strace本身的一些调试信息到标准错误

-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.

-h 输出简要的帮助信息.

-V 输出strace的版本信息.

1.3 命令实例

1) 跟踪可执行程序

# strace -f -F -o ./strace_out.txt myserver

-f -F选项告诉strace同时跟踪fork和vfork出来的进程。-o选项把所有strace输出写入到strace_out.txt文件中,myserver是需要启动的程序。

2) 跟踪服务程序

# strace -o output.txt -T -tt -e trace=all -p 28979

跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。

2. strace工作原理

strace底层用ptrace可以很容易做到,ptrace有一个拦截syscall的功能PTRACE_SYSCALL:

#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid,
            void *addr, void *data);

[ptrace_request]
PTRACE_TRACEME = 0,	           //被调试进程调用
PTRACE_PEEKDATA = 2,	       //查看内存
PTRACE_PEEKUSER = 3,	       //查看struct user 结构体的值
PTRACE_POKEDATA = 5,	       //修改内存
PTRACE_POKEUSER = 6,	       //修改struct user 结构体的值
PTRACE_CONT = 7,		       //让被调试进程继续
PTRACE_SINGLESTEP = 9,	       //让被调试进程执行一条汇编指令
PTRACE_GETREGS = 12,	       //获取一组寄存器(struct user_regs_struct)
PTRACE_SETREGS = 13,	       //修改一组寄存器(struct user_regs_struct)
PTRACE_ATTACH = 16,		       //附加到一个进程
PTRACE_DETACH = 17,		       //解除附加的进程
PTRACE_SYSCALL = 24,	       //让被调试进程在系统调用前和系统调用后暂停 【你需要这个】

PTRACE_SYSCALL 拦截之后,可以用PTRACE_GETREGS获取栈和寄存器的数据,从而获取syscall的详细数据。

新版本 PTRACE_GET_SYSCALL_INFO (since Linux 5.3) ,太新了不推荐,个人还是更推荐直接PTRACE_GETREGS显得专业。

要在linux拦截syscall除了调试器的办法,还有以下两种深入到系统内核的办法:

  • 写一个linux内核module 拦截sys_call_table;

  • 用 linux ebpf 接口。



[参看]:

  1. linux tools

  2. strace工具使用手册

  3. strace工作原理