本课的核心内容如下:

  run命令

  continue命令

  break命令

  backtrace与frame命令

  info break、enable、disable和delete命令

  list命令

  print和ptype命令

为了结合实践,这里以调试Redis源码为例来介绍没一个命令,这里先介绍一些常用命令的基础用法,某些命令的高级用法会在后面讲解。

Redis的最新源码下载地址可以在Redis官网Redis中文网)获得,使用wget命令将Redis源码文件下载下来:

解压:tar zxvf redis-5.0.3.tar.gz

进入生成的 redis-5.0.3 目录使用makefile命令进行编译。makefile命令是Linux程序编译基本的命令,由于本课程的重点是Linux调试,如果读者不熟悉Linux编译可以通过互联网或相关书籍补充一下知识。

步骤:cd redis-5.0.3/    

   make -j 4

   cd src 

      make test

然后启动 redis-server

4.1  run命令

  默认情况下,前面的课程中我们说gdb filename 命令知识个附加的一个调试文件,并没有启动这个程序,需要输入run命令(简写为r)启动这个程序。

(gdb) r
Starting program: /home/wzq/Desktop/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
21463:C 09 Jan 2019 14:33:20.281 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
21463:C 09 Jan 2019 14:33:20.281 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=21463, just started
21463:C 09 Jan 2019 14:33:20.281 # Warning: no config file specified, using the default config. In order to specify a config file use /home/wzq/Desktop/redis-5.0.3/src/redis-server /path/to/redis.conf
21463:M 09 Jan 2019 14:33:20.282 * Increased maximum number of open files to 10032 (it was originally set to 1024).
[New Thread 0x7ffff67ff700 (LWP 21467)]
[New Thread 0x7ffff5ffe700 (LWP 21468)]
[New Thread 0x7ffff57fd700 (LWP 21469)]
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.3 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 21463
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-' 21463:M 09 Jan 2019 14:33:20.283 # Server initialized
21463:M 09 Jan 2019 14:33:20.283 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
21463:M 09 Jan 2019 14:33:20.283 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
21463:M 09 Jan 2019 14:33:20.283 * Ready to accept connections

 

4.2 continue命令

  当GDB触发断点或者使用Ctr+C命令中断下来后,想让程序继续运行,只要输入continue命令即可(简写为c)。当然,如果continue命令继续触发断点,GDB就再次中断下来。

4.3 break命令

  break命令(简写为b)即我们添加断点的命令,可以使用以下方式添加断点:

    break functionname,在函数名为functionname的入口处添加一个断点。

    break LineNo,在当前文件行号为LineNo处添加一个断点。

    break filename:LineNo,在filename文件行号为LineNo处添加一个断点。

(gdb) b main
Breakpoint 1 at 0x555555584cf0: file server.c, line 4003.

  添加好之后,使用run命令重启程序,就可以触发这个断点了,GDB就会停在断点处。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/wzq/Desktop/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=1, argv=0x7fffffffccc8) at server.c:4003
