原文鏈接: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()等待響應,讀取響應然后寫入到標准輸出中。