這份文件收集了有關除錯 Apache httpd 及其模組的工具和技術的筆記。
有更多秘訣?請寄至 docs@httpd.apache.org。謝謝!
如果您使用 gcc 編譯器,您的系統最佳的除錯器可能就是 gdb。這只是在 Apache 上執行 gdb 的簡要摘要,您應該參考 gdb 的資訊和手冊檔案,以取得更多關於 gdb 命令和常見除錯技術的資訊。在執行 gdb 之前,請務必確認伺服器已使用 CFLAGS
中的 -g
選項編譯,以將符號資訊包含在物件檔案中。
在 Apache 上執行 gdb 唯一棘手的部分是強制伺服器進入單一處理程序模式,這樣父處理程序就能進行要求處理工作,而不是分岔子處理程序。我們已提供 -X
選項做為此用途,它在大多數情況下都能正常使用。不過,某些模組不喜歡使用 -X
來啟動,但是如果你只強制執行一個子處理程序 (使用 "MaxClients 1
"),它們就會很樂意,然後你可以使用 gdb 的連線命令來除錯子伺服器。
以下例子帶有以綠色顯示的使用者輸入,顯示 gdb 在當前工作目錄中,針對伺服器可執行檔 (httpd) 執行,並使用伺服器根目錄為 /usr/local/apache
的輸出
% gdb httpd GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16.gnat.1.13 (sparc-sun-solaris2.5), Copyright 1996 Free Software Foundation, Inc... (gdb) b ap_process_request Breakpoint 1 at 0x49fb4: file http_request.c, line 1164. (gdb) run -X -d /usr/local/apache Starting program: /usr/local/apache/src/httpd -X -d /usr/local/apache [ at this point I make a request from another window ] Breakpoint 1, ap_process_request (r=0x95250) at http_request.c:1164 1164 if (ap_extended_status) (gdb) s 1165 ap_time_process_request(r->connection->child_num,... (gdb) n 1167 process_request_internal(r); (gdb) s process_request_internal (r=0x95250) at http_request.c:1028 1028 if (!r->proxyreq && r->parsed_uri.path) { (gdb) s 1029 access_status = ap_unescape_url(r->parsed_uri.path); (gdb) n 1030 if (access_status) { (gdb) s 1036 ap_getparents(r->uri); /* OK... (gdb) n 1038 if ((access_status = location_walk(r))) { (gdb) n 1043 if ((access_status = ap_translate_name(r))) { (gdb) n 1048 if (!r->proxyreq) { (gdb) n 1053 if (r->method_number == M_TRACE) { (gdb) n 1062 if (r->proto_num > HTTP_VERSION(1,0) && ap_... (gdb) n 1071 if ((access_status = directory_walk(r))) { (gdb) s directory_walk (r=0x95250) at http_request.c:288 288 core_server_config *sconf = ap_get_module_... (gdb) b ap_send_error_response Breakpoint 2 at 0x47dcc: file http_protocol.c, line 2090. (gdb) c Continuing. Breakpoint 2, ap_send_error_response (r=0x95250, recursive_error=0) at http_protocol.c:2090 2090 BUFF *fd = r->connection->client; (gdb) where #0 ap_send_error_response (r=0x95250, recursive_error=0) at http_protocol.c:2090 #1 0x49b10 in ap_die (type=403, r=0x95250) at http_request.c:989 #2 0x49b60 in decl_die (status=403, phase=0x62db8 "check access", r=0x95250) at http_request.c:1000 #3 0x49f68 in process_request_internal (r=0x95250) at http_request.c:1141 #4 0x49fe0 in ap_process_request (r=0x95250) at http_request.c:1167 #5 0x439d8 in child_main (child_num_arg=550608) at http_main.c:3826 #6 0x43b5c in make_child (s=0x7c3e8, slot=0, now=907958743) at http_main.c:3898 #7 0x43ca8 in startup_children (number_to_start=6) at http_main.c:3972 #8 0x44260 in standalone_main (argc=392552, argv=0x75800) at http_main.c:4250 #9 0x449fc in main (argc=4, argv=0xefffee8c) at http_main.c:4534 (gdb) s 2091 int status = r->status; (gdb) p status $1 = 403 (gdb)
關於上述範例需要注意幾點
「 gdb httpd
」指令不包含任何 httpd 的命令提示字,這些是在 gdb 中進行「 run
」指令時提供。
我在啟動執行之前先設一個中斷點,讓執行會停在 ap_process_request()
「 s
」指令會逐行執行程式碼並進入被呼叫的程序,而「 n
」(下一個) 指令會逐行執行程式碼但不進入被呼叫的程序。
可以透過「 b
」指令設定其他中斷點,並以「 c
」指令繼續執行。
使用「 where
」(又稱為「 bt
」) 指令可查看一個堆疊回溯,找出被呼叫的程序的順序及它們的參數值。
使用「 p
」指令可列印變數的值。
在根目錄的檔案中,名為 .gdbinit
的檔案提供了有用的巨集,用於列印 httpd 的各種內部結構,例如表格( dump_table
)、聯隊( dump_brigade
) 以及濾網鏈( dump_filters
)。
如果您要偵錯一個可重複的崩潰,只要如上執行 gdb 和提出要求 -- gdb 應會擷取崩潰並提供發生崩潰處的提示。
如果您要偵錯一個明顯的無限迴圈,只要如上執行 gdb 並輸入 Control-C -- gdb 會中斷程序並提供停止程序處的提示。
如果您要偵錯一個系統崩潰,且有崩潰後產生的核心檔案,請執行以下動作
% gdb httpd -c core
(gdb) where
它將(希望)在處理過程中列印核心傾印發生處的堆疊回溯。
回溯可讓您得知被呼叫的程序層級,才能到達程序的特定點。在有些平台上,您可以取得任何程序的即時回溯。
對於基於 SVR4 的 Unix 變體,proc 的 pstack
指令可用來顯示即時回溯。舉例而言,在 Solaris 上,指令如下所示
% /usr/proc/bin/pstack 10623
10623: httpd -d /usr/local/apache
ef5b68d8 poll (efffcd08, 0, 3e8)
ef5d21e0 select (0, ef612c28, 0, 0, 3e8, efffcd08) + 288
00042574 wait_or_timeout (0, 75000, 75000, 7c3e8, 60f40, 52c00) + 78
00044310 standalone_main (5fd68, 75800, 75c00, 75000, 2, 64) + 240
000449f4 main (3, efffeee4, efffeef4, 75fe4, 1, 0) + 374
000162fc _start (0, 0, 0, 0, 0, 0) + 5c
另一項技術是使用 gdb 附加到執行中的程序,然後使用「thread apply all bt」列印回溯,如下所示
sudo gdb /path/to/bin/httpd 10623 --batch --eval-command "where" --eval-command "thread apply all bt" --eval-command "detach" --eval-command "quit" | tee gdb-output.txt
或互動式操作
% gdb httpd 10623
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for
details.
GDB 4.16.gnat.1.13 (sparc-sun-solaris2.5),
Copyright 1996 Free Software Foundation, Inc...
/usr/local/apache/src/10623: No such file or directory.
Attaching to program `/usr/local/apache/src/httpd', process 10623
Reading symbols from /usr/lib/libsocket.so.1...done.
...
0xef5b68d8 in ()
(gdb) where
#0 0xef5b68d8 in ()
#1 0xef5d21e8 in select ()
#2 0x4257c in wait_or_timeout (status=0x0) at http_main.c:2357
#3 0x44318 in standalone_main (argc=392552, argv=0x75800) at...
#4 0x449fc in main (argc=3, argv=0xefffeee4) at http_main.c:4534
(gdb) thread apply all bt
(gdb) detach
(gdb> quit
在根目錄 Apache2 目錄樹中解壓縮 -symbols.zip
檔案(從 Apache 下載網站取得)(通常可在其中找到 bin, htdocs, modules 等)。這些 pdb 檔案應會與它們顯示的.exe,.dll,.so 二進位檔案一起解壓縮,例如 mod_usertrack.pdb 會與 mod_usertrack.so 一起解壓縮。
選擇性:從 Sysinternals 取得 procdump
。
呼叫 drwtsn32
,並確保您建置了一個崩潰傾印檔,而且所有執行緒內容都已傾印,記錄檔和崩潰傾印檔路徑合理,且(視錯誤性質而定)您選擇了適當的崩潰傾印檔類型。(「完整」相當龐大,但有時候程式設計師一定需要載入您的崩潰傾印檔到除錯器中,並開始分解實際發生的情況。對於您第一次執行此程序來說,「精簡」就已足夠。)
此時,您也可以使用 procdump
或 procdump -ma
取代。
請注意,如果您之前已安裝然後解除安裝了其他除錯軟體,您可能需要呼叫 drwtsn32 -i
,才能讓 Dr. Watson 成為您的預設崩潰傾印檔工具。這會取代「向 MS 回報問題」對話方塊。(如果您電腦上已安裝 Visual Studio 或 windbg 等完整除錯器時,請勿執行此動作,除非您先備份 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
登錄結構中的 Debugger
登錄值。使用多種工具的開發人員可能想要在該處保留不同工具的「Debugger」條目的副本,以利快速切換。)
呼叫工作管理員,選擇「顯示來自所有使用者的程序」,並修改「檢視 -> 選取欄位」,以至少包含 PID 和 執行緒數目。您只要變更一次,工作管理員就會記住您的喜好設定。
現在,追蹤發生錯誤導致當機的 Apache。父程序大約有三個執行緒,我們不需要處理。我們需要的子工作程序執行緒更多(比您使用 ThreadsPerChild 指令設定的多一些)。程序名稱是 Apache(1.3 和 2.0 版)或 httpd(2.2 和 2.4 版)。記下子工作程序的 PID。
使用您在上方所記下的 {pid} 數字,呼叫指令
drwtsn32 -p {pid}
或procdump {pid}
瞧!您會在您的「記錄檔路徑」找到一個 drwtsn32.log
檔案,而且如果您選擇「附加到現有的記錄檔」,請瀏覽「App:」部分,直到找到您剛終止的程序為止。現在,您可以識別「堆疊回溯」指向何處,以協助找出伺服器正在做什麼。
您會注意到,許多執行緒看起來很像,它們幾乎都在輪詢下一個連線,而您並不在乎那些執行緒。您會想要找出當您終止它們時,正在要求深處的執行緒,以及該執行緒的堆疊回溯條目。這可以讓大家知道該要求發生錯誤的位置、處理該要求的是哪個處理模組,以及哪個篩選器可能會卡住。
對於子程序間歇性崩潰的情況,必須設定伺服器,並開始讓它產生核心傾印檔,以便進一步分析。
為確保已寫入核心傾印至使用中的目錄(例如 apache
),此 指令必須加入至 httpd.conf
;例如
CoreDumpDirectory /tmp
在啟動伺服器之前,必須解除核心傾印檔案大小的任何程序限制;例如
# ulimit -c unlimited
# apachectl start
在某些平台上,可能需要採取進一步步驟來啟用核心傾印;請參閱下方的 Solaris 和核心傾印 。
當子程序崩潰時,會將類似下列訊息記錄至 error_log
[Mon Sep 05 13:35:39 2005] [notice] child pid 2027 exit signal Segmentation
fault (11), possible coredump in /tmp
如果「/tmp 中可能有核心傾印」文字未出現在錯誤列,請確認 ulimit 已正確設定,且已設定 CoreDumpDirectory
的權限適合,且在需要時已執行特定於平台的步驟( Solaris 和核心傾印 )。
為分析核心傾印,請將核心傾印檔案名稱傳遞至 gdb 指令欄,並在 gdb 提示字元輸入指令 thread apply all bt full
% gdb /usr/local/apache2/bin/httpd /tmp/core.2027
...
Core was generated by `/usr/local/apache2/bin/httpd -k start'
...
(gdb) thread apply all bt full
大部分的 Unix-based 系統中至少會有一個指令,可顯示當執行中的程序存取時出現的系統呼叫和訊號追蹤。此指令在大部分的 SVR4-based 系統上稱為 truss
,在許多其他系統上稱為 trace
或 strace
。
使用 Solaris 上的 truss
指令時一個有用的提示為 -f
選項(通常也用於 strace
),它會指示 truss 追蹤和持續追蹤主程序分岔的任何子程序。取得伺服器完整追蹤最簡單的方法為執行類似下方的動作
% truss -f httpd -d /usr/local/apache >& outfile % egrep '^10698:' outfile
只查看程序 ID 10698 的追蹤。
如果嘗試追蹤串列伺服器,例如使用 worker
MPM,則 truss
選項 -l
會非常有用,因為它會在程序 ID 後列印出 LWP ID。您可以使用類似的指令
% egrep '^10698/1:' outfile
只查看程序 ID 10698 和 LWP ID 1 的追蹤。
truss 的其他有用選項為
-a
列印用於此執行檔的所有指令欄參數。
-e
列印用於此執行檔的所有環境變數。
-d
列印時間戳。
奇怪的是,有時您實際上想要強制伺服器崩潰,以便您可以查看一些瘋狂的行為。正常來說,這可以透過使用 gcore
指令來完成。但是,基於安全性因素,大部分的 Unix 系統不允許 setuid 程序傾印核心,因為檔案內容可能揭露一些應該在記憶體中受保護的內容。
以下有一種在 Solaris 上取得已設定 setuid Apache httpd 程序的核心檔案的方法,而不知道哪個 httpd 子程序可能會當掉 [注意:比較簡單的方法可能是使用上方的第一個區段中的 MaxClients 技巧]。
# for pid in `ps -eaf | fgrep httpd | cut -d' ' -f4` do truss -f -l -t\!all -S SIGSEGV -p $pid 2>&1 | egrep SIGSEGV & done
truss 的 未記錄文件中的「-S」旗標 在收到給定信號(此案例中為 SIGSEGV)時會中止程式運作。此時,您可以使用
# gcore PID
然後再針對 gdb 檢視以上的回溯追蹤。
在 Solaris 上,使用 來讓 setuid()
程式實際傾印出核心。預設上,setuid()
程式不會傾印核心。這就是為什麼以 root 身分啟動 httpd 伺服器,但子程式以不同使用者(例如,apache
)身分執行時,即使 CoreDumpDirectory 指示已設定為適當且可寫入的目錄,以及 ulimit -c
具有足夠大小,也不會產生核心傾印檔。請參見以上的 中斷式當機除錯。
範例
-bash-3.00# coreadm
global core file pattern: /var/core/core.%f.%p.u%u
global core file content: default
init core file pattern: core
init core file content: default
global core dumps: disabled
per-process core dumps: enabled
global setid core dumps: enabled
per-process setid core dumps: enabled
global core dump logging: disabled
這是一個過於深入的主題,無法在此文件當中完整說明。以下是討論和工具的一些指標