1. 程式人生 > >Linux GCC GDB 第二節

Linux GCC GDB 第二節

作用 word 指定位置 glib pid 環境變量 詳細 可用 include

之前想驗證一些關於堆棧的問題,但是沒什麽好方法,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
  1. Core was generated by `./coreTest‘.
  2. Program terminated with signal 11, Segmentation fault.
  3. #0 0x080486e5 in main () at coreTest.cpp:16
  4. 16 cout << s1->i << endl;
  5. 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
  1. (gdb) list
  2. 1 #include<stdio.h>
  3. 2 int main()
  4. 3 {
  5. 4 int i = 10;
  6. 5 i = 11;
  7. 6 printf("the address of i is %p and the value of i is %d\n",&i,i);
  8. 7 }
  9. 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
  1. (gdb) b *main
  2. Breakpoint 1 at 0x80483e4: file testPC.c, line 3.
  3. (gdb) b main
  4. Breakpoint 2 at 0x80483ed: file testPC.c, line 4.
  5. (gdb) info b
  6. Num Type Disp Enb Address What
  7. 1 breakpoint keep y 0x080483e4 in main at testPC.c:3
  8. 2 breakpoint keep y 0x080483ed in main at testPC.c:4

其中

(gdb)info b(reakpoints)

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

[cpp] view plain copy
  1. (gdb) d 1
  2. (gdb) info b
  3. Num Type Disp Enb Address What
  4. 2 breakpoint keep y 0x080483ed in main at testPC.c:4
  5. (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還可以擴展一下用途

[cpp] view plain copy
  1. (gdb)layout asm

以窗口形式顯示匯編代碼

技術分享

5.print:

gdb提供了打印功能:

示例:

(gdb)p(rint) i

打印i變量當前的值。

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

(gdb) p $pc

兩個小疑問:

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

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

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

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

[cpp] view plain copy
  1. (gdb)i(nfo) r(eg)
  2. (gdb)
  3. eax 0x80484f0 134513904
  4. ecx 0xbffff304 -1073745148
  5. edx 0xb 11
  6. ebx 0xb7fc2ff4 -1208209420
  7. esp 0xbffff240 0xbffff240
  8. ebp 0xbffff268 0xbffff268
  9. esi 0x0 0
  10. edi 0x0 0
  11. eip 0x8048406 0x8048406 <main+34>
  12. eflags 0x200282 [ SF IF ID ]
  13. cs 0x73 115
  14. ss 0x7b 123
  15. ds 0x7b 123
  16. es 0x7b 123
  17. fs 0x0 0
  18. gs 0x33 51

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

[cpp] view plain copy
  1. (gdb) p $eax
  2. $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
但是~~~怎麽修改,而且有一種錯覺,通常都是一次定義以後,再怎麽定義都不會變(有時候確實會變~!!!)~~~~~~~~~~~~
找到了

[cpp] view plain copy
  1. (gdb) undisplay <dnums...>

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

[cpp] view plain copy
  1. delete display <dnums...>
  2. disable display <dnums...>
  3. enable display <dnums...>


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

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

[cpp] view plain copy
  1. (gdb) si
  2. 0xb7fec1ec in ?? () from /lib/ld-linux.so.2
  3. 7: x/i $pc
  4. => 0xb7fec1ec: mov %eax,%edi
  5. 6: x/2i $pc
  6. => 0xb7fec1ec: mov %eax,%edi
  7. 0xb7fec1ee: shr $0x8,%edi
  8. 5: x/3i $pc
  9. => 0xb7fec1ec: mov %eax,%edi
  10. 0xb7fec1ee: shr $0x8,%edi
  11. 0xb7fec1f1: mov %edi,%ecx
  12. 4: x/i $pc
  13. => 0xb7fec1ec: mov %eax,%edi

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

[cpp] view plain copy
  1. (gdb) info display
  2. Auto-display expressions now in effect:
  3. Num Enb Expression
  4. 7: y /1bi $pc
  5. 6: y /2bi $pc
  6. 5: y /3bi $pc
  7. 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
  1. (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 第二節