GDB 调试多进程或者多线程应用
GDB 是 linux 系統(tǒng)上常用的 c/c++ 調(diào)試工具, 功能十分強(qiáng)大. 對(duì)于較為復(fù)雜的系統(tǒng), 比如多進(jìn)程系統(tǒng), 如何使用 GDB 調(diào)試呢?
考慮下面這個(gè)三進(jìn)程系統(tǒng) :
進(jìn)程 ProcessChild 是 ProcessParent 的子進(jìn)程, ProcessParentThread 又是 ProcParent 的子線程. 如何使用 GDB 調(diào)試 子進(jìn)程 ProcessChild 或者子線程 ProcessParentThread 呢 ?
實(shí)際上, GDB 沒有對(duì)多進(jìn)程程序調(diào)試提供直接支持. 例如, 使用 GDB 調(diào)試某個(gè)進(jìn)程, 如果該進(jìn)程 fork 了子進(jìn)程, GDB 會(huì)繼續(xù)調(diào)試該進(jìn)程, 子進(jìn)程會(huì)不受干擾地運(yùn)行下去. 如果你事先在子進(jìn)程代碼里設(shè)定了斷點(diǎn), 子進(jìn)程會(huì)收到SIGTRAP 信號(hào)并終止. 那么該如何調(diào)試子進(jìn)程呢? 其實(shí)我們可以利用 GDB 的特點(diǎn)或者其他一些輔助手段來達(dá)到目的. 此外, GDB 也在較新內(nèi)核上加入一些多進(jìn)程調(diào)試支持.
接下來我們?cè)敿?xì)介紹幾種方法, 分別是 follow-fork-mode 方法, attach 子進(jìn)程方法和 GDB wrapper 方法.
1 follow-fork-mode方法
參考 gdb調(diào)試多進(jìn)程和多線程命令
1.1 follow-fork-mode方法簡介
默認(rèn)設(shè)置下, 在調(diào)試多進(jìn)程程序時(shí) GDB 只會(huì)調(diào)試主進(jìn)程. 但是 GDB > V7.0 支持多進(jìn)程的分別以及同時(shí)調(diào)試, 換句話說, GDB 可以同時(shí)調(diào)試多個(gè)程序. 只需要設(shè)置 follow-fork-mode (默認(rèn)值 parent) 和 detach-on-fork (默認(rèn)值 on )即可.
| parent | on | 只調(diào)試主進(jìn)程( GDB 默認(rèn)) |
| child | on | 只調(diào)試子進(jìn)程 |
| parent | off | 同時(shí)調(diào)試兩個(gè)進(jìn)程, gdb 跟主進(jìn)程, 子進(jìn)程 block 在 fork 位置 |
| child | off | 同時(shí)調(diào)試兩個(gè)進(jìn)程, gdb 跟子進(jìn)程, 主進(jìn)程 block 在 fork 位置 |
設(shè)置方法
set follow-fork-mode [parent|child]set detach-on-fork [on|off]- 1
- 2
- 3
查詢正在調(diào)試的進(jìn)程
info inferiors- 1
切換調(diào)試的進(jìn)程
inferior <infer number>- 1
添加新的調(diào)試進(jìn)程
add-inferior [-copies n] [-exec executable]- 1
可以用 file executable 來分配給 inferior 可執(zhí)行文件.
其他 :
remove-inferiors infnodetach inferior- 1
- 2
- 3
GDB 默認(rèn)支持調(diào)試多線程, 跟主線程, 子線程 block 在 create thread
查詢線程
info threads- 1
切換調(diào)試線程
thread <thread number>- 1
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 processprocessParent( );else // fork return 0 in child processprocessChild( );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"); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
主程序 fork 出一個(gè)子進(jìn)程, 然后父進(jìn)程在執(zhí)行的過程中通過 pthread_create 創(chuàng)建出一個(gè)子線程.
輸出:
./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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
1.3 調(diào)試
1.3.1 開始調(diào)試
首先調(diào)試主進(jìn)程, block 子進(jìn)程在 fork 的位置.
set follow-fork-mode parent set detach-on-fork off- 1
- 2
- 1
- 2
接著在主進(jìn)程 fork 的時(shí)候和三個(gè)進(jìn)程(線程)的內(nèi)部均設(shè)置斷電. 下面分別跟蹤三個(gè)進(jìn)程
# 在主進(jìn)程fork的時(shí)候設(shè)置斷點(diǎn) b 16 # 在主進(jìn)程pthread_create的時(shí)候設(shè)置斷點(diǎn) b 36 # 在父進(jìn)程執(zhí)行的開始位置設(shè)置斷點(diǎn) b 28 # 在子進(jìn)程執(zhí)行的開始位置設(shè)置斷點(diǎn) b 48 # 在子線程執(zhí)行的開始位置設(shè)置斷點(diǎn) b 61- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
1.3.2 調(diào)試父進(jìn)程
首先開始調(diào)試父進(jìn)程
# run 運(yùn)行程序 r- 1
- 2
進(jìn)程停在了 fork 的位置
查看進(jìn)程和線程的信息, 由于進(jìn)程停在了 fork 的地址, 還沒有創(chuàng)建子進(jìn)程, 因此只有主進(jìn)程
由于設(shè)置了 follow-fork-mode = parent 和 detach-on-fork = off. 因此在 fork 之后, gdb 進(jìn)程會(huì)開始調(diào)試主進(jìn)程, 而子進(jìn)程會(huì)阻塞在 fork 之后的位置. 而我們?cè)诿總€(gè)進(jìn)程的執(zhí)行函數(shù)路徑的開始都打了斷點(diǎn), 那么我們繼續(xù)執(zhí)行, 進(jìn)程將運(yùn)行主程序, 走到 ProcessParent 的位置.
下面我們來驗(yàn)證一下
#繼續(xù)程序的執(zhí)行 c OR continue #也可以next執(zhí)行 next- 1
- 2
- 3
- 4
可以看到程序停在了主進(jìn)程 ProcessParent 的開始位置.
現(xiàn)在我們查看進(jìn)程和線程的信息, 此時(shí)父進(jìn)程(pid = 9766)創(chuàng)建了子進(jìn)程(pid = 10036), gdb 目前正在調(diào)試父進(jìn)程
info inferiors info threads- 1
- 2
1.3.3 調(diào)試子進(jìn)程
下面我們開始跟進(jìn)子進(jìn)程, 之前通過 info inferiors/threads 來查看進(jìn)程信息的時(shí)候可以看到, 進(jìn)程的編號(hào)信息, 父進(jìn)程(pid = 9766)編號(hào)為 1, 子進(jìn)程(pid = 10036)編號(hào)為 2.
我們接著將 gdb attach 到子進(jìn)程.
inferiors 2- 1
然后 continue 運(yùn)行程序, 程序會(huì)停止在子進(jìn)程 ProcessChild 的位置.
continue- 1
1.3.4 調(diào)試子線程
現(xiàn)在我們繼續(xù)回到主線程然后待其創(chuàng)建子線程之后, 跟蹤子線程.
由于在父進(jìn)程的程序邏輯處 pthread_create 處設(shè)置了斷點(diǎn), 將會(huì)停在 pthread_create 的地方
接著往下執(zhí)行, 這樣子線程就會(huì)被創(chuàng)建, 然后我們查看進(jìn)程和線程的信息
info inferiors info threads- 1
- 2
我們可以查看到 info inferiors 不能看到父進(jìn)程創(chuàng)建的子線程, 但是 info threads 可以看到.
然后開始調(diào)試子線程
thread 3- 1
2 Attach子進(jìn)程
https://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/
眾所周知, GDB 有附著(attach)到正在運(yùn)行的進(jìn)程的功能, 即 attach <pid> 命令. 因此我們可以利用該命令 attach 到子進(jìn)程然后進(jìn)行調(diào)試.
例如我們要調(diào)試某個(gè)進(jìn)程 process, 首先得到該進(jìn)程的 pid
ps -ef | grep process- 1
然后可以通過 pstree 可以看到該進(jìn)程下的線程信息
pstree -H pid- 1
啟動(dòng) GDB attach 到該進(jìn)程
現(xiàn)在就可以調(diào)試了. 一個(gè)新的問題是, 子進(jìn)程一直在運(yùn)行, attach 上去后都不知道運(yùn)行到哪里了. 有沒有辦法解決呢?
一個(gè)辦法是, 在要調(diào)試的子進(jìn)程初始代碼中, 比如 main 函數(shù)開始處, 加入一段特殊代碼, 使子進(jìn)程在某個(gè)條件成立時(shí)便循環(huán)睡眠等待, attach 到進(jìn)程后在該代碼段后設(shè)上斷點(diǎn), 再把成立的條件取消, 使代碼可以繼續(xù)執(zhí)行下去.
至于這段代碼所采用的條件, 看你的偏好了. 比如我們可以檢查一個(gè)指定的環(huán)境變量的值, 或者檢查一個(gè)特定的文件存不存在. 以文件為例, 其形式可以如下 :
void debug_wait(char *tag_file) {while(1){if (tag_file存在)睡眠一段時(shí)間;elsebreak;} }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
當(dāng) attach 到進(jìn)程后, 在該段代碼之后設(shè)上斷點(diǎn), 再把該文件刪除就 OK 了. 當(dāng)然你也可以采用其他的條件或形式, 只要這個(gè)條件可以設(shè)置檢測(cè)即可.
Attach 進(jìn)程方法還是很方便的, 它能夠應(yīng)付各種各樣復(fù)雜的進(jìn)程系統(tǒng), 比如孫子/曾孫進(jìn)程, 比如守護(hù)進(jìn)程(daemon process), 唯一需要的就是加入一小段代碼.
3 gdb swapper
Debugging with GDB學(xué)習(xí)記錄(二)
GDB調(diào)試及其調(diào)試腳本的使用
使用 GDB 調(diào)試多進(jìn)程程序
-
本作品/博文 ( AderStep-紫夜闌珊-青伶巷草 Copyright ?2013-2017 ), 由 成堅(jiān)(gatieme) 創(chuàng)作,
總結(jié)
以上是生活随笔為你收集整理的GDB 调试多进程或者多线程应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android O 前期预研之二:HID
- 下一篇: 深入理解Linux内存管理--目录导航