gdb除錯學習與實踐記錄 -- 常用命令解析1

Asinmy發表於2020-11-02

目錄

Redis 原始碼解壓及編譯

使用 GDB 附加 redis-server 這個程式

run 命令(簡寫為 r)啟動這個程式

ontinue 命令 讓程式繼續執行

break 命令(簡寫為 b)新增斷點

backtrace 命令(簡寫為 bt)檢視當前呼叫堆疊

info break 命令(簡寫為 info b)檢視加了哪些斷點及enable、disable 和 delete 

list 命令(簡寫為 l)檢視當前斷點處的程式碼

print 命令(簡寫為 p)在除錯過程中檢視變數的值、修改當前記憶體中的變數值

ptype (print type)就是輸出一個變數的型別

Redis 原始碼解壓及編譯

  • 進入生成的目錄使用 makefile 命令進行編譯

  • 注意
    • 設定編譯器的選項時使用的是 CFLAGS 選項;如果專案使用的語言是 C++,那麼使用的編譯器一般是 g++,相對應的編譯器選項是 CXXFLAGS。
    • 此處makefile 使用了 -j 選項,其值是 4,表示開啟 4 個程式同時編譯,加快編譯速度。
  •  src 目錄下生成多個可執行程式,其中 redis-server 和 redis-cli 是需要除錯的程式。

使用 GDB 附加 redis-server 這個程式

run 命令(簡寫為 r)啟動這個程式

  • 這是 redis-server 啟動介面,假設程式已經啟動,再次輸入 run 命令則是重啟程式。
  • 在 GDB 介面按 Ctrl + C 快捷鍵讓 GDB 中斷下來,再次輸入 r 命令,GDB 會詢問我們是否重啟程式,輸入 yes 確認重啟。

ontinue 命令 讓程式繼續執行

  • 當 GDB 觸發斷點或者使用 Ctrl + C 命令中斷下來後,想讓程式繼續執行,只要輸入 continue 命令即可(簡寫為 c)。
  • 如果 continue 命令繼續觸發斷點,GDB 就會再次中斷下來

break 命令(簡寫為 b)新增斷點

break functionname在函式名為 functionname 的入口處新增一個斷點
break LineNo在當前檔案行號為 LineNo 處新增一個斷點
break filename:LineNo在 filename 檔案行號為 LineNo 處新增一個斷點
  • 對於一般的 Linux 程式來說,main() 函式是程式入口函式,redis-server 也不例外,知道了函式的名字,就可以直接在 main() 函式處新增一個斷點:

  • 新增好了以後,使用 run 命令重啟程式,就可以觸發這個斷點了,GDB 會停在斷點處

  • redis-server 預設埠號是 6379 ,這個埠號肯定是通過作業系統的 bind() 函式建立的,通過檔案搜尋,找到呼叫這個函式的檔案,其位於 anet.c 455 行。

  • 使用 break 命令在這個地方加一個斷點:
  • 由於程式繫結埠號是 redis-server 啟動時初始化的,為了能觸發這個斷點,再次使用 run 命令重啟下這個程式,GDB 第一次會觸發 main() 函式處的斷點,輸入 continue 命令繼續執行,接著觸發 anet.c:455處的斷點

  • 現在斷點停在第 455 行,所以當前檔案就是 anet.c,可以直接使用“break 行號”新增斷點。
    • 例如,可以在第 458 行、464 行、466 行分別加一個斷點,看看這個函式執行完畢後走哪個 return 語句退出,則可以執行:

  • 新增好這三個斷點以後,使用 continue 命令繼續執行程式,發現程式執行到第 466 行中斷下來

  • 說明 redis-server 繫結埠號並設定偵聽(listen)成功,再開啟一個 SSH 視窗,驗證一下,發現 6379 埠確實已經處於偵聽狀態了:

backtrace 命令(簡寫為 bt)檢視當前呼叫堆疊

  • 接上,redis-server 現在中斷在 anet.c:466 行,可以通過 backtrace 命令來檢視當前的呼叫堆疊:

  • 一共有 6 層堆疊,最頂層是 main() 函式,最底層是斷點所在的 anetListen() 函式,堆疊編號分別是 #0 ~ #5
  • 如果想切換到其他堆疊處,可以使用 frame 命令(簡寫為 f),該命令的使用方法是“frame 堆疊編號(編號不加 #)”。在這裡依次切換至堆疊頂部,然後再切換回 #0 練習一下:

  • 通過檢視上面的各個堆疊,可以得出這裡的呼叫層級關係,即:
    • main() 函式在第 5162 行呼叫了 initServer() 函式
    • initServer() 在第 2795 行呼叫了 listenToPort() 函式
    • listenToPort() 在第 2651 行呼叫了 anetTcp6Server() 函式
    • anetTcp6Server() 在第 524 行呼叫了 _anetTcpServer() 函式
    • _anetTcpServer() 函式在第 501 行呼叫了 anetListen() 函式
    • 當前斷點正好位於 anetListen() 函式中

info break 命令(簡寫為 info b)檢視加了哪些斷點及enable、disable 和 delete 

  • 通過上面的內容片段可以知道,目前一共增加了 6 個斷點,其他資訊比如每個斷點的位置(所在的檔案和行號)、記憶體地址、斷點啟用和禁用狀態資訊也一目瞭然。
  • 如果我們想禁用某個斷點,使用“disable 斷點編號”就可以禁用這個斷點了,被禁用的斷點不會再被觸發;
  • 被禁用的斷點也可以使用“enable 斷點編號”重新啟用。

  • 使用 disable 1 以後,第一個斷點的 Enb 一欄的值由 y 變成 n,重啟程式也不會再次觸發

  • 如果 disable 命令和 enable 命令不加斷點編號,則分別表示禁用和啟用所有斷點

  • 使用“delete 編號可以刪除某個斷點,如 delete 2 3 則表示要刪除的斷點 2 和斷點 3:

  • 如果輸入 delete 不加命令號,則表示刪除所有斷點。

list 命令(簡寫為 l)檢視當前斷點處的程式碼

  • 使用 frame 命令切換到剛才的堆疊 #3 處,然後輸入 list 命令看下效果:

  • 斷點停在第 2795 行,輸入 list 命令以後,會顯示第 2795 行前後的 10 行程式碼,再次輸入 list 命令試一下:

  • 程式碼繼續往後顯示 10 行,第一次輸入 list 命令會顯示斷點處前後的程式碼,繼續輸入 list 指令會以遞增行號的形式繼續顯示剩下的程式碼行,一直到檔案結束為止。
  • list 指令還可以往前和往後顯示程式碼,命令分別是“list + (加號)”和list - (減號)”:

  • list 預設顯示多少行可以通過修改相關的 GDB 配置。
  • list 不僅可以顯示當前斷點處的程式碼,也可以顯示其他檔案某一行的程式碼,更多的用法可以在 GDB 中輸入 help list 檢視(也可以通過):

  • 可以使用 list FILE:LINENUM 來顯示某個檔案的某一行處的程式碼。

print 命令(簡寫為 p)在除錯過程中檢視變數的值、修改當前記憶體中的變數值

  • 使用 print 命令分別列印出 server.port 、server.ipfd 、server.ipfd_count 的值
    • 其中 server.ipfd 顯示 “{0 \}”,這是 GDB 顯示字串或字元資料特有的方式
    • 當一個字串變數或者字元陣列或者連續的記憶體值重複若干次,GDB 就會以這種模式來顯示以節約空間。
  • print 命令不僅可以顯示變數值,也可以顯示進行一定運算的表示式計算結果值,甚至可以顯示一些函式的執行結果值
    • 如輸入 p &server.port 來輸出 server.port 的地址值
    • 如果在 C++ 物件中,可以通過 p this 來顯示當前物件的地址,也可以通過 p *this 來列出當前物件的各個成員變數值
    • 如果有三個變數可以相加( 假設變數名分別叫 a、b、c ),可以使用 p a + b + c 來列印這三個變數的結果值。

  • 若 func() 是一個可以執行的函式,p func() 命令可以輸出該變數的執行結果
    • 如,某個時刻,某個系統函式執行失敗了,通過系統變數 errno 得到一個錯誤碼,則可以使用 p strerror(errno) 將這個錯誤碼對應的文字資訊列印出來
  • print 命令不僅可以輸出表示式結果,同時也可以修改變數的值
    • 將上文中的埠號從 6379 改成 6400 試試:

  • 一個變數值修改後能否起作用要看這個變數的具體位置和作用
    • 對於表示式 int a = b / c ; 如果將 c 修改成 0 ,那麼程式就會產生除零異常。
  • 利用 print 命令,不僅可以檢視程式執行過程中的各個變數的狀態值,也可以通過臨時修改變數的值來控制程式的行為。

ptype (print type)就是輸出一個變數的型別

  • 輸出 Redis 堆疊 #4 的變數 server 和變數 server.port 的型別:

  • 對於一個複合資料型別的變數,ptype 不僅列出了這個變數的型別( 這裡是一個名叫 redisServer 的結構體),而且詳細地列出了每個成員變數的欄位名。

相關文章