之前想驗證一些關於堆棧的問題,但是沒什么好方法,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.
(e)xamine:功能和display差不太多,區別就是display是一種設置,每次跳命令顯示一次,x是主動顯示。
(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:
(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