--- tags: debug, gdb --- # GDB 除錯技巧 contributed by < `hsuedw` > ## 用 x 命令檢視記憶體內容 用一個最簡單的 C 程式來學習如何用 GDB 檢視程式執行期間的記憶體內容。 ```c int main() { int i = 1337; return 0; } ``` 在 C 語言中,變數被用來代表一塊記憶體空間,這塊記憶體空間可以用來儲存資料,表示數值。這塊記憶體空間就是 C99 Standard 中所謂的 object。 > C99 Standard 3.14 > object > region of data storage in the execution environment, the contents of which can represent values 一個變數有兩個重要的數字: 1. object 在記憶體中所佔用區域的==起始位址==。 2. object 在記憶體中所佔用區域的大小。一般以 byte 為單位。 <pre> $ gcc test.c -g -o test $ gdb ./test : (gdb) l 1 int main() 2 { 3 int i = 1234; 4 return 0; 5 } (gdb) b 4 Breakpoint 1 at 0x1138: file test.c, line 4. (gdb) r Starting program: /home/edward/workspace/linux_2022/gdb/test Breakpoint 1, main () at test.c:4 4 return 0; <span style="color: blue;"> (gdb) p &i $1 = (int *) 0x7fffffffe23c (gdb) p sizeof(i) $2 = 4 </span> </pre> 如上面實驗步驟所示, `0x7fffffffe23c` 就是變數 `i` 的記憶體位址, `4` 就是變數 `i` (型別為 `int` ) 所佔用的記憶體區塊大小。 接下來用 `x` 指令檢視變數 `i` 所佔用的記憶體空間中的數值。 ``` (gdb) x/4bx &i 0x7fffffffe23c: 0xd2 0x04 0x00 0x00 ``` > 參考資料: [x command](https://visualgdb.com/gdbreference/commands/x) 在這個範例中,用 `x` 指令檢視記憶體內容時,指定印出格式為 `4` 個 byte ,且顯示為十六進位 ( `x` )。 因為我用的電腦是 Intel x86_64 CPU ,採用 little endian 表示資料,所以由 `0x7fffffffe23c` 起算 4 個 byte 應到著看。正確的資料應為 `0x00 0x00 0x04 0xd2` 。而十六進位 `0x4d2` 就是十進位的 `1234`。 ## GDB 的圖形化介面 在執行 `gdb` 追蹤程式碼的運作時,可加入 `-tui` 選項。即使是在命令列模式下, `gdb` 也可以呈現圖形化介面。 ``` $ gcc hello.c -g -o hello $ gdb -tui ./hello ``` ![gdb gui](https://i.imgur.com/pW3xpIx.jpg) ## 設定參數給被追蹤的程式 * 第一種方法,在 `gdb` 提示訊息下執行 `r` (或 `run` )命令時,同時指定要傳給被追蹤程式的參數就可以了。 ``` $ gdb -q ./test Reading symbols from ./test... (gdb) l 1 #include <stdio.h> 2 3 int main(int argc, char **argv) 4 { 5 printf("%s, %s!\n", argv[1], argv[2]); 6 7 return 0; 8 } (gdb) b 7 Breakpoint 1 at 0x1186: file test.c, line 7. (gdb) r Hello World Starting program: /home/edward/workspace/linux_2022/gdb/test Hello World Hello, World! Breakpoint 1, main (argc=3, argv=0x7fffffffe328) at test.c:7 7 return 0; (gdb) ``` * 第二種方法,啟動 gdb 時,直接給參數 ``` $ gdb -q --args ./test Hello World! Reading symbols from ./test... (gdb) l 1 #include <stdio.h> 2 3 int main(int argc, char **argv) 4 { 5 printf("%s, %s!\n", argv[1], argv[2]); 6 7 return 0; 8 } (gdb) b main Breakpoint 1 at 0x1149: file test.c, line 4. (gdb) r Starting program: /home/edward/workspace/linux_2022/gdb/test Hello World\! Breakpoint 1, main (argc=0, argv=0x7fffffffe320) at test.c:4 4 { (gdb) n 5 printf("%s, %s!\n", argv[1], argv[2]); (gdb) Hello, World!! 7 return 0; (gdb) ``` ## breakpoint ### 設定 breakpoint 可以用 `break` 指令 (或簡寫為 `b` ) 加上 breakpoint 的位置來設定。 breakpoint 的位置可以是 1. 檔案名稱 2. 行號 3. 原始程式碼檔名加上行號 ``` break main 在 main() 函式的開頭設定 breakpoint break 6 在目前這個檔案的第 6 行設定 breakpoint break hello.c:6 在 hello.c 這個檔案的第 6 行設定 breakpoint ``` ``` (gdb) l 1 #include <stdio.h> 2 3 int main() 4 { 5 int a = 1, b = 2; 6 7 a +=b; 8 printf("Hello World!\n"); 9 10 return 0; (gdb) 11 } (gdb) b main Breakpoint 1 at 0x1149: file hello.c, line 4. (gdb) b 5 Breakpoint 2 at 0x1155: file hello.c, line 5. (gdb) b hello.c:8 Breakpoint 3 at 0x1169: file hello.c, line 8. ``` ### 列出所有的 breakpoint 可以用 `i b` 這個指令列出目前所有的 breakpoint 。 ``` (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001149 in main at hello.c:4 2 breakpoint keep y 0x0000000000001155 in main at hello.c:5 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 ``` ### 開啟與關閉 breakpoint 可以用 `i b` 指令列出所有的 breakpoint 後,再用 `disable` 指令加上 breakpoint 的編號暫時讓指定的 breakpoint 失效。 事後可以再用 `enable` 指令加上 breakpoint 的編號來恢復 breakpoint 的功能。 ``` (gdb) disable 1 (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep n 0x0000000000001149 in main at hello.c:4 2 breakpoint keep y 0x0000000000001155 in main at hello.c:5 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 (gdb) enable 1 (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001149 in main at hello.c:4 2 breakpoint keep y 0x0000000000001155 in main at hello.c:5 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 ``` ### 刪除 breakpoint 可以用 `i b` 指令列出所有的 breakpoint 後,再用 `clear` 指令加上 breakpoint 的位置來刪除指定的 breakpoint。 1. 檔案名稱 2. 行號 3. 原始程式碼檔名加上行號 ``` (gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001149 in main at hello.c:4 2 breakpoint keep y 0x0000000000001155 in main at hello.c:5 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 (gdb) clear main (gdb) i b Deleted breakpoint 1 Num Type Disp Enb Address What 2 breakpoint keep y 0x0000000000001155 in main at hello.c:5 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 (gdb) clear 5 (gdb) i b Deleted breakpoint 2 Num Type Disp Enb Address What 3 breakpoint keep y 0x0000000000001169 in main at hello.c:8 (gdb) clear hello.c:8 (gdb) i b Deleted breakpoint 3 No breakpoints or watchpoints. ``` ### 條件斷點 ## 單步執行 * `n` 或 `next` 指令以 statement 為單位,可以依序單步執行目前函式中的每一個 statement。 * 當 statement 為一個函式時,`s` 或 `step` 指令可以進入此函式進行追蹤。 * 使用單步執行時,一般可以搭配 `i locals` 或 `p 變數名稱` 觀察變數的內容。 * `next x` 與 `step x` 可以指定往下走 `x` 條 statement。 ``` (gdb) l 1 int main() 2 { 3 int i = 1; 4 i = 2; 5 i = 3; 6 i = 4; 7 i = 5; 8 i = 6; 9 i = 7; 10 i = 8; (gdb) l 11 i = 9; 12 i = 10; 13 i = 11; 14 i = 12; 15 i = 13; 16 17 return 0; 18 } (gdb) b main Breakpoint 1 at 0x1129: file test.c, line 2. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test Breakpoint 1, main () at test.c:2 2 { (gdb) n 3 int i = 1; (gdb) n 4 i = 2; (gdb) n 5 9 i = 7; (gdb) ``` ## 指定執行位置 `advance` 指令可以指定接下來要直接執行到程式的哪一個位置。有點像快轉。不需要一步步地做單步執行。 ``` (gdb) l 1 #include <stdio.h> 2 3 void bar() 4 { 5 printf("Hello bar!\n"); 6 return; 7 } 8 9 void foo() 10 { (gdb) 11 bar(); 12 } 13 14 int main() 15 { 16 foo(); 17 return 0; 18 } (gdb) Line number 19 out of range; test.c has 18 lines. (gdb) b main Breakpoint 1 at 0x1175: file test.c, line 15. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test Breakpoint 1, main () at test.c:15 15 { (gdb) advance bar bar () at test.c:4 4 { (gdb) n 5 printf("Hello bar!\n"); (gdb) n Hello bar! 6 return; (gdb) ``` ## 觀察變數內容 ### `info locals` 命令 `info locals` 命令可以列出目前所有區域變數的內容。 ``` (gdb) l 1 int main() 2 { 3 int i = 0, j = 1, k = 2; 4 5 i += 1; 6 j += 2; 7 k += 3; 8 9 return 0; 10 } (gdb) b 9 Breakpoint 1 at 0x1152: file test2.c, line 9. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test2 Breakpoint 1, main () at test2.c:9 9 return 0; (gdb) info locals i = 1 j = 3 k = 5 (gdb) ``` ### `print 變數名稱` `print 變數名稱` 可以顯示指定變數的內容。 ``` (gdb) l 1 int main() 2 { 3 int i = 0; 4 5 i += 1; 6 i += 2; 7 8 return 0; 9 } (gdb) b main Breakpoint 1 at 0x1129: file test2.c, line 2. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test2 Breakpoint 1, main () at test2.c:2 2 { (gdb) n 3 int i = 0; (gdb) print i $1 = 0 (gdb) n 5 i += 1; (gdb) print i $2 = 0 (gdb) n 6 i += 2; (gdb) print i $3 = 1 (gdb) n 8 return 0; (gdb) print i $4 = 3 (gdb) ``` ### `display` 命令 除了 `i locals` 與 `p 變數名稱` 外,還可以用 `display` 命令設定想要觀察的變數。 每做一次 `next` ,那些被 `display` 設定為要觀察的變數的內容就會被自動顯示出來。如下面這個範例中的變數 `i`。要注意的是,在 GDB 提示符號 `(gdb)` 後的 statement 是下一個要執行的 statement。 ``` (gdb) l 1 int main() 2 { 3 int i = 1; 4 i = 2; 5 i = 3; 6 i = 4; 7 return 0; 8 } (gdb) b main Breakpoint 1 at 0x1129: file test.c, line 2. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test Breakpoint 1, main () at test.c:2 2 { (gdb) display i 1: i = 0 (gdb) info display Auto-display expressions now in effect: Num Enb Expression 1: y i (gdb) n 3 int i = 1; 1: i = 0 (gdb) n 4 i = 2; 1: i = 1 (gdb) n 5 i = 3; 1: i = 2 (gdb) n 6 i = 4; 1: i = 3 (gdb) n 7 return 0; 1: i = 4 (gdb) ``` ### `watch` 命令 `watch` 命令可以設定想要觀測的變數。只要該變數的的內容有發生變化, GDB 就會把該變數改變前與改變後的內容印出來。 ``` (gdb) l 1 int main() 2 { 3 int i = 0, j = 1; 4 5 j += 1; 6 j *= 2; 7 j -= 3; 8 i += j; 9 j += 4; 10 j += 5; (gdb) 11 12 return 0; 13 } (gdb) b main Breakpoint 1 at 0x1129: file test2.c, line 2. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test2 Breakpoint 1, main () at test2.c:2 2 { (gdb) watch i Hardware watchpoint 2: i (gdb) n 3 int i = 0, j = 1; (gdb) n 5 j += 1; (gdb) n 6 j *= 2; (gdb) n 7 j -= 3; (gdb) n 8 i += j; (gdb) n Hardware watchpoint 2: i Old value = 0 New value = 1 main () at test2.c:9 9 j += 4; (gdb) n 10 j += 5; (gdb) n 12 return 0; (gdb) ``` ### 觀察資料結構 print {type} variable 假設追蹤下列程式碼。 ```c struct test { int a; double b; }; int main() { struct test t; t.a = 123; t.b = 123.456; struct test *pt = &t; return 0; } ``` 編譯原始碼 (`gcc test.c -g -o test`) 後,啟動 `gdb` 進行追蹤。 ``` $ gdb -q test Reading symbols from test... (gdb) l 1 2 struct test { 3 int a; 4 double b; 5 }; 6 7 int main() 8 { 9 struct test t; 10 t.a = 123; (gdb) 11 t.b = 123.456; 12 13 struct test *pt = &t; 14 15 return 0; 16 } (gdb) b 15 Breakpoint 1 at 0x1180: file test.c, line 15. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test Breakpoint 1, main () at test.c:15 15 return 0; (gdb) p {struct test} pt $1 = {a = 123, b = 123.456} (gdb) p pt $2 = (struct test *) 0x7fffffffe2c0 (gdb) p {struct test} 0x7fffffffe2c0 $3 = {a = 123, b = 123.456} ``` ## 在追蹤過程中修改變數內容 ### 用 `print` 指令修改變數內容 可以用 `print 變數名稱 = 新內容` 修改變數的內容。 ``` (gdb) l 1 int main() 2 { 3 int i = 0; 4 5 i += 1; 6 i += 2; 7 i += 3; 8 i += 4; 9 10 return 0; (gdb) 11 } (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test2 Breakpoint 1, main () at test2.c:2 2 { (gdb) n 3 int i = 0; (gdb) n 5 i += 1; (gdb) n 6 i += 2; (gdb) print i $4 = 1 (gdb) print i = 100 $5 = 100 (gdb) n 7 i += 3; (gdb) print i $6 = 102 (gdb) n 8 i += 4; (gdb) print i $7 = 105 (gdb) n 10 return 0; (gdb) print i $8 = 109 (gdb) ``` ### 用 `set` 指令修改變數內容 `set` 指令有兩種用法 1. `set variable 變數名稱 = 新內容` 2. `set (變數名稱 = 新內容)` ``` (gdb) l 1 int main() 2 { 3 int i = 0; 4 5 i += 1; 6 i += 2; 7 i += 3; 8 i += 4; 9 10 return 0; (gdb) 11 } (gdb) b main Breakpoint 1 at 0x1129: file test2.c, line 2. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test2 Breakpoint 1, main () at test2.c:2 2 { (gdb) n 3 int i = 0; (gdb) n 5 i += 1; (gdb) print i $1 = 0 (gdb) n 6 i += 2; (gdb) print i $2 = 1 (gdb) set variable i = 100 (gdb) print i $3 = 100 (gdb) n 7 i += 3; (gdb) print i $4 = 102 (gdb) set (i = 200) (gdb) print i $5 = 200 (gdb) n 8 i += 4; (gdb) print i $6 = 203 (gdb) n 10 return 0; (gdb) print i $7 = 207 (gdb) ``` ## Stack Manipulation `backtrace` 或 `bt` 指令可以顯示目前函式呼叫堆疊的狀況。 ``` (gdb) l 1 2 void bar() 3 { 4 return; 5 } 6 7 void foo() 8 { 9 bar(); 10 } (gdb) 11 12 int main() 13 { 14 foo(); 15 return 0; 16 } (gdb) Line number 17 out of range; test.c has 16 lines. (gdb) b 4 Breakpoint 1 at 0x1131: file test.c, line 4. (gdb) r Starting program: /home/edward/workspace/linux-2022/gdb/test Breakpoint 1, bar () at test.c:4 4 return; (gdb) bt #0 bar () at test.c:4 #1 0x0000555555555146 in foo () at test.c:9 #2 0x000055555555515b in main () at test.c:14 ``` 其他關於堆疊的功能可用 `help stack` 得到說明 ``` (gdb) help stack Examining the stack. The stack is made up of stack frames. Gdb assigns numbers to stack frames counting from zero for the innermost (currently executing) frame. At any time gdb identifies one frame as the "selected" frame. Variable lookups are done with respect to the selected frame. When the program being debugged stops, gdb selects the innermost frame. The commands below can be used to select other frames by number or address. List of commands: backtrace -- Print backtrace of all stack frames, or innermost COUNT frames. bt -- Print backtrace of all stack frames, or innermost COUNT frames. down -- Select and print stack frame called by this one. faas -- Apply a command to all frames (ignoring errors and empty output). frame -- Select and print a stack frame. frame address -- Select and print a stack frame by stack address. frame apply -- Apply a command to a number of frames. frame apply all -- Apply a command to all frames. frame apply level -- Apply a command to a list of frames. frame function -- Select and print a stack frame by function name. frame level -- Select and print a stack frame by level. frame view -- View a stack frame that might be outside the current backtrace. return -- Make selected stack frame return to its caller. select-frame -- Select a stack frame without printing anything. select-frame address -- Select a stack frame by stack address. select-frame function -- Select a stack frame by function name. select-frame level -- Select a stack frame by level. select-frame view -- Select a stack frame that might be outside the current backtrace. up -- Select and print stack frame that called this one. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Type "apropos -v word" for full documentation of commands related to "word". Command name abbreviations are allowed if unambiguous. ``` ## 參考資料 - [x] [Beej's Quick Guide to GDB](https://jasonblog.github.io/note/bggdb/index.html) - [x] [通過 GDB 學習 C 語言](https://jasonblog.github.io/note/gdb/tongguo_gdb_xue_xi_c_yu_yan.html) - [x] [整理常用的gdb技巧](https://jasonblog.github.io/note/gdb/zheng_li_chang_yong_de_gdb_ji_qiao.html) - [ ] [25 GDB Text User Interface](https://sourceware.org/gdb/onlinedocs/gdb/TUI.html) - [ ] [GDB命令基礎,讓你的程序bug無處躲藏](https://jasonblog.github.io/note/gdb/gdbming_ling_ji_chu_ff0c_rang_ni_de_cheng_xu_bug_w.html) - [x] [調試技巧備忘](https://jasonblog.github.io/note/gdb/diao_shi_ji_qiao_bei_wang.html) - [ ] [很經典的GDB調試命令,包括查看變量,查看內存](https://jasonblog.github.io/note/gdb/hen_jing_dian_de_gdb_diao_shi_ming_ling_ff0c_bao_k.html) - [ ] [GDB 反向調試(Reverse Debugging)](https://jasonblog.github.io/note/gdb/gdb_fan_xiang_diao_shi_ff08_reverse_debugging.html) - [x] [GDB下查看記憶體指令(x指令)](https://jasonblog.github.io/note/gdb/gdbxia_cha_kan_ji_yi_ti_zhi_4ee428_x_zhi_4ee429.html) - [ ] [GDB實用教學:自動化你的debug](https://jasonblog.github.io/note/gdb/gdbshi_yong_jiao_xue_ff1a_zi_dong_hua_ni_de_debug.html) - [ ] [GDB中應該知道的幾個調試方法](https://jasonblog.github.io/note/gdb/183.html) - [ ] ------- - [ ] [在程式碼中使用 signal() 插入中斷點,讓 GDB 暫停程式做 debugging](https://jasonblog.github.io/note/gdb/zai_cheng_shi_ma_zhong_shi_yong_signal__cha_ru_zho.html) - [ ] [只要出問題,C4炸藥 gdb 都能搞定](https://jasonblog.github.io/note/gdb/zhi_yao_chu_wen_ti_ff0c3c_del__c4_zha_85e53c_del__.html) - [ ] [懶人除錯法](https://jasonblog.github.io/note/gdb/lan_ren_chu_cuo_fa.html) - [ ] [使用 GDB 以樹狀方式將函式流程印出](https://jasonblog.github.io/note/gdb/186.html) - [ ] [gdb 除錯技術](https://jasonblog.github.io/note/gdb/187.html) - [ ] [使用GDB分析二進位資料](https://jasonblog.github.io/note/gdb/shi_yong_gdb_fen_xi_er_jin_wei_zi_liao.html) - [ ] [用GDB跟蹤觀察共享庫函數的地址翻譯過程](https://jasonblog.github.io/note/gdb/yong_gdb_gen_zong_guan_cha_gong_xiang_ku_han_shu_d.html) - [ ] [GDB調試原理 ptrace系統調用](https://jasonblog.github.io/note/gdb/gdbdiao_shi_yuan_li_ptrace_xi_tong_diao_yong.html) - [ ] [gdb顯示結構體](https://jasonblog.github.io/note/gdb/gdbxian_shi_jie_gou_ti.html) - [ ] [gdb 編譯](https://jasonblog.github.io/note/gdb/189.html) - [ ] [用GDB追蹤glibc代碼執行過程](https://jasonblog.github.io/note/gdb/1811.html) - [ ] [兩種將gdb的輸出信息存到文件的方法](https://jasonblog.github.io/note/gdb/liang_zhong_jiang_gdb_de_shu_chu_xin_xi_cun_dao_we.html) - [ ] [使用call命令在GDB中重複調用某函數](https://jasonblog.github.io/note/gdb/shi_yong_call_ming_ling_zai_gdb_zhong_zhong_fu_dia.html) - [ ] [讓程序崩潰時產生coredump](https://jasonblog.github.io/note/gdb/rang_cheng_xu_beng_kui_shi_chan_sheng_coredump.html) - [ ] [gdb調試帶參數程序](https://jasonblog.github.io/note/gdb/gdbdiao_shi_dai_can_shu_cheng_xu.html) - [ ] [valgrind + gdb](https://jasonblog.github.io/note/gdb/valgrind_+_gdb.html) - [ ] [Trace Function Calls Using GDB Revisited](https://jasonblog.github.io/note/gdb/trace_function_calls_using_gdb_revisited.html) - [ ] [[GDB] 除錯 python 程式 thread 間互相等待 mutex,造成 deadlock 的問題](https://jasonblog.github.io/note/gdb/[gdb]_chu_cuo_python_cheng_shi_thread_jian_hu_xian.html) - [ ] [Linux basic anti-debug](https://www.youtube.com/watch?v=UTVp4jpJoyc) - [ ] [C Programming, Disassembly, Debugging, Linux, GDB](https://www.youtube.com/watch?v=twxEVeDceGw)