一.Strace简介
strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。
strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。
二.Strace的使用
1.系统调用
既然strace是用来跟踪用户空间进程的系统调用和信号的,在进入strace使用的主题之前,我们的先理解什么是系统调用。
系统调用提供用户程序与操作系统之间的接口。操作系统的进程空间分为用户空间和内核空间:
1.1操作系统内核直接运行在硬件上,提供设备管理、内存管理、任务调度等功能。
2.1用户空间通过API请求内核空间的服务来完成其功能——内核提供给用户空间的这些API, 就是系统调用。
在Linux系统上,应用代码通过glibc库封装的函数,间接使用系统调用。
Linux内核目前有300多个系统调用,详细的列表可以通过syscalls手册页查看。这些系统调用主要分为几类:
文件和设备访问类 比如open/close/read/write/chmod等
进程管理类 fork/clone/execve/exit/getpid等
信号类 signal/sigaction/kill 等
内存管理 brk/mmap/mlock等
进程间通信IPC shmget/semget * 信号量,共享内存,消息队列等
网络通信 socket/connect/sendto/sendmsg 等
2.strace有两种运行模式
2.1 通过它启动要跟踪的进程。用法很简单,在原本的命令前加上strace即可。比如我们要跟踪 “ls -lh /var/log/messages” 这个命令的执行,可以这样:
strace ls -lh /var/log/messages
2.2 一种运行模式,是跟踪已经在运行的进程,在不中断进程执行的情况下,理解它在干嘛。 这种情况,给strace传递个-p pid 选项即可。
strace常用选项:
从一个示例命令来看:
strace -tt -T -v -f -e trace=file -o /data/log/strace.log -s 1024 -p 23489
-tt 在每行输出的前面,显示毫秒级别的时间
-T 显示每次系统调用所花费的时间
-v 对于某些相关调用,把完整的环境变量,文件stat结构等打出来。
-f 跟踪目标进程,以及目标进程创建的所有子进程
-e 控制要跟踪的事件和跟踪行为,比如指定要跟踪的系统调用名称
-o 把strace的输出单独写到指定的文件
-s 当系统调用的某个参数是字符串时,最多输出指定长度的内容,默认是32个字节
-p 指定要跟踪的进程pid, 要同时跟踪多个pid, 重复多次-p选项即可。
3.实例运用
3.0 通用用法:
strace -o output.txt -T -tt -e trace=all -p pidnum
上面的含义是 跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
3.1 跟踪nginx, 看其启动时都访问了哪些文件:
strace -tt -T -f -e trace=file -o /data/log/strace.log -s 1024 ./nginx
3.2 strace的-e trace选项
要跟踪某个具体的系统调用,-e trace=xxx即可.
-e trace=file 跟踪和文件访问相关的调用(参数中有文件名)
-e trace=process 和进程管理相关的调用,比如fork/exec/exit_group
-e trace=network 和网络通信相关的调用,比如socket/sendto/connect
-e trace=signal 信号发送和处理相关,比如kill/sigaction
-e trace=desc 和文件描述符相关,比如write/read/select/epoll等
-e trace=ipc 进程见同学相关,比如shmget等
3.3 定位进程异常退出
tracetest.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
exit(1);
}
gcc tracetest.c -o tracetest
strace -C -o strace.log -tt ./tracetest
strace.log
13:30:39.028519 execve("./tracetest", ["./tracetest"], 0x7ffc3f4e8478 /* 95 vars */) = 0
13:30:39.029097 brk(NULL) = 0x5629ccc7e000
13:30:39.029158 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
13:30:39.029219 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
13:30:39.029270 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
13:30:39.029317 fstat(3, {st_mode=S_IFREG|0644, st_size=121229, ...}) = 0
13:30:39.029360 mmap(NULL, 121229, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f47161c4000
13:30:39.029404 close(3) = 0
13:30:39.029445 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
13:30:39.029492 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
13:30:39.029536 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
13:30:39.029579 fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
13:30:39.029619 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f47161c2000
13:30:39.029663 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4715bc8000
13:30:39.029702 mprotect(0x7f4715daf000, 2097152, PROT_NONE) = 0
13:30:39.029746 mmap(0x7f4715faf000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f4715faf000
13:30:39.029795 mmap(0x7f4715fb5000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4715fb5000
13:30:39.029841 close(3) = 0
13:30:39.029895 arch_prctl(ARCH_SET_FS, 0x7f47161c34c0) = 0
13:30:39.029993 mprotect(0x7f4715faf000, 16384, PROT_READ) = 0
13:30:39.030038 mprotect(0x5629cbbf6000, 4096, PROT_READ) = 0
13:30:39.030080 mprotect(0x7f47161e2000, 4096, PROT_READ) = 0
13:30:39.030120 munmap(0x7f47161c4000, 121229) = 0
13:30:39.030219 exit_group(1) = ?
13:30:39.030308 +++ exited with 1 +++
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
0.00 0.000000 0 1 read
0.00 0.000000 0 2 close
0.00 0.000000 0 2 fstat
0.00 0.000000 0 5 mmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 1 brk
0.00 0.000000 0 3 3 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
0.00 0.000000 0 2 openat
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 23 3 total
//每一行都是一条系统调用,等号左边是系统调用的函数名及其参数,右边是该调用的返回值。
可以看出,进程自己退出时(调用exit函数,或者从main函数返回), 最终调用的是exit_group系统调用, 并且strace会输出exited with X(X为退出码)。
可能有人会疑惑,代码里面明明调用的是exit, 怎么显示为exit_group?
这是因为这里的exit函数不是系统调用,而是glibc库提供的一个函数,exit函数的调用最终会转化为exit_group系统调用,它会退出当前进程的所有线程.
3.4 追踪系统调用
straceSystemCall.c
#include <stdio.h>
int main() {
int a = 0;
printf("please input:\n");
scanf("%d", &a);
printf("%9d\n", a);
return 0;
}
gcc straceSystemCall.c -o straceSystemCall
strace ./ straceSystemCall
danny@danny:~/Learing$ strace ./straceSystemCall
execve("./straceSystemCall", ["./straceSystemCall"], 0x7ffe2511bd60 /* 95 vars */) = 0
brk(NULL) = 0x558e5e6e7000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=121229, ...}) = 0
mmap(NULL, 121229, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6563ae8000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/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\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6563ae6000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f65634ec000
mprotect(0x7f65636d3000, 2097152, PROT_NONE) = 0
mmap(0x7f65638d3000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f65638d3000
mmap(0x7f65638d9000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f65638d9000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f6563ae7500) = 0
mprotect(0x7f65638d3000, 16384, PROT_READ) = 0
mprotect(0x558e5ca60000, 4096, PROT_READ) = 0
mprotect(0x7f6563b06000, 4096, PROT_READ) = 0
munmap(0x7f6563ae8000, 121229) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
brk(NULL) = 0x558e5e6e7000
brk(0x558e5e708000) = 0x558e5e708000
write(1, "please input:\n", 14please input:
) = 14
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
//运行straceSystemCall程序,会在这里等待输入
//接下来killall straceSystemCall
//当前程序会收到SIGTERM.
read(0, 0x558e5e6e7670, 1024) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
read(0, 0x558e5e6e7670, 1024) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=9030, si_uid=1000} ---
+++ killed by SIGTERM +++
Terminated
三.strace参数
strace参数
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.
-d 输出strace关于标准错误的调试信息.
-f 跟踪由fork调用所产生的子进程.
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
-h 输出简要的帮助信息.
-i 输出系统调用的入口指针.
-q 禁止输出关于脱离的消息.
-r 打印出相对时间关于,,每一个系统调用.
-t 在输出中的每一行前加上时间信息.
-tt 在输出中的每一行前加上时间信息,微秒级.
-ttt 微秒级输出,以秒了表示时间.
-T 显示每一调用所耗的时间.
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.
-V 输出strace的版本信息.
-x 以十六进制形式输出非标准字符串
-xx 所有字符串以十六进制形式输出.
-a column
设置返回值的输出位置.默认 为40.
-e expr
指定一个表达式,用来控制如何跟踪.格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.
注意有些shell使用!来执行历史记录里的命令,所以要使用\\.
-e trace=
只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.
-e trace=file
只跟踪有关文件操作的系统调用.
-e trace=process
只跟踪有关进程控制的系统调用.
-e trace=network
跟踪与网络有关的所有系统调用.
-e strace=signal
跟踪所有与系统信号有关的 系统调用
-e trace=ipc
跟踪所有与进程通讯有关的系统调用
-e abbrev=
设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.
-e raw=
将指 定的系统调用的参数以十六进制显示.
-e signal=
指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.
-e read=
输出从指定文件中读出 的数据.例如:
-e read=,
-e write=
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令