当前位置: 首页 > news >正文

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

我很意外大部分人都不知道如何使用strace。strace一直是我的首选debug工具,因为它非常的有效,很多问题都能够用它进行排查。

strace是什么?

Strace是一个用来跟踪系统调用的简易工具。它最简单的用途就是跟踪一个程序整个生命周期里所有的系统调用,并把调用参数和返回值以文本的方式输出。

当然它还可以做更多的事情:

strace可以过筛选出特定的系统调用。

strace可以记录系统调用的次数,时间,成功和失败的次数。

strace可以跟踪发给进程的信号。

strace可以通过pid附加到任何正在运行的进程上。

strace类似其他Unix系统上的truss,或者Sun's Dtrace

使用教程

以下这些只是些皮毛的用法:

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

So this version of PHP reads php.ini from /usr/local/lib/php.ini (but it tries /usr/local/bin first).

如果只关心特定的系统调用可以使用以下略微复杂的使用方法:

$ 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)为什么程序打不开这个文件?

是否有遇到过读取文件的时候被拒绝呢,你可以试试下面的命令:

$ 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的时候被挂起了。顺带一说,在这个例子里调用futex挂起可能有很多的原因(Futex是Linux的一种线程同步原语)。上述场景是个正常工作等待处理请求的apache子进程。

“strace -p”可以让你省去很多猜测,不需要重新编译,重启应用打log就能够找到问题。

4)统计程序的调用时间

要对程序进行性能分析往往需要重新编译程序,并打开跟踪选项。用strace可以很容易的附加到进程上查看实时的时间消耗。

如下:

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:~#

启用strace -c -p命令后,在你按ctrl-c退出前程序的调用时间将会打印出来。

在上面的例子里。空闲的Postgres进程大部分时间都在安静的等待select()返回。在每个select()调用中调用getppid() 和time()。这是个标准的event loop方式。

也可以跟踪一次程序的开始和结束,比如下面的“ls”例子

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

如你所料,这个命令花费两个调用用于读取目录实体。

5)为什么连不上服务器?

调试某些进程为什么连不上远程的服务器是个很让人头疼的事情。DNS可能挂了,连接可能断了,服务器可能返回什么无法识别的东西。。。你可以使用tcpdump去分析这些问题,tcpdump是个很棒的工具,但是很多情况下,strace可以给你提供更简洁的信息。如果你的程序里有很多进程连接到某台服务器,用tcpdump来处理就是很蛋疼的事情,因为你会看到太多的信息,没法抓到重点。

以下是一个跟踪“nc”命令连接到www.news.com的80端口的例子:

$ 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服务(名称缓存守护进程,通常用于NIS,YP,LDAP中的名字查找)。在上面的例子里执行失败。

nc命令转而使用DNS(DNS的端口是53,"sin_port=htons(53)" )。可以看到程序调用”sendto()"发送包含www.news.com信息的DNS数据包。因为某些原因,尝试了3次。为什么会这样呢?我猜测可能www.news.com是CNAME ,最后,程序连接从DNS获取到的IP,注意这里,返回EINPROGRESS。这意味这这个连接是非堵塞的。连接成功后nc调用select。

添加read和write到跟踪的系统调用列表里,连接上后敲入test,你会看到如下信息:

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()等待响应,读取响应然后写入到标准输出中。

相关文章:

  • java银行账户系统_用java编的银行账户系统代码
  • java扩展包_CodeRunner 的 Java 扩展 Jar 包支持
  • java session 修改_修改 Servlet 的sessionId
  • qt添加qwt帮助文件_win 7下安装qwt 6.1.0,基于qt 4.8.5
  • java亮眼_一些java处理变量的 让我眼前一亮的
  • 36岁自学python_深入 Python 解释器源码,我终于搞明白了字符串驻留的原理!
  • idea 收费标准_2013年IDEA期限与费用
  • java反射机制学习_java学习之 反射机制
  • 怎样看java文件的编码方式_如何查看Java源文件的编码方式及去掉BOM
  • 蓝桥杯泊松分酒java_蓝桥杯编程大题-泊松分酒 | 学步园
  • java学生登陆界面代码_登录界面 - java代码库 - 云代码
  • java降序排序输出id和结果_Java报表软件--根据订单ID进行升序或降序排列
  • java虚拟机中xms_JVM虚拟机选项:Xms Xmx PermSize MaxPermSize区别
  • java 读取txt并分页_Ajax读取txt并对txt内容进行分页显示功能
  • java规范 文件行数_Java中文件中的行数
  • JavaScript-如何实现克隆(clone)函数
  • iOS小技巧之UIImagePickerController实现头像选择
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • markdown编辑器简评
  • Vue2.0 实现互斥
  • Vue--数据传输
  • Webpack 4 学习01(基础配置)
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 大整数乘法-表格法
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 推荐一个React的管理后台框架
  • ionic入门之数据绑定显示-1
  • ​虚拟化系列介绍(十)
  • #Ubuntu(修改root信息)
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (16)Reactor的测试——响应式Spring的道法术器
  • (LeetCode 49)Anagrams
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)Linux下编译安装log4cxx
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • ***测试-HTTP方法
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .net FrameWork简介,数组,枚举
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • .Net小白的大学四年,内含面经
  • .NET性能优化(文摘)
  • /dev/VolGroup00/LogVol00:unexpected inconsistency;run fsck manually
  • @Import注解详解
  • @property python知乎_Python3基础之:property
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [BZOJ 4034][HAOI2015]T2 [树链剖分]
  • [C++打怪升级]--学习总目录
  • [Docker]五.Docker中Dockerfile详解
  • [Go WebSocket] 多房间的聊天室(五)用多个小锁代替大锁,提高效率