Linux多進程和多線程的一次gdb調試實例

Linux C/C++開發中gdb進行多進程和多線程的調試一直比較麻煩,在CSDN上看到高科的一篇文章《gdb調試多進程和多線程命令》比較有啓發,這裏就本身從新整理並作了一個GDB多進程/線程的調試實踐。html

1 原文整理

默認設置下,在調試多進程程序時gdb只會調試主進程。gdb7以上的版本(gdb --version)支持多進程調試,只須要設置好follow-fork-mode(fork追蹤模式)以及detach-on-fork(指示GDB在fork以後是否斷開某個進程的調試)便可。linux

這兩個參數的設置命令分別是:set follow-fork-mode [parent|child],set detach-on-fork [on|off]。二者結合起來構成了GDB的調試模式:redis

follow-fork-mode  detach-on-fork    說明
    parent              on          GDB默認的調試模式:只調試主進程
    child               on          只調試子進程
    parent              off         同時調試兩個進程,gdb跟主進程,子進程block在fork位置
    child               off         同時調試兩個進程,gdb跟子進程,主進程block在fork位置

查看gdb默認的參數設置:多線程

(gdb) show follow-fork-mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) show detach-on-fork
Whether gdb will detach the child of a fork is on.
(gdb)

2 演示代碼

下面這段代碼的主要流程就是在main函數中fork建立一個子進程,而後在父進程中又建立一個線程,接着就使用gdb進行調試(block子進程)。注意,在調試設置斷點的時候,因爲以前調試的時候代碼最前面沒有加上這7行說明文字,因此設置斷點的行號要加上7。函數

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
67
68
69
/** 
 * @FileName    gdb_pthread.c
 * @Describe    A simple example for the debug of multiprocess and multithreading using gdb in linux system.
 * @Author      vfhky 2016-02-25 22:48 https://typecodes.com/cseries/multilprocessthreadgdb.html
 * @Compile     gcc gdb_pthread.c -g -o gdb_pthread
 * @Reference   http://blog.csdn.net/pbymw8iwm/article/details/7876797
 */
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>


//Parent process handle.
void Parent();
//Child process handle.
void Child();
//Parent process handle after generate a thread.
void * ParentDo( char *argv );

int main( int argc, const char **argv )
{
    int pid;
    pid = fork();
    if(pid != 0)        //add the first breakpoint.
        Parent();
    else
        Child();
    return 0;
}

//Parent process handle.
void Parent()
{
    pid_t pid = getpid();
    char cParent[] = "Parent";
    char cThread[] = "Thread";
    pthread_t pt;

    printf( "[%s]: [%d] [%s]\n", cParent, pid, "step1" );

    if( pthread_create( &pt, NULL, (void *)*ParentDo, cThread ) )
    {
        printf( "[%s]: Can not create a thread.\n", cParent );
    }

    ParentDo( cParent );
    sleep(1);
}

void * ParentDo( char *argv )
{
    pid_t pid = getpid();
    pthread_t tid = pthread_self();     //Get the thread-id selfly.
    char tprefix[] = "thread";

    printf( "[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step2" );         //add the second breakpoint.
    printf( "[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step3" );

    return NULL;
}

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

已知若是直接運行程序,那麼輸出的內容以下:ui

[vfhky@typecodes pthread_key]$ gdb_pthread
[Parent]: [22648] [step1]
[Parent]: [22648] [thread] [139722467432256] [step2]
[Parent]: [22648] [thread] [139722467432256] [step3]
[Thread]: [22648] [thread] [139722450630400] [step2]
[Thread]: [22648] [thread] [139722450630400] [step3]
[Child]: [22649] [step1]
[vfhky@typecodes pthread_key]$

3 gdb調試

3.1 設置調試模式和Catchpointspa

設置調試父子進程,gdb跟主進程,子進程block在fork位置。.net

[vfhky@typecodes pthread_key]$ gdb gdb_pthread
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/vfhky/bin/gdb_pthread...done.
(gdb) set detach-on-fork off
#####catch讓程序在發生某種事件(fork、異常throw、異常catch、動態庫加載等)的時候中止運行
(gdb) catch fork 
Catchpoint 1 (fork)
(gdb) info b
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork
(gdb)

以下圖所示:線程

開啓gdb調試

3.2 開始gdb調試debug

