Redis崩溃调试

背景

Redis的代码质量一直被业内人士称赞,在极高的业务压力下也能有很好的稳定性。但是极端情况下,Redis也是有可能会Crash的。有时候因为种种原因,系统配置问题,磁盘空间写满了,进程权限不够等等,我们可能不会运气那么好,有一个core文件可以拿去调试。这个时候,Redis提供了几种异常崩溃情况下的Crash Report,很多时候我们基于Crash Report,再加上一定的分析就可以直接定位问题了。

Crash Report

在异常崩溃时,Redis会通过设置的signal handler来生成Crash Report,目前Redis为生成Crash Report捕获的异常信号主要有以下几种:

  • SIGSEGV
  • SIGFPE
  • SIGILL
  • SIGBUS

这4种信号应该能包含大部分程序异常崩溃情况了,最常见的就是SIGSEGV段错误了,除0异常SIGFPE有时候也会遇到。

当Redis收到上面4种信号之一时,会在设置的sigsegvHandler()函数中生成Crash Report,如下,

=== REDIS BUG REPORT START: Cut & paste starting from here ===
[19179] 12 Apr 18:47:42.599 #     Redis 2.8.19 crashed by signal: 11
[19179] 12 Apr 18:47:42.599 #     Failed assertion: <no assertion failed> (<no file>:0)
[19179] 12 Apr 18:47:42.599 # --- STACK TRACE
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(logStackTrace+0x4a)[0x7f5be2d6895a]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(debugCommand+0x1b0)[0x7f5be2d69ad0]
/lib64/libpthread.so.0(+0xf500)[0x7f5be3c1a500]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(debugCommand+0x1b0)[0x7f5be2d69ad0]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(call+0x8a)[0x7f5be2d2f12a]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(processCommand+0x5dd)[0x7f5be2d3017d]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(processInputBuffer+0x4d)[0x7f5be2d3b86d]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(readQueryFromClient+0xf0)[0x7f5be2d3cb70]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(aeProcessEvents+0x13d)[0x7f5be2d2804d]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(aeMain+0x2b)[0x7f5be2d2833b]
/home/dejun.xdj/kvs-kernel/src/libredis-server.so(runRedis+0x4f)[0x7f5be2d31eaf]
/home/dejun.xdj/kvs-kernel/src/redis-server /home/dejun.xdj/local/redis/conf/redis_7071.conf *:1071(main+0x180)[0x405db0]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x3d4ac1ecdd]
/home/dejun.xdj/kvs-kernel/src/redis-server /home/dejun.xdj/local/redis/conf/redis_7071.conf *:1071[0x4055e9]
[19179] 12 Apr 18:47:42.599 # --- INFO OUTPUT
...
...
[19179] 12 Apr 18:47:42.599 # --- CLIENT LIST OUTPUT
[19179] 12 Apr 18:47:42.599 # id=2 addr=127.0.0.1:30494 fd=5 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=debug read=0 write=0 type=admin next_opid=-1

[19179] 12 Apr 18:47:42.599 # --- CURRENT CLIENT INFO
[19179] 12 Apr 18:47:42.599 # client: id=2 addr=127.0.0.1:30494 fd=5 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=debug read=0 write=0 type=admin next_opid=-1
[19179] 12 Apr 18:47:42.600 # argv[0]: 'debug'
[19179] 12 Apr 18:47:42.600 # argv[1]: 'segfault'
[19179] 12 Apr 18:47:42.600 # --- REGISTERS
...
...
=== REDIS BUG REPORT END. Make sure to include from START to END. ===

       Please report the crash by opening an issue on github:

           http://github.com/antirez/redis/issues

  Suspect RAM error? Use redis-server --test-memory to verify it.

Crash Report主要包括4部分,

  • STACK TRACE,崩溃时的调用栈信息
  • INFO OUTPUT,崩溃时的Redis info命令输出
  • CLIENT OUTPUT,包括崩溃时的CURRENT CLIENT,这个可以看到崩溃时客户端执行的命令
  • REGISTERS,寄存器信息

其中对于调试最有帮助的就是STACK TRACE信息了,我们直接以上面的Crash Report来说明一下如何调试。

调试

Redis提供了debug segfault命令用于调试,我们直接给Redis发送这个命令,就可以在日志中生成类似于上面的崩溃报告(请不要在生产环境使用这个命令!!!)。

实际调试之前先说明一下,如果编译时没有加上-g选项,可执行文件中没有调试符号信息,是无法进行后面的调试的,考虑到性能的影响很小,Redis默认编译是带有-g选项的。

上面的STACK TRACE信息直接告诉我们了,出core的点在libredis-server.so中,简单分析可以知道是在执行debugCommand时出现段错误,函数后面的+号带的地址是函数内的代码偏移,我们只要知道函数的起始地址就可以获取出core的函数内代码地址了,进而可以通过addr2line获取地址对应的具体的源文件名和行号。

通过nm工具获取函数起始地址,

$nm -l /home/dejun.xdj/kvs-kernel/src/libredis-server.so  | grep debugCommand
000000000006d920 T debugCommand /home/dejun.xdj/kvs-kernel/src/debug.c:255
000000000007f3f0 T pfdebugCommand       /home/dejun.xdj/kvs-kernel/src/hyperloglog.c:1455

我们可以看到debugCommand的起始地址是0x6d920(十六进制),加上偏移0x1b0,可以知道出core的具体地址是0x6dad0,那么我们就可以很方便的获取到具体的行号了,

$addr2line -e /home/dejun.xdj/kvs-kernel/src/libredis-server.so 0x6dad0
/home/dejun.xdj/kvs-kernel/src/debug.c:304

参考源文件我们可以发现debug.c的304行存在非法地址访问,

*((char*)-1) = 'x';

总结

以上只是为了说明调试流程,举的一个简单例子,有时候定位了出core的点,可能还需要更为细致的分析,结合info输出和client输出。在没有core文件的场景下,Crash Report确实能够提供很大的帮助,上面的流程,有兴趣的同学可以直接做成一个脚本,直接分析日志,自动化的获取出core点的信息。

参考:http://antirez.com/news/43

诚聘英才:阿里云-技术专家-KVstore

上一篇:记一次Golang内存分析——基于go pprof


下一篇:predis连接问题(connection refused)排查