用strace排查故障的5種簡單方法(每日一譯)


原文鏈接:5 simple ways to troubleshoot using Strace

我很意外大部分人都不知道如何使用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()等待響應,讀取響應然后寫入到標准輸出中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM