【调试技巧】strace神器的使用方法详解与实践

文章目录

    • 1. Strace命令简介
    • 2. 什么是系统调用
    • 3. Strace的参数
    • 4. Strace的基本用法
    • 5. Strace实战示例
      • 5.1 跟踪特定的系统调用
      • 5.2 跟踪正在执行的程序
      • 5.3 将strace的输出保存到文件中
      • 5.4 打印系统调用摘要
      • 5.5 跟踪自己的程序
    • 6. 总结

1. Strace命令简介

strace命令是一个集诊断调试统计于一体的工具,我们可以用它来监控用户空间进程和内核的交互。比如对应用程序的系统调用信号传递进程状态变更等进行跟踪与分析,以达到解决问题的目的。

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

2. 什么是系统调用

系统调用就是操作系统提供给用户编程时的一些公共子程序,一般为函数或方法。程序可以通过该这些函数或方法向系统内核请求服务

例如,当用户执行read, write, kill, exit等命令时,他们就在进行系统调用。程序会使用各种各样的系统调用来执行各种任务,例如,联网,读写文件,初始化和终止进程等等。

也可以将系统调用看作函数,区别仅在于,系统调用由操作系统提供,系统调用的代码执行时会使CPU的状态由用户态转为核心态,程序运行于内核态。而普通的函数调用由函数库或用户自己提供,由编译链接工具(如gcc)链入用户程序**,库函数的执行不会引起CPU状态的变化,程序运行于用户态**。

3. Strace的参数

在ubuntu下运行 strace -h 命令就可以看到strace命令用法的介绍。

zhongyi@ubuntu:~$ strace -h
usage: strace [-CdffhiqrtttTvVwxxy] [-I n] [-e expr]...
              [-a column] [-o file] [-s strsize] [-P path]...
              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]
   or: strace -c[dfw] [-I n] [-e expr]... [-O overhead] [-S sortby]
              -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]

以下是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=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
输出从指定文件中读出 的数据.例如:
-e read=3,5
-e write=set
输出写入到指定文件中的数据.
-o filename
将strace的输出写入文件filename
-p pid
跟踪指定的进程pid.
-s strsize
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.
-u username
以username 的UID和GID执行被跟踪的命令

4. Strace的基本用法

让我们先看一下strace的基本用法。在下面的命令中,使用strace跟踪cp命令,看看它在复制文件时做了那些事情。

zhongyi@ubuntu:~$ strace cp ~/.bashrc bashrc
execve("/bin/cp", ["cp", "/home/zhongyi/.bashrc", "bashrc"], 0x7ffcc7ed9f60 /* 50 vars */) = 0

strace输出中的每一行都包含:系统调用名称括号中传递给系统调用的参数系统调用返回值

输出中看到的第一个系统调用是execve,该调用用于执行带有指定参数数组的程序。

  • "/bin/cp"表示我们要执行的文件的路径。

  • ["cp", "/home/zhongyi/.bashrc", "bashrc"],表示字符串数组,代表要传递给程序的参数,分别对应程序的名称(cp),源路(/home/zhongyi/.bashrc)和目标路径(bashrc)。

  • 0x7ffcc7ed9f60 /* 50 vars */,表示从调用过程继承了46个变量(在execve函数中,环境变量是从外部environ变量获取的)

复制完成后,execve返回0,代表execve系统调用的返回值。发生错误时,返回非0。

5. Strace实战示例

5.1 跟踪特定的系统调用

在使用strace时,有时,我们可能只希望跟踪特定的系统调用。在这种情况下,我们可以使用-e选项后跟一个表达式,该表达式指示应跟踪的系统调用。假设我们运行与上一个示例相同的命令,但是,我们只希望read系统调用显示在输出中。

zhongyi@ubuntu:~$ strace -e read cp ~/.bashrc bashrc
..............省略若干行......
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20b\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\33\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\20\0\0\0\0\0\0"..., 832) = 832
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
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0 \25\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\16\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0000b\0\0\0\0\0\0"..., 832) = 832
read(3, "nodev\tsysfs\nnodev\ttmpfs\nnodev\tbd"..., 1024) = 450
read(3, "", 1024)                       = 0
read(3, "# ~/.bashrc: executed by bash(1)"..., 131072) = 3771
read(3, "", 131072)                     = 0
+++ exited with 0 +++

read系统调用有三个参数:

  • 3,表示文件描述符,与读取的文件相关联。

  • "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20b\0\0\0\0\0\0"...,表示应读取文件的缓冲区。

  • 832,表示应读取的字节数。

读取成功后,该函数将返回从文件读取的字节数。

5.2 跟踪正在执行的程序

strace可以跟踪正在执行的程序。使用方法为 strace -p pid。要找到程序的pid,可以使用pidof命令,例如

zhongyi@ubuntu:~$ pidof gnome-terminal-server
13075

gnome-terminal 为终端进程

得到gnome-terminal-server的pid为13075。使用strace -p 13075命令实时跟踪终端进程执行时的系统调用,打印出的内容如下所示。使用Ctrl+C可以结束跟踪。