(gdb) r                         ####運行到斷點/捕捉點(第17行處的fork函數,23873是子進程PID)
Starting program: /home/vfhky/bin/gdb_pthread 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Catchpoint 1 (forked process 23873), 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
130       pid = ARCH_FORK ();
(gdb) bt                        #####查看堆棧狀況
#0  0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
#1  0x00000000004007b4 in main (argc=1, argv=0x7fffffffe4c8) at gdb_pthread.c:17
(gdb) info threads              #######顯示運行的線程信息(23869是父進程的PID)
  Id   Target Id         Frame 
* 1    Thread 0x7ffff7fe1740 (LWP 23869) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
(gdb) info inferiors            ######顯示正在調試的進程:1前面的星號表示當前調試的進程(PID 23869)。  
  Num  Description       Executable        
* 1    process 23869     /home/vfhky/bin/gdb_pthread 
(gdb) info b                    ######列出全部斷點和捕捉點,此時已經hit 1 time,即捕捉到了一次fork事件
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork, process 23873                   #####子進程23873
        catchpoint already hit 1 time
(gdb)

這時使用以下命令查看當前CentOS系統全部進程的狀態:發現父進程PID爲23869,經過fork產生的子進程爲23873:

[vfhky@typecodes ~]$ pstree -pul

pstree -pul查看CentOS系統全部進程信息

同時,使用命令cat /proc/23869/status查看當前進程的詳細信息:進程PID爲23869,它的父進程(即GDB進程)爲23859,同時這也是追蹤進程ID,線程數Threads爲1(共享使用該信號描述符的線程數,在POSIX多線程序應用程序中,線程組中的全部線程使用同一個信號描述符)。

proc查看進程的狀態信息

3.3 設置第一個斷點

在程序的第18行設置斷點:

(gdb) b gdb_pthread.c:18
Breakpoint 2 at 0x4007b7: file gdb_pthread.c, line 18.
(gdb) info b                        ######列出全部斷點和捕捉點
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork, process 23873                 ########子進程23873
        catchpoint already hit 1 time
2       breakpoint     keep y   0x00000000004007b7 in main at gdb_pthread.c:18
(gdb)

3.4 執行到第一個斷點

(gdb) c                #####執行到第18行處的斷點
Continuing.
[New process 23873]                     #####父進程23869執行完第1個捕捉點的程序,產生子進程23873
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 2, main (argc=1, argv=0x7fffffffe4c8) at gdb_pthread.c:18            ##########父進程執行到第18行處的斷點
18              if(pid != 0)
(gdb) info threads                      ####查看全部運行的線程,有父進程23869和子進程23873
  Id   Target Id         Frame 
  2    Thread 0x7ffff7fe1740 (LWP 23873) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
* 1    Thread 0x7ffff7fe1740 (LWP 23869) "gdb_pthread" main (argc=1, argv=0x7fffffffe4c8) at gdb_pthread.c:18
(gdb) info inferiors                    #####顯示正在調試的進程
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread                ########子進程
* 1    process 23869     /home/vfhky/bin/gdb_pthread                ########父進程
(gdb) info b                    #######查看當前全部的斷點
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork, process 23873 
        catchpoint already hit 1 time
2       breakpoint     keep y   <MULTIPLE>         
        breakpoint already hit 1 time
2.1                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 2
2.2                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 1
(gdb)

截圖以下:

執行到第1個斷點處查看線程和進程情況

這時使用命令查看當前系統進程的狀態:發現此時仍然只有父進程23869和子進程23873。

[vfhky@typecodes ~]$ pstree -pul

pstree -pul查看CentOS系統全部進程信息

3.5 執行到第一個斷點此時若是切換到子進程23873

(gdb) inferior 2
[Switching to inferior 2 [process 23873] (/home/vfhky/bin/gdb_pthread)]
[Switching to thread 2 (Thread 0x7ffff7fe1740 (LWP 23873))] 
#0  0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
130       pid = ARCH_FORK ();
(gdb) info inferiors                 #####顯示正在調試的進程 
  Num  Description       Executable        
* 2    process 23873     /home/vfhky/bin/gdb_pthread                #####子進程
  1    process 23869     /home/vfhky/bin/gdb_pthread                #####父進程
(gdb)

3.6 從新切換到父進程23869

