注意: 這里是講gdb的高級技巧。如果沒有接觸過gdb,請看這篇:點這里。
gdb是一個功能極其強大的命令行調試器。其實,除了我們常用的 file b s n q disp p
等命令,也有很多高級技巧。雖然有的功能是為系統級調試提供的,但還是有方便之處。
接下來,我將介紹一些高級技巧,希望可以幫助大家。
(溫馨提醒:多用help命令!請提前用 -g
參數編譯)
GDB版本:9.1;系統版本:Arch Linux 245-1
示例代碼:(以下示例均以此代碼為准)
#include <iostream>
#include <cstdio>
using namespace std;
int f(int n){
if(n==0) return 1;
return f(n-1)*n;
}
int main(){
int n;
scanf("%d",&n);
printf("%d\n",f(n));
return 0;
}
1. backtrace
backtrace(簡寫為bt)可以讓你查看棧幀信息。這對調試遞歸的函數很有幫助。
配套命令:
up/down [num]
往棧頂/棧底移動num幀。num默認為1。
frame [num]
切換到第num幀。frame簡寫為f。
Example:
當抵達某個斷點停下后,輸入bt,類似於這樣:
(gdb) bt
#0 f (n=3) at example.cpp:6
#1 0x000055555555519e in f (n=4) at example.cpp:6
#2 0x000055555555519e in f (n=5) at example.cpp:6
#3 0x000055555555519e in f (n=6) at example.cpp:6
#4 0x000055555555519e in f (n=7) at example.cpp:6
#5 0x000055555555519e in f (n=8) at example.cpp:6
#6 0x000055555555519e in f (n=9) at example.cpp:6
#7 0x000055555555519e in f (n=10) at example.cpp:6
#8 0x00005555555551dd in main () at example.cpp:12
(gdb)
幾個示例:
(gdb) up 1
#1 0x000055555555519e in f (n=4) at example.cpp:6
6 return f(n-1)*n;
(gdb) down 1
#0 f (n=3) at example.cpp:6
6 return f(n-1)*n;
(gdb) frame 5 //前面bt輸出的結果中,第一項是序號,frame即切換到對應序號的幀
#5 0x000055555555519e in f (n=8) at example.cpp:6
6 return f(n-1)*n;
(gdb)
2. commands
commands(簡寫為comm)可以在觸發某個(或多個)斷點的時候運行指定gdb命令。
用法:commands [斷點編號1] [斷點編號2] ...
之后,它會讓你逐行輸入要指定的gdb命令。
效果嗎...在到你指定的斷點時,他都會逐行運行你之前輸入的命令。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) comm 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>p "Command test" //指定到達1號斷點時打印這段字符串
>end //以end結束
(gdb) r
Starting program: /run/media/acceptedzhs/SimpleDisk/編程/洛谷/example
10
Breakpoint 1, f (n=10) at example.cpp:6
6 return f(n-1)*n;
$1 = "Command test"
(gdb)
順便提一句,怎么查看斷點編號?運行 info b
即可。
輸出類似這樣:(num是編號)
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000000129e in pre() at UVA10140 Prime Distance.cpp:12
2 breakpoint keep y 0x00000000000012c7 in pre() at UVA10140 Prime Distance.cpp:13
3 breakpoint keep y 0x00000000000012de in pre() at UVA10140 Prime Distance.cpp:14
3. ignore
用法:ignore [斷點編號] [num]
。ignore可縮寫為ig。
效果:在前num次觸發指定斷點時都不停止(即到了第num+1次觸發斷點才停下)
這在調一些循環結構的代碼時比較有用。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) ig 1 4
Will ignore next 4 crossings of breakpoint 1.
(gdb) r
Starting program: /run/media/acceptedzhs/SimpleDisk/編程/洛谷/example
10
Breakpoint 1, f (n=6) at example.cpp:6
6 return f(n-1)*n;
(gdb) //前面4次經過斷點,分別為f(10)、f(9)、f(8)、f(7),都跳過了
//因此f(6)才觸發
4. condition
用法:condition [斷點編號] [條件]
。condition可縮寫為cond。
效果:觸發斷點時,只有指定的條件為真時才停下。
Example:
(gdb) b 6
Breakpoint 1 at 0x1191: file example.cpp, line 6.
(gdb) cond 1 n==5 //只有n等於5時才觸發斷點
(gdb) r
Starting program: /run/media/acceptedzhs/SimpleDisk/編程/洛谷/example
10
Breakpoint 1, f (n=5) at example.cpp:6
6 return f(n-1)*n;
(gdb)
5. 各種breakpoint
什么?斷點還有類型?這里介紹下:
- break(簡寫b)是我們最熟悉的。
- tbreak(簡寫tb):臨時斷點,也就是觸發一次后自動消失。與break用法相同。
- hbreak(簡寫hb):硬件斷點。對我們來說沒什么用。
- rbreak(簡寫rb):根據正則表達式設置斷點。用法:
rbreak [正則表達式]
。
說明一下rbreak。舉個例子,我程序里有兩個函數,dfs1與dfs2。如果我運行 rbreak dfs*
,由於dfs1與dfs2均匹配,所以這兩個函數均會被加上斷點。
Example1:(tbreak)(看到沒,第一次觸發在f函數處的斷點,繼續運行便不會再觸發該斷點了)
(gdb) tb f
Temporary breakpoint 1 at 0x1184: file example.cpp, line 5.
(gdb) r
Starting program: /run/media/acceptedzhs/SimpleDisk/編程/洛谷/example
10 //這里是輸入,10!=3628800
Temporary breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1; //臨時斷點,只停了一次
(gdb) c
Continuing. //繼續,就不會再停了
3628800
[Inferior 1 (process 31859) exited normally]
(gdb)
Example2:(rbreak)(main符合mai*的條件,因此被加了斷點)
(gdb) rb mai*
Breakpoint 1 at 0x11ac: file example.cpp, line 9.
int main();
(gdb)
6. print/display命令輸出格式(可以用簡寫)
用法:print/[format] [變量1] [變量2] ...
當然,如果是display命令,則要換成display/[format] [變量1] [變量2] ...
其中,format是一個小寫字母,指定打印變量值的格式。
format字母 | 對應格式 |
---|---|
x | 按十六進制格式顯示變量 |
d | 按十進制格式顯示變量 |
u | 按十進制格式顯示無符號整型 |
o | 按八進制格式顯示變量 |
t | 按二進制格式顯示變量 |
a | 按十六進制格式顯示變量 |
c | 按字符格式顯示變量 |
f | 按浮點數格式顯示變量 |
比如說,調試狀壓DP的程序時,就可以 p/t [變量名]
來以二進制形式查看變量了。disp同理。
Example:
(gdb) p/x 100
$1 = 0x64
(gdb) p/d 100
$2 = 100
(gdb) p/u 100
$3 = 100
(gdb) p/o 100
$4 = 0144
(gdb) p/t 100
$5 = 1100100
(gdb) p/a 100
$6 = 0x64
(gdb) p/c 100
$7 = 100 'd'
(gdb) p/f 100
$8 = 1.40129846e-43 //浮點數格式不一樣
(gdb)
7. save
其實斷點是可以保存的!比如說,我臨時要重啟一下,又不想丟失當前調試的斷點信息。那么,我們可以將當前的斷點信息保存到一個文件里,到時候再導入。
用法:save breakpoints [文件名]
效果:將當前所有的斷點信息保存到一個指定的文件里。
有人有疑問了,怎么導入斷點信息呢?那就是source命令!
用法:source [文件名]
效果:從指定的文件里導入斷點信息。
Example:
(gdb) b 5
Breakpoint 1 at 0x1184: file example.cpp, line 5.
(gdb) b 6
Breakpoint 2 at 0x1191: file example.cpp, line 6.
(gdb) b 7
Breakpoint 3 at 0x11a2: file example.cpp, line 7.
(gdb) save breakpoints 123 //保存斷點信息
Saved to file '123'.
(gdb) q
---中間退出gdb,再重新進---
(gdb) source 123 //從這個文件中引入斷點信息
Breakpoint 1 at 0x1184: file example.cpp, line 5.
Breakpoint 2 at 0x1191: file example.cpp, line 6.
Breakpoint 3 at 0x11a2: file example.cpp, line 7.
(gdb)
8. call
用法:call [調用語句]
效果:調用指定函數。
比如說,我的程序有一個min函數,我就可以通過 call min(a,b)
來獲取變量a、b的最小值了。
Example:
(gdb) call f(10) //也就是10!
$1 = 3628800
(gdb)
9. finish(縮寫fin)
用法:無參數。
效果:繼續運行,直到當前函數返回。
Example:
Breakpoint 1, main() at example.cpp:10
10 int n;
(gdb) fin
Run till exit from #0 main() at example.cpp:9 //main函數執行完畢,返回了0
[Inferior 1 (process 32243) exited normally]
(gdb)
10. watchpoint
其實,斷點還有一種特殊的類型——watchpoint。(簡寫為wa——似乎不太吉祥)
用法:watch/rwatch/awatch [變量名]
作用:監視指定變量。
- watch(簡寫wa):當指定變量被寫時停下。
- rwatch(簡寫rwa):當指定變量被讀時停下。
- awatch(簡寫awa):當指定變量被讀/寫時停下。
Example:
(gdb) wa n
Hardware watchpoint 2: n
(gdb) c
Continuing.
10
Hardware watchpoint 2: n
Old value = 32767 //此處由於scanf讀入修改了n的值,因此停下
New value = 10
0x00007ffff7ac43a9 in __vfscanf_internal () from /usr/lib/libc.so.6
(gdb)
11. checkpoint
有時候,我們要復現某個bug,這個時候,我們可以創建一個快照,即checkpoint。
命令:checkpoint
(可簡寫為ch)
用法:無參數。
效果:創建一個快照,包含當前調試的所有信息。同時會輸出這個checkpoint的信息,就像這樣:
checkpoint 1: fork returned pid 25776.
其中,數字1便是這個checkpoint的編號。
那么,如何回滾到以前的快照呢?那就是restart命令啦!
用法:restart [checkpoint編號]
效果:回退到指定checkpoint的快照。
Example:
Breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb) ch //創建了編號為1的快照
checkpoint 1: fork returned pid 32213.
(gdb) c
Continuing.
Breakpoint 1, f (n=9) at example.cpp:5
5 if(n==0) return 1;
(gdb) restart 1 //恢復到編號為1的快照
Switching to process 32213
#0 f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb)
12. jump
用法:jump [num]
作用:強制使跳轉至第num行。(中間的行都跳過了)
注意,這個不能跨函數跳轉,否則會出錯。
Example:
(gdb) b 10
Breakpoint 1 at 0x11bb: file example.cpp, line 11.
(gdb) r
Starting program: /run/media/acceptedzhs/SimpleDisk/編程/洛谷/example
Breakpoint 1, main () at example.cpp:11
11 scanf("%d",&n);
(gdb) jump 13
Continuing at 0x5555555551f0.
[Inferior 1 (process 32243) exited normally] //跳到了第13行,main函數已經結束了,因此直接退出程序
(gdb)
13. return
用法:return [argu]
作用:強制使當前函數退出,並返回argu值。(如果該函數本來就沒有返回值,則argu可以省略)
Breakpoint 1, f (n=10) at example.cpp:5
5 if(n==0) return 1;
(gdb) return 15
Make f(int) return now? (y or n) y
#0 0x00005555555551dd in main () at example.cpp:12
12 printf("%d\n",f(n));
(gdb) n
15 //因為前面設定返回15,所以這里輸出15
蒟蒻寫博客不易,懇請大佬點個贊!