Dockerを使ってMySQLのソースコードを読む環境を作ってみる

RDBMSといえば大抵のシステムでは何かしらの製品が使われているかと思います。そして運用を続けていると、RDBMSまわりで何かしらのトラブルが発生し、深く調査することがあるかと思います。マニュアルをしっかり読むのも大事ですが、ソースコードを追うと「なるほどね」という感動を味わえたりとか、エンジニア的に知的好奇心としてソースコードレベルである程度知っておきたい、というのがあるんじゃないかなと思います。

それで、特にミドルウェアのソースコードを読むときは、自分はデバッグビルドしてgdbで動かしながら追いかけることが多いのですが、デバッグビルドのやり方を各自探るのもめんどくさいので、Dockerのイメージを配っていつでも読めるようにしておくと嬉しいんじゃないかなと思いました。

例えば社内でMySQLのソースコードを読んでみようみたいな勉強会があったときに、AWS ECS registryにイメージ置いておいたのでpullしといてね、みたいな感じです。ということで色々やってみました。

もちろんvagrantを使ってVM上にデバッグ環境を作るというのもできますが、特に自分の場合はemacsの環境をいちいち作るのもめんどくさいので、Mac上の設定済みのemacsを活用したいというのもあります。

あ、ちなみにDockerはそんなに詳しくないので間違っていたらすみません。あと、本当はPostgreSQL屋さんなので、MySQLのことで間違っていたらごめんなさい

# 構成
以下のような構成を考えています。

* MySQLをdebug buildしたイメージを作る
* mysqldをDocker上でgdbserverを経由して動かす
* MacOS上のgdbからremote debugする

