您現在的位置是:首頁 > 單機遊戲首頁單機遊戲
GDB除錯的高階技巧
- 2021-12-16
除錯模式怎麼進
GDB是我們平時除錯c/c++程式的利器, 查起復雜的bug問題,比列印大法要好得多,但是也不得不說, gdb在預設情況下用起來並不是很好用,最近學習到幾個高階點的技巧,分享下:
一 美化列印
先上個例子:
#include
gdb除錯:
(gdb) n14 printf(“i:%d,j:%d\n”, t。i,t。j);(gdb) p t$1 = {i = 1, j = 2, str = 0x400710 “test a str”, array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}(gdb) set print pretty(gdb) p t$2 = { i = 1, j = 2, str = 0x400710 “test a str”, array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}(gdb)
set print pretty
透過這個命令可以美化gdb的列印,特別是複雜的資料結構,比較推薦。
set print array-indexes on
列印陣列的下標
這些命令如果記不住怎麼辦, 類似linux啟動,會執行些初始化指令碼,gdb也一樣,指令碼名為:
。gdbinit
將剛才的兩個命令加入進去,就不用每次執行這個命令除錯。
二 動態列印
一般情況下,我們用gdb除錯的時候,先在產生問題的程式碼的呼叫邏輯鏈上設定端點,除錯,有時候為了更方便除錯,我們會在這些關鍵地方加上列印語句。 有時候加的列印程式碼不夠全,來回要反覆幾次。 每次加好列印程式碼後,需要編譯程式,來回折騰幾次,確實比較麻煩。
GDB提供動態列印功能,可以在不修改程式碼情況下,動態插入列印語句,說的比較抽象,舉例說明:
2 #include
上面位置
k <= 10;
這一行越界,除錯下:
(gdb) dprintf 17,“k:%d,array[k]:%d\n”,k,t。array[k]Dprintf 2 at 0x40067f: file 1。c, line 17。(gdb) rStarting program: /test/a。out Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 1。c:1313 test_type_t t = {1,2,“test a str” ,{1,2,3,4,5,6,7,8,9,10}};Missing separate debuginfos, use: debuginfo-install glibc-2。17-196。el7_4。2。x86_64 libgcc-4。8。5-16。el7_4。2。x86_64 libstdc++-4。8。5-16。el7_4。2。x86_64(gdb) cContinuing。i:1,j:2k:0,array[k]:1k:1,array[k]:2k:2,array[k]:3k:3,array[k]:4k:4,array[k]:5k:5,array[k]:6k:6,array[k]:7k:7,array[k]:8k:8,array[k]:9k:9,array[k]:10k:10,array[k]:0index 0 :11[Inferior 1 (process 150109) exited normally](gdb) info bNum Type Disp Enb Address What1 breakpoint keep y 0x00000000004005fc in main(int, char**) at 1。c:13 breakpoint already hit 1 time2 dprintf keep y 0x000000000040067f in main(int, char**) at 1。c:17 breakpoint already hit 11 times printf “k:%d,array[k]:%d\n”,k,t。array[k]
我們透過
dprintf 17,“k:%d,array[k]:%d\n”,k,t。array[k]
在17行動態插入列印語句,然後執行的時候會直接打印出來,17 為行號,後面的語法類似printf。
動態的語句實際是一個斷點(透過info b 可以看到),只不過這個斷點會自動的恢復,這種動態語句是否可以繼續在上面設定端點的,答案是不行的,透過next執行的時候也執行不到這個語句,只是執行到這裡面的時候,可以自動列印變數。
三 斷點資訊的儲存和載入
除錯大型程式的時候,往往會設定很多斷點,如果需要多次除錯,來回敲這些斷點資訊,也是很煩的,可以透過
save breakpoints filename
的方法,將斷點設定資訊儲存到
filename
檔案中,GDB啟動後,在透過
source filename
方式載入:
(gdb) save breakpoints break。bpSaved to file ‘break。bp’。(gdb) quit[root@localhost test]# ls1。c a。out break。bp[root@localhost test]# cat break。bp break maindprintf /test/1。c:17,“k:%d,array[k]:%d\n”,k,t。array[k]
載入:
[root@localhost test]# gdb 。/a。out Reading symbols from /test/a。out。。。done。(gdb) source break。bpBreakpoint 1 at 0x4005fc: file 1。c, line 13。Dprintf 2 at 0x40067f: file 1。c, line 17。(gdb) info bNum Type Disp Enb Address What1 breakpoint keep y 0x00000000004005fc in main(int, char**) at 1。c:132 dprintf keep y 0x000000000040067f in main(int, char**) at 1。c:17 printf “k:%d,array[k]:%d\n”,k,t。array[k]
是不是很方便那。
四 宏列印
如下示例程式碼:
1 #include
如果透過
-g
選項編譯是無法檢視宏定義的,可以透過
-g3
編譯,就可以輕鬆檢視宏定義和宏展開了,如下:
[root@localhost test]# gdb 。/2Reading symbols from /test/2。。。done。(gdb) b mainBreakpoint 1 at 0x40064c: file 2。c, line 7。(gdb) rStarting program: /test/。/2 Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at 2。c:77 char * p = (char*)0x12345;Missing separate debuginfos, use: debuginfo-install glibc-2。17-196。el7_4。2。x86_64 libgcc-4。8。5-16。el7_4。2。x86_64 libstdc++-4。8。5-16。el7_4。2。x86_64(gdb) n8 printf(“max(3,5):%d\n”,MAX(3,5));(gdb) p MAX(3,5)$1 = 5(gdb) macro expand MAX(3,5)expands to: (3)>=(5)?(3):(5)
可以直接透過p命令列印宏,也可以進行
macro expand MAX(3,5)
宏展開,除錯宏的時候比較有用。
五 gdb除錯介面
gdb的除錯一般是透過n執行下一條命令,然後透過l檢視當前的程式碼,不夠直觀,gdb其實也可以類似IDE那樣,顯示一行行程式碼執行,簡單的直接輸入
layout
切換佈局:
再輸入n的時候,箭頭自動下移一行。
更棒的是可以同時顯示原始碼和彙編,輸入
layout split
六 Debug彙編
有時候,比較麻煩的bug,可以需要反編譯為彙編(disassemble /m main),然後除錯, 還是如上的例子程式,除錯如下,彙編模式常需要看暫存器的值,輸入
layout regs
可以同時展示彙編和暫存器的值:
這個模式下好處,可以透過n命令執行一條條看彙編指令。
我還是更喜歡用以下的方式除錯:
gdb) disassemble /m mainDump of assembler code for function main(int, char**):6 { 0x000000000040063d <+0>: push %rbp 0x000000000040063e <+1>: mov %rsp,%rbp 0x0000000000400641 <+4>: sub $0x20,%rsp 0x0000000000400645 <+8>: mov %edi,-0x14(%rbp) 0x0000000000400648 <+11>: mov %rsi,-0x20(%rbp)7 char * p = (char*)0x12345;=> 0x000000000040064c <+15>: movq $0x12345,-0x8(%rbp)8 printf(“max(3,5):%d\n”,MAX(3,5)); 0x0000000000400654 <+23>: mov $0x5,%esi 0x0000000000400659 <+28>: mov $0x400720,%edi 0x000000000040065e <+33>: mov $0x0,%eax 0x0000000000400663 <+38>: callq 0x400510
透過
disassemble /m main
將原始碼和彙編一起顯示,
ni
執行下一條彙編指令。
info register
來顯示彙編資訊,core的位置的指令為:
0x0000000000400676 <+57>: movb $0x1,(%rax)
將1賦值給rax暫存器儲存的地址, 我們可以看下rax暫存器,
p $rax
直接列印,然後透過
x /x 0x12345
來測試下這個地址是否合法,這個地址不是棧的地址,也不是我們申請的堆地址,直接寫,就引起的段錯誤。
七 詩詞欣賞
# 滾滾長江東逝水,浪花淘盡英雄。出自明代[楊慎] 滾滾長江東逝水,浪花淘盡英雄。是非成敗轉頭空。青山依舊在,幾度夕陽紅。白髮漁樵江渚上,慣看秋月春風。一壺濁酒喜相逢。古今多少事,都付笑談中。