CSDN | GitHub |
---|---|
GDB 調試多進程或者多線程應用 | AderXCoding/system/tools/gdb/attach_on_fork |
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可, 轉載請註明出處, 謝謝合作
因本人技術水平和知識面有限, 內容如有紕漏或者需要修正的地方, 歡迎大家指正, 也歡迎大家提供一些其他好的調試工具以供收錄, 鄙人在此謝謝啦
GDB
是 linux
系統上常用的 c/c++
調試工具, 功能十分強大. 對於較爲複雜的系統, 比如多進程系統, 如何使用 GDB
調試呢?
考慮下面這個三進程系統 :
進程 ProcessChild
是 ProcessParent
的子進程, ProcessParentThread
又是 ProcParent
的子線程. 如何使用 GDB
調試 子進程 ProcessChild
或者子線程 ProcessParentThread
呢 ?
實際上, GDB
沒有對多進程程序調試提供直接支持. 例如, 使用 GDB
調試某個進程, 如果該進程 fork
了子進程, GDB
會繼續調試該進程, 子進程會不受干擾地運行下去. 如果你事先在子進程代碼裏設定了斷點, 子進程會收到SIGTRAP
信號並終止. 那麼該如何調試子進程呢? 其實我們可以利用 GDB
的特點或者其他一些輔助手段來達到目的. 此外, GDB
也在較新內核上加入一些多進程調試支持.
接下來我們詳細介紹幾種方法, 分別是 follow-fork-mode
方法, attach
子進程方法和 GDB wrapper
方法.
1 follow-fork-mode方法
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 跟主進程, 子進程 block 在 fork 位置 |
child | off | 同時調試兩個進程, gdb 跟子進程, 主進程 block 在 fork 位置 |
設置方法
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
默認支持調試多線程, 跟主線程, 子線程 block
在 create 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
接着在主進程 fork
的時候和三個進程(線程)的內部均設置斷電. 下面分別跟蹤三個進程
# 在主進程fork的時候設置斷點
b 16
# 在主進程pthread_create的時候設置斷點
b 36
# 在父進程執行的開始位置設置斷點
b 28
# 在子進程執行的開始位置設置斷點
b 48
# 在子線程執行的開始位置設置斷點
b 61
1.3.2 調試父進程
首先開始調試父進程
# run 運行程序
r
進程停在了 fork
的位置
查看進程和線程的信息, 由於進程停在了 fork
的地址, 還沒有創建子進程, 因此只有主進程
由於設置了 follow-fork-mode = parent
和 detach-on-fork = off
. 因此在 fork
之後, gdb
進程會開始調試主進程, 而子進程會阻塞在 fork
之後的位置. 而我們在每個進程的執行函數路徑的開始都打了斷點, 那麼我們繼續執行, 進程將運行主程序, 走到 ProcessParent
的位置.
下面我們來驗證一下
#繼續程序的執行
c OR continue
#也可以next執行
next
可以看到程序停在了主進程 ProcessParent
的開始位置.
現在我們查看進程和線程的信息, 此時父進程(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
然後 continue 運行程序, 程序會停止在子進程 ProcessChild
的位置.
continue
1.3.4 調試子線程
現在我們繼續回到主線程然後待其創建子線程之後, 跟蹤子線程.
由於在父進程的程序邏輯處 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
本作品/博文 ( AderStep-紫夜闌珊-青伶巷草 Copyright ©2013-2017 ), 由 成堅(gatieme) 創作,
採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可. 歡迎轉載、使用、重新發布, 但務必保留文章署名成堅gatieme ( 包含鏈接: http://blog.csdn.net/gatieme ), 不得用於商業目的
基於本文修改後的作品務必以相同的許可發佈. 如有任何疑問,請與我聯繫.