# Docker環境の準備
普段、Macbook使っているので、[Docker for Mac](https://docs.docker.com/engine/installation/mac/)を使ってビルドをします。たぶん他の環境でも動くと思いますが、試していませんので、どなたか試してみてください。

# Dockerfile準備
以下のような感じでデバッグビルドします。あと、gdbserverが欲しいのでapt-getでインストールしておきます。

```
FROM ubuntu:trusty

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install build-essential gdb gdbserver cmake wget curl libncurses5-dev -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'

WORKDIR /mysql-debug
RUN wget http://ftp.jaist.ac.jp/pub/mysql/Downloads/MySQL-5.6/mysql-5.6.34.tar.gz
RUN tar zxf mysql-5.6.34.tar.gz; pushd mysql-5.6.34; CFLAGS=-O0 cmake -DWITH_PIC=1 -DWITH_DEBUG=1 -DCMAKE_INSTALL_PREFIX=/root/mysql-debug; make -j2 install; popd; rm -rf mysql-5.6.34

RUN echo '[mysqld]\nskip-host-cache\nskip-name-resolve\nskip-grant-tables' > /etc/my.cnf

RUN rm /etc/sysctl.d/10-ptrace.conf; sysctl -p

WORKDIR /root/mysql-debug
RUN ./scripts/mysql_install_db --force
```

Dockerはport forwardの仕組みがありますが、Docker上のmysqldから見ると、デフォルトのgrantだと拒否されてしまうので、my.cnfに `skip-grant` を入れておきます。

# docker run
Dockerはデフォルトだとptrace(2)が許可されていません。Docker上でgdbを動かすためには、docker runするときに以下のオプションを渡す必要があります。

* --cap-add=SYS_PTRACE
* --security-opt seccomp=unconfined

あと、symbol tableをremote debugで読み込むためには、実際のバイナリファイルをローカルで読ませるといけるので、Docker側からホストのファイルシステムをmountしてあげて、mysqldをコピーしてあげます。(以下の例だと、/Users/y-asaba/src/debug/hoge/mysqldというのがコピーされている)

まあ、要は以下のような感じで起動してみてください。

```
docker run -v /Users/y-asaba/src/debug/hoge:/mysql-debug --expose 2345 -p 3306:3306 -p 2345:2345 -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined mysqld-debug-build /bin/bash -c 'cd /root/mysql-debug; cp /root/mysql-debug/bin/mysqld /mysql-debug/; sudo gdbserver :2345 ./bin/mysqld --user=root'
```


# MacOSのgdbインストール
Mac上にgdbをインストールします。必ずインストール時に `--with-all-targets` を指定してください。でないと、Docker上でビルドしたmysqldを解釈できなくてデバッグできません。

```
% brew tap homebrew/dupes
% brew install gdb --with-all-targets
```

set osabiを実行して、 `GNU/Linux` が表示されていればokです。

```
% gdb
...
(gdb) set osabi
Requires an argument. Valid arguments are auto, default, none, GNU/Linux, Symbian, NetBSD/a.out, NetBSD/ELF, OpenBSD/ELF, WindowsCE, 
Cygwin, FreeBSD/a.out, FreeBSD/ELF, GNU/Hurd, QNX-Neutrino, Solaris, SVR4, DJGPP, DICOS, Darwin, SDE, AIX, LynxOS178, Newlib, OpenVMS.
```

# デバッグしてみる
`mysql -h 127.0.0.1 -uroot` とコマンドを打てば、Docker上のmysqldにログインできると思うので、まずはそれをやります。ここでは例としてonline DDLを追ってみたい、という想定で書きます。

## gdbの準備
remote debugの準備をします。必要な作業は、

- targetに接続する(target)
- symbol tableを読み込む(file)
- ソースコードのパスをいい感じに解決する(set substitute-path)

です。ソースコードはあらかじめダウンロードしておいてください。(以下の例だと、`/Users/y-asaba/src/debug/mysql-5.6.34`に展開されている想定)

break pointはalter tableのentry pointっぽい `mysql_alter_table` に一旦貼っておきます。

```
(gdb) target remote :2345
Remote debugging using :2345
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.warning: Could not load vsyscall page because no executable was specified
0x00007ffff7ddb2d0 in ?? ()
(gdb) file /Users/y-asaba/src/debug/hoge/mysql-debug/bin/mysqld
Reading symbols from /Users/y-asaba/src/debug/hoge/mysql-debug/bin/mysqld...done.
(gdb) set substitute-path /mysql-debug/mysql-5.6.34 /Users/y-asaba/src/debug/mysql-5.6.34
(gdb) b mysql_alter_table
Breakpoint 1 at 0x8591d1: file /mysql-debug/mysql-5.6.34/sql/sql_table.cc, line 7904.
```

## 止めてみる
break pointを貼ったので、実際にmysqlコマンド上で適当にalter tableを実行して、gdb側で以下のような感じで止めます。ちゃんと止まって `bt` を打つとsymbol名がちゃんと解決されているのを確認できるはずです。

```
mysql> alter table t add column b int;
```

```
(gdb) bt
#0  mysql_alter_table (thd=0x1920940, new_db=0x7fffa0005270 "hoge", new_name=0x0, create_info=0x7fffcdd695c0, table_list=0x7fffa0004d10, alter_info=0x7fffcdd69530, order_num=0, order=0x0, ignore=false) at /mysql-debug/mysql-5.6.34/sql/sql_table.cc:7904
#1  0x00000000009a31a2 in Sql_cmd_alter_table::execute (this=0x7fffa0005338, thd=0x1920940) at /mysql-debug/mysql-5.6.34/sql/sql_alter.cc:313
#2  0x00000000007e87f1 in mysql_execute_command (thd=0x1920940) at /mysql-debug/mysql-5.6.34/sql/sql_parse.cc:4975
#3  0x00000000007ebd42 in mysql_parse (thd=0x1920940, rawbuf=0x7fffa0004c20 "alter table t add column b int", length=30, parser_state=0x7fffcdd6b240) at /mysql-debug/mysql-5.6.34/sql/sql_parse.cc:6385
#4  0x00000000007dec8a in dispatch_command (command=COM_QUERY, thd=0x1920940, packet=0x20d1e41 "alter table t add column b int", packet_length=30) at /mysql-debug/mysql-5.6.34/sql/sql_parse.cc:1339
#5  0x00000000007ddd52 in do_command (thd=0x1920940) at /mysql-debug/mysql-5.6.34/sql/sql_parse.cc:1036
#6  0x00000000007a3cc8 in do_handle_one_connection (thd_arg=0x1920940) at /mysql-debug/mysql-5.6.34/sql/sql_connect.cc:982
#7  0x00000000007a37de in handle_one_connection (arg=0x1920940) at /mysql-debug/mysql-5.6.34/sql/sql_connect.cc:898
#8  0x0000000000e18c7f in heap_create (name=0x7a37de <handle_one_connection(void*)+51> "\270", create_info=0x7fffcdd6be80, res=0x0, created_new_share=0x7fffcdd6be80 "о\326\315\377\177") at /mysql-debug/mysql-5.6.34/storage/heap/hp_create.c:210
#9  0x00007ffff7787184 in ?? ()
#10 0x0000000000000000 in ?? ()
```

あとは気合で読み続ければ、何かがわかり始めるかなと思います。

# Amazon ECS Registryにイメージをアップロードする
Dockerイメージを作ったら、Amazon ECS Registryにアップロードします。以下のような感じでリポジトリを作ります。
(ちなみに個人のAWS consoleへのログインは、これまた個人のGoogle appsのSAMLでやっています。便利)

![Amazon_EC2_Container_Service.png](https://qiita-image-store.s3.amazonaws.com/0/49382/a7c2a20e-6442-60cf-2df6-3d41d4b746c3.png)

リポジトリ作ったらdocker imageをpushします。

```
% aws ecr get-login --region ap-northeast-1 | bash
% docker tag mysqld-debug-build:latest <account id>.dkr.ecr.ap-northeast-1.amazonaws.com/mysqld-debug-build:latest
% docker push <account id>.dkr.ecr.ap-northeast-1.amazonaws.com/mysqld-debug-build:latest
```

pullしてもらう場合は以下のコマンドを叩いてもらいます。

```
docker pull <account id>.dkr.ecr.ap-northeast-1.amazonaws.com/mysqld-debug-build:latest
```

これであとは同じような起動方法を各自実行してもらえれば、勉強会とかのときに捗るかと思います。（社内でそういう勉強会をやりたいと思いつつ、自分はできていませんが。。。）

# ハマリポイント
gdb remote debugはたまに固まることがあるので、その場合はcontainerを作り直して、再度やり直したほうが早いです。 

あと Docker for Macが妙にディスクを消費してしまうので、その場合は以下の手順で掃除してあげると幸せになるかもしれません。

https://github.com/docker/for-mac/issues/371

```
docker rm $(docker ps -a -q)
docker rmi $(docker images -q)
docker volume rm $(docker volume ls |awk '{print $2}')
(dockerを止める)
rm -rf ~/Library/Containers/com.docker.docker/Data/*
```

# 追記
Dockerfileをgithubに置きました。5.7用も作ってあります。
https://github.com/y-asaba/docker-mysql-debug