使用 GDB 調試多進程程序

使用 GDB 調試多進程程序

來源 https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.htmlhtml

 

GDB 是 linux 系統上經常使用的 c/c++ 調試工具,功能十分強大。對於較爲複雜的系統,好比多進程系統,如何使用 GDB 調試呢?考慮下面這個三進程系統:linux

進程

進程

Proc2 是 Proc1 的子進程,Proc3 又是 Proc2 的子進程。如何使用 GDB 調試 proc2 或者 proc3 呢?c++

實際上,GDB 沒有對多進程程序調試提供直接支持。例如,使用GDB調試某個進程,若是該進程fork了子進程,GDB會繼續調試該進程,子進程會不受干擾地運行下去。若是你事先在子進程代碼裏設定了斷點,子進程會收到SIGTRAP信號並終止。那麼該如何調試子進程呢?其實咱們能夠利用GDB的特色或者其餘一些輔助手段來達到目的。此外,GDB 也在較新內核上加入一些多進程調試支持。shell

接下來咱們詳細介紹幾種方法,分別是 follow-fork-mode 方法,attach 子進程方法和 GDB wrapper 方法。安全

follow-fork-mode

在2.5.60版Linux內核及之後,GDB對使用fork/vfork建立子進程的程序提供了follow-fork-mode選項來支持多進程調試。服務器

follow-fork-mode的用法爲:oracle

set follow-fork-mode [parent|child]app

  • parent: fork以後繼續調試父進程,子進程不受影響。
  • child: fork以後調試子進程,父進程不受影響。

所以若是須要調試子進程,在啓動gdb後:函數

1
(gdb) set follow-fork-mode child

並在子進程代碼設置斷點。工具

此外還有detach-on-fork參數,指示GDB在fork以後是否斷開(detach)某個進程的調試,或者都交由GDB控制:

set detach-on-fork [on|off]

  • on: 斷開調試follow-fork-mode指定的進程。
  • off: gdb將控制父進程和子進程。follow-fork-mode指定的進程將被調試,另外一個進程置於暫停(suspended)狀態。

注意,最好使用GDB 6.6或以上版本,若是你使用的是GDB6.4,就只有follow-fork-mode模式。

follow-fork-mode/detach-on-fork的使用仍是比較簡單的,但因爲其系統內核/gdb版本限制,咱們只能在符合要求的系統上才能使用。並且,因爲follow-fork-mode的調試必然是從父進程開始的,對於fork屢次,以致於出現孫進程或曾孫進程的系統,例如上圖3進程系統,調試起來並不方便。

Attach子進程

衆所周知,GDB有附着(attach)到正在運行的進程的功能,即attach <pid>命令。所以咱們能夠利用該命令attach到子進程而後進行調試。

例如咱們要調試某個進程RIM_Oracle_Agent.9i,首先獲得該進程的pid

