Upgrade to Pro — share decks privately, control downloads, hide ads and more …

デバッガでRedisのコードを読んでみよう

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Yoshiyuki Asaba Yoshiyuki Asaba
September 11, 2018
16k

 デバッガでRedisのコードを読んでみよう

freee社内でgdbを使ってRedisのソースコードを読む勉強会をしたときの資料です。

Avatar for Yoshiyuki Asaba

Yoshiyuki Asaba

September 11, 2018
Tweet

More Decks by Yoshiyuki Asaba

Transcript

  1. 3 デバッグ環境準備 今回はRedis4.0.11を対象とします。OSはLinux(Ubuntu)を想定しています。 もしくはDockerを動かせる環境であればなんでも大丈夫です。 • 必要なツールのインストール sudo apt-get install build-essential

    gdb cgdb • Emacsを使いたい人(任意) sudo add-apt-repository ppa:kelleyk/emacs sudo apt-get update sudo apt-get install emacs26-nox • kernelの設定 sudo bash -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'
  2. 6 Dockerを使う場合 % git clone https://github.com/y-asaba/docker-redis-debug.git % cd docker-redis-debug %

    sudo docker build -t debug-redis 4.0 % sudo docker run -it -p 9876:9876 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined debug-redis:latest /bin/bash コンテナ内でのコマンド cd /root/redis-4.0.11/src ./redis-server --port 9876 --protected-mode no & gdb -p pid コンテナ内へのredisへの接続 redis-cli -h 127.0.0.1 -p 9876
  3. Redisって何? 8 • インメモリのいくつかのデータ構造を格納するためのサーバーソフトウェア Key Value Storeの一種 Valueには文字列型(勝手にintegerにする場合もある)、リスト型、位置情報などを入れられる • C言語で書かれている

    • シングルプロセス、シングルスレッド ただしバックグラウンド処理を別スレッドで動かす場合もある • クラスタリングできたり、データを外に書き出したりもできる ただ、RDBMSと比較してデータ信頼性を当てにしないほうが良いです(そういう思想で作って いる)
  4. 10 C言語おさらい(正確に知りたい場合は文献を調べて) • シンプルな言語(だと個人的には思っている) メモリ管理は自分でやらないといけない syntaxはなんとなくわかると思う • 構造体 データの型を定義しているといえるが、一方で必要なサイズのバイト列を定義しているとも言 える

    void* はとくに何でもありな世界 (gdb) ptype robj type = struct redisObject { unsigned int type : 4; unsigned int encoding : 4; unsigned int lru : 24; int refcount; void *ptr; } (gdb) p *val $75 = { type = 0, encoding = 1, lru = 9087676, refcount = 2147483647, ptr = 0x3 } (gdb) x /12x val 0x7fe0a8017bb0: 0x10 0xbc 0xaa 0x8a 0xff 0xff 0xff 0x7f 0x7fe0a8017bb8: 0x03 0x00 0x00 0x00
  5. 11 ハッシュテーブルとハッシュ値 • ハッシュテーブルはキーを何らかのハッシュ関数でハッシュ値にして、巨大な配列へアクセ スする RedisはSipHashを利用 • インデックスの衝突 チェイン法 (Redisはこっち)

    オープンアドレス法 • rehashing だんだんhash tableがでかくなると衝突しやすくなってくるので、サイズを拡張する Redisでどうやっているかはコードを読んでみてね バケット abc 3458911415056046202 ハッシュテーブル Sip hash function ハッシュ値からインデックスに変換
  6. 14 gdbの動かし方 1. main関数から動かす % gdb hoge 2. 今動いているプロセスにアタッチして途中から始める %

    gdb -p pid (pidはps コマンドやRedisのログをみてください) ただ、素のgdbだとつらいので、cgdbかemacsでgdbを動かす、もしくは別のなにかGUIをもつも の(DDD等)を使うのをおすすめします
  7. 16 簡単な練習1 processをアタッチした直後のbacktraceをみてみよう (gdb) attach 7242 ... (gdb) bt #0

    0x00007f324bbb6a13 in epoll_wait () at ../sysdeps/unix/syscall-template.S:84 #1 0x000000000042636c in aeApiPoll (eventLoop=0x7f324b63a0f0, tvp=0x7ffe82eba3c0) at ae_epoll.c:112 #2 0x000000000042702b in aeProcessEvents (eventLoop=0x7f324b63a0f0, flags=11) at ae.c:411 #3 0x0000000000427322 in aeMain (eventLoop=0x7f324b63a0f0) at ae.c:501 #4 0x0000000000434210 in main (argc=3, argv=0x7ffe82eba568) at server.c:3899
  8. 17 簡単な練習2 今動いているスレッド一覧をみてみよう (gdb) info threads Id Target Id Frame

    * 1 Thread 0x7f324c7bc780 (LWP 7242) "redis-server" 0x00007f324bbb6a13 in epoll_wait () at ../sysdeps/unix/syscall-template.S:84 2 Thread 0x7f324b5ff700 (LWP 7243) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185 3 Thread 0x7f324adfe700 (LWP 7244) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185 4 Thread 0x7f324a5fd700 (LWP 7245) "redis-server" pthread_cond_wait@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185
  9. 18 Redis command table server.c 2番目(getCommandとか)が、実際のコマンドを処理する関数なので、それにbreakpointを貼れ ば良い struct redisCommand redisCommandTable[]

    = { {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0}, {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0}, {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0}, … {"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0}, {"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0}, {"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0} };
  10. 19 例題1: SETとGETの挙動を追う SETをまずはみてみましょう • breakpoint setCommandsにbreakpointを設定します redis-cliでset commandを叩く stack

    traceを見てみる frameを移動してみる (gdb) b setCommand Breakpoint 8 at 0x457005: file t_string.c, line 98. (gdb) c Continuing. % redis-cli --port 8000 127.0.0.1:8000> set hoge 111
  11. 20 ハッシュテーブルデータ構造 (gdb) ptype dict type = struct dict {

    dictType *type; void *privdata; dictht ht[2]; long rehashidx; unsigned long iterators; } (gdb) p *d $94 = {type = 0x774060 <dbDictType>, privdata = 0x0, ht = {{table = 0x7fe0a80223c0, size = 8, sizemask = 7, used = 5}, {table = 0x0, size = 0, sizemask = 0, used = 0}}, rehashidx = -1, iterators = 0} (gdb) ptype dictht type = struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } (gdb) p *ht->table[index] $96 = {key = 0x7fe0a8016769, v = {val = 0x7fe0a8017b90, u64 = 140602868071312, s64 = 140602868071312, d = 6.9467046820784346e-310}, next = 0x0}
  12. 21 ハッシュテーブルデータ構造 • dict 通常はht[0]がハッシュテーブルへのポインタ rehash中は[1]にも入る • dictht **tableがバケット(単方向リスト)の配列を指す size:

    bucket size sizemask: ハッシュ値からどのバケットへ入れるかを計算するための値 idx = h & d->ht[table].sizemask; という計算をしているのがそう
  13. 23 iterator (gdb) ptype iter type = struct dictIterator {

    dict *d; long index; int table; int safe; dictEntry *entry; dictEntry *nextEntry; long long fingerprint; } * • dictNextをみるとたどっているのがわかる (gdb) bt #0 dictNext (iter=0x7fe0a801c5a0) at dict.c:565 #1 0x0000000000448dba in keysCommand (c=0x7fe0a811b700) at db.c:529 #2 0x000000000042f9b0 in call (c=0x7fe0a811b700, flags=15) at server.c:2229 #3 0x00000000004305c9 in processCommand (c=0x7fe0a811b700) at server.c:2515 #4 0x0000000000440be2 in processInputBuffer (c=0x7fe0a811b700) at networking.c:1357 #5 0x0000000000440fb5 in readQueryFromClient (el=0x7fe0a803a0f0, fd=8, privdata=0x7fe0a811b700, mask=1) at networking.c:1447 #6 0x0000000000427108 in aeProcessEvents (eventLoop=0x7fe0a803a0f0, flags=11) at ae.c:443 #7 0x0000000000427322 in aeMain (eventLoop=0x7fe0a803a0f0) at ae.c:501 #8 0x0000000000434210 in main (argc=3, argv=0x7ffec121be08) at server.c:3899