gdb調試多線程多進程程序

gdb的簡介和功能:

gdb是GNU開發的一個在Unix,Linux上使用的C/C++和彙編語言程序的調試工具,它主要幫助用戶在調試程序時完成以下工作:

  • 啓動程序,按照用戶要求影響程序的運行
  • 設置斷點,在指定位置停止
  • 當程序停止,檢查它出現什麼問題
  • 動態改變程序的執行環境,可以先糾正一個錯誤,然後再糾正其他錯誤

使用gdb

爲了發揮gdb的功能,需要在編譯源程序時加上-g選項,這樣才能夠在目標代碼加入調試信息。

啓動GDB的方法有以下幾種:

1、gdb <program> 
   program也就是你的執行文件,一般在當前目錄下。

2、gdb <program> core
   用gdb同時調試一個運行程序和core文件,core是程序非法執行後core dump後產生的文件。

3、gdb <program> <PID>
   如果你的程序是一個服務程序,那麼你可以指定這個服務程序運行時的進程ID。gdb會自動attach上去,並調試他。program應該在PATH環境變量中搜索得到。
//比如編譯一個test.c
gcc -o test test.c -g

對於第二種,如下圖:

比如代碼如下:

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

在介紹如何調試多線程,多進程程序之前,我們先了解一些常用gdb命令

l :(字母l)從第一行開始列出源碼
break n :在第n行處設置斷點
break func:在函數func()的入口處設置斷點
info break: 查看斷點信息
r:運行程序
n:單步執行
c:繼續運行
p 變量 :打印變量的值
bt:查看函數堆棧
finish:退出函數
shell 命令行:執行shell命令行
set args 參數:指定運行時的參數
show args:查看設置好的參數
show paths:查看程序運行路徑;
set environment varname [=value] 設置環境變量。如:set env USER=hchen;
show environment [varname] 查看環境變量;

clear 行號n:清除第n行的斷點
delete 斷點號n:刪除第n個斷點
disable 斷點號n:暫停第n個斷點
enable 斷點號n:開啓第n個斷點
step:單步調試如果有函數調用,則進入函數;與命令n不同,n是不進入調用的函數的

list :簡記爲 l ,其作用就是列出程序的源代碼,默認每次顯示10行。
list 行號:將顯示當前文件以“行號”爲中心的前後10行代碼,如:list 12
list 函數名:將顯示“函數名”所在函數的源代碼,如:list main
list :不帶參數,將接着上一次 list 命令的,輸出下邊的內容。

run:簡記爲 r ,其作用是運行程序,當遇到斷點後,程序會在斷點處停止運行,等待用戶輸入下一步命令。
回車:重複上一條命令。
set args:設置運行程序時的命令行參數,如:set args 33 55
show args:顯示命令行參數
continue:簡訊爲 c ,其作用是繼續運行被斷點中斷的程序。
break:爲程序設置斷點。
break 行號:在當前文件的“行號”處設置斷點,如:break 33
break 函數名:在用戶定義的函數“函數名”處設置斷點,如:break cb_button
info breakpoints:顯示當前程序的斷點設置情況

print 表達式:簡記爲 p ,其中“表達式”可以是任何當前正在被測試程序的有效表達式,比如當前正在調試C語言的程序,那麼“表達式”可以是任何C語言的有效表達式,包括數字,變量甚至是函數調用。
print a:將顯示整數 a 的值
print name:將顯示字符串 name 的值
print gdb_test(a):將以變量 a 作爲參數調用 gdb_test() 函數
bt:顯示當前程序的函數調用堆棧。
display 表達式:在單步運行時將非常有用,使用display命令設置一個表達式後,它將在每次單步進行指令後,緊接着輸出被設置的表達式及值。如: display a
watch 表達式:設置一個監視點,一旦被監視的“表達式”的值改變,gdb將強行終止正在被調試的程序。如: watch a
kill:將強行終止當前正在調試的程序
help 命令:help 命令將顯示“命令”的常用幫助信息
call 函數(參數):調用“函數”,並傳遞“參數”,如:call gdb_test(55)
layout:用於分割窗口,可以一邊查看代碼,一邊測試:
quit:簡記爲 q ,退出gdb


我們通過調試多線程多進程程序來使用這些命令:

一些命令的學習:GDB中應該知道的幾個調試方法

多進程調試

默認設置下,在調試多進程程序時GDB只會調試主進程。但是GDB(>V7.0)支持多進程的分別以及同時調試,換句話說,GDB可以同時調試多個程序。

只需要設置follow-fork-mode和detach-on-fork即可。

follow-fork-mode

選項 含義
child 只跟蹤子進程
parent 只跟蹤父進程(默認設置)

deatch-on-fork

