GDB 調試指南

原文鏈接:https://mp.weixin.qq.com/s/9w96AeY4T3vmViHZar9zog

大家好, 好久沒給大家帶來原創乾貨了,導致很多新來的小夥伴以爲我這個號就是個機構號,其實不是,這個是個人號,背後的小編我是一個有血有肉有情懷的人,不管怎麼樣,我的目的是儘量帶給大家優質的乾貨。

 

今天這篇總結了GDB的一個調試技巧,標題取爲「調試指南」一點都不誇張,基本濃縮了GDB調試所有會用到的功能,所有技術人都適用。如果覺得文章有幫助,煩請大家給我個在看分享,鼓勵我輸出更多幹貨給大家。

 

 

00 介紹

GDB(GNU Debugger)是 UNIX 及 UNIX-like 下的強大調試工具,可以調試 ada, c, c++, asm, minimal, d, fortran, objective-c, go, java,pascal 等語言,這一份指南我們主要以 c 來作爲例子。

01 基礎

1.1 判斷程序是否可調試

# gdb helloworld
Reading symbols from helloWorld...(no debugging symbols found)...done.

# gdb helloworld
Reading symbols from helloWorld...done.

上面一種有 no debugging symbols found 表示不可調試,下面是可調式的。

或者 readelf 查看段信息:

1# readelf -S helloworld|grep debug
2  [28] .debug_aranges    PROGBITS         0000000000000000  0000106d
3  [29] .debug_info       PROGBITS         0000000000000000  0000109d
4  [30] .debug_abbrev     PROGBITS         0000000000000000  0000115b
5  [31] .debug_line       PROGBITS         0000000000000000  000011b9
6  [32] .debug_str        PROGBITS         0000000000000000  000011fc

如果沒有輸出任何 debug 信息,也不能調試。

1.2 開啓 gdb 編譯

加上 -g 選項:

gcc -g -o xxx xxx.c

1.3 gdb xxx 進入調試

  • b 行號或函數 添加斷點

  • r 跑到下一個斷點

  • s 單步跟蹤

  • n 單步執行

  • p 查看當前程序的運行數據 比如:p a 輸出a變量的值 輸出格式可以設置: 比如p/d a 十進制輸出a變量的值

  • p array@idx 可以查看數組 array 中 idx 處的值

  • 設置display,比如 display a 這樣以後每次調試都會輸出a變量的值

  • x 查看內存地址中的值 語法:x/

  • l 查看原程序代碼,l 9 列出第9行附件的源碼(l 2,8 列出2-8行之間的數據),l func 列出指定函數附件的源碼

  • p x=8 在調試過程中修改變量x的值,下面生效

  • jump 實現跳轉,可以是文件的行號,也可以是file:line,也可以是+num這種格式 jump address是代碼行的內存地址

  • signal 產生信號量

  • return 強制返回

  • call 強制調用

  • until(簡寫u) 當在一個循環體內時,運行退出循環

  • until +行號 運行至某行停住,不僅僅跳出循環

  • finish 當前函數執行完,並打印函數返回時的堆棧地址和返回值及參數值等信息

  • skip 在 step 時跳過一些不想關注的函數或者某個文件的代碼,如 skip function add 表示跳過函數 add,skip file step.c 跳過文件 step.c,info skip 查看跳過的信息。

  • c 繼續執行 跳到下一個斷點

  • bt 查看堆棧

  • where 報錯時查看哪裏出錯,與 bt 類似

  • info b 查看斷點情況

  • q 退出

  • ptype 輸出結構體類型

  • info registers 顯示寄存器值, info all-registers 顯示所有寄存器

  • info breakpoints 可以查看所有已設置的端點

1.5 命令進階

