gdb調試小結


之前想驗證一些關於堆棧的問題,但是沒什么好方法,printf實在局限,流於表面,只間表象(值、范圍、規律)不見真身(地址、寄存器、過程),所以想到了gdb——一個強大的調試工具,還能看匯編代碼,現在先把這兩天學的常用的命令做一個小結,以后有用到的可能再來更新一下:


括號內為全稱補全,縮寫全稱均可用。

例:(e)x(amine)表示既可以用x又可以用examine

(gdb)代表gdb環境命令行提示符。

關於縮寫,非常類似Linux的shell中的tab功能,但是與shell不同的是有默認選擇:

你不一定要寫全,也不一定只寫首字母,比如(gdb) layout 命令,如果寫個l,那么缺省的是list,搶不過,寫layout——又太麻煩,你只要寫上la、lay、layo都行,搶不上槽沒關系,只要有一點不同,就默認是你了。


1.進入gdb:

#gdb test -q(uiet)

其中test為目標可執行文件,-q代表不打印那一大串版本版權信息之類的刷屏字幕。

這里有個小常識就是用gcc編譯目標文件test時,記得-g,表示可調試。

另外,直接進入gdb而未加載可執行文件,或者加載了目標文件,想換一個其他的——可以使用

(gdb)file test2

或者

(gdb)exec(-file) test2


1.2加載core文件

#gdb execfile core.xxxx

加載execfile出錯產生的core文件,

 

