GDB 是 linux 系統(tǒng)上常用的 c/c++ 調(diào)試工具,功能十分強大。對于較為復(fù)雜的系統(tǒng),比如多進(jìn)程系統(tǒng),如何使用 GDB 調(diào)試呢?考慮下面這個三進(jìn)程系統(tǒng):
進(jìn)程
Proc2 是 Proc1 的子進(jìn)程,Proc3 又是 Proc2 的子進(jìn)程。如何使用 GDB 調(diào)試 proc2 或者 proc3 呢?
實際上,GDB 沒有對多進(jìn)程程序調(diào)試提供直接支持。例如,使用GDB調(diào)試某個進(jìn)程,如果該進(jìn)程fork了子進(jìn)程,GDB會繼續(xù)調(diào)試該進(jìn)程,子進(jìn)程會不受干擾地運行下去。如果你事先在子進(jìn)程代碼里設(shè)定了斷點,子進(jìn)程會收到SIGTRAP信號并終止。那么該如何調(diào)試子進(jìn)程呢?其實我們可以利用GDB的特點或者其他一些輔助手段來達(dá)到目的。此外,GDB 也在較新內(nèi)核上加入一些多進(jìn)程調(diào)試支持。
接下來我們詳細(xì)介紹幾種方法,分別是 follow-fork-mode 方法,attach 子進(jìn)程方法和 GDB wrapper 方法。
follow-fork-mode
在2.5.60版Linux內(nèi)核及以后,GDB對使用fork/vfork創(chuàng)建子進(jìn)程的程序提供了follow-fork-mode選項來支持多進(jìn)程調(diào)試。
follow-fork-mode的用法為:
set follow-fork-mode [parent|child]
- parent: fork之后繼續(xù)調(diào)試父進(jìn)程,子進(jìn)程不受影響。
- child: fork之后調(diào)試子進(jìn)程,父進(jìn)程不受影響。
因此如果需要調(diào)試子進(jìn)程,在啟動gdb后:
(gdb) set follow-fork-mode child
并在子進(jìn)程代碼設(shè)置斷點。
此外還有detach-on-fork參數(shù),指示GDB在fork之后是否斷開(detach)某個進(jìn)程的調(diào)試,或者都交由GDB控制:
set detach-on-fork [on|off]
- on: 斷開調(diào)試follow-fork-mode指定的進(jìn)程。
- off: gdb將控制父進(jìn)程和子進(jìn)程。follow-fork-mode指定的進(jìn)程將被調(diào)試,另一個進(jìn)程置于暫停(suspended)狀態(tài)。
注意,最好使用GDB 6.6或以上版本,如果你使用的是GDB6.4,就只有follow-fork-mode模式。
follow-fork-mode/detach-on-fork的使用還是比較簡單的,但由于其系統(tǒng)內(nèi)核/gdb版本限制,我們只能在符合要求的系統(tǒng)上才能使用。而且,由于follow-fork-mode的調(diào)試必然是從父進(jìn)程開始的,對于fork多次,以至于出現(xiàn)孫進(jìn)程或曾孫進(jìn)程的系統(tǒng),例如上圖3進(jìn)程系統(tǒng),調(diào)試起來并不方便。
Attach子進(jìn)程
眾所周知,GDB有附著(attach)到正在運行的進(jìn)程的功能,即attach
例如我們要調(diào)試某個進(jìn)程RIM_Oracle_Agent.9i,首先得到該進(jìn)程的pid
[root@tivf09 tianq]# ps -ef|grep RIM_Oracle_Agent.9i nobody 6722 6721 0 05:57 ? 00:00:00 RIM_Oracle_Agent.9i root 7541 27816 0 06:10 pts/3 00:00:00 grep -i rim_oracle_agent.9i
通過pstree可以看到,這是一個三進(jìn)程系統(tǒng),oserv是RIM_Oracle_prog的父進(jìn)程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父進(jìn)程。
[root@tivf09 root]# pstree -H 6722
通過 pstree 察看進(jìn)程
啟動GDB,attach到該進(jìn)程
用 GDB 連接進(jìn)程
現(xiàn)在就可以調(diào)試了。一個新的問題是,子進(jìn)程一直在運行,attach上去后都不知道運行到哪里了。有沒有辦法解決呢?
一個辦法是,在要調(diào)試的子進(jìn)程初始代碼中,比如main函數(shù)開始處,加入一段特殊代碼,使子進(jìn)程在某個條件成立時便循環(huán)睡眠等待,attach到進(jìn)程后在該代碼段后設(shè)上斷點,再把成立的條件取消,使代碼可以繼續(xù)執(zhí)行下去。
至于這段代碼所采用的條件,看你的偏好了。比如我們可以檢查一個指定的環(huán)境變量的值,或者檢查一個特定的文件存不存在。以文件為例,其形式可以如下:
void debug_wait(char *tag_file) { while(1) { if (tag_file存在) 睡眠一段時間; else break; } }
當(dāng)attach到進(jìn)程后,在該段代碼之后設(shè)上斷點,再把該文件刪除就OK了。當(dāng)然你也可以采用其他的條件或形式,只要這個條件可以設(shè)置/檢測即可。
Attach進(jìn)程方法還是很方便的,它能夠應(yīng)付各種各樣復(fù)雜的進(jìn)程系統(tǒng),比如孫子/曾孫進(jìn)程,比如守護(hù)進(jìn)程(daemon process),唯一需要的就是加入一小段代碼。
GDB wrapper
很多時候,父進(jìn)程 fork 出子進(jìn)程,子進(jìn)程會緊接著調(diào)用 exec族函數(shù)來執(zhí)行新的代碼。對于這種情況,我們也可以使用gdb wrapper 方法。它的優(yōu)點是不用添加額外代碼。
其基本原理是以gdb調(diào)用待執(zhí)行代碼作為一個新的整體來被exec函數(shù)執(zhí)行,使得待執(zhí)行代碼始終處于gdb的控制中,這樣我們自然能夠調(diào)試該子進(jìn)程代碼。
還是上面那個例子,RIM_Oracle_prog fork出子進(jìn)程后將緊接著執(zhí)行RIM_Oracle_Agent.9i的二進(jìn)制代碼文件。我們將該文件重命名為RIM_Oracle_Agent.9i.binary,并新建一個名為RIM_Oracle_Agent.9i的shell腳本文件,其內(nèi)容如下:
[root@tivf09 bin]# mv RIM_Oracle_Agent.9i RIM_Oracle_Agent.9i.binary [root@tivf09 bin]# cat RIM_Oracle_Agent.9i #!/bin/sh gdb RIM_Oracle_Agent.binary
當(dāng)fork的子進(jìn)程執(zhí)行名為RIM_Oracle_Agent.9i的文件時,gdb會被首先啟動,使得要調(diào)試的代碼處于gdb控制之下。
新的問題來了。子進(jìn)程是在gdb的控制下了,但還是不能調(diào)試:如何與gdb交互呢?我們必須以某種方式啟動gdb,以便能在某個窗口/終端與gdb交互。具體來說,可以使用xterm生成這個窗口。
xterm是X window系統(tǒng)下的模擬終端程序。比如我們在Linux桌面環(huán)境GNOME中敲入xterm命令:
xterm
就會跳出一個終端窗口:
終端
如果你是在一臺遠(yuǎn)程linux服務(wù)器上調(diào)試,那么可以使用VNC(Virtual Network Computing) viewer從本地機器連接到服務(wù)器上使用xterm。在此之前,需要在你的本地機器上安裝VNC viewer,在服務(wù)器上安裝并啟動VNC server。大多數(shù)linux發(fā)行版都預(yù)裝了vnc-server軟件包,所以我們可以直接運行vncserver命令。注意,第一次運行vncserver時會提示輸入密碼,用作VNC viewer從客戶端連接時的密碼??梢栽赩NC server機器上使用vncpasswd命令修改密碼。
[root@tivf09 root]# vncserver New 'tivf09:1 (root)' desktop is tivf09:1 Starting applications specified in /root/.vnc/xstartup Log file is /root/.vnc/tivf09:1.log [root@tivf09 root]# [root@tivf09 root]# ps -ef|grep -i vnc root 19609 1 0 Jun05 ? 00:08:46 Xvnc :1 -desktop tivf09:1 (root) -httpd /usr/share/vnc/classes -auth /root/.Xauthority -geometry 1024x768 -depth 16 -rfbwait 30000 -rfbauth /root/.vnc/passwd -rfbport 5901 -pn root 19627 1 0 Jun05 ? 00:00:00 vncconfig -iconic root 12714 10599 0 01:23 pts/0 00:00:00 grep -i vnc [root@tivf09 root]#
Vncserver是一個Perl腳本,用來啟動Xvnc(X VNC server)。X client應(yīng)用,比如xterm,VNC viewer都是和它通信的。如上所示,我們可以使用的DISPLAY值為tivf09:1?,F(xiàn)在就可以從本地機器使用VNC viewer連接過去:
VNC viewer:輸入服務(wù)器
輸入密碼:
VNC viewer:輸入密碼
登錄成功,界面和服務(wù)器本地桌面上一樣:
VNC viewer
下面我們來修改RIM_Oracle_Agent.9i腳本,使它看起來像下面這樣:
#!/bin/sh export DISPLAY=tivf09:1.0; xterm -e gdb RIM_Oracle_Agent.binary
如果你的程序在exec的時候還傳入了參數(shù),可以改成:
#!/bin/sh export DISPLAY=tivf09:1.0; xterm -e gdb --args RIM_Oracle_Agent.binary $@
最后加上執(zhí)行權(quán)限
[root@tivf09 bin]# chmod 755 RIM_Oracle_Agent.9i
現(xiàn)在就可以調(diào)試了。運行啟動子進(jìn)程的程序:
[root@tivf09 root]# wrimtest -l 9i_linux Resource Type : RIM Resource Label : 9i_linux Host Name : tivf09 User Name : mdstatus Vendor : Oracle Database : rim Database Home : /data/oracle9i/920 Server ID : rim Instance Home : Instance Name : Opening Regular Session...
程序停住了。從VNC viewer中可以看到,一個新的gdb xterm窗口在服務(wù)器端打開了
gdb xterm 窗口
[root@tivf09 root]# ps -ef|grep gdb nobody 24312 24311 0 04:30 ? 00:00:00 xterm -e gdb RIM_Oracle_Agent.binary nobody 24314 24312 0 04:30 pts/2 00:00:00 gdb RIM_Oracle_Agent.binary root 24326 10599 0 04:30 pts/0 00:00:00 grep gdb
運行的正是要調(diào)試的程序。設(shè)置好斷點,開始調(diào)試吧!
注意,下面的錯誤一般是權(quán)限的問題,使用 xhost 命令來修改權(quán)限:
xterm 錯誤
[root@tivf09 bin]# export DISPLAY=tivf09:1.0 [root@tivf09 bin]# xhost + access control disabled, clients can connect from any host
xhost + 禁止了訪問控制,從任何機器都可以連接過來。考慮到安全問題,你也可以使用xhost + <你的機器名>。
小結(jié)
上述三種方法各有特點和優(yōu)劣,因此適應(yīng)于不同的場合和環(huán)境:
- follow-fork-mode方法:方便易用,對系統(tǒng)內(nèi)核和GDB版本有限制,適合于較為簡單的多進(jìn)程系統(tǒng)
- attach子進(jìn)程方法:靈活強大,但需要添加額外代碼,適合于各種復(fù)雜情況,特別是守護(hù)進(jìn)程
- GDB wrapper方法:專用于fork+exec模式,不用添加額外代碼,但需要X環(huán)境支持(xterm/VNC)。
評論
查看更多