GDB程序調試筆記

    最近程序在linux系統中出現程序的進程意外死掉的現象,查看日誌發現是信號量異常致使。結合進程死機異常產生的core文件和程序版本經過GDB工具進行程序的調試,發現是內存的問題。linux

爲了更好的使用GDB工具,查閱資料進行總結,也但願可以幫助開始使用GDB的新手c++

一、GDB是什麼

GDB就是一個幫助程序猿調試程序的工具,相似於windows下的IDE自帶的調試工具(如斷點調試、內存查看、堆棧信息顯示等)。正則表達式

通常來講 GDB 主要調試的是 C/C++的程序。要調試 C/C++的程序,首先在編譯時,必需要把調試信息加到可執行文件中。使用編譯器(cc/gcc/g++)的 -g 參數能夠作到這一點。shell

> cc -g hello.c -o helloexpress

> g++ -g hello.cpp -o hello

若是沒有-g,你將看不見程序的函數名、變量名,所代替的全是運行時的內存地址windows

二、如何啓動GDB

    一、gdb <program>
    program 也就是你的執行文件,通常在當前目錄下。數組

    二、gdb <program> core
    用gdb同時調試一個運行程序和core 文件,core 是程序非法執行後core dump 後產生的文件。bash

    三、gdb <program> <PID>
    若是你的程序是一個服務程序或者已經在運行了,那麼能夠指定這個服務程序運行時的進程 ID。gdb 會自動attach 上去,並調試他。program 應該在PATH 環境變量中搜索獲得函數

    也能夠這樣:先用 gdb <program>關聯上源代碼,並進行 gdb,在 gdb 中用 attach 命令來掛接進程的 PID。並用detach 來取消掛接的進程工具

三、簡單使用

l <-------------------- l 命令至關於list,從第一行開始例出原碼
<-------------------- 直接回車表示,重複上一次命令
break 16 <-------------------- 設置斷點,在源程序第16行處
break func <-------------------- 設置斷點,在函數func()入口處
r <--------------------- 運行程序,run 命令簡寫
n <--------------------- 單條語句執行,next命令簡寫
c <--------------------- 繼續運行程序,continue 命令簡寫
p i <--------------------- 打印變量i的值,print命令簡寫
bt <--------------------- 查看函數堆棧
finish <--------------------- 退出函數
q <--------------------- 退出 gdb

四、程序運行參數和環境操做

    一、GDB中調用 UNIX 的 shell 來執行linux命令

    shell <command string>      

    如:(gdb) shell $PWD
            bash: /home/duxx/test_gdb: is a directory

    二、GDB中執行make命令,從新build 本身的程序

    make <make-args>    等價於「shell make <make-args>」

    三、設置程序運行參數

    set args         可指定運行時參數。
    show args     命令能夠查看設置好的運行參數。

    (gdb) set  args 5
    (gdb) show  args 
    Argument list to give program being debugged when it is started is "5".

    四、運行路徑和環境變量

    path <dir>         可設定程序的運行路徑。
    show paths       查看程序的運行路徑。
    set environment varname [=value]     設置環境變量。如:set env USER=hchen

