CubicLouve

Spring_MTの技術ブログ

gdbを使ってRuby 2.4.0のライブプロセスの情報を取得してみる

Ruby 2.4.0 リリース

2.4.0がリリースされたので、

spring-mt.hatenablog.com

と同じことをやってみようかと思います。

準備

AWSamazon Linux上で試しています。

# cat /etc/system-release
Amazon Linux AMI release 2016.09

gcc4.8.3gdb7.6.1を使ってます。

$ wget https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.0.tar.gz
$ tar zxf ruby-2.4.0.tar.gz
$ cd ruby-2.4.0
$ ./configure optflags="-O0" debugflags="-ggdb3" --prefix="${HOME}/.rbenv/versions/2.4.0-O0_g3"
$ make
$ make install

configureのサマリは下記の通りです。

---
Configuration summary for ruby version 2.4.0

   * Installation prefix: /home/ec2-user/.rbenv/versions/2.4.0-O0_g3
   * exec prefix:         ${prefix}
   * arch:                x86_64-linux
   * site arch:           ${arch}
   * RUBY_BASE_NAME:      ruby
   * ruby lib prefix:     ${libdir}/${RUBY_BASE_NAME}
   * site libraries path: ${rubylibprefix}/${sitearch}
   * vendor path:         ${rubylibprefix}/vendor_ruby
   * target OS:           linux
   * compiler:            gcc
   * with pthread:        yes
   * enable shared libs:  no
   * dynamic library ext: so
   * CFLAGS:              ${optflags} ${debugflags} ${warnflags}
   * LDFLAGS:             -L. -fstack-protector -rdynamic \
                          -Wl,-export-dynamic
   * optflags:            -O0 -fno-fast-math
   * debugflags:          -ggdb3
   * warnflags:           -Wall -Wextra -Wno-unused-parameter \
                          -Wno-parentheses -Wno-long-long \
                          -Wno-missing-field-initializers \
                          -Wno-tautological-compare \
                          -Wno-parentheses-equality \
                          -Wno-constant-logical-operand -Wno-self-assign \
                          -Wunused-variable -Wimplicit-int -Wpointer-arith \
                          -Wwrite-strings -Wdeclaration-after-statement \
                          -Wimplicit-function-declaration \
                          -Wdeprecated-declarations \
                          -Wno-packed-bitfield-compat \
                          -Wsuggest-attribute=noreturn \
                          -Wsuggest-attribute=format
   * strip command:       strip -S -x
   * install doc:         yes
   * man page type:       doc

---

gdbでアタッチするRubyプロセスを作る

まずは、かんたんなhttpサーバーを立ち上げてそのプロセスにアタッチしてみます。

今回は単純なhello worldを返すだけのhttpサーバーを立ててます。(pidは控えておいてください)

ソースは下記reposにあります。

rack-hello_world

アタッチしてみる

サクッとgdbでアタッチした体でいきます。

Cの世界

(gdb) p ruby_version
$1 = "2.4.0"

スレッド情報はどうでしょう。

(gdb) info threads
  Id   Target Id         Frame 
  2    Thread 0x7fe410aad700 (LWP 17067) "ruby-timer-thr" 0x00007fe40fc5f64d in poll () at ../sysdeps/unix/syscall-template.S:81
* 1    Thread 0x7fe410aa4740 (LWP 17058) "rackup" 0x00007fe40fc613c3 in select () at ../sysdeps/unix/syscall-template.S:81

メインスレッド(Rubyのスレッド)とタイマスレッドがいますね。

ここいらでRubyの世界にいきます。

Rubyの世界へ

まずは簡単にRubyのcallbackを表示してみます。

(gdb) call rb_vmdebug_stack_dump_raw_current()

