通过这篇文章你会学习到strace的用法,strace可以帮助你高效地定位进程中的一些错误,关于strace的用处有很多,可以自行发掘
前面我们讲解了gdb调试程序,这篇文章介绍另一个调试跟踪工具strace,同样你可以在linux下执行man strace查看帮助信息
(一)starce是什么
我们直接看man打印的帮助信息
strace - trace system calls and signals
根据上面的描述我们可以知道strace主要是跟踪系统的调用和信号的传递,其实我们还可以用它来监视用户进程和内核的交互,它能通过系统调用来侦测程序运行的详细过程
在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通 过系统调用访问硬件设备。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 | 所有字符串以十六进制形式输出. |
-e expr :指定一个表达式,用来控制如何跟踪,由于格式比较多我们单独拿出来
格式如下:
[qualifier=][!]value1[,value2]...
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.
-e expr | 参数设置 |
---|---|
-e trace=set | 只跟踪指定的系统 调用.例如:-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=set | 设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all. |
-e raw=set | 将指 定的系统调用的参数以十六进制显示. |
-e signal=set | 指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号. |
-e read=set | 输出从指定文件中读出 的数据.例如: |
-o filename | 将strace的输出写入文件filename |
-p pid | 跟踪指定的进程pid. |
-s strsize | 指定输出的字符串的最大长度.默认为32.文件名一直全部输出. |
-u username | 以username 的UID和GID执行被跟踪的命令 |
示例:
(1)根据指定pid进程跟踪
strace -o output.txt -T -tt -e trace=all -p 28979
上面的含义是 跟踪28979进程的所有系统调用(-e trace=all),并统计系统调用的花费时间,以及开始时间(并以可视化的时分秒格式显示),最后将记录结果存在output.txt文件里面。
(2)根据进程名跟踪
strace -o output.txt -T -tt -e trace=all ./a.out
(三)strace的一个分析实例
test.c
#include <stdio.h>
#include <unistd.h>int main(int argc,char * argv[])
{char buff[256]={0};FILE* file=NULL;file=fopen(argv[1],"r");if(file == NULL){printf("fopen error\n");
// return -1;}fread(buff,sizeof(buff),1,file);printf("buff=%s\n",buff);return 0;
}
我们执行:
gcc test.c -o test
./test
上面我们执行时不输入任何参数,肯定会报段错误
fopen error
段错误 (核心已转储)
我们这里使用strace查看信息:
root@lvirtual-machine:~/test# strace -T -tt -e trace=all ./test
22:32:53.349314 execve("./test", ["./test"], [/* 60 vars */]) = 0 <0.000440>
22:32:53.350129 brk(NULL) = 0xb3f000 <0.000097>
22:32:53.350417 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000094>
22:32:53.350769 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000062>
22:32:53.351014 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000127>
22:32:53.351316 fstat(3, {st_mode=S_IFREG|0644, st_size=96373, ...}) = 0 <0.000105>
22:32:53.351585 mmap(NULL, 96373, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0bec216000 <0.000065>
22:32:53.351793 close(3) = 0 <0.000052>
22:32:53.352046 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000068>
22:32:53.352313 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000106>
22:32:53.352597 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 <0.000061>
22:32:53.352789 fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0 <0.000110>
22:32:53.353092 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec215000 <0.000122>
22:32:53.353409 mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0bebc3f000 <0.000117>
22:32:53.353629 mprotect(0x7f0bebdff000, 2097152, PROT_NONE) = 0 <0.000115>
22:32:53.353841 mmap(0x7f0bebfff000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f0bebfff000 <0.000110>
22:32:53.354073 mmap(0x7f0bec005000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0bec005000 <0.000060>
22:32:53.354297 close(3) = 0 <0.000035>
22:32:53.354556 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec214000 <0.000060>
22:32:53.354736 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0bec213000 <0.000045>
22:32:53.354888 arch_prctl(ARCH_SET_FS, 0x7f0bec214700) = 0 <0.000040>
22:32:53.355125 mprotect(0x7f0bebfff000, 16384, PROT_READ) = 0 <0.000051>
22:32:53.355288 mprotect(0x600000, 4096, PROT_READ) = 0 <0.000050>
22:32:53.355487 mprotect(0x7f0bec22e000, 4096, PROT_READ) = 0 <0.000085>
22:32:53.355724 munmap(0x7f0bec216000, 96373) = 0 <0.000094>
22:32:53.356015 brk(NULL) = 0xb3f000 <0.000072>
22:32:53.356203 brk(0xb60000) = 0xb60000 <0.000075>
22:32:53.356426 open(NULL, O_RDONLY) = -1 EFAULT (Bad address) <0.000078>
22:32:53.356664 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 11), ...}) = 0 <0.000065>
22:32:53.356879 write(1, "fopen error\n", 12fopen error
) = 12 <0.000065>
22:32:53.357116 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
22:32:53.542212 +++ killed by SIGSEGV (core dumped) +++
段错误 (核心已转储)
上面输出的信息非常多,但是我们可以找到我们需要的
open(NULL, O_RDONLY) = -1 EFAULT (Bad address)
我们使用starce也就是根据这些系统调用去过滤出我们需要的信息
当我们发现程序运行异常时,我们可以使用strace来跟踪其系统调用,看看这些系统调用有没有异常,进而找到异常的原因。