在學習優秀的原始碼時是 少不了源碼的跟蹤與調試,它不僅是我們解決程式bug的有效途徑 也是我們理解、學習優秀源碼的有效途徑。
本文主要介紹一些源碼調試的方法,並結合Nginx 源碼進行樣本。
1, 利用GDB調試
a,首先你應該熟悉 GDB 調試的一些基本命令(不熟悉的移步 至
用GDB偵錯工具 ,熟悉step,run,break,list,info,continue等命令)。
b, 下載nginx 源碼,這裡使用nginx-1.0.14,解壓檔案。其中auto檔案夾裡包含了configure 運行時的各種命令集合,src是源碼。為了利用
GDB調試Nginx,需要在產生Nginx程式時把 -g 編譯選項開啟。我們需要修改
auto/cc/conf 檔案 把 ngx_compile_opt="-c" 加上 -g 選項
變為 ngx_compile_opt="-c -g", 下一步執行configure命令: sudo ./configure ,然後運行命令:vim objs/Makefile 確認一下-g參數是
否加上了。效果如。
-g 編譯選項已開啟,然後執行命令: sudo make .(如果之前已經執行過make,那麼第二次make時 需要 確保能夠重新編譯,此時可以通過
重新整理所有源檔案時間戳記,間接達到重新編譯出一個新的Nginx可執行程式,命令為: find . -name "*.c" | xargs touch ), 好了,Nginx編譯成功。
c, 啟動 nginx ,在objs目錄下執行命令:sudo ./nginx ,成功運行nginx後執行命令: ps -ef | grep nginx, 查看nginx的master及worker進程的PID,
如果對nginx 背景工作處理序 2177進行gdb調試,那麼可以利用gdb的 -p 選項。 命令:
gdb -p 2177.
當有多個背景工作處理序時 調試起來比較麻煩。我們可以修改配置項,修改檔案 nginx.conf 。加入master_process off; 單進程模式 將監控進程和工作
進程邏輯全部合在一個 進程裡。 此時只有一個進程,可以進行gdb調試效果
我們知道背景工作處理序會停留在epoll_wait處等待相應的事件發生,而這個函數調用被封裝在ngx_process_events_and_timers 中。於是我們在這個函數出設定一個斷點:
b ngx_event.c:ngx_process_events_and_timers ,結合gdb 命令c ,s,n :
採用命令c,使得nginx一直運行,直到遇到第一個斷點,處理事件的方法是ngx_process_events,於是我們用命令 s 跟蹤進去這個函數 .當執行到epoll_wait函數的時候,發
現進程停留在這裡,不能在向下執行。這就證明了 worker子進程阻塞在epoll_wait函數調用處。
此時我們在另一個終端執行下列命令,以向nginx發送訊息: curl -I localhost . 可以看到請求已經發送,正在等待回應。
此時繼續執行命令 c 即可在另一終端得到 回應。此時可以通過bt 命令查看單進程模式下函數調用的過程。。
curl
gdb bt
2,利用strace、 pstack 調試 nginx
a, strace 常用來跟蹤進程執行時的系統調用和所接收的訊號。 在Linux世界,進程不能直接存取硬體裝置,當進程需要訪問硬體裝置(比如讀取磁碟檔案,
接收網路資料等等)時,必須由使用者態模式切換至核心態模式,通 過系統調用訪問硬體裝置。strace可以跟蹤到一個進程產生的系統調用,包括參數,
傳回值,執行消耗的時間。 此外命令 ltrace 用來查看 動態庫函數調用。
b, 那具體是哪一個函數調用呢?在strace輸出結果中並不能找到答案,因其輸出顯示都是系統調用,要顯示程式中函數調用棧資訊,就輪到pstack上場了。
pstack是一個指令碼工具,其核心實現就是使用了gdb以及thread apply all bt命令.
可以通過命令 man 來 查看 Man手冊中 strace、ltrace、pstack的具體具體用法。
我們在此 修改 nginx的設定檔 nginx.conf 使其為 僅具有 一個 master 進程和一個 worker進程. 把1中修改的 master_process off; 註解掉 加上 #
並在設定檔上加上 worker_processes 1;
關閉之前的nginx 重新啟動 nginx。 利用命令: ps -ef | grep nginx 查看當前存在的nginx進程,然後用strace 命令的參數 -p 選項跟蹤 Nginx 背景工作處理序,。
可以看到背景工作處理序阻塞在 epoll_wait 系統調用上,因為此時沒有用戶端請求,nginx就阻塞於此,在另一個終端執行curl 命令: curl -I localhost ,
再來看strace輸出 結果。
strace輸出的每一行記錄一次系統調用,等號左邊是系統調用名以及調用參數,等號右邊是該系統調用的傳回值。
通過上面的系統調用我們可以做出如下分析:
⑴. epoll_wait傳回值為1,表示有1個描述符存在可讀/寫事件,這裡當然是可讀事件。
⑵. accept4接受該請求,返回的數字3表示socket的檔案描述符。
⑶. epoll_ctl把accept4建立的socket通訊端(注意參數3)加入到事件監聽機制裡。
⑷. recv從發生可讀事件的socket檔案描述符內讀取資料,讀取的資料存在第二個參數內,讀取了79個位元組。
⑸. stat64判斷用戶端請求的html檔案是否存在,傳回值為0表示存在。
⑹. open/fstat64開啟並擷取檔案狀態資訊。open檔案返回的檔案描述符為9,後面幾個系統調用都用到這個值。
⑺. writev把回應標頭通過檔案描述符3代表的socket通訊端發給用戶端。
⑻. sendfile64把檔案描述符9代表的響應體通過檔案描述符3代表的socket通訊端發給用戶端。
⑼. 再往檔案描述符4代表的記錄檔內write一條日誌資訊。
⑽. recv看用戶端是否還發了其它待處理的請求/資訊。
⑾. 最後關閉檔案描述符3代表的socket通訊端。
由於strace能夠提供nginx執行過程中的這些內部資訊,所以在出現一些奇怪現象,比如nginx啟動失敗、響應的檔案資料和預期不一致、莫名其妙的Segment Fault段錯誤、
存在效能瓶頸(利用-T選項跟蹤各個函數的消耗時間),利用strace也許能提供一些相關協助。最後,要退出strace跟蹤,按ctrl+c即可。
命令strace跟蹤的是系統調用,對於nginx本身的函數調用關係無法給出更為明朗的資訊,如果我們發現nginx當前運行不正常,想知道nginx當前內部到底在執行什麼函數,
那麼命令pstack就是一個非常方便實用的工具。pstack的使用也非常簡單,後面跟進程id即可,比如在無用戶端請求的情況下,nginx阻塞在epoll_wait系統調用處,此時
利用pstack查看到的nginx函數呼叫堆疊關係如下:
從main()函數到epoll_wait()函數的調用關係一目瞭然,和在gdb內看到的堆棧資訊一樣。我們可以利用此進行分析最佳化等。
除了 1,2 還有方法 加樁調試等方法在此不再敘述。以後有機會可以介紹下特殊應用邏輯的調試。
好了,多謝閱讀。
參考資料:
http://code.google.com/p/nginxsrp/wiki/NginxCodeReview
<深入剖析Nginx>