调试
Redis 服务器进程调试指南
Redis 的开发重点是稳定性。我们尽一切努力确保每个版本都提供稳定且无崩溃的产品。但是,如果您需要调试 Redis 进程本身,请继续阅读。
当 Redis 崩溃时,它会生成一个详细的崩溃报告。但是,有时仅查看崩溃报告还不够,Redis 核心团队也无法独立重现该问题。在这种情况下,我们需要能够重现该问题的用户提供帮助。
本指南介绍了如何使用 GDB 来提供 Redis 开发人员更轻松地跟踪错误所需的信息。
什么是 GDB?
GDB 是 Gnu 调试器:一个能够检查另一个程序内部状态的程序。通常,跟踪和修复错误需要收集更多关于程序在错误发生时状态的信息,因此 GDB 是一个非常有用的工具。
GDB 可以以两种方式使用
- 它可以附加到正在运行的程序,并在运行时检查其状态。
- 它可以使用称为核心文件的工具来检查已终止程序的状态,即程序运行时的内存映像。
从调查 Redis 错误的角度来看,我们需要使用这两种 GDB 模式。能够重现错误的用户将 GDB 附加到正在运行的 Redis 实例,并在发生崩溃时创建core
文件,开发人员将使用该文件来检查 Redis 在崩溃时的内部状态。
通过这种方式,开发人员可以在没有用户帮助的情况下在其计算机上执行所有检查,而用户可以自由地重新启动其生产环境中的 Redis。
编译 Redis 时不进行优化
默认情况下,Redis 使用-O2
开关编译,这意味着启用了编译器优化。这使 Redis 可执行文件更快,但同时它也使 Redis(与任何其他程序一样)更难以使用 GDB 进行检查。
最好将 GDB 附加到使用make noopt
命令(而不是仅使用普通的make
命令)编译的 Redis,而不进行优化。但是,如果您在生产环境中已经运行了 Redis,则如果这会给您带来问题,则无需重新编译和重新启动它。GDB 仍然可以使用已使用优化进行编译的可执行文件。
您不必过分担心不进行优化编译 Redis 会导致性能下降。这不太可能在您的环境中造成问题,因为 Redis 不是很依赖 CPU。
将 GDB 附加到正在运行的进程
如果您有一个正在运行的 Redis 服务器,您可以将 GDB 附加到它,以便如果 Redis 崩溃,就可以检查内部并生成core dump
文件。
将 GDB 附加到 Redis 进程后,它将像往常一样继续运行,没有任何性能损失,因此这不是一个危险的过程。
为了附加 GDB,您首先需要的是正在运行的 Redis 实例的进程 ID(进程的pid)。您可以使用redis-cli
轻松获得它
$ redis-cli info | grep process_id
process_id:58414
在上面的示例中,进程 ID 为58414。
登录到您的 Redis 服务器。
(可选但推荐)启动screen或tmux或任何其他程序,以确保您的 GDB 会话不会在您的 ssh 连接超时时关闭。您可以在这篇文章中了解更多关于 screen 的信息。
通过输入以下命令将 GDB 附加到正在运行的 Redis 服务器
$ gdb <path-to-redis-executable> <pid>
例如
$ gdb /usr/local/bin/redis-server 58414
GDB 将启动并附加到正在运行的服务器,并打印类似于以下内容的信息
Reading symbols for shared libraries + done
0x00007fff8d4797e6 in epoll_wait ()
(gdb)
此时,GDB 已附加,但您的 Redis 实例被 GDB 阻塞。为了让 Redis 实例继续执行,只需在 GDB 提示符下输入continue,然后按回车键。
(gdb) continue
Continuing.
完成!现在您的 Redis 实例已附加了 GDB。现在您可以等待下次崩溃。:)
现在,如果您使用它运行 GDB,则可以通过按Ctrl-a a键组合来分离您的 screen/tmux 会话。
崩溃后
Redis 有一个命令可以模拟分段错误(换句话说,严重的崩溃),使用DEBUG SEGFAULT
命令(当然不要对实际的生产实例使用它!因此,我将使用此命令来使我的实例崩溃,以显示在 GDB 端会发生什么
(gdb) continue
Continuing.
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0xffffffffffffffff
debugCommand (c=0x7ffc32005000) at debug.c:220
220 *((char*)-1) = 'x';
如您所见,GDB 检测到 Redis 崩溃,甚至能够显示导致崩溃的文件名和行号。这已经比 Redis 崩溃报告回溯(仅包含函数名称和二进制偏移量)好多了。
获取堆栈跟踪
首先要做的是使用 GDB 获取完整的堆栈跟踪。这很简单,只需使用bt命令
(gdb) bt
#0 debugCommand (c=0x7ffc32005000) at debug.c:220
#1 0x000000010d246d63 in call (c=0x7ffc32005000) at redis.c:1163
#2 0x000000010d247290 in processCommand (c=0x7ffc32005000) at redis.c:1305
#3 0x000000010d251660 in processInputBuffer (c=0x7ffc32005000) at networking.c:959
#4 0x000000010d251872 in readQueryFromClient (el=0x0, fd=5, privdata=0x7fff76f1c0b0, mask=220924512) at networking.c:1021
#5 0x000000010d243523 in aeProcessEvents (eventLoop=0x7fff6ce408d0, flags=220829559) at ae.c:352
#6 0x000000010d24373b in aeMain (eventLoop=0x10d429ef0) at ae.c:397
#7 0x000000010d2494ff in main (argc=1, argv=0x10d2b2900) at redis.c:2046
这显示了回溯,但我们也希望使用info registers命令转储处理器寄存器
(gdb) info registers
rax 0x0 0
rbx 0x7ffc32005000 140721147367424
rcx 0x10d2b0a60 4515891808
rdx 0x7fff76f1c0b0 140735188943024
rsi 0x10d299777 4515796855
rdi 0x0 0
rbp 0x7fff6ce40730 0x7fff6ce40730
rsp 0x7fff6ce40650 0x7fff6ce40650
r8 0x4f26b3f7 1327936503
r9 0x7fff6ce40718 140735020271384
r10 0x81 129
r11 0x10d430398 4517462936
r12 0x4b7c04f8babc0 1327936503000000
r13 0x10d3350a0 4516434080
r14 0x10d42d9f0 4517452272
r15 0x10d430398 4517462936
rip 0x10d26cfd4 0x10d26cfd4 <debugCommand+68>
eflags 0x10246 66118
cs 0x2b 43
ss 0x0 0
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
请确保在您的错误报告中包含这两个输出。
获取核心文件
下一步是生成核心转储,即正在运行的 Redis 进程的内存映像。这使用gcore
命令完成
(gdb) gcore
Saved corefile core.58414
现在您拥有要发送给 Redis 开发人员的核心转储,但重要的是要理解这恰好包含 Redis 实例在崩溃时所包含的所有数据;Redis 开发人员将确保不会与任何人共享内容,并在不再用于调试目的后立即删除该文件,但请注意,通过发送核心文件,您是在发送您的数据。
发送给开发人员的内容
最后,您可以将所有内容发送给 Redis 核心团队
- 您正在使用的 Redis 可执行文件。
- bt命令生成的堆栈跟踪和寄存器转储。
- 您使用 gdb 生成的核心文件。
- 有关您正在使用的操作系统和 GCC 版本以及 Redis 版本的信息。
谢谢
您的帮助非常重要!许多问题只能通过这种方式跟踪。所以谢谢!