跳转到主要内容

调试步骤

KeyDB 的开发非常注重稳定性:我们在每个版本中都尽最大努力,以确保您能体验到一个非常稳定的产品,并且不会出现崩溃。然而,即使我们尽了最大努力,也不可能 100% 成功地避免所有关键性错误。

当 KeyDB 崩溃时,它会生成一份关于所发生情况的详细报告。然而,有时仅查看崩溃报告是不够的,KeyDB 核心团队也无法独立复现问题。在这种情况下,我们需要能够复现问题的用户的帮助。

这篇简短的指南将展示如何使用 GDB 来提供 KeyDB 开发者更轻松地追踪错误所需的所有信息。

什么是 GDB?#

GDB 是 Gnu Debugger(Gnu 调试器)的缩写:它是一个能够检查另一个程序内部状态的程序。通常,追踪和修复错误就是在错误发生时收集更多关于程序状态的信息,因此 GDB 是一个非常有用的工具。

GDB 有两种使用方式:

  • 它可以附加到一个正在运行的程序上,并在运行时检查其状态。
  • 它可以使用所谓的核心文件(core file)来检查一个已经终止的程序的状态,该文件是程序运行时内存的映像。

从调查 KeyDB 错误的角度来看,我们需要同时使用这两种 GDB 模式:能够复现错误的用户将 GDB 附加到他们正在运行的 KeyDB 实例上,当崩溃发生时,他们创建 core 文件,然后开发者将使用这个文件来检查崩溃时 KeyDB 的内部状态。

通过这种方式,开发者可以在自己的计算机上进行所有检查,而无需用户的帮助,用户则可以自由地在生产环境中重启 KeyDB。

在无优化的情况下编译 KeyDB#

默认情况下,KeyDB 是使用 -O2 开关编译的,这意味着启用了编译器优化。这使得 KeyDB 可执行文件更快,但同时也使 KeyDB(像任何其他程序一样)更难使用 GDB 进行检查。

最好将 GDB 附加到使用 make noopt 命令(而不是仅使用普通的 make 命令)编译的、无优化的 KeyDB 上。但是,如果您在生产环境中已经有一个正在运行的 KeyDB,并且重新编译和重启会给您带来问题,那么就没有必要这样做。即使效果稍差,GDB 仍然可以用于经过优化编译的可执行文件。

如果您能在第一次崩溃后,确保使用 make noopt 重新编译 KeyDB,那就太好了,这样下次追踪问题就会更简单。

您不必担心在无优化情况下编译 KeyDB 会导致性能下降,这很不可能在您的环境中引起问题,因为它通常只涉及很小的百分比,因为 KeyDB 并不是非常受 CPU 限制的(它为了处理查询会进行大量的 I/O 操作)。

将 GDB 附加到正在运行的进程#

如果您已经有一个正在运行的 KeyDB 服务器,您可以将 GDB 附加到它上面,这样如果 KeyDB 崩溃,就可以检查其内部状态并生成一个 core dump 文件。

将 GDB 附加到 KeyDB 进程后,它将像往常一样继续运行,不会有任何性能损失,所以这是一个安全的操作。

为了附加 GDB,您首先需要正在运行的 KeyDB 实例的进程 ID(即进程的 pid)。您可以使用 keydb-cli 轻松获取它:

$ keydb-cli info | grep process_id
process_id:58414

在上面的例子中,进程 ID 是 58414

  • 登录到您的 KeyDB 服务器。

  • (可选但推荐)启动 screentmux 或任何其他程序,以确保您的 GDB 会话不会因为 ssh 连接超时而关闭。如果您不知道 screen 是什么,帮自己一个忙,阅读这篇文章

  • 通过输入以下命令将 GDB 附加到正在运行的 KeyDB 服务器:

    gdb <path-to-keydb-executable> <pid>

    例如:gdb /usr/local/bin/keydb-server 58414

GDB 将启动并附加到正在运行的服务器,并打印出类似下面的内容:

Reading symbols for shared libraries + done
0x00007fff8d4797e6 in epoll_wait ()
(gdb)
  • 此时 GDB 已附加,但您的 KeyDB 实例被 GDB 阻塞了。为了让 KeyDB 实例继续执行,只需在 GDB 提示符下输入 continue,然后按回车键。

    (gdb) continue
    Continuing.
  • 完成!现在您的 KeyDB 实例已附加了 GDB。您可以等待……下一次崩溃了 :)

  • 现在是时候分离您的 screen / tmux 会话了,如果您正在使用它运行 GDB,请按通常的 Ctrl-a a 组合键。

崩溃之后#

KeyDB 有一个命令可以用来模拟分段错误(换句话说,就是一次严重的崩溃),即使用 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 检测到 KeyDB 崩溃了,并且甚至能向我显示导致崩溃的文件名和行号。这已经比 KeyDB 崩溃报告的回溯信息(只包含函数名和二进制偏移量)要好得多了。

获取堆栈跟踪#

首先要做的是用 GDB 获取完整的堆栈跟踪。这就像使用 bt 命令一样简单:(它是 backtrace 的缩写)

(gdb) bt
#0 debugCommand (c=0x7ffc32005000) at debug.c:220
#1 0x000000010d246d63 in call (c=0x7ffc32005000) at KeyDB.c:1163
#2 0x000000010d247290 in processCommand (c=0x7ffc32005000) at KeyDB.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 KeyDB.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

确保在您的错误报告中包含这两种输出。

获取核心文件#

下一步是生成核心转储文件(core dump),即正在运行的 KeyDB 进程的内存映像。这是通过 gcore 命令完成的:

(gdb) gcore
Saved corefile core.58414

现在您有了可以发送给 KeyDB 开发者的核心转储文件,但重要的是要明白,这个文件恰好包含了 KeyDB 实例在崩溃时内部的所有数据;KeyDB 开发者将确保不与任何其他人分享其内容,并会在不再用于调试目的后立即删除该文件,但请注意,通过发送核心文件,您也在发送您的数据。

要发送给开发者的内容#

最后,您可以将所有东西发送给 KeyDB 核心团队:

  • 您正在使用的 KeyDB 可执行文件。
  • bt 命令生成的堆栈跟踪,以及寄存器转储。
  • 您用 gdb 生成的核心文件。
  • 关于您正在使用的操作系统、GCC 版本以及 KeyDB 版本的信息。

谢谢#