五、斷點調試

    一、設置斷點BreakPoint 

    咱們用break 命令來設置斷點。正面有幾點設置斷點的方法:
    break <function>        eg:break func() 
    在進入指定函數時停住。C++中可使用class::function 或 function(type,type)格式來指定函數名。

    break <linenum>        eg:break 8
    在指定行號停住。

    break +offset    (當前行號+offset)    
    break -offset     (當前行號-offset)  

    在當前行號的前面或後面的offset行停住(是在程序運行前和運行時均可設置)。offiset 爲天然數。

    break filename:linenum        eg:break  test.cpp:18
    在源文件filename 的linenum 行處停住。

    break filename:function        eg:break test.cpp:func() 
    在源文件filename 的function 函數的入口處停住。

    break *address        eg:break  0x0804895b
    在程序運行的內存地址處停住。

    break
    break 命令沒有參數時,表示在下一條指令處停住。

    break ... if <condition>
    ...能夠是上述的參數,condition 表示條件,在條件成立時停住。好比在循環境體中,能夠設置
    break if i=100,表示當i爲 100 時停住程序。

    查看斷點時,可以使用info 命令,以下所示:(注:n 表示斷點號)
    info breakpoints [n]
    info break [n]
    eg:
    1       breakpoint     keep y   0x080488bd in func() at test.cpp:19
             breakpoint already hit 1 time
    2       breakpoint     keep y   0x08048852 in test1() at test.cpp:8
    3       breakpoint     keep y   <PENDING>  +2

    二、設置觀察點WatchPoint 

    觀察點通常來觀察某個表達式(變量也是一種表達式)的值是否有變化了,若是有變化,立刻停住程序。咱們有下面的幾種方法來設置觀察點:
    watch <expr>    爲表達式(變量)expr設置一個觀察點。當表達式值有變化時,立刻停住程序。
    rwatch <expr>     當表達式(變量)expr被讀時,停住程序。
    awatch <expr>     當表達式(變量)的值被讀或被寫時,停住程序。
    info watchpoints     列出當前所設置了的全部觀察點

    注意:設置觀察點時,要在其觀察的變量處設置斷點,而後run程序,走到此斷點時,纔可設置觀察點,continue觀察設置的觀察點是否有變化

    三、清除中止點

    clear
    清除全部的已定義的中止點,測試不能所有刪除,使用delete便可

    clear <function>
    clear <filename:function>  
  
    清除全部設置在函數上的中止點。

    clear <linenum>
    clear <filename:linenum>

    清除全部設置在指定行上的中止點。

    delete [breakpoints] [range...]
    刪除指定的斷點,breakpoints 爲斷點號。若是不指定斷點號,則表示刪除全部的斷點。range 表示斷點號的範圍(如:3-7)。其簡寫命令爲d。

    四、修改中止的條件

    能夠在設置斷點的時候加入條件
     break foo if value_a > value_b

    也能夠用 condition 命令來修改斷點的條件。(只有break 和watch 命令支持if,catch 目前暫不支持if)

    condition <bnum> <expression>      eg:  condition 3  i=5(不須要 if)
    修改斷點號爲bnum的中止條件爲expression

    condition <bnum>
    清除斷點號爲bnum的中止條件。

    還有一個比較特殊的維護命令ignore,你能夠指定程序運行時,忽略中止條件幾回。
    ignore <bnum> <count>
    表示忽略斷點號爲bnum的中止條件count次。

六、查看棧信息

    一、查看棧簡單信息 bt

    bt        打印當前的函數調用棧的全部信息   調用順序:從下向上依次調用
    bt <n>
    n 是一個正整數,表示只打印棧頂上n個層的棧信息。
    -n 表一個負整數,表示只打印棧底下n個層的棧信息        eg:

(gdb) bt  <------------------------棧的信息
#0  test1 () at test.cpp:8     <------------------------棧的第0層(棧頂)
#1  0x080488f6 in func () at test.cpp:22      <------------------------棧的第1層
#2  0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33
(gdb) bt 2       <------------------------打印棧頂2個層的棧信息
#0  test1 () at test.cpp:8
#1  0x080488f6 in func () at test.cpp:22
(More stack frames follow...)

    二、切換當前棧,查看當前棧詳細信息 frame

    frame <n>   
    f <n>             n 是一個從0 開始的整數,是棧中的層編號。好比:frame 0,表示棧頂,frame 1,表示棧的第二層。
    up <n>         表示向棧的上面移動 n層,能夠不打n,表示向上移動一層。
    down <n>     表示向棧的下面移動n 層,能夠不打 n,表示向下移動一層。 

    info frame    會打印出這些信息:棧的層編號,當前的函數名,函數參數值,函數所在文件及行號,函數執行到的語句
    info args 打印出當前函數的參數名及其值。
    info locals 打印出當前函數中全部局部變量及其值。
    info catch 打印出當前的函數中的異常處理信息

