用strace除錯程式(zt)

tonykorn97發表於2006-09-05
在理想世界裡,每當一個程式不能正常執行一個功能時,它就會給出一個有用的錯誤提示,告訴你在足夠的改正錯誤的線索。但遺憾的是,我們不是生活在理想世界裡,起碼不總是生活在理想世界裡。有時候一個程式出現了問題,你無法找到原因。

這就是除錯程式出現的原因。strace是一個必不可少的除錯工具,strace用來監視系統呼叫。你不僅可以除錯一個新開始的程式,也可以除錯一個已經在執行的程式(把strace繫結到一個已有的PID上面)。


首先讓我們看一個真實的例子:

[BOLD]啟動KDE時出現問題[/BOLD]

前一段時間,我在啟動KDE的時候出了問題,KDE的錯誤資訊無法給我任何有幫助的線索。

程式碼:

_KDE_IceTransSocketCreateListener: failed to bind listener
_KDE_IceTransSocketUNIXCreateListener: ...SocketCreateListener() failed
_KDE_IceTransMakeAllCOTSServerListeners: failed to create listener for local

Cannot establish any listening sockets DCOPServer self-test failed.


對我來說這個錯誤資訊沒有太多意義,只是一個對KDE來說至關重要的負責程式間通訊的程式無法啟動。我還可以知道這個錯誤和ICE協議(Inter Client Exchange)有關,除此之外,我不知道什麼是KDE啟動出錯的原因。

我決定採用strace看一下在啟動dcopserver時到底程式做了什麼:

程式碼:

strace -f -F -o ~/dcop-strace.txt dcopserver


這裡 -f -F選項告訴strace同時跟蹤fork和vfork出來的程式,-o選項把所有strace輸出寫到~/dcop-strace.txt裡面,dcopserver是要啟動和除錯的程式。

再次出現錯誤之後,我檢查了錯誤輸出檔案dcop-strace.txt,檔案裡有很多系統呼叫的記錄。在程式執行出錯前的有關記錄如下:

程式碼:

27207 mkdir("/tmp/.ICE-unix", 0777) = -1 EEXIST (File exists)
27207 lstat64("/tmp/.ICE-unix", {st_mode=S_IFDIR|S_ISVTX|0755, st_size=4096, ...}) = 0
27207 unlink("/tmp/.ICE-unix/dcop27207-1066844596") = -1 ENOENT (No such file or directory)
27207 bind(3, {sin_family=AF_UNIX, path="/tmp/.ICE-unix/dcop27207-1066844596"}, 38) = -1 EACCES (Permission denied)
27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketCreateListener: failed to "..., 46) = 46
27207 close(3) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketUNIXCreateListener: ...Soc"..., 59) = 59
27207 umask(0) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "MakeAllCOTSServerListeners: fail"..., 64) = 64
27207 write(2, "Cannot establish any listening s"..., 39) = 39


其中第一行顯示程式試圖建立/tmp/.ICE-unix目錄,許可權為0777,這個操作因為目錄已經存在而失敗了。第二個系統呼叫(lstat64)檢查了目錄狀態,並顯示這個目錄的許可權是0755,這裡出現了第一個程式執行錯誤的線索:程式試圖建立屬性為0777的目錄,但是已經存在了一個屬性為0755的目錄。第三個系統呼叫(unlink)試圖刪除一個檔案,但是這個檔案並不存在。這並不奇怪,因為這個操作只是試圖刪掉可能存在的老檔案。

但是,第四行確認了錯誤所在。他試圖繫結到/tmp/.ICE-unix/dcop27207-1066844596,但是出現了拒絕訪問錯誤。.ICE_unix目錄的使用者和組都是root,並且只有所有者具有寫許可權。一個非root使用者無法在這個目錄下面建立檔案,如果把目錄屬性改成0777,則前面的操作有可能可以執行,而這正是第一步錯誤出現時進行過的操作。

所以我執行了chmod 0777 /tmp/.ICE-unix之後KDE就可以正常啟動了,問題解決了,用strace進行跟蹤除錯只需要花很短的幾分鐘時間跟蹤程式執行,然後檢查並分析輸出檔案。

說明:執行chmod 0777只是一個測試,一般不要把一個目錄設定成所有使用者可讀寫,同時不設定粘滯位(sticky bit)。給目錄設定粘滯位可以阻止一個使用者隨意刪除可寫目錄下面其他人的檔案。一般你會發現/tmp目錄因為這個原因設定了粘滯位。KDE可以正常啟動之後,執行chmod +t /tmp/.ICE-unix給.ICE_unix設定粘滯位。