1.5.1 設斷點

  1. info breakpoints 查看所有斷點

  2. b 9 或者 b test.c:9 根據行號設置斷點

  3. b func 根據函數名設置斷點

  4. b test.c:9 if b==0 根據程序某個條件會出現問題,設置該條件斷點(這樣當出現問題時,會卡主,用來判斷是否是該問題)

  5. rbreak print* 對所有 print 開頭的函數都設斷點,rbreak test.c:. 對test.c 中所有函數設斷點

  6. tbreak test.c:9 設 臨時斷點 ,即這個斷點只生效一次

  7. ignore 1 30 忽略某個斷點的前面 30 次執行,從第 31 次開始生效,節約時間

  8. watch a 觀察某個值或表達式,什麼時候發生變化

  9. disable/enable num 禁用/啓用所有/某個斷點

  10. clear 清除所有斷點,用於清除某個函數,某行的斷點,如 clear func 、clear linenum

  11. delete 刪除所有斷點,包括watchpoints, catchpoints,用於刪除斷點號的斷點,如 delete bnum

1.5.2 查看變量

  1. p 'test.c'::a 打印某個文件的變量,p 'main'::b 打印某個函數定義的變量

  2. p *p@10 打印指針指向的內容,@後面爲打印的長度

  3. p *$.next 打印鏈表linkNode的下一個節點內容

  4. p/x c 按十六進制打印內容(x:十六進制,d:十進制,o:八進制,t:二進制,c:字符格式,f:浮點格式)

  5. x addr 查看內存地址值

  6. display e 程序斷住顯示某個變量的值

1.5.3 編輯源碼

啓動調試後,不想退出程序而編輯源碼,如何做呢?

gdb 模式下用的默認編輯器是 /bin/ex ,如果沒有或者想換成其他編輯器,如VIM,可以這樣:

export EDITOR=/usr/bin/vim

gdb 模式下編輯源碼:

(gdb)edit 3  # 編輯第三行
(gdb)edit func # 編輯func函數
(gdb)edit test.c:5 #編輯test.c第五行

完了之後,重新編譯程序( 注意一定要帶上 shell 命令,表明是shell命令 ):

(gdb)shell gcc -g -o main main.c test.c

或者這樣:

啓動是帶上 tui(Text User Interface),可以在多個窗口調試:

gdb main -tui

1.6 帶參數調試

1. 啓動的時候帶上參數

gdb --args xxx 參數

2. 啓動之後 run 帶上參數

# gdb xxx
(gdb)run 參數

3. 啓動之後 set args 設置參數

# gdb xxx
(gdb) set args 參數

02 調試多進程

2.1 attach 方法

  1. 首先找到需調試的子進程:ps -ef | grep xxx 或 pidof 進程名

  2. 進入 gdb 模式,輸入 attach pid

  3. 打斷點,運行進入調試

或者直接這樣:gdb <program> pid(或 gdb <program> --pid pid),gdb 會 自動 attach。

如果出現如下錯誤:

Could not attach to process.  If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user.  For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.

切換到 root 用戶,將 /etc/sysctl.d/10-ptrace.conf 中的

kernel.yama.ptrace_scope = 1

改爲:

kernel.yama.ptrace_scope = 0

2.2 follow-fork-mode mode 方法

  1. 進入 gdb 模式,輸入 set follow-fork-mode mode (mode 可選 parent、child,表示調試父進程還是子進程)

  2. 打斷點

2.3 調試已運行程序

已運行程序通常沒有調試信息,但如果不能停止當前程序重新啓動調試,可以:

同樣的代碼,再編譯出一個帶調試信息的版本,然後:

# gdb
(gdb) file hello
Reading symbols from hello...done.
(gdb)attach 20829

03 調試多線程

gdb 有一組命令能夠輔助多線程的調試:

  • info threads:顯示當前可調式的所有線程,線程 ID 前有 “*” 表示當前被調試的線程。

  • thread id:調試目標 id 指定的線程

  • set scheduler-locking [on|off|step]:多線程環境下,會存在多個線程運行,這會影響調試某個線程的結果,這個命令可以設置調試的時候多個線程的運行情況,on 表示只有當前調試的線程會繼續執行,off 表示不屏蔽任何線程,所有線程都可以執行,step 表示在單步執行時,只有當前線程會執行。

04 coredump 調試

