gdb調試多進程 gdb 多進程 多線程調試方法 gdb調試

關於gdb調試多進程很早之前就看過,但一直沒有總結很深入的學習,今天又碰到這個問題了,記錄下

記得kk跟我說過,在開發程序的時候儘量少調試,儘量少使用gdb等調試工具,儘量多的使用log等方式來定位錯誤,這樣會大大減少調試所需的時間和精力,同時也能快速的定位問題。當然,利用gdb調試還是有很多的優勢的,比如對於我這個菜鳥來說,可以好好學習下強大的gdb~

關於gdb的基本調試,基本已經掌握了,經常會使用的一些命令有:

s----step

n----next

c-----continue

p----print

bt---backtrace

b----break

等等

今天查找多進程調試的時候突然注意到stop命令,之前一直沒有留意,發現這個命令可以在多進程調試中起到作用

下面描述下多進程的調試

關於gdb調試多進程,很經典的一篇文章是IBM developerworks上面的一篇:

http://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.html

詳細內容見下面描述。

IBM developerworks上的這篇文章介紹了三種gdb調試多進程的方法:

1、follow-fork-mode方法

這是一種比較簡單的方式,通過set follow-fork-mode的方式來調試父進程或子進程

2、attach的方式,這種應該是比較常用的方式,也是很多文章中介紹的一種方式。這種方式一般需要增加一些代碼來實現對進程的attach

3、GDB wrapper方法:專用於fork+exec模式,不用添加額外代碼,但需要X環境支持(xterm/VNC)這種方式沒有使用過,一般對於企業開發的話是不是很多都沒有x環境支持的吧(猜測:))

follow-fork-mode方式比較簡單,通過set follow-fork-mode child | parent來覺得跟蹤child還是parent,然後可以設置斷點跟蹤了

attach方法首先要找到待調試進程的pid,一般是通過ps相關命令,例如pstree、ps –aux | grep 進程名稱等等,然後通過attach 關聯到相應的進程。

下面分別是一些介紹文章:

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

進程
進程

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

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

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

follow-fork-mode

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

follow-fork-mode的用法爲:

set follow-fork-mode [parent|child]

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

因此如果需要調試子進程,在啓動gdb後:


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

此外還有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


通過pstree可以看到,這是一個三進程系統,oserv是RIM_Oracle_prog的父進程,RIM_Oracle_prog又是RIM_Oracle_Agent.9i的父進程。


通過 pstree 察看進程
通過 pstree 察看進程

啓動GDB,attach到該進程

用 GDB 連接進程
用 GDB 連接進程

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

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

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


當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腳本文件,其內容如下:


當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命令修改密碼。


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腳本,使它看起來像下面這樣:


如果你的程序在exec的時候還傳入了參數,可以改成:


最後加上執行權限


現在就可以調試了。運行啓動子進程的程序:


程序停住了。從VNC viewer中可以看到,一個新的gdb xterm窗口在服務器端打開了

gdb xterm 窗口
gdb xterm窗口


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

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

xterm 錯誤
xterm 錯誤


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

小結

上述三種方法各有特點和優劣,因此適應於不同的場合和環境:

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

參考資料

======================================================

http://hi.baidu.com/happyge/blog/item/6c69440f1ad5dd3e6059f302.html

碰到多進程的程序如何調試呢?默認情況下,你next下來,跟的路徑都是主進程的,而你想跟的子進程路徑沒跑到?怎麼辦呢?

有幾種方法,今天看了看attach方法,覺得不錯,特記錄如下:

原理:運行多進程程序,得到要跟的子進程的ID;然後用開gdb,用attach+ID,然後stop,爲什麼要stop?防止子進程自己跑完,所以要stop,然後可以設斷點,觀測點,什麼的。設完後,可以step,下一步,向下跟。

主要原理是這樣。下面舉個例子:

#include<stdio.h>
int main()
{
        if(fork() == 0)
        {
                int b = 9;
                sleep(60);
                int a =1;
                int c = 90;
                int d =5;
                printf("child/n");
        }
        else
        {
                wait(NULL);
                printf("parant/n");
        }
        return 0;
}

怎麼樣跟到子進程裏面去 ?

1 後臺運行該程序,可以得到進程ID

2 gdb下,attach+id

3 stop,然後設置斷點,觀察點等等

4 step

(gdb) attach 12606
Attaching to program: /home/purerain/test/f, process 12606
Symbols already loaded for /lib/tls/libc.so.6
Symbols already loaded for /lib/ld-linux.so.2
0xffffe002 in ?? ()
(gdb) stop
(gdb) b 12
Breakpoint 8 at 0x8048402: file fork.c, line 12.
(gdb) c
Continuing.
Breakpoint 7, main () at fork.c:11
11                      int d =5;
(gdb) s
12                      printf("child/n");
(gdb) s
19              return 0;
(gdb) s
20      }
(gdb) s
0x42015574 in __libc_start_main () from /lib/tls/libc.so.6
(gdb) s
Single stepping until exit from function __libc_start_main,
which has no line number information.
Program exited normally.