[BOLD]解決庫依賴問題[/BOLD]

starce的另一個用處是解決和動態庫相關的問題。當對一個可執行檔案執行ldd時,它會告訴你程式使用的動態庫和找到動態庫的位置。但是如果你正在使用一個比較老的glibc版本(2.2或更早),你可能會有一個有bug的ldd程式,它可能會報告在一個目錄下發現一個動態庫,但是真正執行程式時動態連線程式(/lib/ld-linux.so.2)卻可能到另外一個目錄去找動態連線庫。這通常因為/etc/ld.so.conf和/etc/ld.so.cache檔案不一致,或者/etc/ld.so.cache被破壞。在glibc 2.3.2版本上這個錯誤不會出現,可能ld-linux的這個bug已經被解決了。

儘管這樣,ldd並不能把所有程式依賴的動態庫列出來,系統呼叫dlopen可以在需要的時候自動調入需要的動態庫,而這些庫可能不會被ldd列出來。作為glibc的一部分的NSS(Name Server Switch)庫就是一個典型的例子,NSS的一個作用就是告訴應用程式到哪裡去尋找系統帳號資料庫。應用程式不會直接連線到NSS庫,glibc則會透過dlopen自動調入NSS庫。如果這樣的庫偶然丟失,你不會被告知存在庫依賴問題,但這樣的程式就無法透過使用者名稱解析得到使用者ID了。讓我們看一個例子:

whoami程式會給出你自己的使用者名稱,這個程式在一些需要知道執行程式的真正使用者的指令碼程式裡面非常有用,whoami的一個示例輸出如下:
程式碼:

# whoami
root


假設因為某種原因在升級glibc的過程中負責使用者名稱和使用者ID轉換的庫NSS丟失,我們可以透過把nss庫改名來模擬這個環境:
程式碼:

# mv /lib/libnss_files.so.2 /lib/libnss_files.so.2.backup
# whoami
whoami: cannot find username for UID 0


這裡你可以看到,執行whoami時出現了錯誤,ldd程式的輸出不會提供有用的幫助:
程式碼:

# ldd /usr/bin/whoami
libc.so.6 => /lib/libc.so.6 (0x4001f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)


你只會看到whoami依賴Libc.so.6和ld-linux.so.2,它沒有給出執行whoami所必須的其他庫。這裡時用strace跟蹤whoami時的輸出:
程式碼:

strace -o whoami-strace.txt whoami

open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=2352, ...}) = 0
open("/usr/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/usr/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)


你可以發現在不同目錄下面查詢libnss.so.2的嘗試,但是都失敗了。如果沒有strace這樣的工具,很難發現這個錯誤是由於缺少動態庫造成的。現在只需要找到libnss.so.2並把它放回到正確的位置就可以了。

[BOLD]限制strace只跟蹤特定的系統呼叫[/BOLD]

如果你已經知道你要找什麼,你可以讓strace只跟蹤一些型別的系統呼叫。例如,你需要看看在configure指令碼里面執行的程式,你需要監視的系統呼叫就是execve。讓strace只記錄execve的呼叫用這個命令:

程式碼:

strace -f -o configure-strace.txt -e execve ./configure


部分輸出結果為:
程式碼:

2720 execve("/usr/bin/expr", ["expr", "a", ":", "(a)"], [/* 31 vars */]) = 0
2725 execve("/bin/basename", ["basename", "./configure"], [/* 31 vars */]) = 0
2726 execve("/bin/chmod", ["chmod", "+x", "conftest.sh"], [/* 31 vars */]) = 0
2729 execve("/bin/rm", ["rm", "-f", "conftest.sh"], [/* 31 vars */]) = 0
2731 execve("/usr/bin/expr", ["expr", "99", "+", "1"], [/* 31 vars */]) = 0
2736 execve("/bin/ln", ["ln", "-s", "conf2693.file", "conf2693"], [/* 31 vars */]) = 0


你已經看到了,strace不僅可以被程式設計師使用,普通系統管理員和使用者也可以使用strace來除錯系統錯誤。必須承認,strace的輸出不總是容易理解,但是很多輸出對大多數人來說是不重要的。你會慢慢學會從大量輸出中找到你可能需要的資訊,像許可權錯誤,檔案未找到之類的,那時strace就會成為 一個有力的工具了。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/312079/viewspace-245462/,如需轉載,請註明出處,否則將追究法律責任。

相關文章