(gdb) inferior 1
[Switching to inferior 1 [process 23869] (/home/vfhky/bin/gdb_pthread)]
[Switching to thread 1 (Thread 0x7ffff7fe1740 (LWP 23869))] 
#0  main (argc=1, argv=0x7fffffffe4c8) at gdb_pthread.c:18
18              if(pid != 0)
(gdb) info inferiors                 #####顯示正在調試的進程
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread 
* 1    process 23869     /home/vfhky/bin/gdb_pthread 
(gdb)

3.7 設置第二個斷點並調試

在第50行設置斷點繼續調試主進程(使父進程產生線程),其中父進程和線程究竟是誰先執行是由內核調度控制的。

(gdb) b gdb_pthread.c:50
Breakpoint 3 at 0x4008a7: gdb_pthread.c:50. (2 locations)
(gdb) c                    ######繼續執行代碼到第50行處的斷點
Continuing.
[Parent]: [23869] [step1]                              ######第33行父進程打印Parent()函數中的數據
[New Thread 0x7ffff6fdd700 (LWP 24024)]                ######第35行父進程建立了一個線程24024(LWP表示輕量級進程)
[Switching to Thread 0x7ffff6fdd700 (LWP 24024)]            #####已經自動切換到線程24024(LWP表示輕量。進程),也就是GDB繼續調試線程而不是父進程了。

Breakpoint 3, ParentDo (argv=0x7fffffffe390 "Thread") at gdb_pthread.c:50            ######線程24024阻塞在程序的第50行
50              printf( "[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step2" );
(gdb)

這時使用命令查看當前系統進程的狀態:存在父進程23869和子進程23873以及父進程建立的一個線程24024(線程用大括號{}表示)。

[vfhky@typecodes ~]$ pstree -pul

pstree -pul查看CentOS系統全部進程信息

同時,使用命令cat /proc/23869/status查看當前進程的詳細信息:進程PID爲23869,它的父進程(即GDB進程)爲23859,同時這也是追蹤進程ID,線程數Threads爲2(當前父進程23869+線程24024)。

proc查看進程的狀態信息

3.8 查看第二個斷點處的調試信息

(gdb) info inferiors                 #####顯示正在調試的進程 
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread                     ###子進程
* 1    process 23869     /home/vfhky/bin/gdb_pthread                     ###父進程 
(gdb) info threads         ####查看全部運行的線程,父進程2386九、子進程2387三、線程24024,由星號能夠發現目前調試已經切換到了線程24024了。 
  Id   Target Id         Frame 
* 3    Thread 0x7ffff6fdd700 (LWP 24024) "gdb_pthread" ParentDo (argv=0x7fffffffe390 "Thread") at gdb_pthread.c:50
  2    Thread 0x7ffff7fe1740 (LWP 23873) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
  1    Thread 0x7ffff7fe1740 (LWP 23869) "gdb_pthread" ParentDo (argv=0x7fffffffe3a0 "Parent") at gdb_pthread.c:50
(gdb) info b                #####查看設置的全部的斷點breakpoint和捕捉點catchpoint(共3個):
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork, process 23873 
        catchpoint already hit 1 time
2       breakpoint     keep y   <MULTIPLE>         
        breakpoint already hit 1 time
2.1                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 2
2.2                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 1
3       breakpoint     keep y   <MULTIPLE>         
        breakpoint already hit 1 time
3.1                         y     0x00000000004008a7 in ParentDo at gdb_pthread.c:50 inf 2
3.2                         y     0x00000000004008a7 in ParentDo at gdb_pthread.c:50 inf 1
(gdb)

3.9 若是手動切換到線程24024

(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff6fdd700 (LWP 24024))]
#0  ParentDo (argv=0x7fffffffe390 "Thread") at gdb_pthread.c:50
50              printf( "[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step2" );
(gdb) info threads                   #####查看全部運行的線程
  Id   Target Id         Frame 
* 3    Thread 0x7ffff6fdd700 (LWP 24024) "gdb_pthread" ParentDo (argv=0x7fffffffe390 "Thread") at gdb_pthread.c:50
  2    Thread 0x7ffff7fe1740 (LWP 23873) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130
  1    Thread 0x7ffff7fe1740 (LWP 23869) "gdb_pthread" ParentDo (argv=0x7fffffffe3a0 "Parent") at gdb_pthread.c:50
(gdb) info inferiors                 #####顯示正在調試的進程  
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread 
* 1    process 23869     /home/vfhky/bin/gdb_pthread 
(gdb)

3.10 開始執行第二個斷點處的代碼