4003 int main(int argc, char **argv) {
(gdb)

  redis-server默认端口号是6379,我们知道这个端口号肯定是通过操作系统的socket API bind()函数创建的,通过文件搜索,我们找到调用这个函数的文件,其位于anet.c441行。

我们使用break命令在这个地方加一个断点:

(gdb) b anet.c:441
Breakpoint 2 at 0x55555558862b: file anet.c, line 441.

由于程序绑定端口号是redis-server启动时初始化的,为了能触发这个断点,再次使用run命令启动下这个程序,GDB第一次会触发main()函数处的断点,输入continue命令继续运行,接着触发anet.c:441处的断点:

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/wzq/Desktop/redis-5.0.3/src/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main (argc=1, argv=0x7fffffffccc8) at server.c:4003
4003 int main(int argc, char **argv) {
(gdb) c
Continuing.
23268:C 09 Jan 2019 15:02:04.276 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
23268:C 09 Jan 2019 15:02:04.276 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=23268, just started
23268:C 09 Jan 2019 15:02:04.276 # Warning: no config file specified, using the default config. In order to specify a config file use /home/wzq/Desktop/redis-5.0.3/src/redis-server /path/to/redis.conf
23268:M 09 Jan 2019 15:02:04.277 * Increased maximum number of open files to 10032 (it was originally set to 1024). Breakpoint 2, anetListen (err=0x5555559161e0 <server+576> "", s=6, sa=0x555555b2b240, len=28, backlog=511)
at anet.c:441
441 if (bind(s,sa,len) == -1) {
(gdb)

anet.c:441处的代码如下:

现在断点停在第441行,所以当前文件就是anet.c,可以直接使用“break 行号”添加断点,例如,可以在第444行、450行、452行分别加一个断点,看看这个函数执行完毕后走哪个return语句退出,则可以执行:

添加好这三个断点后,我们使用continue命令继续运行程序,发现程序运行到第452行中断下来(即触发Breakpoint 5):

说明redis-server绑定端口并设置侦听(listen)成功,我们可以再打开一个SSH窗口,验证一下,发现6379端口确实已经处于侦听状态了:

wzq@wzq-PC:~/Desktop/redis-5.0.3/src$ lsof -i -Pn | grep redis
redis-ser 23268 wzq 6u IPv6 114334 0t0 TCP *:6379 (LISTEN)

4.4 backtrace 与 frame命令

backtrace命令(简写为bt)用来查看当前调用堆栈。接上,redis-server现在中断在anet.c:452行,可以通过backtrace命令来查看当前的调用堆栈:

(gdb) bt
#0 anetListen (err=0x5555559161e0 <server+576> "", s=6, sa=<optimized out>, len=<optimized out>, backlog=511)
at anet.c:452
#1 0x00005555555887a4 in _anetTcpServer (err=0x5555559161e0 <server+576> "", port=<optimized out>,
bindaddr=<optimized out>, af=10, backlog=511) at anet.c:487
#2 0x000055555558cf07 in listenToPort (port=6379, fds=0x55555591610c <server+364>,
count=0x55555591614c <server+428>) at server.c:1924
#3 0x0000555555591ed0 in initServer () at server.c:2055
#4 0x0000555555585103 in main (argc=<optimized out>, argv=0x7fffffffccc8) at server.c:4160

这里一共有5层堆栈,最顶层是main()函数,最底层是断点所在的anetListen()函数,堆栈编号分别是#0-#4,如果想切换到其他堆栈处,可以使用frame命令(简写f),改命令的使用方法是“frame堆栈编号(编号不加#)”。在这里一次切换至堆栈顶部,然后再切换回#0联系一下:

通过查看上面的各堆栈,可以得出这里的调用层级关系,即:

  main()函数在4160行调用了initServer()函数

  initServer()在第2055行调用了listenToPort()函数

  listenToPort在1924行调用了anetTcp6Server()函数

  _anetTcp6Server()在487行调用了anetListen()函数

  当前断点正好位于anetListen()函数中

4.5 info break、enable、disable、和delete命令

  在程序中加了很多段点,而我们想查看加了那些断点时,可以使用info break命令(简写 info b)。

通过上面的内容可以知道,目前一共增加了5个断点,其他信息比如每个断点的位置(所在的文件和行号)、内存地址、断点启用和禁用状态信息一目了然。如果我们想禁用某个断点使用“disable 断点编号”既可以禁用这个断点了,被禁用的断点不会再被触发;同理,被禁用的断点也可以使用“enable 断点编号”重新启用。

使用disable 1以后,第一个断点的End一栏的值由y变成n,重启程序也不会再次触发。

如果disable命令和enable命令不加断点编号,则分别表示禁用和启用所哟断点。

使用“delete 编号”可以删除某个断点,如delete 2 3则表示删除的断点2和断点3。

同样道理,如果输入delete不叫命令号,则表示删除所有断点。

4.6 list命令

list命令和后面介绍的print命令都是GDB调试中用到的频率最高的命令,list命令(简写为l)可以查看当前断点处的代码。使用frame命令切换到刚才的堆栈#3处,然后输入list命令看下效果:

断点停在2055行,输入list命令以后,会显示第2055行前后的10行代码,再次输入list命令试一下:

  代码继续往后显示10行,也就是说,第一次输入list命令会显示断点处前后的代码,继续输入list指令会以递增行号的形式继续显示剩下的代码行,一直到文件结束为止。当然list指令还可以往前和往后显示代码,命令分别是“list +”和“list -”;

  list默认显示多少行可以通过修改相关的GDB配置,由于我们一般不会修改这个默认显示行数,这里就不再浪费篇幅介绍了。list不仅可以显示当前断点处的代码,也可以显示其他文件某一行的代码,更多的用法可以在GDB中输入help list查看。

  使用GDB的目的是调试,因此更关心的是断点附近的代码,而不是通过GDB阅读代码,GDB并不是一个好的阅读工具。以我自己为例,调试Redis时用GDB调试,而阅读代码使用的却是Visual Studio,如下图所示:

4.7 print 和ptype 命令

  通过print命令(简写为p)我们可以在调试过程中方便地查看变量的值,也可以修改当前内存中的变量值。切换当前断点到堆栈#3,然后打印以下三个变量。

  这里使用print命令分别打印处serve.port、server.ipfd、server.ipfd_count的值,其中server.ipfd显示“{0\}”,这是GDB显示字符串或字符数据特有的方式,当一个字符串变量或者字符数组或者连续的内存值重复若干次,GDB就会以这种模式来显示以节约空间。

  print命令不仅可以显示变量值,也可以显示进行一定运算的表达式计算结果值,甚至可以显示一些函数的执行结果值。

  举个例子,我们可以输入p &server.port来输出server.port的地址值,如果在C++对象中,可以通过p this来显示当前对象的地址,也可以通过p *this来列出当前对象的各个成员变量值,如果有三个变量可以相加(假设变量名分别叫a,b,c),可以使用p a+b+c来打印这三个变量的结果值。

  假设func()是一个可以执行的函数,p func()命令可以输出该变量的执行结果。举一个最常用的例子,某个时刻,某个系统函数执行失败了,通过系统变量errno得到一个错误码,则可以使用p strerror(error),将这个对应的文字信息打印出来,这样就不用费劲地去man手册上查找这个错误码对应的错误含义了。

  print命令不仅可以输出表达式结果,同时也可以修改变量的值,我们尝试将上问中的端口号从6379改成6400试试:

  

(gdb) p server.port = 6400
$4 = 6400
(gdb) p server.port
$5 = 6400

  GDB还有另一个命令叫ptype,顾名思义,其含义是“print type”,就是输出一个变量的类型。例如我们试着输出Redis堆栈#3的变量server和变量server.port的类型:

type = struct redisServer {
pid_t pid;
char *configfile;
char *executable;
char **exec_argv;
int dynamic_hz;
int config_hz;
int hz;
redisDb *db;
dict *commands;
dict *orig_commands;
aeEventLoop *el;
unsigned int lruclock;
int shutdown_asap;
int activerehashing;
int active_defrag_running;
char *requirepass;
char *pidfile;
int arch_bits;
int cronloops;
char runid[41];
int sentinel_mode;
size_t initial_memory_usage;
int always_show_logo;
dict *moduleapi;
list *loadmodule_queue;
int module_blocked_pipe[2];
int port;
int tcp_backlog;
char *bindaddr[16];
int bindaddr_count;
char *unixsocket;
mode_t unixsocketperm;
int ipfd[16];
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) ptype server.port
type = int

  可以看到,对于一个复合数据类型的变量,ptype不仅列出了这个变量的类型,而且还详细地列出了每个成员变量的字段名,有了这个功能,我们在调试时就不用可以去代码文件中查看某个变量的类型定义了。

第04课:GDB常用命令详解(上)的更多相关文章

  1. Linux常用命令详解上

    Linux常用命令详解上 目录 一.shell 二.Linux命令 2.1.内部命令与外部命令的区别 2.2.Linux命令行的格式 2.3.编辑Linux命令行的辅助操作 2.4.获得命令帮助的方法 ...

  2. 第06课:GDB 常用命令详解(中)

    本课的核心内容: info 和 thread 命令 next.step.util.finish.return 和 jump 命令 info 和 thread 命令 在前面使用 info break 命 ...

  3. 第05课:GDB 常用命令详解(上)

    本课的核心内容如下: run 命令 continue 命令 break 命令 backtrace 与 frame 命令 info break.enable.disable 和 delete 命令 li ...

  4. 第07课:GDB 常用命令详解(下)

    本课的核心内容: disassemble 命令 set args 和 show args 命令 tbreak 命令 watch 命令 display 命令 disassemble 命令 当进行一些高级 ...

  5. 第06课:GDB 常用命令详解(下)

    本课的核心内容: disassemble 命令 set args 和 show args 命令 tbreak 命令 watch 命令 display 命令 6.1 disassemble 命令 当进行 ...

  6. 第05课:GDB常用命令详解(中)

    本科核心内容: info和thread命令 next.step.util.finish和return命令 5.1info和thread命令 在前面使用info break命令查看当前断点时介绍过,in ...

  7. Kubernetes,kubectl常用命令详解

    kubectl概述 祭出一张图,转载至 kubernetes-handbook/kubectl命令概述 ,可以对命令族有个整体的概念. 环境准备 允许master节点部署pod,使用命令如下: kub ...

  8. hbase shell基础和常用命令详解(转)

    HBase shell的基本用法 hbase提供了一个shell的终端给用户交互.使用命令hbase shell进入命令界面.通过执行 help可以看到命令的帮助信息. 以网上的一个学生成绩表的例子来 ...

  9. samtools常用命令详解(转)

    转自:samtools常用命令详解 samtools的说明文档:http://samtools.sourceforge.net/samtools.shtml samtools是一个用于操作sam和ba ...

随机推荐

  1. tomcat服务器经常需要重启

    程序看着运行正常,但是点击几下就没反应了. 可能原因:1.tomcat内存不足 2.程序中有资源未释放.比如session(hibernate的)等(需要close)

  2. android 给控件使用自定义字体Typeface

    第一步:将字体资源放在assets 资源文件夹下: 第二步:获取字体资源 Typeface mTf = Typeface.createFromAsset(c.getAssets(), "Op ...

  3. Kotlin之定义函数

    java: int add (int m ,int n){ return m+n; } void process(int m){ Systrm.out.println(m); } kotlin: fu ...

  4. CentOS7 下SaltStack部署

    一,概念SaltStack是一个服务器基础架构集中化管理平台,具备配置管理.远程执行.监控等功能,一般可以理解为简化版的puppet和加强版的func.SaltStack基于Python语言实现,结合 ...

  5. HTML DOM Document对象 元素对象 属性对象 事件对象

    DOM Document对象 DOM 元素 对象 DOM 属性 对象 DOM 事件 菜鸟教程上 总结挺全的,就不多废话,链接点进去即可.. 后期对经常用到的会在此更新一些总结..... 开学了...自 ...

  6. CSS3 —— 盒子模型

    盒子模型 主要的属性就5个:width.height.padding.border.margin.如下:  width和height:内容的宽度.高度(不是盒子的宽度.高度). padding:内边距 ...

  7. Spring Boot 中使用 WebSocket 实现一对多聊天及一对一聊天

    为什么需要WebSocket? 我们已经有了http协议,为什么还需要另外一个协议?有什么好处? 比如我想得到价格变化,只能是客户端想服务端发起请求,服务器返回结果,HTTP协议做不到服务器主动向客户 ...

  8. docker 安装 gitlab 中文社区版

    docker pull twang2218/gitlab-ce-zh 创建一个docker 目录 /usr/local/docker/gitlab 创建一个 docker-compose.yml ve ...

  9. java中String中的endsWith()方法

    解释:endsWith() ——此方法测试字符串是否以指定的后缀 suffix 结束. 此方法的定义:public boolean endsWith(String suffix) 我这里判断的是路径是 ...

  10. HTML协义代码

    这些状态码被分为五大类: 100-199 用于指定客户端应相应的某些动作. 200-299 用于表示请求成功. 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息. 400- ...