ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

2020年7月、Ruby 2.7 を gdb で追う時のメモ

だいたい下記の記事と同じなんだけれど、少しでも新しい何かがあれば。2020年現在、Ruby 2.7.1 を対象に。

techlife.cookpad.com

デバッグしやすい 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 を使うには

spring-mt.hatenablog.com

色々まとまっているが、やっぱりこれもちょっと古く...(いま、Rubyのスレッドって一つだし)。Rubyレベル、Cレベルでのバックトレースの出し方や、 rp は役にたつと思う。

gdbinitは、 $RBENV_ROOT/sources/2.7.1/ruby-2.7.1/.gdbinit のようなところにある。読んでみると色々な関数が定義されていることがわかる。


ほか、何かあれば...という記事。