zhongyi@ubuntu:~$ strace: Process 13075 attached
..............省略若干行......
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
write(4, "\1\0\0\0\0\0\0\0", 8)         = 8
recvmsg(3, {
     msg_name=NULL, msg_namelen=0, msg_iov=[{
     iov_base="\241 \t\f\6\0\200\2\265\1\0\0\316\1\0\0\0\0\0\0\265\2427\242\5\0\0\0\0\0\0\0"..., iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 4096
recvfrom(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\0\0\0\0"..., 48, 0, NULL, NULL) = 48
recvmsg(3, {
     msg_name=NULL, msg_namelen=0, msg_iov=[{
     iov_base="#\204\t\f\32\0\0\0\6\0\2\0\3447q\1\0\0\0\0U\2\0\0\6\0\200\2\0\0\0\0"..., iov_len=4096}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 1768
recvmsg(3, {
     msg_namelen=0}, 0)          = -1 EAGAIN (Resource temporarily unavailable)
poll([{
     fd=3, events=POLLIN}, {
     fd=4, events=POLLIN}, {
     fd=5, events=POLLIN}], 3, 0) = 1 ([{
     fd=4, revents=POLLIN}])
strace: Process 13075 detached

5.3 将strace的输出保存到文件中

如果在启动strace时使用-o选项(的缩写--ouput),则可以将其输出重定向到文件,例如:

zhongyi@ubuntu:~$ strace -p 13075 -o strace_output
strace: Process 13075 attached

执行命令后,会自动创建一个strace_output文件,并将strace的输出写进文件中。使用tail strace_output 可以查看文件的最后10行的内容。使用tail -f strace_output会在屏幕上实时更新写进文件的内容。

5.4 打印系统调用摘要

strace还可以显示指定进程的所有系统调用的摘要信息。例如:

strace -c cp ~/.bashrc bashrc

上面的命令将生成如下报告。

zhongyi@ubuntu:~$ strace -c cp ~/.bashrc bashrc
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 18.22    0.001218          53        23           mmap
 16.11    0.001077          72        15           close
 15.48    0.001035          65        16           mprotect
  9.41    0.000629          52        12           openat
  6.97    0.000466          42        11           read
  6.76    0.000452          38        12           fstat
  4.38    0.000293          29        10        10 access
  3.99    0.000267          89         3           stat
  2.35    0.000157          79         2         2 statfs
  2.21    0.000148          74         2           munmap
  2.06    0.000138          46         3           brk
  2.03    0.000136         136         1           write
  1.99    0.000133          67         2           rt_sigaction
  1.06    0.000071          71         1           arch_prctl
  1.02    0.000068          68         1           geteuid
  1.00    0.000067          67         1           prlimit64
  0.99    0.000066          66         1         1 lseek
  0.99    0.000066          66         1           rt_sigprocmask
  0.99    0.000066          66         1           set_tid_address
  0.99    0.000066          66         1           fadvise64
  0.99    0.000066          66         1           set_robust_list
  0.00    0.000000           0         1           execve
------ ----------- ----------- --------- --------- ----------------
100.00    0.006685                   121        13 total

5.5 跟踪自己的程序

写一个测试程序如下。我们尝试以只读模式打开一个不存在的文件。

#include  
#include  
int main()
{
      
    char str[30]; 
    FILE *fp = fopen("any.txt", "r"); 
    fgets(str, 5, fp); 
    fclose(fp); 
    return 0; 
}

编译运行后,正如我们所料,出现了core dumped。

zhongyi@ubuntu:~/workdir/test$ gcc -Wall test.c -o test
zhongyi@ubuntu:~/workdir/test$ ./test 
Segmentation fault (core dumped)

尝试使用strace跟踪下test文件,看下到底发生了什么。strace文件的输出如下所示,根据输出内容可以分析定位问题所在。

zhongyi@ubuntu:~/workdir/test$ strace ./test 
execve("./test", ["./test"], 0x7ffecbee7d60 /* 51 vars */) = 0
brk(NULL)                               = 0x5621c36a4000
........省略若干行.............
openat(AT_FDCWD, "any.txt", O_RDONLY)   = -1 ENOENT (No such file or directory)
--- SIGSEGV {
     si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=NULL} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

在执行test文件时,进行了很多系统调用。然而,我们只需要关注最后四行即可。

从日志信息可以看出,程序是被SIGSEGV终止的,并且产生了段错误 segmentation fault

ENOENT全称为:Error NO EnTry。openat函数返回值为新的文件描述符,出错时,返回-1。而且,在最后也提醒了,No such file or directory。因此,结合源代码,可以判断是操作了不存在的文件。

SIGSEGV是进程进行了一次无效的内存引用(比如访问了一个未经初始化的指针)。si_signo表示信号值。si_code为信号产生的原因,SEGV_MAPERR表示堆栈映射错误。si_addr表示触发fault的内存地址。

6. 总结

关于strace跟踪进程的讲解就到此结束了。strace常用于内核模块编程和系统编程,特别是与文件有关的错误,段错误等。了解系统级的工作方式,有助于我们调试复杂的问题。因此,strace是极力推荐大家掌握的调试工具。

本文参考

https://blog.csdn.net/guotianqing/article/details/79586639

https://www.cnblogs.com/zhangxuechao/p/11709929.html

https://blog.csdn.net/liuzehn/article/details/80657238

https://blog.csdn.net/thisinnocence/article/details/87827963

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_16933601/article/details/117248806
————————————————
版权声明:本文为CSDN博主「嵌入式与Linux那些事」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16933601/article/details/117248806

你可能感兴趣的:(#,调试技巧,IMX6ULL,驱动开发实战教程,调试,Linux驱动,Linux内核,嵌入式软件,C/C++)