そうすると、プロセスの標準出力(今回はrackupで立ち上げたプロセス)にrubyのCの関数呼出しも含まれて標準出力に表示されます。

 from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/bin/rackup:22:in `<main>'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/bin/rackup:22:in `load'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/bin/rackup:4:in `<top (required)>'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/server.rb:147:in `start'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/server.rb:296:in `start'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/handler/webrick.rb:34:in `run'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:158:in `start'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:33:in `start'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:171:in `block in start'
    from /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:171:in `select'
-- Control frame information -----------------------------------------------
c:0011 p:---- s:0064 e:000063 CFUNC  :select
c:0010 p:0117 s:0056 e:000055 BLOCK  /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:171
c:0009 p:0006 s:0046 e:000045 METHOD /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:33
c:0008 p:0068 s:0042 e:000041 METHOD /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/2.4.0/webrick/server.rb:158
c:0007 p:0161 s:0036 e:000035 METHOD /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/handler/webrick.rb:34
c:0006 p:0231 s:0028 E:0015a8 METHOD /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/server.rb:296
c:0005 p:0016 s:0021 e:000020 METHOD /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/server.rb:147
c:0004 p:0023 s:0016 e:000015 TOP    /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/bin/rackup:4 [FINISH]
c:0003 p:---- s:0013 e:000012 CFUNC  :load
c:0002 p:0136 s:0008 E:0020a0 EVAL   /home/ec2-user/.rbenv/versions/2.4.0-O0_g3/bin/rackup:22 [FINISH]
c:0001 p:0000 s:0003 E:001850 (none) [FINISH]

最新のコールスタックの中身も見てみます。

(gdb) ruby_thread
running_thread set $ruby_thread 
(gdb) vm_frame_type $ruby_thread->cfp
There is no member named flag.

ん、cfp構造体の中からflagがなくなった?

d.hatena.ne.jp

ここで、rb_control_frame_t::flagのメンバをep[0]に移したとのこと。 https://github.com/ruby/ruby/blob/1407e52ba4915ed7f251dec548b5e68f2a5a6a6e/vm_core.h#L994

あと、vmのframe_typeの持ち方も変わっているので、前に定義したgdbvm_frame_type関数も変更しておきます。 ruby/vm_core.h at 1407e52ba4915ed7f251dec548b5e68f2a5a6a6e · ruby/ruby · GitHub

Ruby 2.4.0 for gdbinit · GitHub 再度アタッチ。 ついでにrb_vm_frame_method_entry()も一緒に見てみます。

(gdb) ruby_thread
running_thread set $ruby_thread 
(gdb) vm_frame_type $ruby_thread->cfp
vm_frame_type 55550001  CFUNC 
(gdb) rp rb_id2str(rb_vm_frame_method_entry($ruby_thread->cfp)->called_id)
[PROMOTED] T_STRING: "select" bytesize:6 (embed) encoding:2 coderange:7bit $1 = (struct RString *) 0x5635ba9a5010

ここまでくれば、2.3.3とほとんど変わってないですね。

前と同じように、4つ前のコールスタック(rubyのメソッド呼び出し)のコールスタックも見てみます。

(gdb) set $cfp = $ruby_thread->cfp + 4 # 4つ前のコールスタック
(gdb) vm_frame_type $cfp
vm_frame_type 11110001  METHOD 
(gdb) rp $cfp->iseq->body->location.path # ファイルパス
[PROMOTED] T_STRING: "/home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/handler/webrick.rb" bytesize:106 encoding:2 coderange:7bit $2 = (struct RString *) 0x5635ba951028
(gdb) p/d rb_vm_get_sourceline($cfp) # ファイルの行数
$3 = 34
(gdb) rb_p rb_iseq_disasm($cfp->iseq) # YARV命令列
(gdb) rb_p $cfp->ep[1] # 環境ポインタのデータの中身をdumpする

YARV命令列はこちら

== disasm: #<ISeq:run@/home/ec2-user/.rbenv/versions/2.4.0-O0_g3/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/handler/webrick.rb>
local table (size: 4, argc: 1 [opts: 1, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 4] app<Arg>   [ 3] options<Opt=0>[ 2] environment[ 1] default_host
0000 newhash          0                                               (  25)
0002 setlocal_OP__WC__0 5
0004 trace            8
0006 trace            1                                               (  26)
0008 getinlinecache   15, <is:0>
0011 getconstant      :ENV
0013 setinlinecache   <is:0>
0015 opt_aref_with    <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>, \"RACK_ENV\"
0019 dup              
0020 branchif         25
0022 pop              
0023 putstring        \"development\"
0025 setlocal_OP__WC__0 4
0027 trace            1                                               (  27)
0029 getlocal_OP__WC__0 4
0031 putstring        \"development\"
0033 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
0036 branchunless     42
0038 putstring        \"localhost\"
0040 jump             43
0042 putnil           
0043 setlocal_OP__WC__0 3
0045 trace            1                                               (  29)
0047 getlocal_OP__WC__0 5
0049 putobject        :BindAddress
0051 getlocal_OP__WC__0 5
0053 putobject        :Host
0055 opt_send_without_block <callinfo!mid:delete, argc:1, ARGS_SIMPLE>, <callcache>
0058 dup              
0059 branchif         64
0061 pop              
0062 getlocal_OP__WC__0 3
0064 opt_aset         <callinfo!mid:[]=, argc:2, ARGS_SIMPLE>, <callcache>
0067 pop              
0068 trace            1                                               (  30)
0070 getlocal_OP__WC__0 5
0072 putobject        :Port
0074 dupn             2
0076 opt_aref         <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
0079 dup              
0080 branchif         91
0082 pop              
0083 putobject        8080
0085 opt_aset         <callinfo!mid:[]=, argc:2, ARGS_SIMPLE>, <callcache>
0088 pop              
0089 jump             93
0091 adjuststack      3
0093 trace            1                                               (  31)
0095 getinlinecache   107, <is:1>
0098 pop              
0099 putobject        Object
0101 getconstant      :WEBrick
0103 getconstant      :HTTPServer
0105 setinlinecache   <is:1>
0107 getlocal_OP__WC__0 5
0109 opt_send_without_block <callinfo!mid:new, argc:1, ARGS_SIMPLE>, <callcache>
0112 setinstancevariable :@server, <is:2>
0115 trace            1                                               (  32)
0117 getinstancevariable :@server, <is:2>
0120 putstring        \"/\"
0122 getinlinecache   133, <is:3>
0125 getconstant      :Rack
0127 getconstant      :Handler
0129 getconstant      :WEBrick
0131 setinlinecache   <is:3>
0133 getlocal_OP__WC__0 6
0135 opt_send_without_block <callinfo!mid:mount, argc:3, ARGS_SIMPLE>, <callcache>
0138 pop              
0139 trace            1                                               (  33)
0141 putself          
0142 opt_send_without_block <callinfo!mid:block_given?, argc:0, FCALL|ARGS_SIMPLE>, <callcache>
0145 branchunless     153
0147 getinstancevariable :@server, <is:2>
0150 invokeblock      <callinfo!argc:1, ARGS_SIMPLE>
0152 pop              
0153 trace            1                                               (  34)
0155 getinstancevariable :@server, <is:2>
0158 opt_send_without_block <callinfo!mid:start, argc:0, ARGS_SIMPLE>, <callcache>
0161 trace            16                                              (  35)
0163 leave                                                            (  34)

データはこちら。

#<WEBrick::HTTPServer:0x005635baae7978 @config={:BindAddress=>"localhost", :Port=>9292, :MaxClients=>100, :ServerType=>nil, :Logger=>#<WEBrick::Log:0x005635baae77e8 @level=4, @log=#<IO:<STDERR>>, 
@time_format="[%Y-%m-%d %H:%M:%S]">, :ServerSoftware=>"WEBrick/1.3.1 (Ruby/2.4.0/2016-12-24)", :TempDir=>"/tmp", :DoNotListen=>false, :StartCallback=>nil, :StopCallback=>nil, :AcceptCallback=>nil, :DoNotReverseLookup=>true, :ShutdownSocketWithoutClose=>false,
:RequestTimeout=>30, :HTTPVersion=>#<WEBrick::HTTPVersion:0x005635bafca738 @minor=1, @major=1>, :AccessLog=>[], :MimeTypes=>{"ai"=>"application/postscript", "asc"=>"text/plain", "avi"=>"video/x-msvideo", "bin"=>"application/octet-stream", "bmp"=>"image/bmp", "class"=>"application/octet-stream", "cer"=>"application/pkix-cert", 
"crl"=>"application/pkix-crl", "crt"=>"application/x-x509-ca-cert", "css"=>"text/css", "dms"=>"application/octet-stream", "doc"=>"application/msword", "dvi"=>"application/x-dvi", "eps"=>"application/postscript", "etx"=>"text/x-setext", "exe"=>"application/octet-stream", "gif"=>"image/gif", "htm"=>"text/html", "html"=>"text/html", "jpe"=>"image/jpeg", "jpeg"=>"image/jpeg", "jpg"=>"image/jpeg", "js"=>"application/javascript", "lha"=>"application/octet-stream", "lzh"=>"application/octet-stream", "mov"=>"video/quicktime", "mpe"=>"video/mpeg", "mpeg"=>"video/mpeg", "mpg"=>"video/mpeg", "pbm"=>"image/x-portable-bitmap", "pdf"=>"application/pdf", "pgm"=>"image/x-portable-graymap", "png"=>"image/png", "pnm"=>"image/x-portable-anymap", "ppm"=>"image/x-portable-pixmap", "ppt"=>"application/vnd.ms-powerpoint", "ps"=>"application/postscript", "qt"=>"video/quicktime", "ras"=>"image/x-cmu-raster", "rb"=>"text/plain", "rd"=>"text/plain", "rtf"=>"application/rtf", "sgm"=>"text/sgml", "sgml"=>"text/sgml", "svg"=>"image/svg+xml", "tif"=>"image/tiff", "tiff"=>"image/tiff", "txt"=>"text/plain", "xbm"=>"image/x-xbitmap", "xhtml"=>"text/html", "xls"=>"application/vnd.ms-excel", "xml"=>"text/xml", "xpm"=>"image/x-xpixmap", "xwd"=>"image/x-xwindowdump", "zip"=>"application/zip"}, 
:DirectoryIndex=>["index.html", "index.htm", "index.cgi", "index.rhtml"], :DocumentRoot=>nil, :DocumentRootOptions=>{:FancyIndexing=>true}, :RequestCallback=>nil, :ServerAlias=>nil, :InputBufferSize=>65536, :OutputBufferSize=>65536, :ProxyAuthProc=>nil, :ProxyContentHandler=>nil, :ProxyVia=>true, :ProxyTimeout=>true, :ProxyURI=>nil, :CGIInterpreter=>nil, :CGIPathEnv=>nil, :Escape8bitURI=>false, :environment=>"development", :pid=>nil, :config=>"/home/ec2-user/rack-hell_world/config.ru"}, @status=:Running, @logger=#<WEBrick::Log:0x005635baae77e8 @level=4, @log=#<IO:<STDERR>>, @time_format="[%Y-%m-%d %H:%M:%S]">, @tokens=#<Thread::SizedQueue:0x005635baae76d0>, @listeners=[#<TCPServer:fd 7>], @shutdown_pipe=[#<IO:fd 8>, #<IO:fd 9>], 
@http_version=#<WEBrick::HTTPVersion:0x005635bafca738 @minor=1, @major=1>, @mount_tab=#<WEBrick::HTTPServer::MountTable:0x005635baae67a8 @tab={""=>[Rack::Handler::WEBrick, [#<Rack::ContentLength:0x005635baaeb488 @app=#<Rack::Chunked:0x005635baaeb730 @app=#<Rack::CommonLogger:0x005635baaeb938 @app=#<Rack::ShowExceptions:0x005635bab2dfb8 @app=#<Rack::Lint:0x005635bab2e058 @app=#<Rack::TempfileReaper:0x005635bab2e120 @app=#<Application:0x005635bab3dda0>>, @content_length=nil>>,
@logger=#<IO:<STDERR>>>>>]]}, @scanner=/^()(?=\/|$)/>, @virtual_hosts=[]>

こちらも2.3.3のときと同じ感じでいけました。

cfp周りで変更が入っていそうなので、おいおい調べられればよいなあ。。。