# 除錯筆記
## GNU gdb是什麼?
gdb(GNU Debugger)是一個開源的程式除錯器,對C/C++程式來說是一個強而有力的工具之一。類似於Visual Studio中的debugger,可以對某一行程式埋斷點進行除錯。
## 前製作業
1.安裝gdb:
>Ubuntu/Debian 安裝:
>```$ sudo apt install gdb```
2.編譯程式:
啟用gcc/clang的```-g```選項生成帶有debug information的執行檔
> ```$ gcc -g myfile.c -o myfile```
## gdb, 啟動
```$ gdb myfile```
註1:此處可以加上```-q```選項隱藏以下開場白
註2:最近習慣加上```-tui```選項方便查看程式
```bash
GNU gdb (GDB) 14.2
Copyright (C) 2023 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-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from myfile...
(gdb)
```
可以看到此時的prompt變成了```(gdb)```,表示我們進入了debug環境,可以開始輸入gdb的指令來debug了。
## gdb的基本指令
### quit
如同[vim](https://stackoverflow.com/questions/11828270/how-do-i-exit-vim "怎麼離開vim?")編輯器,許多人時常卡在其中離不開XD,此使用quit指令來離開,或是使用exit也可以。
---
### run
> 使用 **run** 來執行你的程式,也可以在後面加上程式自己定義的參數讀入,如以下範例程式:
``` c++=
#include <iostream>
#include <cstring>
using std::cout;
using std::cerr;
using std::endl;
signed main(int argc, char **argv) {
if ( argc == 1 ) {
cerr << "Error: No arguments exist" << endl;
return -1; // no arguments error
} else if ( argc == 2 && strcmp(argv[1], "-v") == 0 ) {
cout << "Program version: 0.0.1" << endl;
} else {
cerr << "Error: arguments not found" << endl;
return -2; // no arguments match
}
return 0;
}
```
1. 若輸入 ```(gdb) run -v``` 效果等同於 ```$ ./a.out -v```。
2. 若你的程式含有有標準輸入輸出,也可以對其使用redirection < 及 > 符號,
像是 ```(gdb) run -v < inval.in > outval.out``` 。
3. 如果使用自己的參數後,接下來的 **run** 若不帶參數也會自動使用上一次使用的參數 ```-v```。
若想顯示參數可以使用 ```(gdb) show args```,
若想將參數改成 ```-b``` 可以使用```(gdb) set args -b```
show args 輸出結果:
```Argument list to give program being debugged when it is started is "-v".```
---
### list
> **list** 指令可以顯示部份的程式碼,以下是我常用的形式。
1. (gdb) **list** line1,line2
▴ 顯示 line1 ~ line2 的程式碼
2. (gdb) **list** function-name
▴ 顯示 function-name 上下的程式碼
3. (gdb) **list** line
▴ 顯示 line 上下的程式碼
---
### whatis / print
> **whatis** 指令可以顯示數或是資料的型態,例如:
```bash
(gdb) whatis p
type = int *
(gdb) whatis argv
type = char **
```
> **print** 指令可以讓你檢查變數內容,例如:
```bash
(gdb) print a
$1 = 10
(gdb) print v
$2 = std::vector of length 10, capacity 10 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
```
可以看到印出結果後的開頭都是以```$n```為首,在gdb中稱為**歷史值**,可以使用歷史值來代替**print**的參數,例如:
```bash
(gdb) print $1-1
$3 = 9
```
如果想印出連續記憶體(array)則可以使用:*記憶體位置 @ 長度* 的方式訪問,例如:
```bash
(gdb) print arr@10
$4 = {0x555555589340, 0x7fffffffde73, 0x555555589310,
0x555555589338, 0x555555589338, 0x55ad66ad3ea30500,
0x7fffffffdff8, 0x7fffffffdff8, 0x7fffffffded0, 0x555555555470}
(gdb) print *arr@10
$5 = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90}
```
當然也可以使用剛剛提到的歷史值,例如:
```bash
(gdb) print $5[5] # 使用歷史值取特定元素
$6 = 50
(gdb) print $5[5]@5 # 也可以從 idx:5 開始印出接下來的五個元素
$7 = {50, 60, 70, 80, 90}
(gdb) print $5[5] = $5[4]*10 # 另一種用法,直接賦值給 idx:5 並印出來
$8 = 400
(gdb) print *arr@10 # 可以看到arr陣列也被改動了
$9 = {0, 10, 20, 30, 40, 400, 60, 70, 80, 90}
(gdb) print 'myfile.cc' ::Inf # 指定哪個檔案中的值並印出來
$10 = 2139062143
```
如果想印出C語言中的結構體或是C++中的類別型態,使用 ```(gdb) ptype``` 可以將其完整印出來,
例如:
```bash
(gdb) print Mystruct
$11 = {x = 1431868176, y = 3.0611365e-41, flag = 56}
(gdb) ptype Mystruct
type = struct Obj {
int x;
float y;
bool flag;
}
```
註1: 可以使用 ```set print array``` 垂直方式印出陣列,若要關閉則是 ```set print array off```。
註2: ```(gdb) set variable = expression``` 可以直接將程式中的值進行更動,
如同上面範例中的 ```print $5[5] = $5[4]*10``` 一樣
---
### breakpoints
> **breakpoints**(中斷點)
> 可以說是debugger不可或缺的指令之一,能夠讓你的程式在指定位置打住方便debug。以下是它的常見用法:
1. break line-number
▴ 在特定行號設斷點
2. break function-name
▴ 在特定函式前停止
3. break line-or-function if condition
▴ 若條件符合,則會在特定行號或函式前停止
4. break filename: line-or-function
▴ 若有多個檔案,則可以先指定檔名
```bash
(gdb) break 40
Breakpoint 1 at 0x5555555552da: file myfile.cc, line 40.
(gdb) break Return_Val
Breakpoint 2 at 0x5555555554cc: file myfile.cc, line 31.
(gdb) break 42 if i == 5
Breakpoint 3 at 0x555555555311: file myfile.cc, line 42.
(gdb) break myfile2.cc: 50
Breakpoint 4 at 0x555555555254: file myfile2.cc, line 50.
```
若要刪除breakpoints,可以使用 ```delete``` 或是 ```clear``` 指令。
btw, 有另一個指令叫 ```tbreak``` 它是一個 **暫時的** 中斷點,
一般情況下的中斷點在程式執行完後並不會被拿掉,不過tbreak會在程式執行後**失效**。
但並沒有被刪除,可以使用 ```enable``` 指令來讓暫時中斷點再度生效!
---
### next / step / continue
1. 當gdb執行到breakpoints時想讓它繼續執行,可以使用 ```(gdb) next``` 執行下一行程式。
2. 若想執行到下個breakpoints懶得一行一行next, 可以使用 ```(gdb) continue``` 跳轉到下個斷點。
3. gdb遇到呼叫函式時,使用next並不會實際進入函式,此時就可以使用 ```(gdb) step``` **走進** 那個函式進行debug。
---
### watchpoints / display
> **watchpoint**(監看站)
顧名思義,可以監看一個值。每當這個值有變化,gdb將會印出他的old value及new value。
以下是它的常見用法:
1. watch value
▴ 監看value是否有變化
2. watch condition
▴ 也可以用前面所提的break-if方式
```bash
(gdb) watch i
Hardware watchpoint 4: i
(gdb) watch i > 5
Hardware watchpoint 5: i > 5
```
在 i 發生變化時,會打印出:
```bash
Hardware watchpoint 4: i
Old value = 5
New value = 6
Hardware watchpoint 5: i > 5
Old value = false
New value = true
```
> **display**(自動顯示)
有點類似watchpoints,差別在於display會在每次gdb執行後印出指定的值,適合用在需要持續觀察一個值的變化,例如:
```bash
(gdb) display *arr@10
2: *ar@10 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
(gdb) n
2: *ar@10 = {0, 10, 0, 0, 0, 0, 0, 0, 0, 0}
(gdb) n
2: *ar@10 = {0, 10, 20, 0, 0, 0, 0, 0, 0, 0}
```
若要刪除display,可以使用 ```(gdb) undisplay number``` 命令,
其中的number是一個 **識別數字** 可以透過 ```(gdb) info display``` 查看。
---
### info
> **info**指令可以讓我們查看breakpoints, watchpoints, display等斷點狀態,以下為範例:
```bash
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555555232 in sol() at myfile.cc:42
2 breakpoint del y 0x0000555555555221 in sol() at myfile.cc:41
(gdb) info display
There are no auto-display expressions now.
(gdb) info watchpoints
No watchpoints.
```
此外,**enable** 和 **disable** 兩指令可以讓斷點生效或失效
---
### call / finish / return
> 以下簡短介紹這三個指令:
1. ```(gdb) call name```
▴ 直接呼叫並執行函式
2. ```(gdb) finish```
▴ 結束目前函式執行並印出回傳值(假如有的話)
3. ```(gdb) return value```
▴ 取消目前的函式執行,並回傳value
---
### where / frame
> **where**(backtrace) 指令可以讓你知道目前所有的堆疊。每當gdb進入一個函式,就會往下新增一個堆疊,大概長這樣:
| main function() |
|:-- |
|sol(void) |
|Return_Val(int, int) |
```bash
(gdb) where
#0 Return_Val (a=10, b=20) at c.cc:38
#1 0x00005555555553b0 in sol () at c.cc:55
#2 0x0000555555555470 in main () at c.cc:62
```
這裡可以使用 ```up``` 及 ```down``` 指令來移動你目前所在堆疊。
```up n``` 及 ```down n``` 則是往上或往下 n 層。越往下表示離main()越遠,反之則越近。
> **frame** 指令則是能讓你知道你目前所在的堆疊位置,例如:
```bash
(gdb) frame
#0 Return_Val (a=10, b=20) at c.cc:38
```
---
## 待補還沒研究的功能 😓
- [ ] 自動執行命令(commands)
- [ ] 機器語言功能
- [ ] 信號(signal)
- [ ] gdb個人化設定
- [ ] ~~Emacs界面~~
---
## 指令的簡稱
以下統整了一些常用的指令簡稱:
(出處:++*O'reilly GNU 程式設計*++ P.191)
| 簡稱 | 指令 |
|:-- | :-- |
|b | break |
|c | continue |
|d | delete |
|f | frame |
|h | help |
|i | info |
|l | list |
|n | next |
|p | print |
|q | quit |
|r | run |
|s | step |
## 快速參考
以下為一般gdb指令
(出處:++*O'reilly GNU 程式設計*++ P.195, P.196)
| 指令 | 用途 |
|:-- | :-- |
|advance |直接前進到某一行程式,類似C語言中的goto |
|backtrace |顯示程式中目前位置及堆疊紀錄(同義字:where)|
|breakpoint |設定中斷點 |
|cd |變更目前目錄 |
|clear |刪除上一個中斷點 |
|commands |表列遇到中斷點時要執行的命令 |
|continue |從中斷點繼續執行 |
|delete |刪除中斷點或監看站 |
|display |在程式中斷時顯示變數或運算式 |
|down |下移堆疊框到另一個函式 |
|frame |選擇下一個continue命令的堆疊框 |
|info |顯示程式相關資訊。如 info breakpoints 顯示所有中斷點及監看站 |
|jump |跳到原始碼中其他地方執行 |
|kill |結束gd控制下執行的進程 |
|list |原始程式碼列表 |
|next |執行下一行,不會走進函式 |
|print |印出變數或運算式的值 |
|pwd |顯示目前目錄 |
|ptype |顯示資料型別的內容,如C的struct或C++的class |
|quit |離開gdb |
|reverse-search |在原始程式檔中往前尋找運算式 |
|run |設定變數值 |
|search |在原始程式檔中尋找運算式 |
|set variable |設定變數值 |
|signal |發出信號給執行中程序 |
|step |執行下一行,會走進函式 |
|undisplay |取消display; 不顯示運算式 |
|until |完成目前迴圈 |
|up |上移堆疊框到另一個函式 |
|watch |設定監看站 |
|whatis |顯示變數或函式的型別 |
---
此筆記參考書籍:++*Programming with GNU Software*++ 之C及C++程式除錯器篇