1. 程式人生 > >一步一步學除錯——gdb命令小結

一步一步學除錯——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和彙編同時顯示

部分標準描述屬於借鑑,完全個人總結,權威性不強,不全面且定製化目的性強(為後邊帖子做基礎鋪墊)~慎重轉載

後續會根據個人經驗進行更新補全。

歡迎交流~

相關推薦

除錯——gdb命令小結

之前想驗證一些關於堆疊的問題,但是沒什麼好方法,printf實在侷限,流於表面,只間表象(值、範圍、規律)不見真身(地址、暫存器、過程),所以想到了gdb——一個強大的除錯工具,還能看彙編程式碼,現在先把這兩天學的常用的命令做一個小結,以後有用到的可能再來更新一下: 括號

用debugserver + lldb代替gdb進行動態除錯(整理與補充)

原文章出處:http://bbs.iosre.com/t/debugserver-lldb-gdb/65/12 *** 以下部分內容摘自《iOS應用逆向工程》第二版,以iOS 8為環境編寫,應該也支援iOS 7,請大家注意。 *** 因為Apple已經棄gdb投lldb,所以隨著我動態除錯的次數越來越頻繁,

1.開發(遊戲賬服數據庫的使用 Erlang 服務器)

http ats 日誌收集 yield data obj 開發 用戶 nbsp mysql 與mongodb的特點與優劣 http://www.cnblogs.com/eternal1025/p/5419905.html 首先我們來分析下mysql 與mongodb的特

Vue(八)

nod png 路由配置 ring 圖片 sca -a 基本 routes 本篇完成如下場景: 1、系統包含首頁、客戶信息查詢、登錄三個模塊 2、默認進入系統首頁,如果要進行用戶查詢,則需要進行登錄授權 3、查詢用戶後點擊列表項,則進入詳情頁面 基於上述場景需求描述

JVM-運行時數據區域

count 運行期 存儲 編譯期 動態 局部變量表 編譯 表空間 機制 程序計數器(Program Counter Register) 像我們平時讀書一樣,當我們在去做別的事情之前,我們會對我們讀到什麽地方了做一個標記,方便我們再回來的時候接著重新讀。如果

多線程-synchronized

通過 alt not 設置 hand 獲取鎖 執行 本質 支持   當線程執行請求synchronized方法或塊時,monitor會設置幾個虛擬邏輯數據結構來管理這些多線程。         請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有線程鎖的線程unlo

J2SE-ConcurrentHashMap原理

等待 鏈表 初始 方案 超過 喚醒 cas 分割 成功   ConcurrentHshMap的數據結構是由一個Segment數組和多個HashEntry數組組成,在Segement數組中包含了HashEntry數組。數據結構如下圖所示:   Segement數組的意義就是將

JVM-垃圾回收

-- 覆蓋 不可達 建立 class對象 如果 而且 節點 棧幀   垃圾回收器在對對象進行回收前,首先要判斷對象是否還“活著”。判斷方法有以下兩種 引用計數法 給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1。當引用失效時,計數器值就減

JVM-垃圾回收算法

無法 程序 gen 適合 順序分配 存在 進行 大小 產生 標記-清除算法 算法分為標記和清除兩個階段:首先標記所有需要回收的對象,在標記完成後統一回收所有被標記的對象。 該算法存在的缺點:   1、 效率問題:標記和清除兩個過程的效率

JVM-Java內存模型

jvm 值傳遞 都是 ssi 方法參數 自己 vol 字節 ati 主內存與工作內存 Java內存模型的主要目標是定義程序中各個變量的訪問規則。即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。這裏的變量和Java編程中所說的變量有所區別,它包

JVM-垃圾回收器

trac 部分 current 可控 吞吐量 收集器 控制 需要 但是 Serial收集器 Serial收集器是最基本、歷史最悠久的收集器。這個收集器是一個單線程的收集器。它在進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。Serial收集器是

Python-基礎篇

class 亂碼 eight mov 字符串 if else 10個 顯示 ref 1、安裝   地址:https://www.python.org/downloads/windows/   安裝完成過後,配置環境變量,比如:path後面計入;C:\Python27(可

MySQL---18 MySQL常用函式(2)

18.2 數值函式 MySQL所支援的常用數值函式有: 函式 功能 ABS(x) 返回數值x的絕對值 CEIL(x) 返回大於x的最小整數值

MySQL---18 MySQL常用函式(1)

18.1 字串函式 MySQL所支援的字串函式有: 函式 功能 CANCAT(str1,str2,…strn) 連線字串str1、str2、……、strn為一個完整字串

MySQL---17 MySQL運算子

17.1 算術運算子 MySQL中,算術運算子包含加、減、乘、除、求模。 運算子 描述 表示式形式 + 加法 x1+x2+x3+…+xn

MySQL----16 多表資料記錄查詢之子查詢

16.1 為什麼使用子查詢 日常工作中,經常會用到多表查詢,而在進行多表查詢時,首先會對兩個表進行笛卡爾積操作,然後再選取符合匹配條件的資料記錄。在進行笛卡爾積操作的時候,會生成兩個資料表中資料記錄數的乘積條資料記錄。如果這兩個表的資料記錄比較大,則在進行笛卡爾積操作時就會造

MySQL----15 多表資料記錄查詢之合併查詢

在MySQL中通過關鍵字 union來實現並操作,即可以通過 union將多個select語句查詢合併在一起組成新的關係。 下面的例項基於資料庫company,有如下兩張表: (1) 計算機系的學生表:cstudent (2) 音樂系的學生表:mstuden

MySQL----14 奪標資料記錄查詢之外連線

14.1 外連線之左連線 所謂左連線,就是指新關係中執行匹配條件時,以關鍵字left join左邊的表為參考表。 例如:執行SQL語句“left join on”,在資料庫company中,查詢每個員工的姓名、工種、領導姓名。由於名為Jones的員工已經是manag

MySQL----13 多表資料記錄查詢之內連線

前面學的查詢都是單表查詢,而在實際應用中,經常需要實現在一個查詢語句中顯示多張表的資料,這就是所謂的多表資料記錄查詢,簡稱連線查詢。 12.1 關係資料操作 (1)並(UNION) “並”就是把具有相同欄位數目和欄位型別的表合併到一起。 (2)笛卡兒

MySQL----12 分組資料查詢

12.1 簡單分組查詢 語法: select function() from table_name where condition group by field; 例如: mysql> select * from employee group