選項 含義
on 只調試父進程或子進程的其中一個(根據follow-fork-mode來決定)(默認設置)
off 父子進程都在gdb的控制之下,其中一個進程正常調試(根據follow-fork-mode決定),另一個進程會被設置爲暫停狀態。

在Linux下,我們可以查看或者修改這兩者,命令如下:

show follow-fork-mode
show detach-on-fork
set follow-fork-mode [parent|child]
set detach-on-fork [on|off]

下面我們以一個多進程的小程序來學習如何使用gdb調試。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

void father_process();
void child_process();

int main()
{
    pid_t pid = fork();

    if (pid < 0)
    {
        fprintf(stderr, "fork failure\n");
        exit(-1);
    }
    else if (pid > 0)
    {
        father_process();
    }
    else
    {
        child_process();
    }
    return 0;
}

void father_process()
{
    pid_t pid = getpid();
    printf("father pid = %d\n", pid);
    printf("hello world\n");
}

void child_process()
{
    pid_t pid = getpid();
    printf("child pid = %d\n", pid);
}

這裏寫圖片描述

我們使用show命令查看默認的調試設置,與我們上面所說的一致。默認只調試父進程以及只調試單進程,我們可以通過上面的set命令修改。

這裏寫圖片描述

我們設置調試子進程,且父進程暫停。下面開始調試:

1. 設置斷點並查看

這裏寫圖片描述

2. 運行程序,查詢正在調試的進程

顯示GDB調試的所有inferior,GDB會爲他們分配ID。其中帶有*的進程是正在調試的inferior。( GDB將每一個被調試程序的執行狀態記錄在一個名爲inferior的結構中。一般情況下一個inferior對應一個進程,每個不同的inferior有不同的地址空間。inferior有時候會在進程沒有啓動的時候就存在。)

這裏寫圖片描述

由於我之前設置設置調試子進程,且父進程暫停,且將斷點打各自打在父子進程運行的函數中,因此當子進程一運行就會進入child_ process,執行該函數。如下圖,屏幕打印:

child pid = 5294

這裏寫圖片描述

3.切換調試的進程

使用 inferior <infer number>

這裏寫圖片描述

4.其他

上面都是必須掌握的調試多進程的指令,必須要熟悉,下面還有一些指令最好也能學習以下:

1. add-inferior [-copies n] [-exec executable]
添加新的調試進程,可以用file executable來分配給inferior可執行文件。增加n個inferior並執行程序爲executable。如果不指定n只增加一個inferior。如果不指定executable,則執行程序留空,增加後可使用file命令重新指定執行程序。這時候創建的inferior其關聯的進程並沒啓動。

2. remove-inferiors infno
刪除一個infno號的inferior。如果inferior正在運行,則不能刪除,所以刪除前需要先kill或者detach這個inferior。

3. clone-inferior [-copies n] [infno]
複製n個編號是infno的inferior。如果不指定n的話,就只複製一個inferior。如果不指定infno,則就複製正在調試的inferior。

4. detach inferior
detach掉編號是infno的inferior。注意這個inferior還存在,可以再次用run命令執行它。

5. kill inferior infno
kill掉infno號inferior。注意這個inferior仍然存在,可以再次用run等命令執行它。

這裏寫圖片描述

多線程調試

在多線程編程時,當我們需要調試時,有時需要控制某些線程停在斷點,有些線程繼續執行。有時需要控制線程的運行順序。有時需要中斷某個線程,切換到其他線程。這些都可以通過gdb實現。

GDB默認支持調試多線程,跟蹤主線程,子線程block在create thread。

多線程調試最常用可能就是下面幾個命令:

info thread 查看當前進程的線程。

thread < ID > 切換調試的線程爲指定ID的線程。

break file.c:100 thread all 在file.c文件第100行處爲所有經過這裏的線程設置斷點。

set scheduler-locking off|on|step

在使用step或者continue命令調試當前被調試線程的時候,其他線程也是同時執行的,怎麼只讓被調試程序執行呢?通過這個命令就可以實現這個需求。

選項 含義
off 不鎖定任何線程,也就是所有線程都執行,這是默認值。
on 只有當前被調試程序會執行。
step 在單步的時候,除了next過一個函數的情況(熟悉情況的人可能知道,這其實是一個設置斷點然後continue的行爲)以外,只有當前線程會執行。

我們以一個簡單的多線程程序來學習gdb調試。

/*************************************************************************
    > File Name: thread.c
    > Author: xuyang
    > Mail: [email protected] 
    > Created Time: 2017-06-15 17:32:16
 ************************************************************************/

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

void* thread1(void* arg)
{
    printf("thread1, tid is %lu\n", pthread_self());
    return NULL;
}

void* thread2(void* arg)
{
    printf("thread2, tid is %lu\n", pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

打斷點,然後運行到斷點處。由於還沒有創建新線程,用info threads查看此時只有一個主線程。

這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

發佈了96 篇原創文章 · 獲贊 119 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章