gdb進(jìn)階調(diào)試技巧

整理一下在linux下C/C++用gdb工具debug一些提高效率的操作。基本的gdb操作就不在這里贅述了。

  • 打印各種變量
    x 命令
    在gdb中可以使用x命令,來(lái)打印內(nèi)存中的值。具體的格式是x/nfu addr。 含義為以f格式打印從addr開(kāi)始的n個(gè)長(zhǎng)度單元為u的內(nèi)存值。
    1. n表示打印的n個(gè)u的長(zhǎng)度
    2. f 表示打印格式,u,d表示10進(jìn)制的無(wú)符號(hào)和有符號(hào)數(shù); x表示16進(jìn)制;t表示二進(jìn)制
    3. u表示單元長(zhǎng)度,b表示一個(gè)byte,w表示4個(gè)byte
    int main()
    {
      int val = 0xa;
      
      char buff[1024];
      memset(buff, 0, sizeof(1024));
    
      char name[]="hello world";
      strcpy(buff, name);
      
      int arr[10] = {0};
      arr[0] = 0xa;
      arr[1] = 10;
      arr[2] = 20;
    
      return 0;
    }
    
    (gdb) x/16db &arr
    0x7fffffffe010:   10  0   0   0   10  0   0   0
    0x7fffffffe018:   20  0   0   0   0   0   0   0
    
    (gdb) x/16sb &buff
    0x7fffffffe050:    "hello world"
    0x7fffffffe05c:    "\177"
    0x7fffffffe05f:    ""
    0x7fffffffe060:    "<縱?\177"
    0x7fffffffe067:    ""
    0x7fffffffe068:    "X\227\177"
    0x7fffffffe06f:    ""
    0x7fffffffe070:    "\020暫?\177"
    0x7fffffffe077:    ""
    0x7fffffffe078:    ""
    0x7fffffffe079:    ""
    0x7fffffffe07a:    ""
    0x7fffffffe07b:    ""
    0x7fffffffe07c:    "\001"
    0x7fffffffe07e:    ""
    0x7fffffffe07f:    ""
    

  • 日志打印到某個(gè)文件
    set pagination off
    set logging file a.log
    set logging overwrite
    set logging on
    

  • -tui 圖形界面選項(xiàng)
    gdb a.out -tui # gdb tui 啟動(dòng)
    gdb attach pid -tui # gdb tui attach



  • gdb定義函數(shù)
    在開(kāi)發(fā)中有時(shí)會(huì)遇到需要看一個(gè)很大的數(shù)組,里面有沒(méi)有被寫壞的元素。如果數(shù)組中還有嵌套其他結(jié)構(gòu)體,每個(gè)依次打印出來(lái)非常麻煩。這里可以使用gdbinit,定義一個(gè)函數(shù),依次遍歷數(shù)組,打印超過(guò)某個(gè)值的元素。比如:
    const int MAX_LEN = 1024;
    
    struct DEBUG_DETAIL_INFO{
      int id;
      int extra_info;
    };
    
    struct DEBUG_ARR{
      int num;
      DEBUG_DETAIL_INFO info_arr[MAX_LEN];
    };
    
    int main()
    {
      DEBUG_ARR arr;
      init_arr(100, arr);
      
      // todo
      return 0;
    }
    

gdb調(diào)用函數(shù)有兩種方式:

  1. gdb啟動(dòng)時(shí),會(huì)在當(dāng)前目錄下查找 .gdbinit 文件
  2. gdb在運(yùn)行時(shí),在命令行輸入 source xx.gdb文件 來(lái)加載

gdb啟動(dòng)的時(shí)候,會(huì)在當(dāng)前目錄或home目錄自動(dòng)加載gdbinit腳本。在腳本中定義gdb函數(shù)。(或者gdb attach上去。)

define print_if_my_arr
  printf "argc %d, arg %d \n", $argc, $arg0

  set $num = arr.num
  printf "print_func num %d \n", $num

  set $idx = 0
  while $idx < $num
      if arr.info_arr[$idx].id >= $arg0
          printf "idx %d id %d \n", $idx, arr.info_arr[$idx].id
      end
      set $idx = $idx + 1
  end
end

document print_if_my_arr
  example : print_if_my_arr 10
  desc    : print DEBUG_ARR ele if val >= 10
end

gdb實(shí)測(cè)一把

gdbinit文件同理也可以插入其他命令,比如各種讓輸出比較直觀的命令等:

set print pretty on
set print object on

gdb定義的常用函數(shù)可以放到一個(gè)文件里面,需要的時(shí)候通過(guò)source命令加載。

define mybt
    set logging file output_bt.log
    set logging on
    bt
    set logging off
end

使用的時(shí)候

gdb attach xxxx
source xxx/path/xxx.gdb
mybt

  • gdb 條件斷點(diǎn)
    # 文件名 + 行號(hào)的條件斷點(diǎn)
    b xxx.cpp:20 if value == 100   #xx.cpp 行號(hào)20 如果value == 100 觸發(fā)斷點(diǎn)
    
    # 包含一些簡(jiǎn)單計(jì)算的斷點(diǎn)
    (gdb) p 100
    $2 = 100
    (gdb) b test1.cpp:8 if i + $2 == 130
    

  • ignore 命令
    斷點(diǎn)觸發(fā)被忽略xx次
    ignore <break_list> count

  • command命令
    command <break_list>

    觸發(fā)斷點(diǎn)后自動(dòng)執(zhí)行command里面定義的命令

