您現在的位置是:首頁 > 單機遊戲首頁單機遊戲

GDB除錯的高階技巧

簡介(gdb) nimax(3,5):59 printf(“print a test strn”)

除錯模式怎麼進

GDB是我們平時除錯c/c++程式的利器, 查起復雜的bug問題,比列印大法要好得多,但是也不得不說, gdb在預設情況下用起來並不是很好用,最近學習到幾個高階點的技巧,分享下:

一 美化列印

先上個例子:

#include typedef struct { int i ; int j; char * str; int array[10];} test_type_t;int main (int argc, char * argv[]){ test_type_t t = {1,2,“test a str” ,{1,2,3,4,5,6,7,8,9,10}}; printf(“i:%d,j:%d\n”, t。i,t。j); return 0;}

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 3 4 typedef struct { 5 int i ; 6 int j; 7 char * str; 8 int array[10]; 9 } test_type_t; 10 11 int main (int argc, char * argv[]) 12 { 13 test_type_t t = {1,2,“test a str” ,{1,2,3,4,5,6,7,8,9,10}}; 14 printf(“i:%d,j:%d\n”, t。i,t。j); 15 16 for (int k = 0; k <= 10; k++ ) { 17 t。array[k] += 10; 18 } 19 printf(“index 0 :%d” ,t。array[0]); 20 return 0; 21 }

上面位置

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 2 3 #define MAX(x,y) (x)>=(y)?(x):(y) 4 5 int main(int argc , char * argv[]) 6 { 7 char * p = (char*)0x12345; 8 printf(“max(3,5):%d\n”,MAX(3,5)); 9 printf(“print a test str\n”); 10 *p = 1; 11 printf(“end\n”); 12 }

如果透過

-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

切換佈局:

GDB除錯的高階技巧

再輸入n的時候,箭頭自動下移一行。

更棒的是可以同時顯示原始碼和彙編,輸入

layout split

GDB除錯的高階技巧

六 Debug彙編

有時候,比較麻煩的bug,可以需要反編譯為彙編(disassemble /m main),然後除錯, 還是如上的例子程式,除錯如下,彙編模式常需要看暫存器的值,輸入

layout regs

可以同時展示彙編和暫存器的值:

GDB除錯的高階技巧

這個模式下好處,可以透過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 9 printf(“print a test str\n”); 0x0000000000400668 <+43>: mov $0x40072d,%edi 0x000000000040066d <+48>: callq 0x400530 10 *p = 1; 0x0000000000400672 <+53>: mov -0x8(%rbp),%rax 0x0000000000400676 <+57>: movb $0x1,(%rax)11 printf(“end\n”); 0x0000000000400679 <+60>: mov $0x40073e,%edi 0x000000000040067e <+65>: callq 0x400530 12 } 0x0000000000400683 <+70>: mov $0x0,%eax 0x0000000000400688 <+75>: leaveq 0x0000000000400689 <+76>: retq End of assembler dump。(gdb) ni8 printf(“max(3,5):%d\n”,MAX(3,5));(gdb) ni0x0000000000400659 8 printf(“max(3,5):%d\n”,MAX(3,5));(gdb) ni0x000000000040065e 8 printf(“max(3,5):%d\n”,MAX(3,5));(gdb) ni0x0000000000400663 8 printf(“max(3,5):%d\n”,MAX(3,5));(gdb) nimax(3,5):59 printf(“print a test str\n”);(gdb) ni0x000000000040066d 9 printf(“print a test str\n”);(gdb) niprint a test str10 *p = 1;(gdb) info register

透過

disassemble /m main

將原始碼和彙編一起顯示,

ni

執行下一條彙編指令。

info register

來顯示彙編資訊,core的位置的指令為:

0x0000000000400676 <+57>: movb $0x1,(%rax)

將1賦值給rax暫存器儲存的地址, 我們可以看下rax暫存器,

p $rax

直接列印,然後透過

x /x 0x12345

來測試下這個地址是否合法,這個地址不是棧的地址,也不是我們申請的堆地址,直接寫,就引起的段錯誤。

七 詩詞欣賞

# 滾滾長江東逝水,浪花淘盡英雄。出自明代[楊慎] 滾滾長江東逝水,浪花淘盡英雄。是非成敗轉頭空。青山依舊在,幾度夕陽紅。白髮漁樵江渚上,慣看秋月春風。一壺濁酒喜相逢。古今多少事,都付笑談中。

Top