(gdb) info frame          <------------------ 打印出當前棧的詳細信息
Stack level 0, frame at 0xbffff3f0:
 eip = 0x8048852 in test1 (test.cpp:8); saved eip = 0x80488f6
 called by frame at 0xbffff410
 source language c++.
 Arglist at 0xbffff3e8, args: 
 Locals at 0xbffff3e8, Previous frame's sp is 0xbffff3f0
 Saved registers:
  ebx at 0xbffff3e4, ebp at 0xbffff3e8, eip at 0xbffff3ec
(gdb) f 0       <------------------ 打印棧頂信息和代碼
#0  test1 () at test.cpp:8
8               int* ppp = (int *)new int[10];
(gdb) f 2
#2  0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33
33              func();
(gdb) up 1      <------------------ 當前棧的指向向上移動一層即#1的棧
#1  0x080488f6 in func () at test.cpp:22
22              test1();
(gdb) info frame    <------------------ 打印當前棧的詳細信息
Stack level 1, frame at 0xbffff410:
 eip = 0x80488f6 in func (test.cpp:22); saved eip = 0x804895b
 called by frame at 0xbffff440, caller of frame at 0xbffff3f0
 source language c++.
 Arglist at 0xbffff408, args: 
 Locals at 0xbffff408, Previous frame's sp is 0xbffff410
 Saved registers:
  ebp at 0xbffff408, eip at 0xbffff40c

七、代碼查看

查看源碼 list

在程序編譯時必定要加上-g 的參數,把源程序信息編譯到執行文件中,list簡寫l。

list <linenum>
顯示程序第linenum行的周圍的源程序(通常是打印當前行的上 5 行和下 5 行)。

list <function>
顯示函數名爲function 的函數的源程序(若是顯示函數是是上 2 行下 8 行,默認是 10 行)。

list
顯示當前行後面的源程序(默認行數)。

list -
顯示當前行前面的源程序(默認行數)。

指定顯示的行數

也能夠定製顯示的範圍,使用下面命令能夠設置一次顯示源程序的行數。
set listsize <count>
設置一次顯示源代碼的行數。

show listsize
查看當前listsize 的設置。

list 命令還有下面的用法:
list <first>, <last> 顯示從first行到last行之間的源代碼。
list , <last> 顯示從當前行到 last 行之間的源代碼(包含首尾行)。
list + 日後顯示源代碼(默認行數)。

通常來講在list 後面能夠跟如下這們的參數:
<linenum> 行號。
<+offset> 當前行號的正偏移量。
<-offset> 當前行號的負偏移量。
<filename:linenum> 哪一個文件的哪一行。
<function> 函數名。
<filename:function> 哪一個文件中的哪一個函數。
<*address> 程序運行時的語句在內存中的地址。

搜索源代碼

forward-search <regexp>
search <regexp>

向前面搜索。
reverse-search <regexp>
所有搜索。
其中,<regexp>就是正則表達式,也主一個字符串的匹配模式

八、程序運行時數據查看

查看程序的變量或地址
一、全局變量(全部文件可見的)    eg:    p   g
二、靜態全局變量(當前文件可見的)
三、局部變量(當前Scope 可見的)    eg: p   m
若是你的局部變量和全局變量發生衝突(也就是重名),通常狀況下是局部變量會隱藏全局變量,查看格式:file::variable eg:  p 'test.cpp'::m
查看某個函數的變量:function::variable  eg:沒有成功

數組查看 「@」
格式:*內存地址@內存長度   「@」的左邊是第一個內存的地址的值,「@」的右邊則你你想查看內存的長度

int arr[]={2,23,5,6,7,8};    
(gdb) p *ppp@10
$2 = {1, 2, 0, 0, 0, 0, 0, 0, 0, 0}
int* ppp = (int *)new int[10];
(gdb) p *arr@6
$3 = {2, 23, 5, 6, 7, 8}

輸出格式
用GDB的數據顯示格式:
x 按十六進制格式顯示變量。
d 按十進制格式顯示變量。
u 按十六進制格式顯示無符號整型。
o 按八進制格式顯示變量。
t 按二進制格式顯示變量。
a 按十六進制格式顯示變量。
c 按字符格式顯示變量。
f 按浮點數格式顯示變量。