command的命令另一個(gè)妙用

  • gdb return
    進(jìn)入某個(gè)函數(shù)執(zhí)行,不想執(zhí)行里面的邏輯,直接gdb return 出去。里面的邏輯就不會(huì)執(zhí)行了。

  • gdb gcore
    gcore命令可以從當(dāng)前環(huán)境導(dǎo)出成為一個(gè)core文件,供日后分析。



  • handle SIGPIPE nostop
    為了gdb不被信號(hào)斷開(kāi),可以屏蔽掉各種pipe信號(hào)。

  • gdb -ex 參數(shù)
    生產(chǎn)環(huán)境操作的時(shí)候可以使用

    gdb -ex "bt" -batch -p pid
    

    跳出死循環(huán)

  • display 命令

    對(duì)于debug需要關(guān)注的變量名,可以使用display打印出來(lái)。每次gdb操作的時(shí)候,都會(huì)顯示出來(lái)。示意圖如下:

  • info命令
    info sharedlibrary  #顯示共享庫(kù)
    (gdb) info sharedlibrary 
    From                To                  Syms Read   Shared Object Library
    0x00007ffff7dd5050  0x00007ffff7df4854  Yes (*)     /lib64/ld-linux-x86-64.so.2
    0x00007ffff7aceb60  0x00007ffff7b84bb2  Yes (*)     /lib64/libstdc++.so.6
    0x00007ffff76c9510  0x00007ffff77687ca  Yes (*)     /lib64/libm.so.6
    0x00007ffff74a7dc0  0x00007ffff74b8a25  Yes (*)     /lib64/libgcc_s.so.1
    0x00007ffff7104900  0x00007ffff724ef0f  Yes (*)     /lib64/libc.so.6
    

  • gdb調(diào)用函數(shù)
    void func() {  xxx; }
    
    b func
    call func()  // gdb 直接調(diào)用這個(gè)函數(shù)
    
    gdb調(diào)用class的成員函數(shù)
    // todo
    

  • strip命令 (這個(gè)和gdb關(guān)系不大)
    strip命令可以極大的降低可執(zhí)行文件的體積,其實(shí)就是去掉符號(hào)表和調(diào)試信息等。當(dāng)gdb的時(shí)候,拷貝strip下來(lái)的.debug文件到需要gdb的機(jī)器上,然后進(jìn)行g(shù)db attach。這里gdb上來(lái)以后,會(huì)自動(dòng)找到對(duì)應(yīng)的.debug文件。
     cd xxxx_app_bin_path
     objcopy --only-keep-debug xxxx_app ./.debug/xxxx.debug
     strip --strip-debug --strip-unneeded xxxx_app
     objcopy --add-gnu-debuglink=./.debug/xxxx_app.debug xxxx_app
    

GDB的基本原理

gdb有兩種跟蹤進(jìn)程的方法,第一種是gdb + a.out的方式啟動(dòng)進(jìn)程,第二種的方式是gdb去attach一個(gè)已經(jīng)啟動(dòng)了的進(jìn)程。這種方式都是利用ptrace系統(tǒng)調(diào)用去跟蹤被調(diào)試的進(jìn)程。

ptrace共有四個(gè)參數(shù):
long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);

// 兩種gdb的區(qū)別在于參數(shù)不同
PTRACE_TRACEME 和 PTRACE_ATTACH

ptrace系統(tǒng)函數(shù)是Linux內(nèi)核提供的一個(gè)用于進(jìn)程跟蹤的系統(tǒng)調(diào)用,通過(guò)它,一個(gè)進(jìn)程(gdb)可以讀寫另外一個(gè)進(jìn)程(test)的指令空間、數(shù)據(jù)空間、堆棧和寄存器的值。而且gdb進(jìn)程接管了test進(jìn)程的所有信號(hào),也就是說(shuō)系統(tǒng)向test進(jìn)程發(fā)送的所有信號(hào),都被gdb進(jìn)程接收到,這樣一來(lái),test進(jìn)程的執(zhí)行就被gdb控制了,從而達(dá)到調(diào)試的目的。

也就是說(shuō),如果沒(méi)有g(shù)db調(diào)試,操作系統(tǒng)與目標(biāo)進(jìn)程之間是直接交互的;如果使用gdb來(lái)調(diào)試程序,那么操作系統(tǒng)發(fā)送給目標(biāo)進(jìn)程的信號(hào)就會(huì)被gdb截獲,gdb根據(jù)信號(hào)的屬性來(lái)決定:在繼續(xù)運(yùn)行目標(biāo)程序時(shí)是否把當(dāng)前截獲的信號(hào)轉(zhuǎn)交給目標(biāo)程序,如此一來(lái),目標(biāo)程序就在gdb發(fā)來(lái)的信號(hào)指揮下進(jìn)行相應(yīng)的動(dòng)作。

第一種方式是通過(guò)gdb去fork一個(gè)子進(jìn)程,子進(jìn)程去執(zhí)行被調(diào)試的進(jìn)程。然后建立父子關(guān)系。如下圖所示:

第二種方式是gdb去attach被調(diào)試的子進(jìn)程。如果想對(duì)一個(gè)已經(jīng)執(zhí)行的進(jìn)程B進(jìn)行調(diào)試,那么就要在gdb這個(gè)父進(jìn)程中調(diào)用ptrace(PTRACE_ATTACH,[其他參數(shù)]),此時(shí),gdb進(jìn)程會(huì)attach(綁定)到已經(jīng)執(zhí)行的進(jìn)程B,gdb把進(jìn)程B收養(yǎng)成為自己的子進(jìn)程,而子進(jìn)程B的行為等同于它進(jìn)行了一次 PTRACE_TRACEME操作。此時(shí)gdb進(jìn)程會(huì)發(fā)送SIGSTO信號(hào)給子進(jìn)程B,子進(jìn)程B接收到SIGSTOP信號(hào)后,就會(huì)暫停執(zhí)行進(jìn)入TASK_STOPED狀態(tài),表示自己準(zhǔn)備好被調(diào)試了。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容