GDB 調試多進程或者多線程應用

CSDN GitHub
GDB 調試多進程或者多線程應用 AderXCoding/system/tools/gdb/attach_on_fork


知識共享許可協議

本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作

因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的調試工具以供收錄, 鄙人在此謝謝啦


GDBlinux 系統上常用的 c/c++ 調試工具, 功能十分強大. 對於較爲複雜的系統, 比如多進程系統, 如何使用 GDB 調試呢?

考慮下面這個三進程系統 :

進程 ProcessChildProcessParent 的子進程, ProcessParentThread 又是 ProcParent 的子線程. 如何使用 GDB 調試 子進程 ProcessChild 或者子線程 ProcessParentThread 呢 ?

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

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

1 follow-fork-mode方法


參考 gdb調試多進程和多線程命令

1.1 follow-fork-mode方法簡介


默認設置下, 在調試多進程程序時 GDB 只會調試主進程. 但是 GDB > V7.0 支持多進程的分別以及同時調試, 換句話說, GDB 可以同時調試多個程序. 只需要設置 follow-fork-mode (默認值 parent) 和 detach-on-fork (默認值 on )即可.

follow-fork-mode detach-on-fork 說明
parent on 只調試主進程( GDB 默認)
child on 只調試子進程
parent off 同時調試兩個進程, gdb 跟主進程, 子進程 blockfork 位置
child off 同時調試兩個進程, gdb 跟子進程, 主進程 blockfork 位置

設置方法

set follow-fork-mode [parent|child]

set detach-on-fork [on|off]

查詢正在調試的進程

info inferiors

切換調試的進程

inferior <infer number>

添加新的調試進程

add-inferior [-copies n] [-exec executable]

可以用 file executable 來分配給 inferior 可執行文件.

其他 :

remove-inferiors infno

detach inferior

GDB 默認支持調試多線程, 跟主線程, 子線程 blockcreate thread

查詢線程

info threads

切換調試線程

thread <thread number>

1.2 示例程序例程

#include <stdio.h>
#include <pthread.h>

#include <unistd.h>

void processParent( );
void processChild( );

void * processParentworker(void *arg);

int main(int argc, const char *argv[])
{

    int pid;

    pid = fork( );

    if(pid != 0)    // fork return child pid in parent process
        processParent( );
    else        // fork return 0 in child process
        processChild( );

    return 0;
}

void processParent( )
{
    pid_t pid = getpid();
    char prefix[] = "ProcessParent: ";
    //char tprefix[] = "thread ";
    int tstatus;
    pthread_t pt;

    printf("%s%d %s\n", prefix, pid, "step1");

    tstatus = pthread_create(&pt, NULL, processParentworker, NULL);
    if(tstatus != 0)
    {
        printf("ProcessParent: Can not create new thread.");
    }

    processParentworker(NULL);
    sleep(1);
}

void * processParentworker(void *arg)
{
    pid_t pid = getpid( );
    pthread_t tid = pthread_self( );
    char prefix[] = "ProcessParentThread: ";
    char tprefix[] = "thread ";

    printf("%s%d %s%ld %s\n", prefix, pid, tprefix, tid, "step2");
    printf("%s%d %s%ld %s\n", prefix, pid, tprefix, tid, "step3");

    return NULL;
}

void processChild( )
{
    pid_t pid = getpid( );
    char prefix[] = "ProcessChild: ";
    printf("%s%d %s\n", prefix, pid, "step1");
    printf("%s%d %s\n", prefix, pid, "step2");
    printf("%s%d %s\n", prefix, pid, "step3");
}

主程序 fork 出一個子進程, 然後父進程在執行的過程中通過 pthread_create 創建出一個子線程.

輸出:

 ./test
ProcessParent: 7993 step1
ProcessChild: 7994 step1
ProcessChild: 7994 step2
ProcessChild: 7994 step3
ProcessParentThread: 7993 thread 140427861296960 step2
ProcessParentThread: 7993 thread 140427861296960 step3
ProcessParentThread: 7993 thread 140427853031168 step2
ProcessParentThread: 7993 thread 140427853031168 step3

