Linux GCC 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文件,
[cpp] view plain copy
- 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
[cpp] view plain copy
- (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()設立斷點,星號代表進入前,插結果——一目了然~!
[cpp] view plain copy
- (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“了麽,用得上:
[cpp] view plain copy
- (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等等等等,
具體到底能打印那些,又要牽扯到另一條命令了,下面看一例:
[cpp] view plain copy
- (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可驗證:
[cpp] view plain copy- (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...>為編號,但是直觀感覺上像是覆蓋的,至少不知道怎麽調回原來的設置
另外還有
[cpp] view plain copy
- 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打印顯示表,可以查到自己的設置。
[cpp] view plain copy
- (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提到功能太多,方法太多,想知道最詳細的,請
[cpp] view plain copy- (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和匯編同時顯示
Linux GCC GDB 第二節