redis调试起步
1.下载redis源码并安装
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar xzf redis-5.0.7.tar.gz
mv redis-5.0.7 redis
make MALLOC=libc CFLAGS="-g -O0"
make install
2.使用gdb调试
gdb redis-server
进入命令行界面:
(gdb) start
Temporary breakpoint 1 at 0x394c4: file server.c, line 4040.
Starting program: /usr/local/bin/redis-server
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe018) at server.c:4040
4040 int main(int argc, char **argv) {
使用start命令启动调试,看到程序停止在程序入口server.c:4040处.
具体的启动流程不再看了,重点关注执行命令的流程:
使用c命令,使程序执行,运行起来后,看看当前程序的堆栈:
(gdb) bt
#0 0x00007ffff7344bb7 in epoll_wait (epfd=5, events=0x555555928580, maxevents=4192, timeout=100) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1 0x000055555557f242 in aeApiPoll (eventLoop=0x5555559201f0, tvp=0x7fffffffde70) at ae_epoll.c:112
#2 0x000055555557ff06 in aeProcessEvents (eventLoop=0x5555559201f0, flags=11) at ae.c:411
#3 0x00005555555801fd in aeMain (eventLoop=0x5555559201f0) at ae.c:501
#4 0x000055555558dcd4 in main (argc=1, argv=0x7fffffffe018) at server.c:4234
可以看到当前程序的#3在aeMain函数上,这是一个epoll时间轮询函数,当接收到客户端请求或者设定的轮询时间达到时候会唤醒执行:
打一个断点:
(gdb) br ae.c:501
Breakpoint 2 at 0x5555555801ec: file ae.c, line 501.
(gdb) info br
Num Type Disp Enb Address What
2 breakpoint keep y 0x00005555555801ec in aeMain at ae.c:501
然后用客户端连接一下:redis-cli:
执行一个简单的命令:
127.0.0.1:6379> set foo bar
程序进入断点:
│442 if (!invert && fe->mask & mask & AE_READABLE) { │
>│443 fe->rfileProc(eventLoop,fd,fe->clientData,mask); //事件处理函数 │
│444 fired++; │
│445 }
看一下客户端传过来的值(进行了封装):
(gdb) s
readQueryFromClient (el=0x5555559201f0, fd=8, privdata=0x55555594a2e0, mask=1) at networking.c:1519
(gdb) s
(gdb) p *c
$1 = {id = 3, fd = 8, db = 0x555555934a10, name = 0x0, querybuf = 0x555555949491 "", qb_pos = 0, pending_querybuf = 0x5555559414e3 "", querybuf_peak = 0, argc = 0, argv = 0x555555941980,
cmd = 0x0, lastcmd = 0x5555558a34a0 <redisCommandTable+13440>, reqtype = 0, multibulklen = 0, bulklen = -1, reply = 0x555555949270, reply_bytes = 0, sentlen = 0, ctime = 1576494900,
lastinteraction = 1576494911, obuf_soft_limit_reached_time = 0, flags = 0, authenticated = 0, replstate = 0, repl_put_online_on_ack = 0, repldbfd = 0, repldboff = 0, repldbsize = 0,
replpreamble = 0x0, read_reploff = 0, reploff = 0, repl_ack_off = 0, repl_ack_time = 0, psync_initial_offset = 0, replid = '\000' <repeats 40 times>, slave_listening_port = 0,
slave_ip = '\000' <repeats 45 times>, slave_capa = 0, mstate = {commands = 0x0, count = 0, cmd_flags = 0, minreplicas = 0, minreplicas_timeout = 0}, btype = 0, bpop = {timeout = 0,
keys = 0x5555559492b0, target = 0x0, xread_count = 0, xread_group = 0x0, xread_consumer = 0x0, xread_retry_time = 0, xread_retry_ttl = 0, xread_group_noack = 0, numreplicas = 0,
reploffset = 0, module_blocked_handle = 0x0}, woff = 0, watched_keys = 0x555555949320, pubsub_channels = 0x555555949360, pubsub_patterns = 0x5555559493d0, peerid = 0x0,
client_list_node = 0x5555559419d0, bufpos = 0, buf = "*200\r\n*6\r\n$4\r\nincr\r\n:2\r\n", '\000' <repeats 9532 times>...}
从fd文件流中读取发送的消息:
1545 nread = read(fd, c->querybuf+qblen, readlen);
读取完后查看一下redis客户端发送过来的内容:
(gdb) p c->querybuf
$4 = (sds) 0x55555594e505 "*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
解析客户端命令:
1285 int processMultibulkBuffer(client *c)
客户端的参数存储在argv里,参数的个数argc
(gdb) p (char*)(server.current_client.argv[0].ptr)
$12 = 0x555555949543 "set"
(gdb) p (char*)(server.current_client.argv[1].ptr)
$13 = 0x555555949523 "foo"
(gdb) p (char*)(server.current_client.argv[2].ptr)
$14 = 0x555555949503 "bar"
(gdb) p server.current_client.argc
$15 = 3
命令解析:
2590 c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
解析为set命令的结构:
(gdb) p *c->cmd
$17 = {name = 0x555555661df9 "set", proc = 0x5555555b4823 <setCommand>, arity = -3, sflags = 0x555555661dfd "wm", flags = 5, getkeys_proc = 0x0, firstkey = 1, lastkey = 1, keystep = 1,
microseconds = 0, calls = 0}
接下来是大量判断server当前状态以及配置是否允许执行命令
最终执行命令:
│2763 call(c,CMD_CALL_FULL);
处理每次执行命令后的基于时间的流程:
469 processed += processTimeEvents(eventLoop);
看看客户端的执行返回:
127.0.0.1:6379> set foo bar
OK
(1477.89s)
好了,redis基本的命令执行过程使用gdb观察完毕.