1.3 調試


1.3.1 開始調試


首先調試主進程, block 子進程在 fork 的位置.

set follow-fork-mode parent
set detach-on-fork off
show follow-fork-mode
show detach-on-fork

show_follow_fork_mode

接着在主進程 fork 的時候和三個進程(線程)的內部均設置斷電. 下面分別跟蹤三個進程

# 在主進程fork的時候設置斷點
b 16
# 在主進程pthread_create的時候設置斷點
b 36
# 在父進程執行的開始位置設置斷點
b 28
# 在子進程執行的開始位置設置斷點
b 48
# 在子線程執行的開始位置設置斷點
b 61

1.3.2 調試父進程


首先開始調試父進程

# run 運行程序
r

進程停在了 fork 的位置

進程停在了 <code>fork</code> 的位置

查看進程和線程的信息, 由於進程停在了 fork 的地址, 還沒有創建子進程, 因此只有主進程

查看進程的信息

由於設置了 follow-fork-mode = parentdetach-on-fork = off. 因此在 fork 之後, gdb 進程會開始調試主進程, 而子進程會阻塞在 fork 之後的位置. 而我們在每個進程的執行函數路徑的開始都打了斷點, 那麼我們繼續執行, 進程將運行主程序, 走到 ProcessParent 的位置.

下面我們來驗證一下

#繼續程序的執行
c OR continue
#也可以next執行
next

可以看到程序停在了主進程 ProcessParent 的開始位置.

進程停在了主程序 <code>processparent</code> 的位置

現在我們查看進程和線程的信息, 此時父進程(pid = 9766)創建了子進程(pid = 10036), gdb 目前正在調試父進程

info inferiors
info threads

查看進程的信息

1.3.3 調試子進程


下面我們開始跟進子進程, 之前通過 info inferiors/threads 來查看進程信息的時候可以看到, 進程的編號信息, 父進程(pid = 9766)編號爲 1, 子進程(pid = 10036)編號爲 2.

我們接着將 gdb attach 到子進程.

inferiors 2

attch 到子進程

然後 continue 運行程序, 程序會停止在子進程 ProcessChild 的位置.

continue

程序會停止在子進程 <code>ProcessChild</code> 的位置

1.3.4 調試子線程


現在我們繼續回到主線程然後待其創建子線程之後, 跟蹤子線程.

由於在父進程的程序邏輯處 pthread_create 處設置了斷點, 將會停在 pthread_create 的地方

停在pthread_create的地方

接着往下執行, 這樣子線程就會被創建, 然後我們查看進程和線程的信息

info inferiors
info threads

我們可以查看到 info inferiors 不能看到父進程創建的子線程, 但是 info threads 可以看到.

查看進程和線程信息

然後開始調試子線程

thread 3

開始調試子線程

2 Attach子進程


https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/

衆所周知, GDB 有附着(attach)到正在運行的進程的功能, 即 attach <pid> 命令. 因此我們可以利用該命令 attach 到子進程然後進行調試.

例如我們要調試某個進程 process, 首先得到該進程的 pid

ps -ef | grep process

然後可以通過 pstree 可以看到該進程下的線程信息

pstree -H pid

啓動 GDB attach 到該進程

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

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

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

void debug_wait(char *tag_file)
{
    while(1)
    {
        if (tag_file存在)
            睡眠一段時間;
        else
            break;
    }
}

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

Attach 進程方法還是很方便的, 它能夠應付各種各樣複雜的進程系統, 比如孫子/曾孫進程, 比如守護進程(daemon process), 唯一需要的就是加入一小段代碼.

3 gdb swapper


Debugging with GDB學習記錄(二)

GDB調試及其調試腳本的使用

使用 GDB 調試多進程程序


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