GDB偵錯之基本指令介紹

2020-08-08 21:30:42

其餘相關內容可參考個人部落格

GDB簡介

show language 可檢視當前偵錯環境語言
set language 可檢視GDB支援的所有語言種類
set language <語言> 可設定當前偵錯環境語言

What is GDB?
GDB, the GNU Project debugger, allows you to see what is going on `inside’ another program while it executes – or what another program was doing at the moment it crashed.
GDB can do four main kinds of things (plus other things in support of these) to help you catch bugs in the act:
1.Start your program, specifying anything that might affect its behavior.
2.Make your program stop on specified conditions.
3.Examine what has happened, when your program has stopped.
4.Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.
Those programs might be executing on the same machine as GDB (native), on another machine (remote), or on a simulator. GDB can run on most popular UNIX and Microsoft Windows variants, as well as on Mac OS X.

run

  • 啓動被偵錯的程式,縮寫爲 r
  • 若程式執行需要參數,可以在run後面直接加入執行參數,或者在run之前執行set args

備註:stop可暫停程式執行,使用info program可檢視程式執行狀態即停止原因

attach

掛接到已在執行的進程來偵錯 attach <process-id>

attach時,再次run,可將原進程殺掉,從頭執行程式

可以使用pidof 進程名來獲得進程ID

list

  1. 檢視原始碼list,縮寫l

    • 直接輸入l爲從第一行開始顯示,
    • l line num 可顯示行號附近的原始碼
    • l function 可顯示函數名稱附近的原始碼
    • l filename:linenum 可顯示原始碼檔案filename的linenum附近原始碼
    • l filename:function 可顯示原始碼檔案filename的function函數附近原始碼
    • 設定一次列出的行數
    (gdb) set listsize 20
    (gdb) show listsize
    Number of source lines gdb will list by default is 20.
    
    • l first,last列印first到last行之間的原始碼,first可預設,代表當前位置
  2. search
    可使用該命令搜尋函數、變數等

break及相關斷點指令

  1. break
    在指定的行或函數處設定斷點,縮寫爲 b
    • break 當不帶參數時,在所選棧幀中執行的下一條指令處設定斷點。
    • break function-name在函數體入口處打斷點
    • break line-number 在當前原始碼檔案指定行的開始處打斷點。
    • break -Nbreak +N 在當前原始碼行前面或後面的 N 行開始處打斷點,N 爲正整數。
    • break filename:linenum 在原始碼檔案 filename 的 linenum 行處打斷點。
    • break filename:function 在原始碼檔案 filename 的 function 函數入口處打斷點。
    • break address 在程式指令的地址處打斷點,注意使用時在地址前面加入*
    • break … if cond 設定條件斷點,… 代表上述參數之一(或無參數),cond爲條件表達式,僅在 cond 值非零時暫停程式執行。

若你需要打斷點的函數存在若幹(函數過載),這是GDB會給你列出一個所有該函數的列表,可自行選擇

  1. tbreak
    設定臨時斷點,參數同 break,但在程式第一次停住後會被自動刪除
  2. info breakpoints
    列印未刪除的所有斷點,觀察點和捕獲點的列表,縮寫爲 i b
  3. disable
    禁用斷點,縮寫爲 dis
  4. clear
    清除指定行或函數處的斷點
  5. delete
    清除指定num的斷點,num爲info b中的序號

補充:在程式執行時,若沒有遇到斷點,可直接輸入crtl+c停止程式執行,進入GDB命令列

  1. 斷點條件管理

    • condition
      用法:
      condition breakNum expr修改斷點的停止條件爲expr
      condition breakNum清除斷點的停止條件
    • ignore
      用法:ignore breakNUm count忽略斷點count次

    上述breakNum均爲info b中顯示的斷點號

  2. 執行緒斷點

    當你的程式是多執行緒時,你可以定義你的斷點是否在所有的執行緒上,或是在某個特定的執行緒。
    break line thread threadNo
    其中line爲你的原始碼行數,threadNo爲info threads命令中GDB給出的執行緒ID,若不指定threadNo,則爲所有執行緒打斷點。

範例如下

(gdb) b 27
Breakpoint 1 at 0x400777: file main.c, line 27.
(gdb) b 28
Breakpoint 2 at 0x400785: file main.c, line 28.
(gdb) b 29
Breakpoint 3 at 0x40078f: file main.c, line 29.
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400777 in main at main.c:27
2       breakpoint     keep y   0x0000000000400785 in main at main.c:28
3       breakpoint     keep y   0x000000000040078f in main at main.c:29
(gdb) disable 1
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000400777 in main at main.c:27
2       breakpoint     keep y   0x0000000000400785 in main at main.c:28
3       breakpoint     keep y   0x000000000040078f in main at main.c:29
(gdb) enable 1
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400777 in main at main.c:27
2       breakpoint     keep y   0x0000000000400785 in main at main.c:28
3       breakpoint     keep y   0x000000000040078f in main at main.c:29
(gdb) clear 27
Deleted breakpoint 1 
(gdb) info b
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000400785 in main at main.c:28
3       breakpoint     keep y   0x000000000040078f in main at main.c:29

catch捕捉

可設定捕捉點來補捉程式執行時的一些事件。如:載入共用庫(動態鏈接庫)或是C++的異常。設定捕捉點的格式爲:
catch event
當event發生時,停住程式。event可以是下面 下麪的內容:

  • throw 一個C++拋出的異常。
  • catch 一個C++捕捉到的異常。
  • exec 呼叫系統呼叫exec時。
  • fork 呼叫系統呼叫fork時。
  • vfork 呼叫系統呼叫vfork時。
  • load 或 load 載入共用庫(動態鏈接庫)時。
  • unload 或 unload 解除安裝共用庫(動態鏈接庫)時。

tcatch event
只設置一次捕捉點,當程式停住以後,應點被自動刪除。

step、next、finish、continue控制指令

  1. step
    單步跟蹤,如果有函數呼叫,會進入該函數,縮寫爲 s

  2. next
    單步跟蹤,如果有函數呼叫,不會進入該函數,縮寫爲n

單步執行加入i參數可單補執行彙編程式碼

  1. finish
    執行直到選擇的棧幀返回,縮寫爲 fin(即執行到當前函數返回)

  2. continue
    當gdb遇到斷點停下時,恢復程式執行,縮寫爲 c

print、watch、display及相關檢視指令

  1. print
    print/f ,列印變數或表達式的值,縮寫爲 p,其中/f爲可選參數,可指定列印輸出格式,並且使用該命令可以直接給變數或者表達式賦值

    若區域性變數和全域性變數衝突,或想列印其他檔案或函數中的某個變數時,可用
    file::variable
    function::variable來指示

    (gdb) p a
    $9 = 100
    (gdb) p a=10
    $10 = 10
    
    • (1)列印陣列或記憶體內容

      • p 陣列名稱,若變數本身就是陣列,這樣可列印陣列內容
      • p *指針@個數,若變數爲動態申請的記憶體,想要按照指針型別列印內容,可使用該方式

      範例如下:

      (gdb) p testArray 
      $1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
      (gdb) p pTmp 
      $2 = (int *) 0x602010
      (gdb) p *pTmp@10
      $3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
      
    • (2)若字串過長,或者陣列過大,則可以設定print列印的元素個數來顯示完全

      set print elements number-of-elements,設定顯示元素個數
      show print elements,檢視顯示元素個數
      set print null-stop <on><off>,如果打開了這個選項,那麼顯示字串時則遇到結束符停止顯示,預設關閉
      show print null-stop

      (gdb) p testArray 
      $4 = "global_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_te"...
      (gdb) set print elements 1000
      (gdb) p testArray 
      $5 = "global_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_testglobal_array_t"...
      
    • (3)設定列印結構體或者類時,將成員分行列印,避免堆積在一起不易檢視

      set print pretty <on><off>,開啓/關閉分行列印
      show print pretty,檢視當前顯示形式

      (gdb) p testStruct 
      $1 = {para1 = 100, para2 = 200, testArray = "global_array_testglobal_array_testglobal_array_testglobal_array_"}
      (gdb) set print pretty
      (gdb) p testStruct 
      $2 = {
      para1 = 100, 
      para2 = 200, 
      testArray = "global_array_testglobal_array_testglobal_array_testglobal_array_"
      }
      
    • (4)設定列印陣列時,每個元素單獨佔一行
      set print array <on><off>,開啓/關閉元素獨佔一行的方式
      show print array,檢視當前顯示形式

      (gdb) p testArray 
      $4 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
      (gdb) set print array on
      (gdb) p testArray 
      $5 =   {1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      0}
      
    • (5)設定顯示結構體時,是否顯示其內的聯合體數據
      set print union <on><off>,開啓/關閉顯示其內的聯合體數據
      show print union,檢視當前顯示形式

    • (6)檢視歷史數據
      在每次使用print命令時,GDB都會爲其輸出編號:$1,$2,$3等
      show value列印所有歷史數據
      show value n列印以n爲中心的10條歷史數據
      show value +列印上次顯示的歷史數據的之後10條歷史數據

  2. watch

    • 爲表達式(或變數)設定觀察點,當表達式(或變數)的值有變化時,暫停程式執行
    • rwatch expr 當表達式(變數)expr被讀時,停住程式
    • awatch expr 當表達式(變數)的值被讀或被寫時,停住程式。
    • 用法: watch [-l|-location] <expr> 每當一個表達式的值改變時,觀察點就會暫停程式執行

    如果給出了 -l 或者 -location,則它會對 expr 求值並觀察它所指向的記憶體。例如,watch *(int *)0x12345678 將在指定的地址處觀察一個 4 位元組的區域(假設 int 佔用 4 個位元組)

  3. display
    display/fmt <expr><addr>每次程式停止時列印expr表達式或addr地址中的內容的值(自動顯示)
    fmt爲顯示格式,且支援i參數顯示彙編程式碼

    關於display的相關控制命令:

    • undisplay <num>,刪除自動顯示項
    • enable <num>,使能自動顯示項
    • disable <num>,失能自動顯示項(不刪除)
    • info display,檢視display的相關資訊,上述三個命令的參數均爲info display中列出的num

    範例如下

    (gdb) display a
    1: a = 10
    (gdb) info display
    Auto-display expressions now in effect:
    Num Enb Expression
    1:   y  a
    (gdb) display b
    2: b = 2
    (gdb) info display
    Auto-display expressions now in effect:
    Num Enb Expression
    2:   y  b
    1:   y  a
    (gdb) undisplay 1
    (gdb) info display
    Auto-display expressions now in effect:
    Num Enb Expression
    2:   y  b
    (gdb) c
    Continuing.
    progran is start
    
    Breakpoint 5, main () at main.c:29
    29	    test();
    2: b = 2
    (gdb) 
    
  4. examine
    檢視記憶體,縮寫爲x,用法: x/nfu <addr>

    n、f 和 u 都是可選參數,用於指定要顯示的記憶體以及如何格式化。addr 是要開始顯示記憶體的地址的表達式。

    • n 重複次數(預設值是 1),指定要顯示多少個單位(由 u 指定)的記憶體值。

    • f 顯示格式(初始預設值是 x),顯示格式是 print(‘x’,‘d’,‘u’,‘o’,‘t’,‘a’,‘c’,‘f’,‘s’) 使用的格式之一,再加 i(機器指令)。

    • u 單位大小,b 表示單位元組,h 表示雙位元組,w 表示四位元組,g 表示八位元組。

    例如

    (gdb) x &a
    0x7fffffffe47c:	0x0000000a
    (gdb) x/d &a
    0x7fffffffe47c:	10
    (gdb) x &b
    0x7fffffffe478:	2
    (gdb) x/4x &b
    0x7fffffffe478:	0x00000002	0x0000000a	0x00000000	0x00000000
    
  5. info register
    檢視暫存器的值,其中register可簡寫爲reg
    info register,檢視暫存器(除了浮點暫存器)
    info all-register,檢視所有暫存器(包括浮點暫存器)
    info register regName,檢視指定暫存器regName的內容

    (gdb) info reg
    rax            0x4006e7	4196071
    rbx            0x0	0
    rcx            0x400730	4196144
    rdx            0x7fffffffe578	140737488348536
    rsi            0x7fffffffe568	140737488348520
    rdi            0x1	1
    rbp            0x7fffffffe480	0x7fffffffe480
    rsp            0x7fffffffe480	0x7fffffffe480
    r8             0x7ffff7dd5e80	140737351868032
    r9             0x0	0
    r10            0x7fffffffdfa0	140737488347040
    r11            0x7ffff7a2f410	140737348039696
    r12            0x4005b0	4195760
    r13            0x7fffffffe560	140737488348512
    r14            0x0	0
    r15            0x0	0
    rip            0x4006eb	0x4006eb <main+4>
    eflags         0x246	[ PF ZF IF ]
    cs             0x33	51
    ss             0x2b	43
    ds             0x0	0
    es             0x0	0
    fs             0x0	0
    gs             0x0	0
    

backtrace

檢視程式呼叫棧的資訊,縮寫爲 bt, 加入full參數,bt full可列印出所有的變數資訊

  • backtrace n n爲正整數,表示之列印棧頂上n層的棧資訊
  • backtrace -n -n爲負數,表示之列印棧底n層的棧資訊
  • frame n n爲正整數,表示第幾層棧,frame可簡寫爲f,使用該命令可切換當前你關注的棧資訊
  • up n n爲正整數,該命令表示向棧的上面移動n層
  • down n n爲正整數,該命令表示向棧的下面 下麪移動n層
  • 上面的命令,都會列印出移動到的棧層的資訊。如果你不想讓其打出資訊。你可以使用這三個命令
    • select-frame 對應於 frame 命令
    • up-silently n 對應於 up 命令
    • down-silently n 對應於 down 命令
  • info frame該命令可列印出當前棧的詳細資訊
    • the address of the frame
    • the address of the next frame down (called by this frame)
    • the address of the next frame up (caller of this frame)
    • the language in which the source code corresponding to this frame is written
    • the address of the frame’s arguments
    • the address of the frame’s local variables
    • the program counter saved in it (the address of execution in the caller frame) which registers were saved in the frame
  • info args列印當前棧的arguments
  • info locals列印當前函數中的所有區域性變數及其值
  • info catch列印出當前的函數中的例外處理資訊
(gdb) bt
#0  add (a=1, b=2) at add.cpp:6
#1  0x00000000004007d4 in main () at main.c:32
(gdb) bt full
#0  add (a=1, b=2) at add.cpp:6
No locals.
#1  0x00000000004007d4 in main () at main.c:32
        testStruct = {
          para1 = 100, 
          para2 = 200, 
          testArray = "global_array_testglobal_array_testglobal_array_testglobal_array_"
        }
        strArray = "local_array_test"
        a = 10
        b = 1634890337

斷點命令

我們可以使用GDB提供的commands命令來設定停止點的執行命令。當執行的程式在被停止住時,可使其執行指定命令,這很有利行自動化偵錯。對基於GDB的自動化偵錯是一個強大的支援,格式如下:

commands [breakNum]
... command-list ...
end

爲斷點號breakNum指寫一個命令列表。當程式被該斷點停住時,gdb會依次執行命令列表中的命令。

注意設定時,先輸入commands [breakNum],然後GDB會提示你繼續輸入命令列表,一行一個命令

範例如下,一圖流 ♪(^∀^●)ノ:

如果你要清除斷點上的命令序列,那麼只要簡單的執行一下commands命令,並直接在打個end就行了。

指定原始碼路徑

某些時候,用-g編譯過後的執行程式中只是包括了原始檔的名字,沒有路徑名。GDB提供了可以讓你指定原始檔的路徑的命令,以便GDB進行搜尋。

現在大部分執行程式中都會包括原始檔的路徑,可簡單使用nm -l命令檢視,可發現每個符號後都有原始檔的路徑、名稱和行號

GDB可以在沒有原始碼的情況下偵錯,但只能偵錯彙編程式碼

directory dirname ...使用該命令可指定原始檔路徑,其中directory可簡寫爲dir
directory可清除所有的自定義原始檔路徑
show directory可檢視自定義原始檔路徑

(gdb) dir /home
Source directories searched: /home:$cdir:$cwd
(gdb) show dir
Source directories searched: /home:$cdir:$cwd
(gdb) dir
Reinitialize source path to empty? (y or n) y
Source directories searched: $cdir:$cwd
(gdb) show dir
Source directories searched: $cdir:$cwd

檢視原始碼載入記憶體

其實這裏就是檢視程式執行時,原始碼在記憶體當中的地址
info line + <line><func><file:line><file:func>

使用disassemble命令可直接檢視原始碼的彙編程式碼,其中會同時顯示載入地址

反彙編

disassemble <func><'file'::func><start,end><start,+length>命令可檢視彙編程式碼,具體參數介紹如下

  • /m,同時列印原始碼
  • /r,同時列印16進位制機器碼
  • func,列印指定函數的彙編程式碼
  • 'file'::func,列印指定檔案中的某個函數的彙編程式碼,格式必須按照前面的寫,注意單引號
  • start,end,列印start到end地址之間的彙編程式碼
  • start,+length,列印start到start+length之間的彙編程式碼

原文如下:

(gdb) help disas
Disassemble a specified	section	of memory.
Default	is the function	surrounding the	pc of the selected frame.
With a /m modifier, source lines are included (if available).//同時列印原始碼
With a /r modifier, raw	instructions in	hex are	included.//同時列印16進位制機器碼
With a single argument,	the function surrounding that address is dumped.
Two arguments (separated by a comma) are taken as a range of memory to dump,
  in the form of "start,end", or "start,+length".

Note that the address is interpreted as	an expression, not as a	location
like in	the "break" command.
So, for	example, if you	want to	disassemble function bar in file foo.c
you must type "disassemble 'foo.c'::bar" and not "disassemble foo.c:bar".

改變程式的執行

在GDB中可以根據自己的偵錯思路來動態地在GDB中更改當前被偵錯程式的執行線路或是其變數的值

博主在學習了函數呼叫的堆疊變化後,曾嘗試在GDB中通過改變暫存器的值,達到改變執行路線的效果,原來GDB直接提供了這個功能
具體的嘗試見另一篇文章《GDB偵錯之改變程式執行流程》

  1. 改變變數值
    print a=100,使用print命令即可直接改變變數值

  2. 跳轉執行
    jump line,指定下一條語句的執行點
    jump *address,指定下一條語句的執行地址

    注意,jump命令不會改變當前的程式棧中的內容,所以,當你從一個函數跳到另一個函數時,當函數執行完返回時進行彈棧操作時必然會發生錯誤,所以最好是同一個函數中進行跳轉。
    而博主的那篇文章則是使用改變棧內容達到安全呼叫和返回的效果的,和GDB的jump還是有些不同的,嘿嘿