だいたい下記の記事と同じなんだけれど、少しでも新しい何かがあれば。2020年現在、Ruby 2.7.1 を対象に。
デバッグしやすい Ruby をインストールする
rbenvで入れ直す場合
RUBY_CONFIGURE_OPTS
で最適化を切るオプションを渡す- 国分さんの記事の通り
-g
などは渡す必要がないそう、デフォルトが-ggdb3
なのでそれでOK
- 国分さんの記事の通り
-k
でソースコードを残す
が留意点。こういう感じで。
$ RUBY_CONFIGURE_OPTS='optflags=-O0' rbenv install 2.7.1 -k
関数を探す
ソースコードを頑張って追いかける。あるいはとりあえずそれらしいものを readelf -s
などで探しても良いかと思う。
$ readelf -s /home/vagrant/.rbenv/versions/2.7.1/bin/ruby Symbol table '.dynsym' contains 24 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ruby_run_node 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ruby_init 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND ruby_options 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab ...
rb_
で始まる、Rubyのそれぞれのメソッドの実装部分は libruby.so 側にある。libruby.soを分けないオプションもあるのかもしれない(あれば教えてください)。
$ ldd /home/vagrant/.rbenv/versions/2.7.1/bin/ruby linux-vdso.so.1 (0x00007ffd734e1000) libruby.so.2.7 => /home/vagrant/.rbenv/versions/2.7.1/lib/libruby.so.2.7 (0x00007fc61f23f000) $ $ readelf -s /home/vagrant/.rbenv/versions/2.7.1/lib/libruby.so.2.7 | grep rb_ | head -20 308: 000000000027f2a4 100 FUNC GLOBAL DEFAULT 12 rb_const_list 309: 000000000009c2ff 80 FUNC GLOBAL DEFAULT 12 rb_dir_getwd 310: 00000000002b0f93 50 FUNC GLOBAL DEFAULT 12 rb_frame_method_id_and_cl 311: 00000000000bfd58 217 FUNC GLOBAL DEFAULT 12 rb_loaderror_with_path 313: 000000000017a79e 190 FUNC GLOBAL DEFAULT 12 rb_inspect 314: 00000000000a24c3 52 FUNC GLOBAL DEFAULT 12 rb_enc_find 315: 0000000000612648 8 OBJECT GLOBAL DEFAULT 26 rb_cRational 316: 00000000002aa9fe 226 FUNC GLOBAL DEFAULT 12 rb_throw_obj 317: 00000000002b7647 309 FUNC GLOBAL DEFAULT 12 rb_profile_frame_classpat 319: 000000000027a090 18 FUNC GLOBAL DEFAULT 12 rb_gvar_val_getter 320: 000000000005f253 51 FUNC GLOBAL DEFAULT 12 rb_class_protected_instan 321: 00000000002a7d3b 370 FUNC GLOBAL DEFAULT 12 rb_apply 322: 00000000002703a0 136 FUNC GLOBAL DEFAULT 12 rb_str_encode 323: 00000000002548ae 28 FUNC GLOBAL DEFAULT 12 rb_thread_alone 324: 0000000000225eb1 53 FUNC GLOBAL DEFAULT 12 rb_str_conv_enc 325: 00000000002a0b50 374 FUNC GLOBAL DEFAULT 12 rb_clear_method_cache_by_ 326: 00000000001c3f75 85 FUNC GLOBAL DEFAULT 12 rb_range_new 327: 000000000009934b 15 FUNC GLOBAL DEFAULT 12 rb_fiber_current 328: 0000000000612368 8 OBJECT GLOBAL DEFAULT 26 rb_cArray 329: 00000000006125d8 8 OBJECT GLOBAL DEFAULT 26 rb_cClass
やってみよう
$ gdb --args /home/vagrant/.rbenv/versions/2.7.1/bin/ruby -e 'p Object.new' GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
rb_inspect
をつかまえたいんだけれど、この関数は読み込み前の libruby.so.2.7
の方にあるのでpendingがどうこう言われる。「y」を押す。
(gdb) b rb_inspect Function "rb_inspect" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (rb_inspect) pending.
run
で動かすとちゃんと止まる。
(gdb) run Starting program: /home/vagrant/.rbenv/versions/2.7.1/bin/ruby -e p\ Object.new [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, rb_inspect (obj=93824996526120) at object.c:680 680 VALUE str = rb_obj_as_string(rb_funcallv(obj, id_inspect, 0, 0));
list
backtrace
next
などガチャガチャやって、飽きたら continue
で再開。
(gdb) list 675 * the result must be ASCII only. 676 */ 677 VALUE 678 rb_inspect(VALUE obj) 679 { 680 VALUE str = rb_obj_as_string(rb_funcallv(obj, id_inspect, 0, 0)); 681 682 rb_encoding *enc = rb_default_internal_encoding(); 683 if (enc == NULL) enc = rb_default_external_encoding(); 684 if (!rb_enc_asciicompat(enc)) { (gdb) bt #0 rb_inspect (obj=93824996526120) at object.c:680 #1 0x00007ffff78c747e in rb_p (obj=93824996526120) at io.c:7801 #2 0x00007ffff78c75d7 in rb_f_p_internal (arg=140737488343280) at io.c:7827 #3 0x00007ffff7888555 in rb_ensure (b_proc=0x7ffff78c757e <rb_f_p_internal>, data1=140737488343280, e_proc=0x7ffff77f7e6c <rb_ary_pop>, data2=93824994527560) at eval.c:1129 #4 0x00007ffff7a1acc8 in rb_uninterruptible (b_proc=0x7ffff78c757e <rb_f_p_internal>, data=140737488343280) at thread.c:5563
...
(gdb) c Continuing. #<Object:0x000055555596c828> [Inferior 1 (process 26355) exited normally]
初期化処理を追いかける、みたいな場合は ruby_init()
とか ruby_setup()
などで止めるとそれっぽい気がする(いい関数があれば教えてください)。
Breakpoint 1, ruby_setup () at eval.c:57 57 { (gdb) l 52 * @retval 0 if succeeded. 53 * @retval non-zero an error occurred. 54 */ 55 int 56 ruby_setup(void) 57 { 58 enum ruby_tag_type state; 59 60 if (GET_VM()) 61 return 0; (gdb) n 60 if (GET_VM()) (gdb) n 63 ruby_init_stack((void *)&state); (gdb) n 70 prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); (gdb) n 72 Init_BareVM(); (gdb) n 73 Init_heap(); (gdb) n 74 rb_vm_encoded_insn_data_table_init();
Ruby の .gdbinit
を使うには
色々まとまっているが、やっぱりこれもちょっと古く...(いま、Rubyのスレッドって一つだし)。Rubyレベル、Cレベルでのバックトレースの出し方や、 rp
は役にたつと思う。
gdbinitは、 $RBENV_ROOT/sources/2.7.1/ruby-2.7.1/.gdbinit
のようなところにある。読んでみると色々な関数が定義されていることがわかる。
ほか、何かあれば...という記事。