使用strace工具故障排查的5种简单方法

本文源自5 simple ways to troubleshoot using strace

strace 是一个非常简单的工具,用来跟踪可执行程序的系统调用(system call)。最简单的使用是,它追踪可行程序运行时的整个生命周期,输出每一个系统调用的名字,参数和返回值。 
但是它还可以做更多的事情:

  1. 它可以基于系统调用或者系统调用组来过滤
  2. 它可以通过计算制定系统调用的次数,花费的时间以及成功和失败的次数来描述系统调用的使用
  3. 它可以追踪发送给进程的信号(signal)
  4. 它可以通过进程id(pid)号加入到任意正在运行的进程上

如何使用

这里只是简单的描述strace如何使用,并不打算对此做深入分析

  1. 找出一个程序启动时读取了哪个配置文件

    有的时候,你发发现,无论你如何修改配置文件,应用程序并没有按照你的思路去运行,这是什么原因?一个浅显但容易忽视的考虑是,应用程序启动时读取了你认为要读取的配置文件了吗?看下面的例子:

     $ strace php 2>&1 | grep php.ini          open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)          open("/usr/local/lib/php.ini", O_RDONLY) = 4          lstat64("/usr/local/lib/php.ini", {st_mode=S_IFLNK|0777, st_size=27,        ...}) = 0          readlink("/usr/local/lib/php.ini", "/usr/local/Zend/etc/php.ini",        4096) = 27          lstat64("/usr/local/Zend/etc/php.ini", {st_mode=S_IFREG|0664,st_size=40971, ...}) = 0

    上述php程序程序会首先从/usr/local/bin/下读取php.ini文件,也许不是你想的首先从/usr/local/lib/下读取。
    上述的输出会很多,我们甚至可以通过参数来指定只追踪我们关心的系统调用,类似如下:

      $ strace -e open php 2>&1 | grep php.ini           open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or         directory)           open("/usr/local/lib/php.ini", O_RDONLY) = 4
  2. 为什么程序没有打开我的文件?

    每一个可执行程序读取文件时,如果权限不够,则会遭拒绝。而如果文件找不到,也并不会报错,除非你在程序里设置了错误处理,So,如果程序没有读取我的文件,我该如何跟踪呢?

     $ strace -e open,access 2>&1 |grep your-filename

    检查open()和access()系统调用的输出结果,看看是什么原因

  3. 进程此刻正在做什么?

    你的程序突然消耗了大量的CPU,或者程序似乎被挂起了,那么我们通过进程的pid号看看此刻它正在做什么

     root@dev:~# strace -p 15427           Process 15427 attached - interrupt to quit           futex(0x402f4900, FUTEX_WAIT, 2, NULL           Process 15427 detached

    通过跟踪,你知道程序挂起的原因是正在调用futex()。

  4. 程序的时间花在什么地方

    你总是希望程序能够按照你的意愿去工作,也希望它能在正确的时间做正确的事情,甚至希望它是最优的,尽可能在程序运行的周期内,消耗的90%以上的资源都是在做需要做的事情,而不是简单的等待。也许,下面的这个指令可以帮上你的忙:

     root@dev:~# strace -c -p 11084
    Process 11084 attached - interrupt to quit
    Process 11084 detached
    % time seconds usecs/call calls errors syscall
    ------ ----------- ----------- --------- --------- ----------------
    94.59 0.001014 48 21 select
    2.89 0.000031 1 21 getppid
    2.52 0.000027 1 21 time
    ------ ----------- ----------- --------- --------- ----------------
    100.00 0.001072 63 total
    root@dev:~#

    如果你是跟踪的后台守护进程,可以通过上面的指令跟踪一段时间,然后按ctrl+c退出,strace会根据获得信息描述出上面的结果。
    上述的例子说明当前进程(postmaster)最要的时间花在等待select()函数上,在每调用一次select函数后,它分别调用getpid函数和time函数. 如果是非后台守护进程,那strace可以跟踪进程的开始至结束,类似下面这样:

     root@dev:~# strace -c >/dev/null ls
    % time seconds usecs/call calls errors syscall
    ------ ----------- ----------- --------- --------- ----------------
    23.62 0.000205 103 2 getdents64
    18.78 0.000163 15 11 1 open
    15.09 0.000131 19 7 read
    12.79 0.000111 7 16 old_mmap
    7.03 0.000061 6 11 close
    4.84 0.000042 11 4 munmap
    4.84 0.000042 11 4 mmap2
    4.03 0.000035 6 6 6 access
    3.80 0.000033 3 11 fstat64
    1.38 0.000012 3 4 brk
    0.92 0.000008 3 3 3 ioctl
    0.69 0.000006 6 1 uname
    0.58 0.000005 5 1 set_thread_area
    0.35 0.000003 3 1 write
    0.35 0.000003 3 1 rt_sigaction
    0.35 0.000003 3 1 fcntl64
    0.23 0.000002 2 1 getrlimit
    0.23 0.000002 2 1 set_tid_address
    0.12 0.000001 1 1 rt_sigprocmask
    ------ ----------- ----------- --------- --------- ----------------
    100.00 0.000868 87 10 total

    ls程序大部分时间花在读取目录条目上面。

  5. 为什么我不能连接到服务器?

    调试进程不能连接到服务器是一个痛苦的事情,因为原因很多,比如DNS失效啦,连接被挂起啦,服务器返回异常数据啦,服务器本身异常啦,等等。一般网络调试方面,很多人会想到另外一个非常不错的工具–tcpdump。但它的参数太多了,而且你要从上百个连接进程中找出其中一个进程为什么不能连接恐怕是一件非常费力的工作。strace 其实也能在这种情景下帮上你的忙,它仅仅输出与系统调用相关的数据,从而可以让我们的注意力更集中。类似下面这样:

     $ strace -e poll,select,connect,recvfrom,sendto nc www.news.com 80
    sendto(3, "\24\0\0\0\26\0\1\3\255\373NH\0\0\0\0\0\0\0\0", 20, 0, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, 28) = 0
    poll([{fd=3, events=POLLOUT, revents=POLLOUT}], 1, 0) = 1
    sendto(3, "\213\321\1\0\0\1\0\0\0\0\0\0\3www\4news\3com\0\0\34\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
    poll([{fd=3, events=POLLIN, revents=POLLIN}], 1, 5000) = 1
    recvfrom(3, "\213\321\201\200\0\1\0\1\0\1\0\0\3www\4news\3com\0\0\34\0\1\300\f"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, [16]) = 153
    connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, 28) = 0
    poll([{fd=3, events=POLLOUT, revents=POLLOUT}], 1, 0) = 1
    sendto(3, "k\374\1\0\0\1\0\0\0\0\0\0\3www\4news\3com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
    poll([{fd=3, events=POLLIN, revents=POLLIN}], 1, 5000) = 1
    recvfrom(3, "k\374\201\200\0\1\0\2\0\0\0\0\3www\4news\3com\0\0\1\0\1\300\f"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, [16]) = 106
    connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, 28) = 0
    poll([{fd=3, events=POLLOUT, revents=POLLOUT}], 1, 0) = 1
    sendto(3, "\\\2\1\0\0\1\0\0\0\0\0\0\3www\4news\3com\0\0\1\0\1", 30, MSG_NOSIGNAL, NULL, 0) = 30
    poll([{fd=3, events=POLLIN, revents=POLLIN}], 1, 5000) = 1
    recvfrom(3, "\\\2\201\200\0\1\0\2\0\0\0\0\3www\4news\3com\0\0\1\0\1\300\f"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("62.30.112.39")}, [16]) = 106
    connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("216.239.122.102")}, 16) = -1 EINPROGRESS (Operation now in progress)
    select(4, NULL, [3], NULL, NULL) = 1 (out [3])

    那么,上述的输出,说明进程发生了什么呢?
    注意到这个进程尝试连接/var/run/nscd/socket连接了吗?这意味着nc程序首先会去连接NSCD- Name Service Cache Daemon - 它通常用于设置和NIS,YP,LDAP或者类似目录协议相关的域名查询配置上。在上述例子中,连接失败了。

    接下来进程开始连接到DNS,这点可以从sin_port=htons(53)输出可以看出。你可以看到,它接着做了一个sendto()的调用,发出了一个包含www.news.com信息的DNS包。然后读取返回的包数据,不知什么原因,它做了三次这样的尝试。一个可能的原因是www.news.com是一条CNAME记录。多次请求可能是nc程序处理的一种方式。

    最后,它总算是发起了connect()操作,注意这个操作的返回结果是EINPROGRESS,这意味着这个连接是非阻塞式的,nc希望继续,于是它调用了select()

    增加read,write调用到strace跟踪的系统调用列表里,可以让我们看到下面的一些结果:

     read(0, "test\n", 1024)                 = 5
    write(3, "test\n", 5) = 5
    poll([{fd=3, events=POLLIN, revents=POLLIN}, {fd=0, events=POLLIN}], 2, -1) = 1
    read(3, "

    上述表示它从读取”test” + 标准输入的一行信息,然后写入网络连接,接着调用poll来等待回应,然后读取网络反馈的信息并写到标准输出。

使用strace工具故障排查的5种简单方法的更多相关文章

  1. WPF编程 ,TextBlock 显示百分数值的一种简单方法。

    原文:WPF编程 ,TextBlock 显示百分数值的一种简单方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/ ...

  2. Mac上打开终端的7种简单方法

    终端机是用于给Mac命令的便捷工具,尽管它可能会吓倒许多人.毕竟,这不像输入句子然后Mac响应那样简单.如果您有兴趣学习使用Terminal或只想输入一两个命令,我们在下面列出了一些文章,可以帮助您使 ...

  3. Honeywords项目——检查密码是否被破解的一种简单方法

    Honeywords项目使用一种简单的方法来改进hash后的密码的安全性——为每个账户维护一个额外的honeywords(假密码).如果有黑客拿到了密码的文件,然后试图用brute froce的方式破 ...

  4. Qt实现软件自动更新的一种简单方法

    前言 最近在学习Qt开发上位机,想实现一个检查更新的功能,网上搜索了一大圈,发现实现过程都很复杂,关键是代码看不懂,所以就自己开发一种简单的方式来实现.实现效果如下: 点击"检查更新&quo ...

  5. SSM/SSH框架的MySQL 读写分离实现的一种简单方法

    简介 MySQL已经是使用最为广泛的一种数据库,往往实际使用过程中,为实现高可用及高性能,项目会采用主丛复制的方式实现读写分离.MySQL本身支持复制,通过简单的配置即可实现一主多从的配置,具体实现可 ...

  6. 用strace排查故障的5种简单方法(每日一译)

    原文链接:5 simple ways to troubleshoot using Strace 我很意外大部分人都不知道如何使用strace.strace一直是我的首选debug工具,因为它非常的有效 ...

  7. Flask生成SECRET_KEY(密钥)的一种简单方法

    SECRET_KEY是Flask中比较重要的一个配置值.本文介绍一种比较简单的生成SECRET_KEY的方法. Session, Cookies以及一些第三方扩展都会用到SECRET_KEY值,这是一 ...

  8. 下载文件的一种简单方法js

    我在做的一个项目有一部分要下载附件,可是我们公司用了一个包和网上的用response的解决方法冲突,而网上的js解决方法又用到了ActiveXObj我们经理不让用这个.还好我一个同事很利害用了一个很简 ...

  9. Jquery显示和隐藏的4种简单方法

    Html代码:  <div class="topicList">  <h3><span>学习天地</span></h3> ...

随机推荐

  1. FLASH CC 2015 CANVAS (二)html中写JS调用flash中的元件、函数、变量

    注意 此贴 为个人边“开荒”边写,所以不保证就是最佳做法,也难免有错误! 正式教程会在后续开始更新 当你导出第一个canvas后,你会在保存fla的文件夹里 (每个项目默认位置)看到 如下文件,(请先 ...

  2. T-SQL Apply的用法

    SQL Server 2005 新增 cross apply 和 outer apply 联接语句,增加这两个东东有啥作用呢? 我们知道有个 SQL Server 2000 中有个 cross joi ...

  3. Bootstrap日期和时间表单组件运用兼容ie8

    准备动作先到下载Bootstrap日期和时间组件. 1:引入bootstrap.min.css,因为bootstrap-datetimepicker里面的很多样式依赖bootstarp的主样式,字体文 ...

  4. iOS - Swift NSLocale 本地化信息

    前言 public class NSLocale : NSObject, NSCopying, NSSecureCoding NSLocale 类返回本地化信息,主要体现在"语言" ...

  5. iOS - File Archive/UnArchive 文件压缩/解压

    1.ZipArchive 方式 ZipArchive 只能对 zip 类文件进行压缩和解压缩 GitHub 网址:https://github.com/ZipArchive/ZipArchive Zi ...

  6. [转载] Linux下高并发socket最大连接数所受的各种限制

    原文: http://mp.weixin.qq.com/s?__biz=MzAwNjMxNjQzNA==&mid=207772333&idx=1&sn=cfc8aadb422f ...

  7. UVA 11468【AC自动机+DP】

    dp[i][j]表示走了i步走到j结点的概率.初始值dp[0][0] = 1.当走到的结点不是单词尾结点时,才能走过去. !end[i]&&last[i] == root时,该结点才可 ...

  8. 【Todo】LR-逻辑回归

    因为逻辑回归对于计算广告学非常重要.也是我们平时广告推荐.CTR预估最常用到的算法.所以单独开一篇文章讨论. 参考这篇文章:http://www.cnblogs.com/sparkwen/p/3441 ...

  9. 使用Ant打包工具 基本介绍

    由于使用java,javac,jar等工具进行编译打包,即繁琐低效又容易出错,因此Ant出现了. Ant的出现就是专门为了打包编译java代码的,使用之前得稍微学一下.Ant的运行起来主要是依靠配置文 ...

  10. MFC编程入门之五(MFC消息映射机制概述)

    在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作.比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应. 一.什 ...