在軟體部署中使用 strace 進行調試
我的大部分工作都涉及到部署軟體系統,這意味著我需要花費很多時間來解決以下問題:
- 這個軟體可以在原開發者的機器上工作,但是為什麼不能在我這裡運行?
- 這個軟體昨天可以在我的機器上工作,但是為什麼今天就不行?
這是一種調試的類型,但是與一般的軟體調試有所不同。一般的調試通常只關心代碼的邏輯,但是在軟體部署中的調試關注的是程序的代碼和它所在的運行環境之間的相互影響。即便問題的根源是代碼的邏輯錯誤,但軟體顯然可以在別的機器上運行的事實意味著這類問題與運行環境密切相關。
所以,在軟體部署過程中,我沒有使用傳統的調試工具(例如 gdb
),而是選擇了其它工具進行調試。我最喜歡的用來解決「為什麼這個軟體無法在這台機器上運行?」這類問題的工具就是 strace
。
什麼是 strace?
strace 是一個用來「追蹤系統調用」的工具。它主要是一個 Linux 工具,但是你也可以在其它系統上使用類似的工具(例如 DTrace 和 ktrace)。
它的基本用法非常簡單。只需要在 strace
後面跟上你需要運行的命令,它就會顯示出該命令觸發的所有系統調用(你可能需要先安裝好 strace
):
$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6) = 6
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
這些系統調用都是什麼?它們就像是操作系統內核提供的 API。很久以前,軟體擁有直接訪問硬體的許可權。如果軟體需要在屏幕上顯示一些東西,它將會與視頻硬體的埠和內存映射寄存器糾纏不清。當多任務操作系統變得流行以後,這就導致了混亂的局面,因為不同的應用程序將「爭奪」硬體,並且一個應用程序的錯誤可能致使其它應用程序崩潰,甚至導致整個系統崩潰。所以 CPU 開始支持多種不同的特權模式(或者稱為「保護環」)。它們讓操作系統內核在具有完全硬體訪問許可權的最高特權模式下運行,於此同時,其它在低特權模式下運行的應用程序必須通過向內核發起系統調用才能夠與硬體進行交互。
在二進位級別上,發起系統調用相比簡單的函數調用有一些區別,但是大部分程序都使用標準庫提供的封裝函數。例如,POSIX C 標準庫包含一個 write()
函數,該函數包含用於進行 write
系統調用的所有與硬體體系結構相關的代碼。
簡單來說,一個應用程序與其環境(計算機系統)的交互都是通過系統調用來完成的。所以當軟體在一台機器上可以工作但是在另一台機器無法工作的時候,追蹤系統調用是一個很好的查錯方法。具體地說,你可以通過追蹤系統調用分析以下典型操作:
- 控制台輸入與輸出 (IO)
- 網路 IO
- 文件系統訪問以及文件 IO
- 進程/線程生命周期管理
- 原始內存管理
- 訪問特定的設備驅動
什麼時候可以使用 strace?
理論上,strace
適用於任何用戶空間程序,因為所有的用戶空間程序都需要進行系統調用。strace
對於已編譯的低級程序最有效果,但如果你可以避免運行時環境和解釋器帶來的大量額外輸出,則仍然可以與 Python 等高級語言程序一起使用。
當軟體在一台機器上正常工作,但在另一台機器上卻不能正常工作,同時拋出了有關文件、許可權或者不能運行某某命令等模糊的錯誤信息時,strace
往往能大顯身手。不幸的是,它不能診斷高等級的問題,例如數字證書驗證錯誤等。這些問題通常需要組合使用 strace
(有時候是 ltrace
)和其它高級工具(例如使用 openssl
命令行工具調試數字證書錯誤)。
本文中的示例基於獨立的伺服器,但是對系統調用的追蹤通常也可以在更複雜的部署平台上完成,僅需要找到合適的工具。
一個簡單的例子
假設你正在嘗試運行一個叫做 foo
的伺服器應用程序,但是發生了以下情況:
$ foo
Error opening configuration file: No such file or directory
顯然,它沒有找到你已經寫好的配置文件。之所以會發生這種情況,是因為包管理工具有時候在編譯應用程序時指定了自定義的路徑,所以你應當遵循特定發行版提供的安裝指南。如果錯誤信息告訴你正確的配置文件應該在什麼地方,你就可以在幾秒鐘內解決這個問題,但如果沒有告訴你呢?你該如何找到正確的路徑?
如果你有權訪問源代碼,則可以通過閱讀源代碼來解決問題。這是一個好的備用計劃,但不是最快的解決方案。你還可以使用類似 gdb
的單步調試器來觀察程序的行為,但使用專門用於展示程序與系統環境交互作用的工具 strace
更加有效。
一開始, strace
產生的大量輸出可能會讓你不知所措,幸好你可以忽略其中大部分的無用信息。我經常使用 -o
參數把輸出的追蹤結果保存到單獨的文件里:
$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL) = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 >