要點

原始碼存放庫

文件

參與

子專案

相關專案

其他

Apache 偵錯指南

這份文件收集了有關除錯 Apache httpd 及其模組的工具和技術的筆記。

有更多秘訣?請寄至 docs@httpd.apache.org。謝謝!

  1. 使用 gdb

  2. 在 Unix 中取得即時回溯

  3. 在 Windows 中取得即時回溯

  4. 除錯間歇性異常

  5. 使用 truss

  6. 讓伺服器轉儲核心

  7. Solaris 和核心轉儲

  8. 取得並分析 TCP 封包追蹤

使用 gdb

如果您使用 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) 

關於上述範例需要注意幾點

  1. gdb httpd 」指令不包含任何 httpd 的命令提示字,這些是在 gdb 中進行「 run 」指令時提供。

  2. 我在啟動執行之前先設一個中斷點,讓執行會停在 ap_process_request()

  3. s 」指令會逐行執行程式碼並進入被呼叫的程序,而「 n 」(下一個) 指令會逐行執行程式碼但不進入被呼叫的程序。

  4. 可以透過「 b 」指令設定其他中斷點,並以「 c 」指令繼續執行。

  5. 使用「 where 」(又稱為「 bt 」) 指令可查看一個堆疊回溯,找出被呼叫的程序的順序及它們的參數值。

  6. 使用「 p 」指令可列印變數的值。

在根目錄的檔案中,名為 .gdbinit 的檔案提供了有用的巨集,用於列印 httpd 的各種內部結構,例如表格( dump_table )、聯隊( dump_brigade ) 以及濾網鏈( dump_filters )。

如果您要偵錯一個可重複的崩潰,只要如上執行 gdb 和提出要求 -- gdb 應會擷取崩潰並提供發生崩潰處的提示。

如果您要偵錯一個明顯的無限迴圈,只要如上執行 gdb 並輸入 Control-C -- gdb 會中斷程序並提供停止程序處的提示。

如果您要偵錯一個系統崩潰,且有崩潰後產生的核心檔案,請執行以下動作

    % gdb httpd -c core
    (gdb) where

它將(希望)在處理過程中列印核心傾印發生處的堆疊回溯。

在 Unix 上取得即時回溯

回溯可讓您得知被呼叫的程序層級,才能到達程序的特定點。在有些平台上,您可以取得任何程序的即時回溯。

對於基於 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

在 Windows 上取得即時回溯

  1. 在根目錄 Apache2 目錄樹中解壓縮 -symbols.zip 檔案(從 Apache 下載網站取得)(通常可在其中找到 bin, htdocs, modules 等)。這些 pdb 檔案應會與它們顯示的.exe,.dll,.so 二進位檔案一起解壓縮,例如 mod_usertrack.pdb 會與 mod_usertrack.so 一起解壓縮。

  2. 選擇性:從 Sysinternals 取得 procdump

  3. 呼叫 drwtsn32,並確保您建置了一個崩潰傾印檔,而且所有執行緒內容都已傾印,記錄檔和崩潰傾印檔路徑合理,且(視錯誤性質而定)您選擇了適當的崩潰傾印檔類型。(「完整」相當龐大,但有時候程式設計師一定需要載入您的崩潰傾印檔到除錯器中,並開始分解實際發生的情況。對於您第一次執行此程序來說,「精簡」就已足夠。)

    此時,您也可以使用 procdumpprocdump -ma 取代。

  4. 請注意,如果您之前已安裝然後解除安裝了其他除錯軟體,您可能需要呼叫 drwtsn32 -i,才能讓 Dr. Watson 成為您的預設崩潰傾印檔工具。這會取代「向 MS 回報問題」對話方塊。(如果您電腦上已安裝 Visual Studio 或 windbg 等完整除錯器時,請勿執行此動作,除非您先備份 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug 登錄結構中的 Debugger 登錄值。使用多種工具的開發人員可能想要在該處保留不同工具的「Debugger」條目的副本,以利快速切換。)

  5. 呼叫工作管理員,選擇「顯示來自所有使用者的程序」,並修改「檢視 -> 選取欄位」,以至少包含 PID執行緒數目。您只要變更一次,工作管理員就會記住您的喜好設定。

  6. 現在,追蹤發生錯誤導致當機的 Apache。父程序大約有三個執行緒,我們不需要處理。我們需要的子工作程序執行緒更多(比您使用 ThreadsPerChild 指令設定的多一些)。程序名稱是 Apache(1.3 和 2.0 版)或 httpd(2.2 和 2.4 版)。記下子工作程序的 PID。

  7. 使用您在上方所記下的 {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

使用『truss/trace/strace』來追蹤系統呼叫和訊號

大部分的 Unix-based 系統中至少會有一個指令,可顯示當執行中的程序存取時出現的系統呼叫和訊號追蹤。此指令在大部分的 SVR4-based 系統上稱為 truss ,在許多其他系統上稱為 tracestrace

使用 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 的其他有用選項為

讓伺服器傾印核心

奇怪的是,有時您實際上想要強制伺服器崩潰,以便您可以查看一些瘋狂的行為。正常來說,這可以透過使用 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 與核心傾印檔

在 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

取得和分析 TCP 封包追蹤

這是一個過於深入的主題,無法在此文件當中完整說明。以下是討論和工具的一些指標