(gdb) c
Continuing.
[Thread]: [23869] [thread] [140737337218816] [step2]            #####線程24024執行第50行處,打印數據
[Thread]: [23869] [thread] [140737337218816] [step3]            #####線程24024執行第51行處,打印數據
[Thread 0x7ffff6fdd700 (LWP 24024) exited]                      #####線程24024退出
[Switching to Thread 0x7ffff7fe1740 (LWP 23869)]                #####切換到父進程中去

Breakpoint 3, ParentDo (argv=0x7fffffffe3a0 "Parent") at gdb_pthread.c:50                #####父進程繼續停在第50行處的斷點
50              printf( "[%s]: [%d] [%s] [%lu] [%s]\n", argv, pid, tprefix, tid, "step2" );
(gdb) info inferiors                    ######列出正在調試進程(父進程23869和子進程23873),1前面的星號表示當前調試的進程(父進程23869)。 
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread 
* 1    process 23869     /home/vfhky/bin/gdb_pthread 
(gdb) info threads                     ######查看全部運行的線程
  Id   Target Id         Frame 
  2    Thread 0x7ffff7fe1740 (LWP 23873) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130            #####子進程23873
* 1    Thread 0x7ffff7fe1740 (LWP 23869) "gdb_pthread" ParentDo (argv=0x7fffffffe3a0 "Parent") at gdb_pthread.c:50          #####父進程23869
(gdb)

這時使用命令查看當前系統進程的狀態:存在父進程23869和子進程23873,其中線程24024已經結束了。

[vfhky@typecodes ~]$ pstree -pul

pstree -pul查看CentOS系統全部進程信息

3.11 繼續調試父進程

此時,因爲線程的退出,父進程做爲自動選擇的要調試的線程。

(gdb) c
Continuing.
[Parent]: [23869] [thread] [140737354012480] [step2]        #####父進程23869執行第50行
[Parent]: [23869] [thread] [140737354012480] [step3]        #####父進程23869執行第51行
[Inferior 1 (process 23869) exited normally]                #####正在調試的父進程23869退出
(gdb) info inferiors             ######顯示正在調試的進程
  Num  Description       Executable        
  2    process 23873     /home/vfhky/bin/gdb_pthread        #####fork建立的子進程23873
* 1    <null>            /home/vfhky/bin/gdb_pthread        #####fork建立的父進程23869已經退出 
(gdb) info threads              ####顯示正在運行的線程:只存在子進程23873,父進程23869已經退出 
  Id   Target Id         Frame 
  2    Thread 0x7ffff7fe1740 (LWP 23873) "gdb_pthread" 0x00007ffff709b50c in __libc_fork () at ../nptl/sysdeps/unix/sysv/linux/fork.c:130

No selected thread.  See `help thread'.         #####提示沒有被選中的要調試的線程
(gdb) info b                                    #####查看全部的斷點
Num     Type           Disp Enb Address            What
1       catchpoint     keep y                      fork, process 23873 
        catchpoint already hit 1 time
2       breakpoint     keep y   <MULTIPLE>         
        breakpoint already hit 1 time
2.1                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 2
2.2                         y     0x00000000004007b7 in main at gdb_pthread.c:18 inf 1
3       breakpoint     keep y   <MULTIPLE>         
        breakpoint already hit 2 times
3.1                         y     0x00000000004008a7 in ParentDo at gdb_pthread.c:50 inf 2      #####子進程23873
3.2                         y     0x00000000004008a7 in ParentDo at gdb_pthread.c:50 inf 1      #####父進程23869
(gdb)

這時使用命令查看當前系統進程的狀態:只有子進程23873(由內核init進程接管這個孤兒進程),父進程23869也已經結束了。

[vfhky@typecodes ~]$ pstree -pul

pstree -pul查看CentOS系統全部進程信息

再用ps ux命令查看子進程23873:

ps ux命令查看子進程信息

4 附錄

在gdb中,常常用到的恢復程序運行和單步調試的命令有:

continue        繼續運行程序直到下一個斷點(相似於VS裏的F5)
next            逐過程步進,不會進入子函數(相似VS裏的F10)
setp            逐語句步進,會進入子函數(相似VS裏的F11)
until           運行至當前語句塊結束
finish          運行至函數結束並跳出,並打印函數的返回值(相似VS的Shift+F11)
相關文章
相關標籤/搜索