一. 加入調試信息
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 的前面都會有一個數字如下圖所示:
這個數字其實是個指令地址值,它是當在某個函數 f() 的內部要調用某個函數 g() 的時候,那個 g() 函數調用返回後要執行的那條指令的地址。好像有點繞,舉個簡單列子說明一下:
顯然運行此函數會 core,我們用 gdb 分析這個 core 的時候,顯示如下:
所以在 bt 結果顯示的 f() 函數對應的 frame 的那個地址值就是 g() 函數返回以後開始執行的那一條指令。那麼這個地址是在什麼時候放到棧裏面的呢? 就是在執行 “callq 0x400698 <g(int, int)>” 這條指令時候,它會首先將下面的那條 “mov %eax,-0x4(%rbp)” 指令的地址 “0x0000000000400747” 壓入到當前棧頂,然後跳轉去執行 g() 函數的機器指令。所以,執行完 callq 那條指令後棧的佈局爲:
理解了在調用一個函數的時候會將這個函數調用的後面一條指令壓入到棧中,以及這個返回地址的存儲位置在棧中相對於被函數調用對應的 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 是掛在我們二方庫的一個算法模塊中,同時此模塊沒有詳細的調試符號。如下圖所示: