GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序。一般来说,GDB主要帮助我们完成以下四个方面的功能:
[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):
1 2 3 4 5 6 7 8 9
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,会提示用户要关闭当前调试进程,是否继续退出:
1 2 3 4 5 6 7 8 9 10 11
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)命令即可。
1 2 3 4 5 6 7 8 9
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
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行下断点:
1 2 3 4 5 6 7 8 9 10 11 12 13
(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)
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)
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.
(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
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.
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启动后查询端口监听情况:
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会自动继续往后执行;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include<iostream>
using std::cout; using std::endl;
intmain(){ int a = 0; cout << "aaa" << endl; if (a == 0) cout << "hello" << endl; else cout << "world" << endl;
cout << "bbb" << endl; return0; }
以上述代码为例,正常执行的输出为:
1 2 3
aaa hello bbb
使用 jump 命令跳到第 12 行:
1 2 3 4 5 6 7 8 9 10 11 12 13
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”,如果单个参数之间有空格,可以使用引号将参数包裹起来;
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>
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编号 可以删除对某个变量的监视。
(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 命令,则其他线程也会执行;