1. 默認設置下,在調試多進程程序時GDB只會調試主進程。但是GDB(>V7.0)支持多進程的分別以及同時調試,換句話說,GDB可以同時調試多個程序。只需要設置follow-fork-mode(默認值:parent)和detach-on-fork(默認值:on)即可。
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
2. GDB默認支持調試多線程,跟主線程,子線程block在create thread。
查詢線程:info threads
切換調試線程:thread <thread number>
例程:
#include <pthread.h>
void processA();
void processB();
void * processAworker(void *arg);
int main(int argc, const char *argv[])
{
int pid;
pid = fork();
if(pid != 0)
processA();
else
processB();
return 0;
}
void processA()
{
pid_t pid = getpid();
char prefix[] = "ProcessA: ";
char tprefix[] = "thread ";
int tstatus;
pthread_t pt;
printf("%s%lu %s\n", prefix, pid, "step1");
tstatus = pthread_create(&pt, NULL, processAworker, NULL);
if( tstatus != 0 )
{
printf("ProcessA: Can not create new thread.");
}
processAworker(NULL);
sleep(1);
}
void * processAworker(void *arg)
{
pid_t pid = getpid();
pthread_t tid = pthread_self();
char prefix[] = "ProcessA: ";
char tprefix[] = "thread ";
printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");
return NULL;
}
void processB()
{
pid_t pid = getpid();
char prefix[] = "ProcessB: ";
printf("%s%lu %s\n", prefix, pid, "step1");
printf("%s%lu %s\n", prefix, pid, "step2");
printf("%s%lu %s\n", prefix, pid, "step3");
}
輸出:
ProcessB: 803 step1
ProcessB: 803 step2
ProcessB: 803 step3
ProcessA: 802 thread 3077555904 step2
ProcessA: 802 thread 3077555904 step3
ProcessA: 802 thread 3077553008 step2
ProcessA: 802 thread 3077553008 step3
調試:
1. 調試主進程,block子進程。
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is off.
(gdb) catch fork
Catchpoint 1 (fork)
(gdb) r
[Thread debugging using libthread_db enabled]
Catchpoint 1 (forked process 3475), 0x00110424 in __kernel_vsyscall ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686
(gdb) break test.c:14
Breakpoint 2 at 0x8048546: file test.c, line 14.
(gdb) cont
[New process 3475]
[Thread debugging using libthread_db enabled]
Breakpoint 2, main (argc=1, argv=0xbffff364) at test.c:14
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6.i686
(gdb) info inferiors
Num Description Executable
2 process 3475 /home/cnwuwil/labs/c-lab/test
* 1 process 3472 /home/cnwuwil/labs/c-lab/test
2. 切換到子進程:
[Switching to inferior 2 [process 3475] (/home/cnwuwil/labs/c-lab/test)]
[Switching to thread 2 (Thread 0xb7fe86c0 (LWP 3475))]
#0 0x00110424 in ?? ()
(gdb) info inferiors
Num Description Executable
* 2 process 3475 /home/cnwuwil/labs/c-lab/test
1 process 3472 /home/cnwuwil/labs/c-lab/test
(gdb) inferior 1
[Switching to inferior 1 [process 3472] (/home/cnwuwil/labs/c-lab/test)]
[Switching to thread 1 (Thread 0xb7fe86c0 (LWP 3472))]
#0 main (argc=1, argv=0xbffff364) at test.c:14
(gdb) info inferiors
Num Description Executable
2 process 3475 /home/cnwuwil/labs/c-lab/test
* 1 process 3472 /home/cnwuwil/labs/c-lab/test
3. 設斷點繼續調試主進程,主進程產生兩個子線程:
Breakpoint 3 at 0x804867d: file test.c, line 50. (2 locations)
(gdb) cont
ProcessA: 3472 step1
[New Thread 0xb7fe7b70 (LWP 3562)]
ProcessA: 3472 thread 3086911168 step2
Breakpoint 3, processAworker (arg=0x0) at test.c:50
(gdb) info inferiors
Num Description Executable
2 process 3475 /home/cnwuwil/labs/c-lab/test
* 1 process 3472 /home/cnwuwil/labs/c-lab/test
(gdb) info threads
3 Thread 0xb7fe7b70 (LWP 3562) 0x00110424 in __kernel_vsyscall ()
2 Thread 0xb7fe86c0 (LWP 3475) 0x00110424 in ?? ()
* 1 Thread 0xb7fe86c0 (LWP 3472) processAworker (arg=0x0) at test.c:50
4. 切換到主進程中的子線程,注意:線程2為前面產生的子進程
[Switching to thread 3 (Thread 0xb7fe7b70 (LWP 3562))]#0 0x00110424 in __kernel_vsyscall ()
(gdb) cont
ProcessA: 3472 thread 3086911168 step3
ProcessA: 3472 thread 3086908272 step2
[Switching to Thread 0xb7fe7b70 (LWP 3562)]
Breakpoint 3, processAworker (arg=0x0) at test.c:50
(gdb) info threads
* 3 Thread 0xb7fe7b70 (LWP 3562) processAworker (arg=0x0) at test.c:50
2 Thread 0xb7fe86c0 (LWP 3475) 0x00110424 in ?? ()
1 Thread 0xb7fe86c0 (LWP 3472) 0x00110424 in __kernel_vsyscall ()
(gdb) thread 1
inux下應用程序的調試工具主要就是gdb,可能你已經習慣了IDE形式的調試工具。也許剛開始使用gdb作為調試工具,會有諸多的不變,但是一旦你學會了如何使用gdb你就會被其富有魔力的功能所吸引的,下面開始逐步的學習linux下gdb的使用方式。
一直以來對於gdb在多線程調試方面的應用好奇,最近,由於項目需要,學習了linux下的gdb在多線程下的調試方法。下面就結合一個簡單的案例介紹一下gdb的多線程調試方法。其中,本例子還介紹了如何調試鏈接有靜態庫的多線程應用程序。
1.理論介紹
gdb支持的用於多線程調試的工具如下:
-
能夠自動的提醒新線程的創建。
-
‘thred threadno’,實現在不同線程間切換。
-
‘info thead’,可以查看存在的線程信息。
-
‘thread applay [ threadno] [all] args’ ,在指定的線程上執行特定的命令args。
-
可以在線程中設置特定的斷點。
-
‘set print thread-events’,用於設定是否提示線程啟動或停止時的信息。
-
‘set libthread-db-search-path path’,用於是用戶可以自己制定 libthread-db 的路徑信息 。
-
'set scheduler-locking mode',在某些操作系統中,你可以通過鎖住OS的調度行為,這樣可以就可以改變GDB默認的行為,達到同一時間只有一個線程在運行的目的。
-
off:沒有鎖定,所有線程可以在任何時間運行。
-
on:鎖定線程,只有當前線程在運行。
-
step:該模式是對single-stepping模式的優化。此模式會阻止其他線程在當前線程單步調試時,搶占當前線。因此調試的焦點不會被以外的改變。其他線程不可能搶占當前的調試線程。其他線程只有下列情況下會重新獲得運行的機會:
-
當你‘next’一個函數調用的時候。
-
當你使用諸如‘continue’、‘until‘、’finish‘命令的時候。
-
其他線程遇到設置好的斷點的時候。
-
1.1線程創建提醒
當應用程序創建線程的時候,如果你設置了’set print thread-events‘,那么他會自動提示新創建線程的信息,GNU/linux 下的提示信息如下:
- [New Thread 0x41e02940 (LWP 25582)]
1.2顯示線程信息
info thread用於顯示系統中正在運行的所有線程的信息。信息主要包括如下幾項:
-
GDB分配的id號。
-
目標系統定義的線程id(systag)。
-
線程的名字,如果存在的話,會被顯示出來。用戶可以自定義線程的名字,或者由程序自己指定。
-
線程相關的棧的信息。
其中,*表示當前正在運行的線程,下面是一個多線程的相關信息。
- (gdb) info threads
- Id Target Id Frame
- 3 process 35 thread 27 0x34e5 in sigpause ()
- 2 process 35 thread 23 0x34e5 in sigpause ()
- * 1 process 35 thread 13 main (argc=1, argv=0x7ffffff8)
- at threadtest.c:68
1.3切換線程
thread threadno用於在同步線程之間實現切換。threadno即上面顯示的GDB自定義的線程的id號。線程切換成功后,會打印該線程的相關信息,比如棧信息。
- (gdb) thread 2
- [Switching to thread 2 (Thread 0xb7fdab70 (LWP 12747))]
- #0 some_function (ignore=0x0) at example.c:8
- 8 printf ("hello\n");
變量$_thread 記錄了當前線程的id號。你或許在設置斷點條件或腳本的時候會用到該變量。
1.4執行命令
thread apply[threadid|all] command,該工具用於在一個或多個線程執行指定的命令command。threadid可以是一個或多個線程id,或是一個范圍值,例如,1-3
1.5定義/find線程名
thread name,可以通過該工具實現線程名的重新定義。一般,系統會為每一個線程定義一個名字,例如GNU/linux,使用該命令后會將系統定義的線程名稱覆蓋掉。
thread find [regexp],其中regexp可以是線程的systag,例如,LWP 25582中的25582,或線程名(系統定義的或用戶自定義的)
2.實例調試
下面通過一個實例,具體演示一下gdb thread調試。
2.1靜態庫編譯
下面為一個簡單的函數用於打印不同的字符串。
- #include<iostream>
- #include<string>
- #include"print.h"
- using namespace std;
- void print(string words)
- {
- std::cout << words << std::endl;
- }
將其編譯成靜態庫
- g++ -c print.cpp
- ar crs libprint.a print.o
2.2 鏈接靜態庫
下面為一個多線程程序的打印程序,很簡單
- #include<iostream>
- #include<pthread.h>
- #include"print.h"
- void* threadPrintHello(void* arg)
- {
- while(1)
- {
- sleep(5);
- print(string("Hello"));
- }
- }
- void* threadPrintWorld(void* arg)
- {
- while(1)
- {
- sleep(5);
- print(string("World"));
- }
- }
- int main( int argc , char* argv[])
- {
- pthread_t pid_hello , pid_world;
- int ret = 0;
- ret = pthread_create(&pid_hello , NULL , threadPrintHello , NULL);
- if( ret != 0 )
- {
- std::cout << "Create threadHello error" << std::endl;
- return -1;
- }
- ret = pthread_create(&pid_world , NULL , threadPrintWorld , NULL);
- if( ret != 0 )
- {
- std::cout << "Create threadWorld error" << std::endl;
- return -1;
- }
- while(1)
- {
- sleep(10);
- std::cout << "In main thread" << std::endl;
- }
- pthread_join(pid_hello , NULL);
- pthread_join(pid_world , NULL);
- return 0;
- }
編譯程序
- g++ -o thread thread.cpp -lpthread -lprint
2.3調試程序
啟動程序
- $./thread
進程id
- $ps aux |grep thred
- 1000 24931 0.0 0.0 21892 912 pts/0 tl+ 03:04 0:00 src/thread
attach該進程
- $sudo gdb thread 24931
顯示線程信息
- (gdb) info thread
- Id Target Id Frame
- * 3 Thread 0xb7471b40 (LWP 24932) "threadPrintHello" threadPrintHello (arg=0x0) at thread.cpp:10
- 2 Thread 0xb6c70b40 (LWP 24933) "thread" 0xb7779424 in __kernel_vsyscall ()
- 1 Thread 0xb7473700 (LWP 24931) "thread" 0xb7779424 in __kernel_vsyscall ()