Core was generated by `./coreTest'.
Program terminated with signal 11, Segmentation fault.
#0  0x080486e5 in main () at coreTest.cpp:16
16		cout << s1->i << endl;	
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.25.el6.i686 libgcc-4.4.5-6.el6.i686 libstdc++-4.4.5-6.el6.i686

 

可以看到,s1是NULL指針,從NULL指針找成員變量,core dumped。

core.xxxx是core文件的文件名,沒修改設置的話,應該在當前路徑。不過大前提是打開了core文件記錄:

# ulimit -c 

查看core文件大小,0則代表關閉

# ulimit -c 1000

設置core文件大小,有大小代表打開,則出錯能產生文件。


2.斷點的設立:

(gdb)b(reakpoints) <rowNums...>

<rowNums...>代表想要設立斷點的行數

忘了哪行是什么?沒關系,你可以用list

 

(gdb) list
1	#include<stdio.h>
2	int main()
3	{
4		int i = 10;
5		i = 11;
6		printf("the address of i is %p and the value of i is %d\n",&i,i);
7	}
8	

另外,list也可以設置顯示行數和指定位置的

(gdb)list

(gdb)list 10

(gdb)list 5,10

(gdb)func

比如默認顯示10行,可以指定第5到第10行,指定顯示某函數代碼,等等。

不過最好的建議還是開倆終端,一邊看代碼,一邊調試,看着舒服。

另外

 

(gdb) layout

也可以顯示程序代碼,還是用框子圈起來的,高大上。


(gdb)b func

(gdb)b *func

在函數func()設立斷點,星號代表進入前,插結果——一目了然~!

 

(gdb) b *main
Breakpoint 1 at 0x80483e4: file testPC.c, line 3.
(gdb) b main
Breakpoint 2 at 0x80483ed: file testPC.c, line 4.
(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x080483e4 in main at testPC.c:3
2       breakpoint     keep y   0x080483ed in main at testPC.c:4
其中

 

(gdb)info b(reakpoints)

相當於列表打印所有已設立的斷點。有了斷點,當然也可以刪掉斷點,看到列表中左邊的”Num“了么,用得上:

 

(gdb) d 1
(gdb) info b
Num     Type           Disp Enb Address    What
2       breakpoint     keep y   0x080483ed in main at testPC.c:4
(gdb) 
(gdb)d(elete) Num

 

代表刪除第Num個斷點。可以看到第一個斷點被刪了。

3.基本調試流程

有了斷點,就該用上了。

第一步,開始運行程序:

(gdb)r(un)


(gdb)n(ext)


(gdb)s(tep)


和其他調試相仿,這兩條分別代表step over和step in,


(gdb)c(ontinue)

run和continue功能其實差不多,都是繼續往下運行,直到下一個斷點停下來,不過場合不一樣罷了:run是開始運行前的啟動命令,continue是運行中的命令。



 

4.匯編style:

基本的流程走完了,該引入匯編了。

i代表指令(instruction)

不很確定,至少你不能用instruction代替i,至少,先理解為匯編的意思。

前邊的指令加上i就顯示了匯編代碼,例如:

n(ext)i

s(tep)i


要想一步一步看匯編代碼和執行過程,

(gdb)ni

(gdb)si

是必不可少的,不過你可以用回車表示繼續使用上一次的命令。


前邊提到list和layout顯示源代碼,其實layout還可以擴展一下用途

(gdb)layout asm

 

以窗口形式顯示匯編代碼




5.print:

gdb提供了打印功能:

示例:

(gdb)p(rint) i

打印i變量當前的值。

不僅程序中的變量,寄存器的值也能打印

(gdb) p $pc

兩個小疑問:

5.1.$pc代表什么,除了它還能打印什么?

這句話其實就是打印程序計數器的值。

先說寄存器,除了$pc,還有%esp,%edp等等等等,

具體到底能打印那些,又要牽扯到另一條命令了,下面看一例:

 

(gdb)i(nfo) r(eg)
(gdb)
eax            0x80484f0    134513904
ecx            0xbffff304    -1073745148
edx            0xb    11
ebx            0xb7fc2ff4    -1208209420
esp            0xbffff240    0xbffff240
ebp            0xbffff268    0xbffff268
esi            0x0    0
edi            0x0    0
eip            0x8048406    0x8048406 <main+34>
eflags         0x200282    [ SF IF ID ]
cs             0x73    115
ss             0x7b    123
ds             0x7b    123
es             0x7b    123
fs             0x0    0
gs             0x33    51

上邊看到的都可以print,而且能發現個小規律,這個info reg打印的,除了最左邊是寄存器名稱外,中間是寄存器存的值(也就是一個內存地址),右邊是這個值對應的內存地址中的值。打印一下$eax可驗證:
(gdb) p $eax
$3 = 134513904

 

其實用法遠不止於此,比如p $打印上一次打印的值,$$打印上上次打印過的值。print其實是有計數器的,每次print打印,其實都有一個類似count++在內部發生,使用print $num 能顯示第num個打印結果(如上,p $3就等價於p $eax),其他還有blabla~~~

至於為什么是$,貪心的外國人把各種變量都弄成美元$了,所以這個也是gdb下設置的環境變量~~開個玩笑。

其實,我猜,$也是為了區分變量和表達式吧~print可以打印表達式的。

與其說print不只打印變量(左值),還打印表達式(右值),不如說,按描述,print本來就是打印表達式的,只不過表達式包括變量。

欲知詳情,可以使用help查看使用說明


5.2.C語言中printf有打印格式控制,那么gdb的print呢?

也有~

(gdb)p i
(gdb)p/a i
(gdb)p/c i
(gdb)p/f i
(gdb)p/x i
(gdb)p/o i
(gdb)p/d i
(gdb)p/t i
......

反斜杠后邊這幾個參數分別控制打印的進制與格式:
f浮點,c字符。。。
t為二進制,o八,x十六,d十
另外:a和x同樣是打印十六進制,區別呢?可能就是不同名但同功能

理念有點像C語言編程時候加printf打印變量來監視程序。在gdb中你也可以隨時打印各變量的值,而且更為強大(不用像C到處插打印命令,還能逐條執行,打印變量加地址加寄存器你說強大不)。


6.display

這是一種設置,設置好了調試過程中每一步都回顯一次,有點像echo吧~~

示例:

(gdb) display /3i $pc
中,3指的是一次顯示幾行,不輸入,缺省為1
但是~~~怎么修改,而且有一種錯覺,通常都是一次定義以后,再怎么定義都不會變(有時候確實會變~!!!)~~~~~~~~~~~~
找到了

(gdb) undisplay <dnums...>
<dnums...>為編號,但是直觀感覺上像是覆蓋的,至少不知道怎么調回原來的設置
另外還有

 

 

delete display <dnums...>
disable display <dnums...>
enable display <dnums...>

至於怎么靈活用?是先info一下,然后再enable一下,就代表當前使用這種顯示?
還是他們同時顯示?所以造成了我“有些設置不起作用,有些能起作用”的錯覺。
因為(行數)比原來多能“立刻見效”,比原來少則不能?

 

清空了重新si一遍,真正的原因是同時顯示好幾份。終端刷屏相似度太高眼花繚亂啊有木有~又沒有clear功能~
一行的也有,二行的也有,三行的也有。所有設置的順次顯示一遍

(gdb) si
0xb7fec1ec in ?? () from /lib/ld-linux.so.2
7: x/i $pc
=> 0xb7fec1ec:    mov    %eax,%edi
6: x/2i $pc
=> 0xb7fec1ec:    mov    %eax,%edi
   0xb7fec1ee:    shr    $0x8,%edi
5: x/3i $pc
=> 0xb7fec1ec:    mov    %eax,%edi
   0xb7fec1ee:    shr    $0x8,%edi
   0xb7fec1f1:    mov    %edi,%ecx
4: x/i $pc
=> 0xb7fec1ec:    mov    %eax,%edi

 

通過info display打印顯示表,可以查到自己的設置。

 

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
7:   y  /1bi $pc
6:   y  /2bi $pc
5:   y  /3bi $pc
4:   y  /1bi $pc

 

最后,想看一下全部匯編代碼,直接

(gdb)disassemble

或者去用objdump(題外)

7.bt

最近調服務器發現普通打印法已經很難跟住bug了,另一個原因是段錯誤就系統重啟(其實是工程設置的信號處理,SIGSEGV段錯誤信號重啟系統。),而gdb能在重啟之前截斷程序運行,從而卡在出錯點,防止重啟系統。

不過光卡在那也不行啊,一層套一層,那么多的函數調用,你不進去看不到東西啊,而且再輸入n(next)往下走是看不到已經運行完的錯誤的,所以就談到bt(backtrace)命令——回溯。


8.運行中的進程

都知道,運行gdb加載文件,或在gdb內部用file加載文件,想再運行程序等於新開一個進程。

現在想調試一個運行中的進程,而不是新啟動一個進程。

#ps -aux | grep execFile

找到運行中的進程PID,

使用

#gdb execFile PID

#gdb 

(gdb) attach PID

即可連接正在運行中的進程,進行調試。



 

9.etc.

其他的亂七八糟,例如examine。


(e)xamine:功能和display差不太多,區別就是display是一種設置,每次跳命令顯示一次,x是主動顯示。

x/3i $pc顯示3條指令(3為示范,數字可選)
(gdb)(e)x(amine)
語法:
x/<n/f/u> <addr>
n選擇從當前地址向后顯示幾個
f是顯示格式,還有s字符串和i整型
u表示從當前地址往后請求的字節數,如果不指定的話,GDB默認是4個bytes。u參數可以用下面的字符來代替,b表示單字節,h表示雙字節,w表示四字節,g表示八字節。當我們指定了字節長度后,GDB會從指內存定的內存地址開始,讀寫指定字節,並把其當作一個值取出來。

例子:
x/3uh 0x54320表示,從地址0x54320讀取,h表示以雙字節為單位,3表示三個單位,u表示十六進制


=========================================================================================================================2016.2.21補充,

set 設置變量,可以在運行時通過斷點加手動設置的方法來動態的改變變量值。也可以在程序運行前設置程序運行參數》》set args hello world

shell,顧名思義,使用shell命令,省得切出去了,另外,貌似也可以達到set args的效果。因為這下你終於可以直接在gdb界面通過命令行運行程序了

(gdb) shell ls

(gdb) shell ./a.out param1 param2

兩例見

http://blog.csdn.net/huqinweI987/article/details/50706743


===========================================================================================================

0.HELP

前邊print提到功能太多,方法太多,想知道最詳細的,請
(gdb) help print
關於help的強大和使用方法,不贅述了,使用

(gdb) help

就什么都知道了。



----------------------------------------------------------------------------------------------------------------

ADDITIONAL:

GDB7.0以上(7.4)

可用如下套路:

(gdb)set disassemble-next-on

(gdb)b main
(gdb)r
(gdb)ni
(gdb)ni
.....
這個也是比較不錯比較直觀的方式

disas /m main
讓C和匯編同時顯示

 

參考:http://blog.csdn.net/huqinwei987/article/details/23548239



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM