strace命令的使用
本文介绍一下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 接口。
[参看]:

