调试
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 进行检查。
最好使用 make noopt
命令在不使用优化的情况下将 GDB 附加到 Redis(而不是仅仅使用简单的 make
命令)。但是,如果你已经在生产环境中运行 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 或任何其他程序,以确保如果你的 ssh 连接超时,你的 GDB 会话不会被关闭。你可以在 这篇文章 中了解有关 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,然后按 Enter。
(gdb) continue
Continuing.
完成!现在你的 Redis 实例已附加 GDB。现在你可以等待下一次崩溃。 :)
现在是时候分离你的 screen/tmux 会话了,如果你使用它运行 GDB,请按 Ctrl-a a 键组合。
崩溃后
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 版本的信息。
谢谢
你的帮助非常重要!许多问题只能通过这种方式来跟踪。所以,谢谢!