天天看點

《深入剖析Nginx》——2.6 特殊應用邏輯的調試

本節書摘來自異步社群《深入剖析nginx》一書中的第2章,第2.6節,作者: 高群凱 更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

前面所講的調試方法都是針對nginx本身很容易跑到的邏輯,而對于某些隻有在特定情況下才會被執行到的代碼,又該怎樣去調試呢?舉個例子,我們知道nginx裡有大量的逾時處理,比如,如果讀取用戶端請求頭部資料逾時,nginx就将執行對應的逾時處理函數,假設我想通過單步執行的方式來了解這部分相關邏輯,無疑就得讓nginx的執行邏輯走到這條路徑上來。由于此時影響nginx行為的決定因素是用戶端所發送的請求頭部資料,我們就必須在用戶端做動作來構造出這種場景。一般的浏覽器,如ie、firefox等送出請求的行為基本已經固定,而常用的指令行工具,比如curl、wget的源代碼又略顯複雜,定制它們的請求動作和改變環境來構造所需的場景相對較為麻煩,是以一種更便利的方法就是我們自己寫個socket通信的用戶端即可,而這并不需要多少代碼。

下面給出一個測試示例用代碼,為了簡單,是以伺服器ip和端口都是固定在代碼裡的,用于發送資料的函數write()調用也未做傳回值判斷等(後續還有其他類似測試代碼也是如此,這點請注意)。

該程式的代碼比較簡單,變量req_header存儲的是http請求頭部資料,被注釋掉的是正常的請求頭,而我這裡使用的請求頭是不完整的(正常請求頭可以用wget、curl或wireshark1等工具獲得,異常請求頭必須根據自己所預期場景來進行構造,比如在這裡,其他異常情況的請求頭可能導緻nginx以其他錯誤方式傳回而不是進行逾時監控),是以這會使得nginx在接收到該請求後,持續等待進一步的頭部資料,直到逾時。編譯這個源代碼得到應用程式request_timeout。

将接受http請求的nginx工作程序綁定到gdb,然後在逾時函數ngx_event_expire_timers()内的第149行下斷點并按c繼續。

這個斷點是nginx已經捕獲到逾時事件,設定其逾時旗标并調用對應的回調函數進行處理。在另一個gdb内執行request_timeout,當然,我們需要讓它停止在第47行2,避免程式退出,導緻它與nginx工作程序之間的連接配接斷開。等待約60秒(nginx讀取請求頭部資料的預設逾時時間為60秒,可通過配置指令client_header_timeout修改)後,attach到nginx工作程序的gdb就會斷下來,按s跟進函數,再順着執行路徑而下就會發現此時nginx将執行到這個邏輯裡。

将執行到第976行的if判斷内部,即連接配接逾時,我們看到對于在讀取請求頭部資料逾時的情況下,nginx工作程序最後所做的幾步主要工作,即日志記錄、關閉請求并傳回。通過這樣一個執行個體,我們也就了解了如何去調試這樣的特殊應用邏輯,不僅僅隻是針對用戶端,對于後端應用伺服器也能如此進行模拟構造。

上面示範的環境構造步驟,雖然比較簡單且能真實模拟,但畢竟需要我們了解它的細節,也就是需知道觸發這種情況的前提條件,如果前提條件比較多,那麼模拟起來可能還是比較麻煩,其實,如果我們隻是了解一下nginx如果這樣執行會怎麼樣,那麼完全可以通過利用gdb的p指令或set指令修改對應條件變量的值來達到目的。比如在前面的例子裡,在一般情況下,rev->timedout為0,即不逾時而無法執行第977-980行代碼,但我又想看一下執行這幾條語句的情況會怎麼樣,那麼就可以像下面這樣做。

通過執行“p rev->timedout=1”把變量rev->timedout的值改為1,這樣就執行到第977行了,當然,如上所示,set指令也可以改變nginx執行變量的值。值得特别注意的是,這樣做僅僅隻是因為改變了條件判斷的變量值而使得nginx程式執行路徑發生變化,但是其在新的路徑上,可能由于使用的某些變量值不是原本所期望的情況而導緻執行異常。

繼續閱讀