block_given?

ジャングル ブロック ナイ アルノハ jump ダケ。CC-BY-SA 3.0

MRIを読む (4) Macでもデバッグしたい

マシンが非力なので仮想マシン上では動作が重たい。 Mac上でデバッグする方法を覚える。

Homebrewでいれたrbenv + ruby-buildをつかって2.1.4をインストールしている状態。

どのコンパイラが使われたのか

clangである。なのでclangのデバッガを使ってソースを追う。

$ ruby -r rbconfig -e 'puts RbConfig::CONFIG["CC"]'
clang

デバッガ(lldb)でrubyを実行

lldbでrubyのデバッグ初級編 - CubicLouve

この方のブログ、よくあたる。

同じような感じで実行してみる

$ lldb `rbenv which ruby`
(lldb) target create "/usr/local/var/rbenv/versions/2.1.4/bin/ruby"
Current executable set to '/usr/local/var/rbenv/versions/2.1.4/bin/ruby' (x86_64).
(lldb) b rb_io_puts                                                                                                                                                                              Breakpoint 1: where = ruby`rb_io_puts, address = 0x0000000100070750
(lldb) r -e 'puts :hi'
Process 87570 launched: '/usr/local/var/rbenv/versions/2.1.4/bin/ruby' (x86_64)
Process 87570 stopped
* thread #1: tid = 0x1ad973, 0x0000000100070750 ruby`rb_io_puts, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100070750 ruby`rb_io_puts
ruby`rb_io_puts:
-> 0x100070750:  pushq  %rbp
   0x100070751:  movq   %rsp, %rbp
   0x100070754:  pushq  %r15
   0x100070756:  pushq  %r14
(lldb) bt
* thread #1: tid = 0x1ad973, 0x0000000100070750 ruby`rb_io_puts, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100070750 ruby`rb_io_puts
    frame #1: 0x00000001001880b0 ruby`vm_call0_body + 2320
    frame #2: 0x000000010017943f ruby`rb_funcallv + 207
    frame #3: 0x000000010018a9d7 ruby`vm_call_cfunc + 1063
    frame #4: 0x000000010018a322 ruby`vm_call_method + 866
    frame #5: 0x0000000100171aad ruby`vm_exec_core + 11933
    frame #6: 0x00000001001804a1 ruby`vm_exec + 129
    frame #7: 0x000000010018142c ruby`rb_iseq_eval_main + 396
    frame #8: 0x0000000100046744 ruby`ruby_exec_internal + 148
    frame #9: 0x000000010004666e ruby`ruby_run_node + 78
    frame #10: 0x000000010000143f ruby`main + 79
    frame #11: 0x00007fff8e1045fd libdyld.dylib`start + 1
    frame #12: 0x00007fff8e1045fd libdyld.dylib`start + 1

これでは読めないのでは。

ビルドし直す

man clangすると

-g  Generate debug information.  Note that Clang debug information works best at -O0.

とあるので、この通りにビルドする。既存の設定を確認する。

$ ruby -r rbconfig -e 'puts RbConfig::CONFIG["CFLAGS"]'
-O3 -Wno-error=shorten-64-to-32 -pipe

-g-O0を渡したがソースコードが表示されない。StackOverflowの以下の記事を参考にrbenv install-kオプションを渡す。

ruby - How do I get rbenv to keep debugging symbols? - Stack Overflow

実行

$ RUBY_CFLAGS='-g -O0' RUBY_CONFIGURE_OPTS="--with-readline-dir=/usr/local/opt/readline --with-openssl-dir=/usr/local/opt/openssl" rbenv install 2.1.4 -k

もう一度

でたね。

$ lldb `rbenv which ruby`
(lldb) target create "/usr/local/var/rbenv/versions/2.1.4/bin/ruby"
Current executable set to '/usr/local/var/rbenv/versions/2.1.4/bin/ruby' (x86_64).
(lldb) b rb_io_puts
Breakpoint 1: where = ruby`rb_io_puts + 19 at io.c:6989, address = 0x00000001000a53f3
(lldb) r -e 'puts :hi'
Process 96042 launched: '/usr/local/var/rbenv/versions/2.1.4/bin/ruby' (x86_64)
Process 96042 stopped
* thread #1: tid = 0x23767f, 0x00000001000a53f3 ruby`rb_io_puts(argc=1, argv=0x0000000102000028, out=4321012640) + 19 at io.c:6989, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001000a53f3 ruby`rb_io_puts(argc=1, argv=0x0000000102000028, out=4321012640) + 19 at io.c:6989
   6986     VALUE line;
   6987
   6988     /* if no argument given, print newline. */
-> 6989      if (argc == 0) {
   6990     rb_io_write(out, rb_default_rs);
   6991     return Qnil;
   6992     }
(lldb)

実行中のプロセスにアタッチする

前回と同じように実行中のプロセスにアタッチする

$ ruby -e 'loop { puts :hi; sleep 5 }' > /dev/null &
[1] 5396
$ lldb `rbenv which ruby`
(lldb) target create "/usr/local/var/rbenv/versions/2.1.4/bin/ruby"
Current executable set to '/usr/local/var/rbenv/versions/2.1.4/bin/ruby' (x86_64).
(lldb) b rb_io_puts                                                                                                                       Breakpoint 1: where = ruby`rb_io_puts + 19 at io.c:6989, address = 0x00000001000a53f3
(lldb) process attach -p 5396
Process 5396 stopped
(lldb) process continue
Process 5396 resuming
Process 5396 stopped
* thread #1: tid = 0x23fbba, 0x00000001078a63f3 ruby`rb_io_puts(argc=1, argv=0x0000000107c38050, out=140508413144000) + 19 at io.c:6989, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001078a63f3 ruby`rb_io_puts(argc=1, argv=0x0000000107c38050, out=140508413144000) + 19 at io.c:6989
   6986     VALUE line;
   6987
   6988     /* if no argument given, print newline. */
-> 6989     if (argc == 0) {
   6990     rb_io_write(out, rb_default_rs);
   6991     return Qnil;
   6992     }

アタッチできたようだ。 lldbの使い方についてはまた必要になったとき調べる。

実行中のプロセスのcoreをとる

(lldb) process save-core /tmp/core

とれたコアは

$ lldb `rbenv which ruby` -c /tmp/core

のような感じで実行できる。 putsの引数の:hiをCから引きたかったけど

(lldb) p rb_sym_to_s(argv[0])
(lldb) expr rb_sym_to_s(argv[0])

などでうまく見れない。rb_define_methodしたメソッドを引く方法を調べたほうがよさそうだ。

まとめ

  • ruby -r rbconfig -e 'puts RbConfig::CONFIG["CC"], RbConfig::CONFIG["CFLAGS"]'でビルド時のオプションを確認できる
  • clangコンパイルしたrubyデバッグするときはllvmを使う
  • rbenvを使っている場合RUBY_CFLAGSでビルド時のコンパイルオプションを指定できる
  • rbenv install X -k-kオプションでソースコードを取っておかないとデバッグ時にソースコードが表示できない
  • lldbrubyを実行する方法、実行中のrubyプロセスにアタッチする方法がわかった
  • lldbで実行中のプロセスのコアを取る方法がわかった