coredump 調試依賴於 core 文件,core 文件是程序非法執行後 core dump 後產生的文件。這是 Linux 系統的一種保護機制,當出現某些連開發和測試費了九牛二虎之力都沒能發現的問題時,Linux 系統還提供了最後一道屏障,通過 core 文件就可以讓這些問題原形畢露。

4.1 開啓 core dump

要想讓程序崩潰時產生 core 文件,需要開啓,輸入 ulimit -c,如果輸出爲 0,表示默認關閉 core dump。

有兩種方式可以開啓,一種就是通過 ulimit 命令,一種是在程序中寫代碼開啓,這裏只講第一種,第二種參考文末的引用1。

ulimit -c unlimied  # 表示不限制core文件大小
ulimit -c 10        # 設置最大大小,單位爲塊,一塊默認爲512字節

上面是臨時開啓,永久開啓要修改 /etc/security/limits.conf 文件,增加一行:

# /etc/security/limits.conf
# <domain>        <type>  <item>  <value>
    *               soft    core    unlimited

這樣就可以生成 core 文件,文件名就是 core,並且默認在當前程序所在目錄下生成,如果要指定目錄,則可以 echo "/tmp/corefile-%e-%p-%t" > /proc/sys/kernel/core_pattern 設置 core 文件保存在目錄 "/tmp/corefile" 下,文件名格式爲 “core-命令名-pid-時間戳”

還可以通過 echo 1 > /proc/sys/kernel/core_uses_pid 使得生成的 core 文件變成 core.pid,pid 是該進程的 pid。

4.2 調試 core dump

使用

gdb <program> core文件名

或者 gdb 啓動後,使用

  • -core <file>

  • -c <file>

來調試 core 文件

下面是一個例子:

#include <stdio.h>
int func(int *p)
{
    int y = *p;
    return y;
}
int main()
{
    int *p = NULL;
    return func(p);
}

編譯:gdb -g -o core_dump core_dump.c,用 gdb 查看 core 文件

root@root:~$ gcc core_demo.c -o core_demo -g
root@root:~$ ./core_demo
Segmentation fault (core dumped)

root@root:~$ gdb core_demo core_demo.core.24816
...
Core was generated by './core_demo'.
Program terminated with signal 11, Segmentation fault.
#0  0x080483cd in func (p=0x0) at core_demo.c:5
5       int y = *p;
(gdb)  where
#0  0x080483cd in func (p=0x0) at core_demo.c:5
#1  0x080483ef in main () at core_demo.c:12
(gdb) info frame
Stack level 0, frame at 0xffd590a4:
 eip = 0x80483cd in func (core_demo.c:5); saved eip 0x80483ef
 called by frame at 0xffd590c0
 source language c.
 Arglist at 0xffd5909c, args: p=0x0
 Locals at 0xffd5909c, Previous frame's sp is 0xffd590a4
 Saved registers:
  ebp at 0xffd5909c, eip at 0xffd590a0
(gdb)

可以看到,我們可以還原 core_demo 執行時的場景,並使用 where 查看當前程序調用函數棧幀,還可以使用 gdb 中的命令查看寄存器,變量等信息。

常見問題

問題 1

開啓 GDB 調試時出現:

Missing separate debuginfos, use: debuginfo-install libgcc-4.8.5-28.el7_5.1.x86_64 numactl-libs-2.0.9-7.el7.x86_64

解決:

  1. 修改文件/etc/yum.repos.d/CentOS-Debuginfo.repo中的enabled參數,將其值修改爲 1

  2. yum install nss-softokn-debuginfo --nogpgcheck

  3. debuginfo-install glibc 如果出現下面的問題: -bash: debuginfo-install: command not found,則先安裝yum-utils,使用命令: yum install yum-utils

  4. 分別安裝問題提示的兩個庫:use: debuginfo-install libgcc-4.8.5-28.el7_5.1.x86_64 numactl-libs-2.0.9-7.el7.x86_64

參考

1 https://www.cnblogs.com/Anker/p/6079580.html

2 http://www.cnblogs.com/hazir/p/linxu_core_dump.htm

3 https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

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