======================================================

使用GDB最好的文檔就是其名爲'Debugging with GDB' 的參考手冊。手冊中有一小章節提到了如何調試多進程程序。一般情況下,如果被gdb調試的程序中調用fork派生出一個新的子進程,這時gdb調試的仍然 還是父進程,其子進程的執行不被理會。如果之前你在子進程的執行routine上設置了斷點,那麼當子進程執行到那個斷點時,子進程會因爲收到一個 SIGTRAP信號而自行終止,除非你在子進程中攔截了該信號。

那麼使用GDB該如何調試多進程程序呢?在其參考手冊中提供了一種通用方法,這裏說說(GDB在某些平臺上如HP-UX,還提供了更簡便的方法,不過不具備通用性,這裏不說):

[測試程序]
我們先看看我們的測試程序:
/* in eg1.c */

int wib(int no1, int no2)
{
        int result, diff;
        diff = no1 - no2;
        result = no1 / diff;
        return result;
}

int main()
{
        pid_t   pid;

        pid = fork();
        if (pid <0) {
                printf("fork err/n");
                exit(-1);
        } else if (pid == 0) {
                /* in child process */
                sleep(60); ------------------ (!)

                int     value   = 10;
                int     div     = 6;
                int     total   = 0;
                int     i       = 0;
                int     result  = 0;

                for (i = 0; i < 10; i++) {
                        result = wib(value, div);
                        total += result;
                        div++;
                        value--;
                }

                printf("%d wibed by %d equals %d/n", value, div, total);
                exit(0);
        } else {
                /* in parent process */
                sleep(4);
                wait(-1);
                exit(0);
        }
}
該測試程序中子進程運行過程中會在wib函數中出現一個'除0'異常。現在我們就要調試該子進程。

[調試原理]
不 知道大家發現沒有,在(!)處在我們的測試程序在父進程fork後,子進程調用sleep睡了60秒。這就是關鍵,這個sleep本來是不該存在於子進程 代碼中的,而是而了使用GDB調試後加入的,它是我們調試的一個關鍵點。爲什麼要讓子進程剛剛運行就開始sleep呢?因爲我們要在子進程睡眠期間,利用 shell命令獲取其process id,然後再利用gdb調試外部進程的方法attach到該process id上,調試該進程。

[調試過程]
我覺上面的調試原理的思路已經很清晰了,剩下的就是如何操作的問題了。我們來實踐一次吧!
我所使用的環境是Solaris OS 9.0/GCC 3.2/GDB 6.1。

GDB 調試程序的前提條件就是你編譯程序時必須加入調試符號信息,即使用'-g'編譯選項。首先編譯我們的源程序'gcc -g -o eg1 eg1.c'。編譯好之後,我們就有了我們的調試目標eg1。由於我們在調試過程中需要多個工具配合,所以你最好多打開幾個終端窗口,另外一點需要注意的 是最好在eg1的working directory下執行gdb程序,否則gdb回提示'No symbol table is loaded'。你還得手工load symbol table。好了,下面我們就'按部就班'的開始調試我們的eg1。

執行eg1:
eg1 &   --- 讓eg1後臺運行吧。

查找進程id:
ps -fu YOUR_USER_NAME

運行gdb:
gdb
(gdb) attach xxxxx  --- xxxxx爲利用ps命令獲得的子進程process id
(gdb) stop --- 這點很重要,你需要先暫停那個子進程,然後設置一些斷點和一些Watch
(gdb) break 37 -- 在result = wib(value, div);這行設置一個斷點,可以使用list命令察看源代碼
Breakpoint 1 at 0x10808: file eg1.c, line 37.
(gdb) continue
Continuing.

Breakpoint 1, main () at eg1.c:37
37                              result = wib(value, div);
(gdb) step
wib (no1=10, no2=6) at eg1.c:13
13              diff = no1 - no2;
(gdb) continue
Continuing.

Breakpoint 1, main () at eg1.c:37
37                              result = wib(value, div);
(gdb) step
wib (no1=9, no2=7) at eg1.c:13
13              diff = no1 - no2;
(gdb) continue
Continuing.

Breakpoint 1, main () at eg1.c:37
37                              result = wib(value, div);
(gdb) step
wib (no1=8, no2=8) at eg1.c:13
13              diff = no1 - no2;
(gdb) next
14              result = no1 / diff;
(gdb) print diff
$6 = 0        ------- 除數爲0,我們找到罪魁禍首了。
(gdb) next
Program received signal SIGFPE, Arithmetic exception.
0xff29d830 in .div () from /usr/lib/libc.so.1

至此,我們調試完畢。

======================================================

總結來看,follow-fork-mode是最方面的,但是作用有限;attach要修改下源代碼,但也不是很麻煩

有空還得研究下gdb的手冊~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章