目录
info break 命令(简写为 info b)查看加了哪些断点及enable、disable 和 delete
Redis 源码解压及编译
- 进入生成的目录使用 makefile 命令进行编译
- 注意:
- 设置编译器的选项时使用的是 CFLAGS 选项;如果项目使用的语言是 C++,那么使用的编译器一般是 g++,相对应的编译器选项是 CXXFLAGS。
- 此处makefile 使用了 -j 选项,其值是 4,表示开启 4 个进程同时编译,加快编译速度。
- src 目录下生成多个可执行程序,其中 redis-server 和 redis-cli 是需要调试的程序。
使用 GDB 附加 redis-server 这个程序
run 命令(简写为 r)启动这个程序
- 这是 redis-server 启动界面,假设程序已经启动,再次输入 run 命令则是重启程序。
- 在 GDB 界面按 Ctrl + C 快捷键让 GDB 中断下来,再次输入 r 命令,GDB 会询问我们是否重启程序,输入 yes 确认重启。
ontinue 命令 让程序继续运行
- 当 GDB 触发断点或者使用 Ctrl + C 命令中断下来后,想让程序继续运行,只要输入 continue 命令即可(简写为 c)。
- 如果 continue 命令继续触发断点,GDB 就会再次中断下来。
break 命令(简写为 b)添加断点
break functionname 在函数名为 functionname 的入口处添加一个断点 break LineNo 在当前文件行号为 LineNo 处添加一个断点 break filename:LineNo 在 filename 文件行号为 LineNo 处添加一个断点
- 对于一般的 Linux 程序来说,main() 函数是程序入口函数,redis-server 也不例外,知道了函数的名字,就可以直接在 main() 函数处添加一个断点:
- 添加好了以后,使用 run 命令重启程序,就可以触发这个断点了,GDB 会停在断点处
- redis-server 默认端口号是 6379 ,这个端口号肯定是通过操作系统的 bind() 函数创建的,通过文件搜索,找到调用这个函数的文件,其位于 anet.c 455 行。
- 使用 break 命令在这个地方加一个断点:
- 由于程序绑定端口号是 redis-server 启动时初始化的,为了能触发这个断点,再次使用 run 命令重启下这个程序,GDB 第一次会触发 main() 函数处的断点,输入 continue 命令继续运行,接着触发 anet.c:455处的断点
- 现在断点停在第 455 行,所以当前文件就是 anet.c,可以直接使用“break 行号”添加断点。
- 例如,可以在第 458 行、464 行、466 行分别加一个断点,看看这个函数执行完毕后走哪个 return 语句退出,则可以执行:
- 添加好这三个断点以后,使用 continue 命令继续运行程序,发现程序运行到第 466 行中断下来
- 说明 redis-server 绑定端口号并设置侦听(listen)成功,再打开一个 SSH 窗口,验证一下,发现 6379 端口确实已经处于侦听状态了:
backtrace 命令(简写为 bt)查看当前调用堆栈
- 接上,redis-server 现在中断在 anet.c:466 行,可以通过 backtrace 命令来查看当前的调用堆栈:
- 一共有 6 层堆栈,最顶层是 main() 函数,最底层是断点所在的 anetListen() 函数,堆栈编号分别是 #0 ~ #5
- 如果想切换到其他堆栈处,可以使用 frame 命令(简写为 f),该命令的使用方法是“frame 堆栈编号(编号不加 #)”。在这里依次切换至堆栈顶部,然后再切换回 #0 练习一下:
- 通过查看上面的各个堆栈,可以得出这里的调用层级关系,即:
- main() 函数在第 5162 行调用了 initServer() 函数
- initServer() 在第 2795 行调用了 listenToPort() 函数
- listenToPort() 在第 2651 行调用了 anetTcp6Server() 函数
- anetTcp6Server() 在第 524 行调用了 _anetTcpServer() 函数
- _anetTcpServer() 函数在第 501 行调用了 anetListen() 函数
- 当前断点正好位于 anetListen() 函数中
info break 命令(简写为 info b)查看加了哪些断点及enable、disable 和 delete
- 通过上面的内容片段可以知道,目前一共增加了 6 个断点,其他信息比如每个断点的位置(所在的文件和行号)、内存地址、断点启用和禁用状态信息也一目了然。
- 如果我们想禁用某个断点,使用“disable 断点编号”就可以禁用这个断点了,被禁用的断点不会再被触发;
- 被禁用的断点也可以使用“enable 断点编号”重新启用。
- 使用 disable 1 以后,第一个断点的 Enb 一栏的值由 y 变成 n,重启程序也不会再次触发:
- 如果 disable 命令和 enable 命令不加断点编号,则分别表示禁用和启用所有断点:
- 使用“delete 编号”可以删除某个断点,如 delete 2 3 则表示要删除的断点 2 和断点 3:
- 如果输入 delete 不加命令号,则表示删除所有断点。
list 命令(简写为 l)查看当前断点处的代码
- 使用 frame 命令切换到刚才的堆栈 #3 处,然后输入 list 命令看下效果:
- 断点停在第 2795 行,输入 list 命令以后,会显示第 2795 行前后的 10 行代码,再次输入 list 命令试一下:
- 代码继续往后显示 10 行,第一次输入 list 命令会显示断点处前后的代码,继续输入 list 指令会以递增行号的形式继续显示剩下的代码行,一直到文件结束为止。
- list 指令还可以往前和往后显示代码,命令分别是“list + (加号)”和“list - (减号)”:
- list 默认显示多少行可以通过修改相关的 GDB 配置。
- list 不仅可以显示当前断点处的代码,也可以显示其他文件某一行的代码,更多的用法可以在 GDB 中输入 help list 查看(也可以通过):
- 可以使用 list FILE:LINENUM 来显示某个文件的某一行处的代码。
print 命令(简写为 p)在调试过程中查看变量的值、修改当前内存中的变量值
- 使用 print 命令分别打印出 server.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(errno) 将这个错误码对应的文字信息打印出来
- print 命令不仅可以输出表达式结果,同时也可以修改变量的值
- 将上文中的端口号从 6379 改成 6666 试试:
- 一个变量值修改后能否起作用要看这个变量的具体位置和作用
- 对于表达式 int a = b / c ; 如果将 c 修改成 0 ,那么程序就会产生除零异常。
- 利用 print 命令,不仅可以查看程序运行过程中的各个变量的状态值,也可以通过临时修改变量的值来控制程序的行为。
ptype (print type)就是输出一个变量的类型
- 输出 Redis 堆栈 #4 的变量 server 和变量 server.port 的类型:
- 对于一个复合数据类型的变量,ptype 不仅列出了这个变量的类型( 这里是一个名叫 redisServer 的结构体),而且详细地列出了每个成员变量的字段名。