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查看此時只有一個主線程。