(gdb) p m
$5 = 10
(gdb) p/x m
$6 = 0xa
(gdb) p/o m
$7 = 012
(gdb) p/c m
$8 = 10 '\n'
(gdb) p/f m
$9 = 1.40129846e-44

查看內存的值 examine
格式:x/<n/f/u> <addr>    examine 命令(簡寫是x)來查看內存地址中的值
n、f、u是可選的參數
n 是一個正整數,表示顯示內存的長度,也就是說從當前地址向後顯示幾個地址的內容。
f 表示顯示的格式,參見上面。若是地址所指的是字符串,那麼格式能夠是 s,若是地十是指令地址,那麼格式能夠是 i。
u 表示從當前地址日後請求的字節數,若是不指定的話,GDB 默認是 4 個 bytes。u 參數能夠用下面的字符來代替,b 表示單字節,h 表示雙字節,w 表示四字節,g 表示八字節。當咱們指定了字節長度後,GDB會從指內存定的內存地址開始,讀寫指定字節,並把其看成一個值取出來。
<addr>表示一個內存地址

(gdb) p/x m   (查看變量m的值)
$10 = 0xa
(gdb) p/x &m    (查看變量m的地址)
$11 = 0xbffff3d8
(gdb) x 0xbffff3d8 (根據內存地址查看其值)
0xbffff3d8:     0x0000000a
(gdb) x/4ub 0xbffff3d8 (查看此內存後4個單字節的值)
0xbffff3d8:     10      0       0       0
(gdb) x/2uh 0xbffff3d8    (查看此內存後2個雙字節的值)
0xbffff3d8:     10      0

GDB環境變量設置與查看
一、GDB的環境變量和UNIX 同樣,也是以$起頭  
二、環境變量能夠定義任一的類型。包括結構體和數組
三、查看當前所設置的全部的環境變量    show convenience
四、查看單一變量  p  $變量名  eg:p   $foo
eg: set $foo = 12  
      set $pp="abcdef"


查看寄存器的值
info registers 查看寄存器的狀況。(除了浮點寄存器)
info all-registers 查看全部寄存器的狀況。(包括浮點寄存器)
info registers <regname ...> 查看所指定的寄存器的狀況。
p $寄存器名字    看看指定的寄存器的狀況 eg: p $eip

九、改變程序的執行

一、修改變量值

使用GDB的 print 命令便可完成:eg  (gdb) print x=4【C/C++的語法,Pascal的語法:x:=4】
變量和GDB中的參數衝突,set var 命令來修改:eg  set var width=47

二、跳轉執行

GDB 能夠修改程序的執行順序,可讓程序執行隨意跳躍。
jump <linespec>
指定下一條語句的運行點。<linespce>能夠是文件的行號,能夠是 file:line 格式,能夠是+num 這種偏移量格式
jump <address>
這裏的<address>是代碼行的內存地址。
注意,jump 命令不會改變當前的程序棧中的內容,因此,當你從一個函數跳到另外一個函數時,當函數運行完返回時進行彈棧操做時必然會發生錯誤,可能結果仍是很是奇怪的,甚至於產生程序 CoreDump。因此最好是同一個函數中進行跳轉

三、產生信號

使用 singal 命令,能夠產生一個信號量給被調試的程序
語法是:signal <singal>,UNIX 的系統信號量一般從1 到 15。因此<singal>取值也在這個範圍
注意:single 命令和 shell 的 kill 命令不一樣,系統的 kill 命令發信號給被調試程序時,運行程序立刻會被GDB停住,而single 命令所發出一信號則是直接發給被調試程序的

四、強制函數返回
return
return <expression>

使用 return 命令取消當前函數的執行,並當即返回,若是指定了<expression>,那麼該表達式的值會
被認做函數的返回值。

五、強制調用函數

call <expr>
表達式中能夠一是函數,以此達到強制調用函數的目的。並顯示函數的返回值,若是函數返回值是
void,那麼就不顯示。

最後給你們推薦一本書:陳皓的《用GDB調試程序》。但願對你們有所幫助。

相關文章
相關標籤/搜索