1
2
3
[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能夠看到,這是一個三進程系統,oserv是RIM_Oracle_prog的父進程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父進程。

1
[root@tivf09 root]# pstree -H 6722
經過 pstree 察看進程

經過 pstree 察看進程

啓動GDB,attach到該進程

用 GDB 鏈接進程

用 GDB 鏈接進程

如今就能夠調試了。一個新的問題是,子進程一直在運行,attach上去後都不知道運行到哪裏了。有沒有辦法解決呢?

一個辦法是,在要調試的子進程初始代碼中,好比main函數開始處,加入一段特殊代碼,使子進程在某個條件成立時便循環睡眠等待,attach到進程後在該代碼段後設上斷點,再把成立的條件取消,使代碼能夠繼續執行下去。

至於這段代碼所採用的條件,看你的偏好了。好比咱們能夠檢查一個指定的環境變量的值,或者檢查一個特定的文件存不存在。以文件爲例,其形式能夠以下:

1
2
3
4
5
6
7
8
9
10
void debug_wait(char *tag_file)
{
     while(1)
     {
         if (tag_file存在)
             睡眠一段時間;
         else
             break;
     }
}

當attach到進程後,在該段代碼以後設上斷點,再把該文件刪除就OK了。固然你也能夠採用其餘的條件或形式,只要這個條件能夠設置/檢測便可。

Attach進程方法仍是很方便的,它可以應付各類各樣複雜的進程系統,好比孫子/曾孫進程,好比守護進程(daemon process),惟一須要的就是加入一小段代碼。

GDB wrapper

不少時候,父進程 fork 出子進程,子進程會緊接着調用 exec族函數來執行新的代碼。對於這種狀況,咱們也可使用gdb wrapper 方法。它的優勢是不用添加額外代碼。

其基本原理是以gdb調用待執行代碼做爲一個新的總體來被exec函數執行,使得待執行代碼始終處於gdb的控制中,這樣咱們天然可以調試該子進程代碼。

仍是上面那個例子,RIM_Oracle_prog fork出子進程後將緊接着執行RIM_Oracle_Agent.9i的二進制代碼文件。咱們將該文件重命名爲RIM_Oracle_Agent.9i.binary,並新建一個名爲RIM_Oracle_Agent.9i的shell腳本文件,其內容以下:

1
2
3
4
[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

當fork的子進程執行名爲RIM_Oracle_Agent.9i的文件時,gdb會被首先啓動,使得要調試的代碼處於gdb控制之下。

新的問題來了。子進程是在gdb的控制下了,但仍是不能調試:如何與gdb交互呢?咱們必須以某種方式啓動gdb,以便能在某個窗口/終端與gdb交互。具體來講,可使用xterm生成這個窗口。

xterm是X window系統下的模擬終端程序。好比咱們在Linux桌面環境GNOME中敲入xterm命令:

xterm

xterm

就會跳出一個終端窗口:

終端

終端

若是你是在一臺遠程linux服務器上調試,那麼可使用VNC(Virtual Network Computing) viewer從本地機器鏈接到服務器上使用xterm。在此以前,須要在你的本地機器上安裝VNC viewer,在服務器上安裝並啓動VNC server。大多數linux發行版都預裝了vnc-server軟件包,因此咱們能夠直接運行vncserver命令。注意,第一次運行vncserver時會提示輸入密碼,用做VNC viewer從客戶端鏈接時的密碼。能夠在VNC server機器上使用vncpasswd命令修改密碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[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應用,好比xterm,VNC viewer都是和它通訊的。如上所示,咱們可使用的DISPLAY值爲tivf09:1。如今就能夠從本地機器使用VNC viewer鏈接過去:

VNC viewer:輸入服務器

VNC viewer:輸入服務器

輸入密碼:

VNC viewer:輸入密碼

VNC viewer:輸入密碼

登陸成功,界面和服務器本地桌面上同樣:

VNC viewer

VNC viewer

下面咱們來修改RIM_Oracle_Agent.9i腳本,使它看起來像下面這樣:

1
2
#!/bin/sh
export DISPLAY=tivf09:1.0; xterm -e gdb RIM_Oracle_Agent.binary

若是你的程序在exec的時候還傳入了參數,能夠改爲:

1
2
#!/bin/sh
export DISPLAY=tivf09:1.0; xterm -e gdb --args RIM_Oracle_Agent.binary $@

最後加上執行權限

1
[root@tivf09 bin]# chmod 755 RIM_Oracle_Agent.9i

如今就能夠調試了。運行啓動子進程的程序:

1
2
3
4
5
6
7
8
9
10
11
12
[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窗口在服務器端打開了

gdb xterm 窗口

gdb xterm窗口

1
2
3
4
[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

運行的正是要調試的程序。設置好斷點,開始調試吧!

注意,下面的錯誤通常是權限的問題,使用 xhost 命令來修改權限:

xterm 錯誤

xterm 錯誤

1
2
3
[root@tivf09 bin]# export DISPLAY=tivf09:1.0
[root@tivf09 bin]# xhost +
access control disabled, clients can connect from any host

xhost + 禁止了訪問控制,從任何機器均可以鏈接過來。考慮到安全問題,你也可使用xhost + <你的機器名>。

小結

上述三種方法各有特色和優劣,所以適應於不一樣的場合和環境:

  • follow-fork-mode方法:方便易用,對系統內核和GDB版本有限制,適合於較爲簡單的多進程系統
  • attach子進程方法:靈活強大,但須要添加額外代碼,適合於各類複雜狀況,特別是守護進程
  • GDB wrapper方法:專用於fork+exec模式,不用添加額外代碼,但須要X環境支持(xterm/VNC)。
 

相關主題

相關文章
相關標籤/搜索