調試——GDB、日志
- 1. gdb常用指令
- 2. 如何生成core文件并調試?
- 3. 如何調試正在運行的程序
- 4. 調試多進程程序
- 5. 調試多線程程序
- 6. log日志
- gcc編譯器可以幫我們發現語法錯誤,但是對業務邏輯錯誤卻無能為力。當我們想找出邏輯錯誤時,就需要調試,也就是要用gdb調試
- GDB(gdb)全稱“GNU symbolic debugger”,是 Linux 下常用的程序調試器。 為了能夠使用 gdb 調試,需要在代碼編譯的時候加上-g,如
g++ -g -o test test.cpp
1. gdb常用指令
b(break) 10 :在第10行打上斷點
r(run) :開始運行程序,直到遇到斷點,并在斷點這一行停下來(第一個運行的命令)
c(continue) :繼續運行,一般先停下來,再用continue繼續運行,直到遇到下一個斷點
n(next) :執行當前這一行(徹底執行完,如果當前這一行是函數調用,則執行完這個函數)
p(print) name :顯示變量name的值
p name++ :后面可以寫一行代碼,也就是一個表達式(什么都行,包括改變變量、輸出都行)
set var name="仲書頡" :set var可以設置變量的值,這里設置name的值為仲書頡
set args 參數1 參數2:set args可以設置main(args,argv[])函數的參數,參數1就是argv[1]的值,參數2就是argv[2]的值-注意,當參數1內有空格時,要這樣表示:set args "參數1" 參數2
s(step) :執行當前這一行-這一行不是函數時,和n(next)一樣-這一行是函數時,s會進入函數的內部,執行函數內部的第一行(想進入函數里面,必須要用step)-注意,s并不能進入庫函數或第三方函數,只能進入我們自己定義的函數(有函數源碼)
l(list) :顯示10行代碼(基本不用,因為我們會另開一個窗口看源代碼,一般不用list來看)
bt :查看執行到目前這一行為止,函數的調用棧
info b :顯示斷點的信息(有幾個,哪幾個)
info threads :顯示已創建的線程的信息,帶*號的就是當前被調試的線程
thread 2 :切換到id為2的線程來調試
2. 如何生成core文件并調試?
當程序出現段錯誤(core dump)時,用gdb調試core文件,可以立即定位到段錯誤所在行****,非常方便。但是core文件默認是不生成的,如何生成core文件呢?
在程序出現段錯誤后,我們需要在Linux中輸入:
ulimit -a
會顯示這樣的結果:
我們只需要關注第一行,上面顯示core文件大小為0
將core文件大小改為unlimited
ulimit -c unlimited
再出入ulimit -a后,顯示:
gdb打開core文件
設置完core文件大小后,我們重新運行源程序,就會生成core文件。我們可以通過 **ls **來查看。
然后就用gdb調試即可:
gdb 可執行文件 core文件
#如:gdb book core19356
會顯示:
可見,直接顯示在第7行出現段錯誤
注:什么是段錯誤
**訪問未分配或權限不足的內存 所產生的錯誤,就是段錯誤。有以下幾種情況:
**
3. 如何調試正在運行的程序
如果我們使用gdb調試一個這個在運行的程序,那么這個程序就會立即停止運行。此時為了查看程序運行到說明地方,我們需要用 bt 命令,來查看函數的調用棧——最上面的就是正在運行的函數。
我們可用使用其他調試命令,如n、s等,可以讓原本停下的程序繼續運行一行。通過組合使用 n、s 和 bt 命令,可以調試正在運行的程序:
4. 調試多進程程序
注:被調試的進程會停止運行,將在調試命令下一步一步執行。
調試父進程(默認)
默認就是調試父進程,或者也可以顯示指定:
set follow-fork-mode parent
此時子進程將自動執行;而父進程會阻塞(因為先前設置了斷點),等待我們的調試
調試子進程
需要顯示指定:
set follow-fork-mode child
此時父進程將自動執行;而子進程會阻塞(因為先前設置了斷點),等待我們的調試
調試模式
在確定調試子進程還是父進程后,就可以設置調試模式了,一共有兩種調試模式:
調試當前進程時,其他進程繼續運行
這是默認的情況,也可以顯示指定:
set detach-on-fork on
調試當前進程時,其他進程被gdb掛起,不能運行
需要顯示指定:
set detach-on-fork off
如果我們是在調試父進程時采用了第二種調試模式,那么在調試父進程時,子進程不會自動運行。即使父進程調試執行完,子進程也不會運行。
查看和切換被調試的進程
在設置第二種調試模式之后(即 set detach-on-fork off),我們才能查看和切換被調試的進程。
查看被調試的進程
info inferiors
序號1的前面有*號,表示我們正在調試的進程是 序號為1的進程
**切換要調試的進程**
inferior 進程id
#如 inferior 2,表示改為調試進程2,不再調試進程1
5. 調試多線程程序
注:如果使用的是POSIX線程庫的話,在編譯時還要加上 -l pthread
查看進程、線程以及線程之間的關系
//shell命令(非gdb命令):
ps aux | grep 過濾條件 #查看當前運行的進程ps -aL | grep 過濾條件 #查看當前運行的線程pstree -p 主線程id #查看主線程和子線程之間的關系
線程調試的基礎命令
info threads #顯示已創建的線程的信息,帶*號的就是當前被調試的線程thread 3 #切換到id為3的線程來調試
設置線程調試模式
**調試當前線程時,其他線程繼續運行**
默認的情況,也可以顯示指定:
set scheduler-locking off
**調試當前線程時,其他線程全部阻塞**
需要顯示指定:
set scheduler-locking on
讓指定線程執行指定命令
thread apply 線程id 命令
#如:thread apply 2 n,表示讓線程3往下執行一次(即使當前調試的不是線程2,也可以執行該語句)
另,也可以讓所有線程執行同一個語句:
thread apply all 命令
6. log日志
設置斷點和單步調試 會嚴重影響線程之間的競爭狀態。因為當一個線程在斷點處停住了,而讓另一個線程跑,就會導致并發被破壞,此時我們看到的只是一個和諧的假象。而log日志,就可以避免斷點和單步的副作用。我們可以輸出log日志,讓程序每一步運行的時間都可以在日志文件中查到。
開源日志框架——freecplus
項目里需要同時包含_cmpublic.h、_freecplus.h、_freecplus.cpp這三個文件。第一個里全是程序用到的頭文件;第二個文件包含了第一個文件,它定義了函數和類的聲明;第三個文件包含了第二個文件,它是對函數和類的具體實現。_cmpublic.h_freecplus.cpp_freecplus.h
具體使用過程:
- 包含#include<_freecplus.h>(第一個文件就不用寫了,因為已經包含了)
- 定義日志文件類(全局):
CLogFile logfile;
- 創建日志文件
logfile.Open("/tmp/gdbfork.log","w+");
/*
/tmp/gdbfork.log是日志文件名,可以自己定義; w+是打開文件的方式,直接寫上就好
*/
- 將所有的輸出語句(printf、cout等)改為輸出到日志文件
logfile.Write("Hello World");
- 編譯并運行項目
g++ gdbfork.cpp _freecplus.cpp -o gdbfork
./gdbfork #此時日志已經把所有的內容 輸出的日志文件里面了
- 最后打開日志文件來查看即可
vi /tmp/gdbfork.log
我們也可以一邊讓程序運行,一邊打開事先創建的日志文件,這樣就可以觀測** 程序運行的實時狀態**。