1. 概述
GDB 全稱“GNU symbolic debugger”,從名稱上不難看出,它誕生於 GNU 計划(同時誕生的還有 GCC、Emacs 等),是 Linux 下常用的程序調試器。發展至今,GDB 已經迭代了諸多個版本,當下的 GDB 支持調試多種編程語言編寫的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。實際場景中,GDB 更常用來調試 C 和 C++ 程序。一般來說,GDB主要幫助我們完成以下四個方面的功能:
- 啟動你的程序,可以按照你的自定義的要求隨心所欲的運行程序。
- 在某個指定的地方或條件下暫停程序。
- 當程序被停住時,可以檢查此時你的程序中所發生的事。
- 在程序執行過程中修改程序中的變量或條件,將一個bug產生的影響修正從而測試其他bug。
使用GDB調試程序,有以下兩點需要注意:
- 要使用GDB調試某個程序,該程序編譯時必須加上編譯選項
-g
,否則該程序是不包含調試信息的; - GCC編譯器支持
-O
和-g
一起參與編譯。GCC編譯過程對進行優化的程度可分為5個等級,分別為 :
- -O/-O0: 不做任何優化,這是默認的編譯選項 ;
- -O1:使用能減少目標文件大小以及執行時間並且不會使編譯時間明顯增加的優化。 該模式在編譯大型程序的時候會花費更多的時間和內存。在 -O1下:編譯會嘗試減少代 碼體積和代碼運行時間,但是並不執行會花費大量時間的優化操作。
- -O2:包含 -O1的優化並增加了不需要在目標文件大小和執行速度上進行折衷的優化。 GCC執行幾乎所有支持的操作但不包括空間和速度之間權衡的優化,編譯器不執行循環 展開以及函數內聯。這是推薦的優化等級,除非你有特殊的需求。 -O2會比 -O1啟用多 一些標記。與 -O1比較該優化 -O2將會花費更多的編譯時間當然也會生成性能更好的代 碼。
- -O3:打開所有 -O2的優化選項並且增加 -finline-functions, -funswitch-loops,-fpredictive-commoning, -fgcse-after-reload and -ftree-vectorize優化選項。這是最高最危險 的優化等級。用這個選項會延長編譯代碼的時間,並且在使用 gcc4.x的系統里不應全局 啟用。自從 3.x版本以來 gcc的行為已經有了極大地改變。在 3.x,,-O3生成的代碼也只 是比 -O2快一點點而已,而 gcc4.x中還未必更快。用 -O3來編譯所有的 軟件包將產生更 大體積更耗內存的二進制文件,大大增加編譯失敗的機會或不可預知的程序行為(包括 錯誤)。這樣做將得不償失,記住過猶不及。在 gcc 4.x.中使用 -O3是不推薦的。
- -Os:專門優化目標文件大小 ,執行所有的不增加目標文件大小的 -O2優化選項。同時 -Os還會執行更加優化程序空間的選項。這對於磁盤空間極其緊張或者 CPU緩存較小的 機器非常有用。但也可能產生些許問題,因此軟件樹中的大部分 ebuild都過濾掉這個等 級的優化。使用 -Os是不推薦的。
2. 啟用GDB調試
GDB調試主要有三種方式:
- 直接調試目標程序:gdb ./hello_server
- 附加進程id:gdb attach pid
- 調試core文件:gdb filename corename
3. 退出GDB
- 可以用命令:q(quit的縮寫)或者 Ctr + d 退出GDB。
- 如果GDB attach某個進程,退出GDB之前要用命令 detach 解除附加進程。
4. 常用命令
命令名稱 | 命令縮寫 | 命令說明 |
---|---|---|
run | r | 運行一個待調試的程序 |
continue | c | 讓暫停的程序繼續運行 |
next | n | 運行到下一行 |
step | s | 單步執行,遇到函數會進入 |
until | u | 運行到指定行停下來 |
finish | fi | 結束當前調用函數,回到上一層調用函數處 |
return | return | 結束當前調用函數並返回指定值,到上一層函數調用處 |
jump | j | 將當前程序執行流跳轉到指定行或地址 |
p | 打印變量或寄存器值 | |
backtrace | bt | 查看當前線程的調用堆棧 |
frame | f | 切換到當前調用線程的指定堆棧 |
thread | thread | 切換到指定線程 |
break | b | 添加斷點 |
tbreak | tb | 添加臨時斷點 |
delete | d | 刪除斷點 |
enable | enable | 啟用某個斷點 |
disable | disable | 禁用某個斷點 |
watch | watch | 監視某一個變量或內存地址的值是否發生變化 |
list | l | 顯示源碼 |
info | i | 查看斷點 / 線程等信息 |
ptype | ptype | 查看變量類型 |
disassemble | dis | 查看匯編代碼 |
set args | set args | 設置程序啟動命令行參數 |
show args | show args | 查看設置的命令行參數 |
5. 常用命令示例
5.1 run命令
默認情況下,以 gdb ./filename
方式啟用GDB調試只是附加了一個調試文件,並沒有啟動這個程序,需要輸入run命令(簡寫為r)啟動這個程序:
[root@localhost src]# gdb ./redis-server
GNU gdb (GDB) 8.0
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
...此處省略很多行...
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./redis-server...done.
(gdb) r
Starting program: /data/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
...此處省略很多行...
34628:M 09 Nov 2020 00:10:16.318 * DB loaded from disk: 0.000 seconds
以上是以redis-server程序為例,輸入 r 后啟動了redis服務器,在GDB界面按 Ctrl + C 讓GDB中斷下來,再次輸入 r 命令,GDB會詢問是否重啟程序,輸入 y(或yes):
34628:M 09 Nov 2020 00:10:16.318 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
退出GDB,會提示用戶要關閉當前調試進程,是否繼續退出:
34636:M 09 Nov 2020 00:10:31.427 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) q
A debugging session is active.
Inferior 1 [process 34636] will be killed.
Quit anyway? (y or n) y
[root@localhost src]#
5.2 continue命令
當GDB觸發斷點或者使用 Ctrl + C 命令中斷下來后,想讓程序繼續運行,只要輸入 continue(簡寫為c)命令即可。
34839:M 09 Nov 2020 00:37:56.364 * DB loaded from disk: 0.000 seconds
34839:M 09 Nov 2020 00:37:56.364 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) c
Continuing.
34839:M 09 Nov 2020 00:46:16.004 * 100 changes in 300 seconds. Saving...
34839:M 09 Nov 2020 00:46:16.046 * Background saving started by pid 34887
5.3 break命令
break命令(簡寫為b)用於添加斷點,可以使用以下幾種方式添加斷點:
- break FunctionName,在函數的入口處添加一個斷點;
- break LineNo,在當前文件行號為LineNo處添加斷點;
- break FileName:LineNo,在FileName文件行號為LineNo處添加一個斷點;
- break FileName:FunctionName,在FileName文件的FunctionName函數的入口處添加斷點;
- break -/+offset,在當前程序暫停位置的前/后 offset 行處下斷點;
- break ... if cond,下條件斷點;
分別舉例介紹以上幾種下斷點方法:
- 在
main
函數入口處下斷點:
35274:M 09 Nov 2020 01:46:16.910 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) b main
Breakpoint 1 at 0x430890: file server.c, line 4003.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffe648) at server.c:4003
4003 int main(int argc, char **argv) {
(gdb)
2. 在當前文件的4041行下斷點:
(gdb) b 4041
Breakpoint 2 at 0x4308bc: file server.c, line 4041.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000430890 in main at server.c:4003
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004308bc in main at server.c:4041
(gdb) c
Continuing.
Breakpoint 2, main (argc=1, argv=0x7fffffffe648) at server.c:4041
4041 zmalloc_set_oom_handler(redisOutOfMemoryHandler);
(gdb)
3. redis-server的默認端口是6379,綁定端口是需要調用bind
函數,搜到該函數調用位置是在 anet.c 文件的441行:
在這里添加一個斷點:
3208:M 09 Nov 2020 18:33:14.205 * DB loaded from disk: 0.000 seconds
3208:M 09 Nov 2020 18:33:14.205 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) b anet.c:441
Breakpoint 1 at 0x429b72: file anet.c, line 441.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
3232:C 09 Nov 2020 18:38:18.129 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3232:C 09 Nov 2020 18:38:18.129 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=3232, just started
3232:C 09 Nov 2020 18:38:18.129 # Warning: no config file specified, using the default config. In order to specify a config file use /data/redis-5.0.3/src/redis-server /path/to/redis.conf
3232:M 09 Nov 2020 18:38:18.129 * Increased maximum number of open files to 10032 (it was originally set to 4096).
Breakpoint 1, anetListen (err=0x7b4320 <server+576> "", s=6, sa=0x9c5ee0, len=28, backlog=511) at anet.c:441
441 if (bind(s,sa,len) == -1) {
(gdb)
4. 也可以選擇在某個文件的某個函數的入口處添加斷點,以hset
命令的回調函數為例:
struct redisCommand redisCommandTable[] = { //此處省略之前的命令注冊代碼 {"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0}, //此處省略之后的命令注冊代碼 };
查到是 t_hash.c 文件的hsetCommand
函數,在此處下斷點:
3232:M 09 Nov 2020 18:40:49.044 * DB loaded from disk: 0.000 seconds
3232:M 09 Nov 2020 18:40:49.044 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) b t_hash.c:hsetCommand
Breakpoint 2 at 0x45e870: file t_hash.c, line 530.
(gdb) c
Continuing.
然后通過redis客戶端redis-cli連上redis-server,並輸入一條hset
命令:
[root@localhost ~]# redis-cli
127.0.0.1:6379> hset maptest key1 value1
(integer) 1
(3.54s)
斷點被觸發:
Thread 1 "redis-server" hit Breakpoint 2, hsetCommand (c=0x7ffff7b0d0c0) at t_hash.c:530
530 void hsetCommand(client *c) {
(gdb) c
Continuing.
5.4 info break、enable、disable和delete命令
命令格式及作用:
- info break,也可簡寫為 i b,作用是顯示當前所有斷點信息;
- disable 斷點編號,禁用某個斷點,使得斷點不會被觸發;
- enable 斷點編號,啟用某個被禁用的斷點;
- delete 斷點編號,刪除某個斷點。
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000430890 in main at server.c:4003
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004308bc in main at server.c:4041
breakpoint already hit 1 time
3 breakpoint keep y 0x0000000000429b72 in anetListen at anet.c:441
breakpoint already hit 2 times
4 breakpoint keep y 0x000000000045e870 in hsetCommand at t_hash.c:530
(gdb) disable 4
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000430890 in main at server.c:4003
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004308bc in main at server.c:4041
breakpoint already hit 1 time
3 breakpoint keep y 0x0000000000429b72 in anetListen at anet.c:441
breakpoint already hit 2 times
4 breakpoint keep n 0x000000000045e870 in hsetCommand at t_hash.c:530
(gdb) enable 4
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000430890 in main at server.c:4003
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004308bc in main at server.c:4041
breakpoint already hit 1 time
3 breakpoint keep y 0x0000000000429b72 in anetListen at anet.c:441
breakpoint already hit 2 times
4 breakpoint keep y 0x000000000045e870 in hsetCommand at t_hash.c:530
(gdb) delete 3
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000430890 in main at server.c:4003
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004308bc in main at server.c:4041
breakpoint already hit 1 time
4 breakpoint keep y 0x000000000045e870 in hsetCommand at t_hash.c:530
斷點信息中第四列 *Enb*,當斷點啟動時是 y,斷點被禁用后是 n。
5.5 backtrace和frame命令
命令格式及作用:
- backtrace,也可簡寫為 bt,用於查看當前調用堆棧。
- frame 堆棧編號,也可簡寫為 f 堆棧編號,用於切換到其他堆棧處。
Thread 1 "redis-server" hit Breakpoint 4, hsetCommand (c=0x7ffff7b0d0c0) at t_hash.c:530
530 void hsetCommand(client *c) {
(gdb) bt
#0 hsetCommand (c=0x7ffff7b0d0c0) at t_hash.c:530
#1 0x000000000042d320 in call (c=0x7ffff7b0d0c0, flags=15) at server.c:2437
#2 0x000000000043168d in processCommand (c=0x7ffff7b0d0c0) at server.c:2729
#3 0x000000000043e7af in processInputBuffer (c=0x7ffff7b0d0c0) at networking.c:1446
#4 0x00000000004288d3 in aeProcessEvents (eventLoop=0x7ffff7a300a0, flags=11) at ae.c:443
#5 0x0000000000428bfb in aeMain (eventLoop=0x7ffff7a300a0) at ae.c:501
#6 0x0000000000430d9e in main (argc=<optimized out>, argv=0x7fffffffe648) at server.c:4197
(gdb) f 1
#1 0x000000000042d320 in call (c=0x7ffff7b0d0c0, flags=15) at server.c:2437
2437 c->cmd->proc(c);
(gdb) f 2
#2 0x000000000043168d in processCommand (c=0x7ffff7b0d0c0) at server.c:2729
2729 call(c,CMD_CALL_FULL);
(gdb) f 3
#3 0x000000000043e7af in processInputBuffer (c=0x7ffff7b0d0c0) at networking.c:1446
1446 if (processCommand(c) == C_OK) {
(gdb) f 4
#4 0x00000000004288d3 in aeProcessEvents (eventLoop=0x7ffff7a300a0, flags=11) at ae.c:443
443 fe->rfileProc(eventLoop,fd,fe->clientData,mask);
(gdb) f 5
#5 0x0000000000428bfb in aeMain (eventLoop=0x7ffff7a300a0) at ae.c:501
501 aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
(gdb) f 6
#6 0x0000000000430d9e in main (argc=<optimized out>, argv=0x7fffffffe648) at server.c:4197
4197 aeMain(server.el);
(gdb) c
Continuing.
5.6 list命令
命令格式及作用:
- list,輸出上一次list命令顯示的代碼后面的代碼,如果是第一次執行list命令,則會顯示當前正在執行代碼位置附近的代碼;
- list -,帶一個減號,顯示上一次list命令顯示的代碼前面的代碼;
- list LineNo,顯示當前代碼文件第 LineNo 行附近的代碼;
- list FileName:LineNo,顯示 FileName 文件第 LineNo 行附近的代碼;
- list FunctionName,顯示當前文件的 FunctionName 函數附近的代碼;
- list FileName:FunctionName,顯示 FileName 文件的 FunctionName 函數附件的代碼;
- list from,to,其中from和to是具體的代碼位置,顯示這之間的代碼;
list命令默認只會輸出 10 行源代碼,也可以使用如下命令修改:
- show listsize,查看 list 命令顯示的代碼行數;
- set listsize count,設置 list 命令顯示的代碼行數為 count;
5.7 print和ptype命令
命令格式及作用:
- print param,用於在調試過程中查看變量的值;
- print param=value,用於在調試過程中修改變量的值;
- print a+b+c,可以進行一定的表達式計算,這里是計算a、b、c三個變量之和;
- print func(),輸出
func
函數執行的結果,常見的用途是打印系統函數執行失敗原因:print strerror(errno)
; - print *this,在c++對象中,可以輸出當前對象的各成員變量的值;
8099:M 10 Nov 2020 04:06:23.436 * DB loaded from disk: 0.000 seconds
8099:M 10 Nov 2020 04:06:23.436 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) b listenToPort
Breakpoint 1 at 0x42fa20: file server.c, line 1913.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /data/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
8107:C 10 Nov 2020 04:06:32.413 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8107:C 10 Nov 2020 04:06:32.413 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=8107, just started
8107:C 10 Nov 2020 04:06:32.413 # Warning: no config file specified, using the default config. In order to specify a config file use /data/redis-5.0.3/src/redis-server /path/to/redis.conf
8107:M 10 Nov 2020 04:06:32.414 * Increased maximum number of open files to 10032 (it was originally set to 4096).
Breakpoint 1, listenToPort (port=6379, fds=0x7b424c <server+364>, count=0x7b428c <server+428>) at server.c:1913
1913 int listenToPort(int port, int *fds, int *count) {
(gdb) p port
$1 = 6379
(gdb) p port=6378
$2 = 6378
(gdb) c
Continuing.
如上所示,在listenToPort
函數入口處下斷點,輸入 r,redis-server重啟時觸發斷點,打印得到 port 的值為6379,再通過 p port=6378
將監聽的端口改成6378,redis-server啟動后查詢端口監聽情況:
[root@localhost ~]# netstat -apn | grep redis-server
tcp 0 0 0.0.0.0:6378 0.0.0.0:* LISTEN 8131/redis-server *
tcp 0 0 :::6378 :::* LISTEN 8131/redis-server *
[root@localhost ~]#
5.8 whatis和ptype命令
命令格式及功能:
- whatis val,用於查看變量類型;
- ptype val,作用和 whatis 類似,但功能更強大,可以查看復合數據類型,會打印出該類型的成員變量。
5.9 thread命令
命令格式及作用:
- info thread,查看當前進程的所有線程運行情況;
- thread 線程編號,切換到具體編號的線程上去;
14139:M 10 Nov 2020 22:55:24.849 * DB loaded from disk: 0.000 seconds
14139:M 10 Nov 2020 22:55:24.849 * Ready to accept connections
^C
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) info thread
Id Target Id Frame
* 1 Thread 0x7ffff7feaf40 (LWP 14139) "redis-server" 0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
2 Thread 0x7ffff176a700 (LWP 14140) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
3 Thread 0x7fffeef69700 (LWP 14141) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
4 Thread 0x7fffec768700 (LWP 14142) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) thread 3
[Switching to thread 3 (Thread 0x7fffeef69700 (LWP 14141))]
#0 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) info thread
Id Target Id Frame
1 Thread 0x7ffff7feaf40 (LWP 14139) "redis-server" 0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
2 Thread 0x7ffff176a700 (LWP 14140) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 3 Thread 0x7fffeef69700 (LWP 14141) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
4 Thread 0x7fffec768700 (LWP 14142) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) c
Continuing.
從以上可以看出,前面帶 * 號的是當前所在線程,第一列 Id 指的是線程編號,后面括號中 LWP 14139,LWP(Light Weight Process)表示輕量級進程,即我們所說的線程,線程ID為 14139。
目前通過 info thread 可知redis-server一共啟動了4個線程,其中1個主線程,3個工作線程,通過 bt 命令可知線程1是主線程,因為其調用堆棧最頂層是main函數。
Thread 1 "redis-server" received signal SIGINT, Interrupt.
0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
(gdb) info thread
Id Target Id Frame
* 1 Thread 0x7ffff7feaf40 (LWP 14139) "redis-server" 0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
2 Thread 0x7ffff176a700 (LWP 14140) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
3 Thread 0x7fffeef69700 (LWP 14141) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
4 Thread 0x7fffec768700 (LWP 14142) "redis-server" 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) bt
#0 0x00000038212e9243 in epoll_wait () from /lib64/libc.so.6
#1 0x000000000042875e in aeApiPoll (tvp=<optimized out>, eventLoop=0x7ffff7a300a0) at ae_epoll.c:112
#2 aeProcessEvents (eventLoop=0x7ffff7a300a0, flags=11) at ae.c:411
#3 0x0000000000428bfb in aeMain (eventLoop=0x7ffff7a300a0) at ae.c:501
#4 0x0000000000430d9e in main (argc=<optimized out>, argv=0x7fffffffe648) at server.c:4197
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff176a700 (LWP 14140))]
#0 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) bt
#0 0x000000382160b68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x0000000000483346 in bioProcessBackgroundJobs (arg=0x0) at bio.c:176
#2 0x0000003821607aa1 in start_thread () from /lib64/libpthread.so.0
#3 0x00000038212e8c4d in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
5.10 next、step命令
next 和 step 都是單步執行,但也有差別:
- next 是 單步步過(step over),即遇到函數直接跳過,不進入函數內部。
- step 是 單步步入(step into),即遇到函數會進入函數內部。
5.11 return、finish命令
return 和 finish 都是退出函數,但也有差別:
- return 命令是立即退出當前函數,剩下的代碼不會執行了,return 還可以指定函數的返回值。
- finish 命令是會繼續執行完該函數剩余代碼再正常退出。
5.12 until命令
以下是GDB對 until 命令的解釋:
(gdb) help until Execute until the program reaches a source line greater than the current or a specified location (same args as break command) within the current frame.
該命令使得程序執行到指定位置停下來,命令參數和 break 命令一樣。
5.13 jump命令
命令格式及作用:
- jump LineNo,跳轉到代碼的 LineNo 行的位置;
- jump +10,跳轉到距離當前代碼下10行的位置;
- jump *0x12345678,跳轉到 0x12345678 地址的代碼處,地址前要加星號;
jump 命令有兩點需要注意的:
- 中間跳過的代碼是不會執行的;
- 跳到的位置后如果沒有斷點,那么GDB會自動繼續往后執行;
#include <iostream> using std::cout; using std::endl; int main() { int a = 0; cout << "aaa" << endl; if (a == 0) cout << "hello" << endl; else cout << "world" << endl; cout << "bbb" << endl; return 0; }
以上述代碼為例,正常執行的輸出為:
aaa hello bbb
使用 jump 命令跳到第 12 行:
Reading symbols from aaa...done.
(gdb) b main
Breakpoint 1 at 0x400880: file aaa.cpp, line 7.
(gdb) r
Starting program: /opt/test/aaa
Breakpoint 1, main () at aaa.cpp:7
7 int a = 0;
(gdb) jump 12
Continuing at 0x4008c7.
world
bbb
[Inferior 1 (process 14716) exited normally]
5.14 disassemble命令
該命令用於查看某段代碼的匯編指令。
5.15 set args 和 show args命令
很多程序啟動需要我們傳遞參數,set args 就是用來設置程序啟動參數的,show args 命令用來查詢通過 set args 設置的參數,命令格式:
- set args args1,設置單個啟動參數 args1;
- set args "-p" "password",如果單個參數之間有空格,可以使用引號將參數包裹起來;
- set args args1 args2 args3,設置多個啟動參數,參數之間用空格隔開;
- set args,不帶參數,則清除之前設置的參數;
Reading symbols from redis-server...done.
(gdb) show args
Argument list to give program being debugged when it is started is "".
(gdb) set args ../redis.conf
(gdb) show args
Argument list to give program being debugged when it is started is "../redis.conf".
(gdb) set args
(gdb) show args
Argument list to give program being debugged when it is started is "".
(gdb) set args ../redis.conf
(gdb) r
Starting program: /data/redis-5.0.3/src/redis-server ../redis.conf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
15046:C 11 Nov 2020 01:24:31.573 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
Reading symbols from redis-cli...done.
(gdb) show args
Argument list to give program being debugged when it is started is "".
(gdb) set args "-p" "6378"
(gdb) show args
Argument list to give program being debugged when it is started is ""-p" "6378"".
(gdb) r
Starting program: /data/redis-5.0.3/src/redis-cli "-p" "6378"
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
127.0.0.1:6378>
5.16 tbreak命令
該命令時添加一個臨時斷點,斷點一旦被觸發就自動刪除,使用方法同 break。
5.17 watch命令
watch 命令用來監視一個變量或者一段內存,當這個變量或者內存的值發生變化時,GDB就會中斷下來。被監視的某個變量或內存地址會產生一個 watch point(觀察點)。
命令格式:
- watch 整型變量;
- watch 指針變量,監視的是指針變量本身;
- watch *指針變量,監視的是指針所指的內容;
- watch 數組變量或內存區間;
#include <iostream> using std::cout; using std::endl; int main() { int a = 0; a = 10; int * p; *p = 5; int * q; q = p; int c[5]; c[0] = 1; c[1] = 1; c[2] = 1; c[3] = 1; c[4] = 1; return 0; }
對於以上代碼,通過 watch 命令分別對變量 a、*p、q、c 進行監視:
Reading symbols from aaa...done.
(gdb) b main
Breakpoint 1 at 0x4006ec: file aaa.cpp, line 7.
(gdb) r
Starting program: /opt/test/aaa
Breakpoint 1, main () at aaa.cpp:7
7 int a = 0;
(gdb) watch a
Hardware watchpoint 2: a
(gdb) watch *p
Hardware watchpoint 3: *p
(gdb) watch q
Hardware watchpoint 4: q
(gdb) c
Continuing.
Hardware watchpoint 2: a
Old value = 0
New value = 10
main () at aaa.cpp:11
11 *p = 5;
(gdb) c
Continuing.
Hardware watchpoint 3: *p
Old value = 1
New value = 5
main () at aaa.cpp:14
14 q = p;
(gdb) c
Continuing.
Hardware watchpoint 4: q
Old value = (int *) 0x400580 <_start>
New value = (int *) 0x7fffffffe680
main () at aaa.cpp:17
17 c[0] = 1;
(gdb) info watch
Num Type Disp Enb Address What
2 hw watchpoint keep y a
breakpoint already hit 1 time
3 hw watchpoint keep y *p
breakpoint already hit 1 time
4 hw watchpoint keep y q
breakpoint already hit 1 time
(gdb) del 2
(gdb) del 3
(gdb) info watch
Num Type Disp Enb Address What
4 hw watchpoint keep y q
breakpoint already hit 1 time
(gdb) watch c
Hardware watchpoint 5: c
(gdb) c
Continuing.
Hardware watchpoint 5: c
Old value = {555809696, 56, 4196256, 0, 0}
New value = {1, 56, 4196256, 0, 0}
main () at aaa.cpp:18
18 c[1] = 1;
(gdb) c
Continuing.
Hardware watchpoint 5: c
Old value = {1, 56, 4196256, 0, 0}
New value = {1, 1, 4196256, 0, 0}
main () at aaa.cpp:19
19 c[2] = 1;
(gdb) c
Continuing.
Hardware watchpoint 5: c
Old value = {1, 1, 4196256, 0, 0}
New value = {1, 1, 1, 0, 0}
main () at aaa.cpp:20
20 c[3] = 1;
(gdb) c
Continuing.
Hardware watchpoint 5: c
Old value = {1, 1, 1, 0, 0}
New value = {1, 1, 1, 1, 0}
main () at aaa.cpp:21
21 c[4] = 1;
(gdb) c
Continuing.
Hardware watchpoint 5: c
Old value = {1, 1, 1, 1, 0}
New value = {1, 1, 1, 1, 1}
main () at aaa.cpp:23
23 return 0;
(gdb) c
Continuing.
Watchpoint 4 deleted because the program has left the block in
which its expression is valid.
Watchpoint 5 deleted because the program has left the block in
which its expression is valid.
0x000000382121ed20 in __libc_start_main () from /lib64/libc.so.6
(gdb)
當 watch 的變量或內存因超出作用域失效時,GDB 會有如下提示信息:
Watchpoint 4 deleted because the program has left the block in which its expression is valid.
通過 info watch 命令可以查看當前所有監視的變量,通過 delete watch編號 可以刪除對某個變量的監視。
5.18 call命令
命令格式及作用:
- call func(),執行 func() 函數,同 print func()。
5.19 help命令
通過 help 命令可以查看目標命令的具體用法。
6. GDB多線程調試
6.1 概述
多線程程序的編寫更容易產生異常或 Bug(例如線程之間因競爭同一資源發生了死鎖、多個線程同時對同一資源進行讀和寫等等)。GDB調試器不僅僅支持調試單線程程序,還支持調試多線程程序。本質上講,使用GDB調試多線程程序的過程和調試單線程程序類似,不同之處在於,調試多線程程序需要監控多個線程的執行過程。
用GDB調試多線程程序時,該程序的編譯需要添加 -lpthread
參數。
6.2 一些命令
- info thread,查看當前調試程序啟動了多少個線程,並打印出各個線程信息;
- thread 線程編號,將該編號的線程切換為當前線程;
- thread apply 線程編號1 線程編號2 ... command,將GDB命令作用指定對應編號的線程,可以指定多個線程,若要指定所有線程,用 all 替換線程編號;
- break location thread 線程編號,在 location 位置設置普通斷點,該斷點只作用在特定編號的線程上;
6.3 一些術語
- all-stop mode,全停模式,當程序由於任何原因在GDB下停止時,不止當前的線程停止,所有的執行線程都停止。這樣允許你檢查程序的整體狀態,包括線程切換,不用擔心當下會有什么改變。
- non-stop mode,不停模式,調試器(如VS2008和老版本的GDB)往往只支持 all-stop 模式,但在某些場景中,我們可能需要調試個別的線程,並且不想在調試過程中影響其他線程的運行,這樣可以把GDB的調式模式由 all-stop 改成 non-stop,7.0 版本的GDB引入了 non-stop 模式。在 non-stop 模式下 continue、next、step 命令只針對當前線程。
- record mode,記錄模式;
- replay mode,回放模式;
- scheduler-locking ,調度鎖;
(gdb) help set scheduler-locking Set mode for locking scheduler during execution. off == no locking (threads may preempt at any time) on == full locking (no thread except the current thread may run) This applies to both normal execution and replay mode. step == scheduler locked during stepping commands (step, next, stepi, nexti). In this mode, other threads may run during other commands. This applies to both normal execution and replay mode. replay == scheduler locked in replay mode and unlocked during normal execution.
- schedule-multiple,多進程調度;
(gdb) help set schedule-multiple Set mode for resuming threads of all processes. When on, execution commands (such as 'continue' or 'next') resume all threads of all processes. When off (which is the default), execution commands only resume the threads of the current process. The set of threads that are resumed is further refined by the scheduler-locking mode (see help set scheduler-locking).
6.4 設置線程鎖
使用GDB調試多線程程序時,默認的調試模式是:一個線程暫停運行,其他線程也隨即暫停;一個線程啟動運行,其他線程也隨即啟動。但在一些場景中,我們希望只讓特定線程運行,其他線程都維持在暫停狀態,即要防止線程切換,要達到這種效果,需要借助 set scheduler-locking 命令。
命令格式及作用:
- set scheduler-locking on,鎖定線程,只有當前或指定線程可以運行;
- set scheduler-locking off,不鎖定線程,會有線程切換;
- set scheduler-locking step,當單步執行某一線程時,其他線程不會執行,同時保證在調試過程中當前線程不會發生改變。但如果在該模式下執行 continue、until、finish 命令,則其他線程也會執行;
- show scheduler-locking,查看線程鎖定狀態;