GDB 筆記

一. 加入調試信息

    gcc -g *.c

    gcc -rdynamic: http://www.tuicool.com/articles/EvIzUn


二. 啓動 gdb

1. 啓動新進程

    gdb a.out

    run 參數1 參數2

2. 調試已有進程

    gdb -p 進程號

3. 調試 core

    gdb a.out --core=corefile


三. 斷點

    break 行號/函數名/文件名:行號

    break +offset

    break -offset      在當前行號的前面或後面的 offset 行停住

    info b                 查看已經設置的斷點

    continue            運行到下一個斷點

    delete [breakpoints] [range]       刪除指定的斷點,如果不指定 breakpoints,則刪除所有

    disable [breakpoints] [range]      停止指定的斷點,並不刪除

    clear                  清除所有已定義的斷點

    clear func          清除所有設置在函數上的斷點


四. 斷點保存

    編輯文件例如 break 並輸入中斷點:

    break main
    保存後, gdb 可執行文件 -x break 即可自動設置斷點


五. 條件斷點

    b 行號/文件名:行號 if 表達式        if 後無需括號

    condition 斷點號 expression         修改表達式

    condition 斷點號                           刪除表達式


六. 跟蹤

    la(layout)         顯示源碼

    print 變量

    p/x 變量            以十六進制查看

    print var=1       除了顯示變量的值之外,還可以用於爲變量賦值

    set variable var=1    另外一種爲變量賦值的方法

    next                  執行下一行語句,如果是函數,不進入

    step                  執行下一行語句,如果是函數,進入

    continue           從下一個斷點處繼續運行

    finish                 執行到當前函數結束,返回到調用它的函數中

    until                   無參:執行完循環的剩餘部分,直到到達當前循環體外的下一行代碼

    until [filename:]linenumber

    until [filename:]function        以上 2 種用法,在到達指定的行號或者函數時停止

    set print element xxx    設置顯示的字符長度,默認爲 512,在字符串過長時無法顯示全部,設置爲 0 表示全部顯示

    x/FMT address  Examine memory。

FMT->n/f/u
n: repeat count
f: o(octal), x(hex), d(decimal), u(unsigned decimal),t(binary), f(float), a(address), i(instruction), c(char) and s(string)
u: b(byte), h(halfword), w(word), g(giant, 8 bytes)


七. 查看信息

    info threads

    info breakpoints

    info locals            顯示局部變量

    info args              顯示函數參數

    info registers       顯示寄存器數據

    info variables       顯示全局和靜態變量

    info functions       顯示所有的函數名稱

    info program        顯示被調試程序的執行狀態

    info files               顯示被調試文件的詳細信息

    info stack             顯示棧信息

    info frame            顯示更詳細的棧信息

    whatis                  顯示變量的類型

    ptype                   顯示結構體的定義


八. 調試線程

    info threads    顯示當前可調試的所有線程

    break thread_test.c:123 thread all    在所有線程中相應的行上設置斷點

    thread apply ID1 ID2 command         讓一個或者多個線程執行GDB命令command。

    thread apply all command                 讓所有被調試線程執行GDB命令command。

    set scheduler-locking off|on|step       實際使用過多線程調試的人都可以發現,在使用 step 或者 continue 命令調試當前被調試線程的時候,其他線程也是同時執行的,怎麼只讓被調試程序執行呢?通過這個命令就可以實現這個需求。off 不鎖定任何線程,也就是所有線程都執行,這是默認值。 on 只有當前被調試程序會執行。 step 在單步的時候,除了 next 過一個函數的情況(熟悉情況的人可能知道,這其實是一個設置斷點然後 continue 的行爲)以外,只有當前線程會執行。


    non-stop mode:For some multi-threaded targets, gdb supports an optional mode of operation in which you can examine stopped program threads in the debugger while other threads continue to execute freely. This minimizes intrusion when debugging live systems, such as programs where some threads have real-time constraints or must continue to respond to external events. This is referred to as non-stop mode.

啓動命令參考:gdb -iex "set target-async on" -iex "set pagination off" -iex "set non-stop on" ...

參考:http://sourceware.org/gdb/download/onlinedocs/gdb/Non_002dStop-Mode.html#Non_002dStop-Mode


九. 主動生成 core

    程序運行時,2 種方法生成 core 文件:

1. gcore [-o filename] pid  

    該命令不影響程序的運行。

2. sudo kill -s SIGABRT pid

    發送 SIGABRT 信號給當前進程,該方法會終止當前進程的運行。


十. GDB 進階

    以下內容摘自某大牛的分享。

    我們在用 GBD 的 bt 命令來查看某個線程的棧回溯的時候,每個 frame 的前面都會有一個數字如下圖所示:

wKiom1UP4uaQHZo6AAGjD0WngpU633.jpg

    這個數字其實是個指令地址值,它是當在某個函數 f() 的內部要調用某個函數 g() 的時候,那個 g() 函數調用返回後要執行的那條指令的地址。好像有點繞,舉個簡單列子說明一下:

wKioL1UP5K7gn3QVAAEvtUqHBtg819.jpg

    顯然運行此函數會 core,我們用 gdb 分析這個 core 的時候,顯示如下:

wKioL1UP5NaCJl9tAAKsUSJ3-ik540.jpg

    所以在 bt 結果顯示的 f() 函數對應的 frame 的那個地址值就是 g() 函數返回以後開始執行的那一條指令。那麼這個地址是在什麼時候放到棧裏面的呢? 就是在執行 “callq 0x400698 <g(int, int)>” 這條指令時候,它會首先將下面的那條 “mov   %eax,-0x4(%rbp)” 指令的地址 “0x0000000000400747” 壓入到當前棧頂,然後跳轉去執行 g() 函數的機器指令。所以,執行完 callq 那條指令後棧的佈局爲:

wKiom1UP4-TDlxWOAAGSKowTCec867.jpg

    理解了在調用一個函數的時候會將這個函數調用的後面一條指令壓入到棧中,以及這個返回地址的存儲位置在棧中相對於被函數調用對應的 frame 的位置關係是成功手動分析棧的第一步,也是最重要的一步,這個我們在後面會看到。顯然此步不難理解。下面看看第二個重要的知識點:理解函數調用的參數傳遞方式和看彙編理解函數內部對於參數的處理。

    我們知道 AMD64 架構下 CPU 的通用寄存器翻了一倍,從原來的 8 個變成了16 個。除了原來的 8 個寄存器從原來的 exx 擴充成 64 位的 rxx,新增加的 8 個爲:r8~r15。寄存器多了以後,函數調用參數傳遞的方式就不像以前 x86 下面通過棧來保存了(當然原來也有 fastcall 的調用方式約定),爲了更快, GCC x64 下函數調用參數傳遞的約定爲:

    前6個參數依次存放在寄存器:%rdi, %rsi, %rdx, %rcx, %r8, %r9中.

    多餘的參數才依次保存在棧上。

    這個知識點很重要,因爲通過它再結合被調用函數彙編代碼我們就能知道每個參數被保存在何處了(在較少數的情況下,函數的參數在被調用函數中是沒有被保存就被直接使用了,大家可以思考這是在什麼情形下?),從而我們就有可能在棧上搜索找到它們了。

    對於寄存器的使用,還有幾個重要的約定:

    Caller’s registers: %rbx, %rbp, %r12, %r13, %r14, %r15

    Callee’s registers: %rdi, %rsi, %rdx, %rcx, %r8, %r9

    Others:

        %rsp: stack pointer

        %rax: return value

        %rip: next instruction to execute

    怎麼理解寄存器是 Caller 的還是 Callee 的呢?其實這是一種很嚴格對於寄存器使用的約定。是 Caller 的寄存器就意味着被調用函數(Callee)不能隨使用這些寄存器,如果一定要使用,可以,但是從被調用函數返回的時候這些寄存器中的內容必須是和調用前一模一樣的。在實現上如果被調用函數要使用這些寄存器則必須在函數執行開始的地方將這些寄存器中的內容先保存到棧中,等到函數返回的時候再把一開始保留的值再存回去,這就是 “Caller’s register” 的意思:Callee 你可以用,但要幫我恢復成原來的值讓我感知不道它們的變化。

    是 Callee 的寄存器的意思是被調用函數可以隨意使用他們,不必考慮改變他們對於調用者的影響。

    理解以上兩點也是非常重要的,有時候通過它們可以找到我們要找的某個變量的值。舉個例子,假如一個參數被保存到了 %rbx 中,但是它沒有被存在本 frame 對應的棧空間中,此時我們用 bt 回溯會看到這個參數被 optimized out 了。但是是不是我們就一定沒有機會找到這個變量的值了呢?其實不然,假如被調用函數中要使用 %rbx,那麼它就會把 %rbx 的值先保持在棧中,然後通過後面講的棧搜索,我們就可以在被調用函數棧的 frame 中定位到這個變量了。

    下面就是手動搜索棧了。我們用的 GDB 的命令爲:

    (gdb) x/1000ag $rsp

    怎麼理解這個命令呢? 這個命令的意思是:從寄存器 %rsp 中的地址開始,以每 8 個字節對應的數值當做一個地址值來嘗試尋找器對應符號,然後執行 1000 次。其實這一步就是搜索我們 bt 命令顯示的每個 frame 前面的對應的函數返回地址,然後定位到我們想要分析的 frame。再然後,我們就可以利用我們第二步所講的函數參數使用分析方法來定位我們想要找的參數和變量了。

    沒有什麼比一個有代表性的例子更能說明問題了。下面要介紹的一個例子是我們線上引擎出現的一個 coredump 分析過程。這個 core 是掛在我們二方庫的一個算法模塊中,同時此模塊沒有詳細的調試符號。如下圖所示:





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章