Hacking Ruby
    with
   Python
     @tyamadajp
光ある所、影があり

コードある所、闇がある
闇の名は、バグ。

そしてバグを殺す者、
Ruby Debugger
~僕と契約して闇デバッガになろうよ!~
みなさんオススメのデバッガは?
0. p+?            (?!)
1. debug.rb?      (旧型)
2. ruby-debug? (普通)
3. rb-trepanning? (新型)
4. others?

#ベースの機能を生かした
# JRuby, Rubinius 用のとかも
#ありますよね
デバッガのいい所
・どこまでも覗ける
・動いている状態を覗ける
・あろうことか、手まで出せる

「言語仕様?そんなの関係ねぇ!」

「もし闇の住人がデバッガーの
       解説ページを読んだら」
さて今宵のデバッガは・・・
さて今宵のデバッガは・・・




  GDB
さて今宵のデバッガは・・・


   The
   GNU
   Debugger
さて今宵のデバッガは・・・




… の Python 拡張
さて今宵のデバッガは・・・




 GDB/Python
       明日の Ruby のために、
       今夜は Python
なにそれこわい
こわくないよ!> GDB/Python
1. GDB を 100% API control
 → ブレークポイントの操作とか

2. GDB そのものも拡張
 →新コマンド追加 / (gdb)...
 →簡易関数の追加 / $func(...)
 → print ハイジャック / pretty printer
こんな感じ(実装例)
(gdb) p self
$299 = (struct RTypedData *) 0x64b2c0,
   type=iseq ($300), data=0x731ee0 ($301)

(gdb) pp self
$302 = (struct RTypedData *) 0x64b2c0,
   type=iseq ($303), data=0x731ee0 ($304)
$305 = (const rb_data_type_t *) 0x7ffff7dbe780
$306 = (rb_iseq_t *) 0x731ee0, <compiled>:3,
   type=ISEQ_TYPE_TOP



      ・・・まあ色々デコードしてくれます
それ $RUBY/.gdbinit でできるよ?
ええ、あるのですが・・・
・主力は内蔵 eval や dump 関数?
・なので展開能力が控えめ ( ぬるぽ避け? )
  (gdb) rp recv
  T_CLASS: $1156 = (struct RClass *) 0x655310


      →ソース読解の道具が欲しかった
      →自分でデコードするのがよい練習
ぬるぽを恐れず何でも展開
(gdb) pp recv
$1158 = (struct RClass *) 0x655310 [Time]
dump: {basic = {flags = 2, klass = 6640360},
 ptr=0x6e4e20, m_tbl=0x6e4e40, iv_index_tbl=0x0}
m_tbl: +, -, <=>, _dump, asctime, ctime, …

(gdb) pp ruby_current_thread->cfp->iseq 2
$1072 = (rb_iseq_t *) 0x75c9c0, MAIN,
   /home/tai/tarai.rb:1(p=0x70a260/$1073,
                         l=0x70a260/$1074)
line#0003:               VALUE ラップもの、 ID 、
      $1075 putstring NODE 、 rb_iseq_t 、 rb_vm_t...
                         自動で型判定し p or pp で
      $1076 getclassvariable
      …                  中身を徹底表示
GDB script ではダメですか?
・ まともな制御構文がない
 → if else if else if else end end end
・ スコープ概念が(ほとんど)ない
・ 遅い。当人比で最大 100+ 倍以上

・ 限りなく貧弱なデータ操作機能とライブラリ

     「 GDB script がやられたか・・・」
     「所詮あやつは言語以前の存在」
GDB Python API - basic
import gdb
# vmは gdb.Value オブジェクトだが rb_vm_t* の型を保持
vm = gdb.parse_and_eval(“ruby_current_vm”)

# これも gdb.Value オブジェクトだが、 rb_thread_t* の型を維持
th = vm['running_thread']
# いわゆる構造体ダンプ :(gdb) p *ruby_current_vm
print(vm.dereference())
# ポインタ演算したいときには char* にしたり
pointer_op(th.cast(gdb.lookup_type(“char”).pointer()))

# CLI 側で参照できるよう $ や $N に戻したりもできる
gdb.execute(“p (%s)%ld” % (th.type, long(th)))
gdb.execute(“set $foo = %ld” % long(th))
GDB Command – extending gdb
import gdb
class HelloCommand(gdb.Command):
   """Sample GDB command in Python"""
   def __init__(self):
      super(self.__class__, self).__init__(
          "hello-cmd", gdb.COMMAND_OBSCURE)
  def invoke(self, arg, from_tty):
     args = gdb.string_to_argv(arg)
     print("arg is [%s]" % ", ".join(args))
HelloCommand()
                                   拡張1つにクラスを1つ

※ ロードは (gdb) python execfile(“hello.py”) などで
GDB Command – easy way
import gdb
@gdbcommand(“hello-cmd“)
def hello(*args):
   """
   Sample GDB command in Python
   Usage: hello-cmd args
   """
   print("arg is [%s]" % ", ".join(args))




                                     バイト数 50% カット!
GDB Command – easy way, impl
def gdbcommand(*args):
   """Turns decorated function into GDB command"""
   opts = [args[0], gdb.COMMAND_OBSCURE] #FIXME

  def wrap(func):
     name = opts[0] or func.func_name
     def init(self):
        super(self.__class__, self).__init__(name, *opts[1:])
     def invoke(self, arg, from_tty):
        func(*gdb.string_to_argv(arg))
     type("", (gdb.Command,), {
        '__doc__': func.__doc__, '__init__': init, 'invoke': invoke,
     })()
     return func
  return wrap                           デコレータ、 Ruby にも
                                       本気で欲しくなったり
  ※ スライドに収めるため機能とコードをカットしています。バグってたらごめんなさい
GDB Pretty Printer
class HelloPrinter(object):
   """Print (hello_t *) type"""
   def __init__(self, val): self.val = val

   # 表示する文字列又は gdb.Value を返す。後者の場合は
   # 再度 pretty-printing 処理が試みられる。以下はダミー。
   def to_string(self): return "hogehoge"
   # 表示ヒント。 "array", "map", "string" のいずれかを返す
   def display_hint(self): return "string"
# カスタムプリンタが反応できるようチェッカを登録
def ckval(gv):
   if gv.type == gdb.lookup_type("hello_t").pointer():
      return HelloPrinter(gv)
   return None                    print 以外にも、あらゆる
gdb.pretty_printers.append(ckval) 表示処理に適用される
おまけ: Ruby と Python の狂演
$ rlwrap gdb -q -readnow --args ./ruby1.9.1 tarai.rb
(gdb) b vm_exec
(gdb) run
(gdb) python sys.argv = [“gdb”] # GDB 側の漏れ対応
(gdb) python execfile("/usr/bin/ipython")
IPython 0.10.2 -- An enhanced Interactive Python.
In [1]: from gdb import *
In [2]: vm = parse_and_eval("ruby_current_vm")
In [3]: vm
Out[3]: <gdb.Value object at 0x207bb70>
In [4]: p vm['main_thread'].type
struct rb_thread_struct *


  補完処理が壊れたり、単に GDB/Python スクリプト
  書くよりバグ率高いですが、変態的な可能性を感じる
おまけ: gdb.rb – GDB/Ruby in Py*
https://github.com/tmm1/gdb.rb

 「 gdb hooks for MRI/REE (and some for YARV) 」

(gdb) b vm_exec if $interactive == 0
(gdb) run
(gdb) set $interactive = 1 #自己呼び出しのブロック防止
(gdb) python execfile("ruby-gdb.py")
(gdb) ruby eval Thread.list
[#<Thread:0x00000000650cc0 run>]
(gdb) ruby objects
  HEAPS      24
  SLOTS    9816            実は金曜日に発見して
  LIVE     2717 (27.68%)   かなりへこんだ。
  FREE     7099 (72.32%)   Ruby 内部の機能をフルに
                          使って自分自身をデバッグ
 complex     1 (0.04%)
 bignum      2 (0.07%)
まとめ
1. Ruby のデバッグ・学習に使ってみた
2. GDB/Python は強力なツール
3.正直わけがわからないよ!

「明日の Ruby のために、今夜は Python 」


参考文献:
・ sourceware.org/gdb/onlinedocs/gdb/Python-API.html
・ sourceware.org/gdb/wiki/PythonGdbTutorial
・ gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python/

Hacking Ruby with Python

  • 1.
    Hacking Ruby with Python @tyamadajp
  • 2.
  • 3.
  • 4.
  • 5.
    みなさんオススメのデバッガは? 0. p+? (?!) 1. debug.rb? (旧型) 2. ruby-debug? (普通) 3. rb-trepanning? (新型) 4. others? #ベースの機能を生かした # JRuby, Rubinius 用のとかも #ありますよね
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    さて今宵のデバッガは・・・ GDB/Python 明日の Ruby のために、 今夜は Python
  • 12.
  • 13.
    こわくないよ!> GDB/Python 1. GDBを 100% API control → ブレークポイントの操作とか 2. GDB そのものも拡張  →新コマンド追加 / (gdb)...  →簡易関数の追加 / $func(...)  → print ハイジャック / pretty printer
  • 14.
    こんな感じ(実装例) (gdb) p self $299= (struct RTypedData *) 0x64b2c0, type=iseq ($300), data=0x731ee0 ($301) (gdb) pp self $302 = (struct RTypedData *) 0x64b2c0, type=iseq ($303), data=0x731ee0 ($304) $305 = (const rb_data_type_t *) 0x7ffff7dbe780 $306 = (rb_iseq_t *) 0x731ee0, <compiled>:3, type=ISEQ_TYPE_TOP ・・・まあ色々デコードしてくれます
  • 15.
    それ $RUBY/.gdbinit でできるよ? ええ、あるのですが・・・ ・主力は内蔵eval や dump 関数? ・なので展開能力が控えめ ( ぬるぽ避け? )   (gdb) rp recv   T_CLASS: $1156 = (struct RClass *) 0x655310       →ソース読解の道具が欲しかった       →自分でデコードするのがよい練習
  • 16.
    ぬるぽを恐れず何でも展開 (gdb) pp recv $1158= (struct RClass *) 0x655310 [Time] dump: {basic = {flags = 2, klass = 6640360}, ptr=0x6e4e20, m_tbl=0x6e4e40, iv_index_tbl=0x0} m_tbl: +, -, <=>, _dump, asctime, ctime, … (gdb) pp ruby_current_thread->cfp->iseq 2 $1072 = (rb_iseq_t *) 0x75c9c0, MAIN, /home/tai/tarai.rb:1(p=0x70a260/$1073, l=0x70a260/$1074) line#0003: VALUE ラップもの、 ID 、 $1075 putstring NODE 、 rb_iseq_t 、 rb_vm_t... 自動で型判定し p or pp で $1076 getclassvariable … 中身を徹底表示
  • 17.
    GDB script ではダメですか? ・まともな制御構文がない  → if else if else if else end end end ・ スコープ概念が(ほとんど)ない ・ 遅い。当人比で最大 100+ 倍以上 ・ 限りなく貧弱なデータ操作機能とライブラリ      「 GDB script がやられたか・・・」      「所詮あやつは言語以前の存在」
  • 18.
    GDB Python API- basic import gdb # vmは gdb.Value オブジェクトだが rb_vm_t* の型を保持 vm = gdb.parse_and_eval(“ruby_current_vm”) # これも gdb.Value オブジェクトだが、 rb_thread_t* の型を維持 th = vm['running_thread'] # いわゆる構造体ダンプ :(gdb) p *ruby_current_vm print(vm.dereference()) # ポインタ演算したいときには char* にしたり pointer_op(th.cast(gdb.lookup_type(“char”).pointer())) # CLI 側で参照できるよう $ や $N に戻したりもできる gdb.execute(“p (%s)%ld” % (th.type, long(th))) gdb.execute(“set $foo = %ld” % long(th))
  • 19.
    GDB Command –extending gdb import gdb class HelloCommand(gdb.Command): """Sample GDB command in Python""" def __init__(self): super(self.__class__, self).__init__( "hello-cmd", gdb.COMMAND_OBSCURE) def invoke(self, arg, from_tty): args = gdb.string_to_argv(arg) print("arg is [%s]" % ", ".join(args)) HelloCommand() 拡張1つにクラスを1つ ※ ロードは (gdb) python execfile(“hello.py”) などで
  • 20.
    GDB Command –easy way import gdb @gdbcommand(“hello-cmd“) def hello(*args): """ Sample GDB command in Python Usage: hello-cmd args """ print("arg is [%s]" % ", ".join(args)) バイト数 50% カット!
  • 21.
    GDB Command –easy way, impl def gdbcommand(*args): """Turns decorated function into GDB command""" opts = [args[0], gdb.COMMAND_OBSCURE] #FIXME def wrap(func): name = opts[0] or func.func_name def init(self): super(self.__class__, self).__init__(name, *opts[1:]) def invoke(self, arg, from_tty): func(*gdb.string_to_argv(arg)) type("", (gdb.Command,), { '__doc__': func.__doc__, '__init__': init, 'invoke': invoke, })() return func return wrap デコレータ、 Ruby にも 本気で欲しくなったり ※ スライドに収めるため機能とコードをカットしています。バグってたらごめんなさい
  • 22.
    GDB Pretty Printer classHelloPrinter(object): """Print (hello_t *) type""" def __init__(self, val): self.val = val # 表示する文字列又は gdb.Value を返す。後者の場合は # 再度 pretty-printing 処理が試みられる。以下はダミー。 def to_string(self): return "hogehoge" # 表示ヒント。 "array", "map", "string" のいずれかを返す def display_hint(self): return "string" # カスタムプリンタが反応できるようチェッカを登録 def ckval(gv): if gv.type == gdb.lookup_type("hello_t").pointer(): return HelloPrinter(gv) return None print 以外にも、あらゆる gdb.pretty_printers.append(ckval) 表示処理に適用される
  • 23.
    おまけ: Ruby とPython の狂演 $ rlwrap gdb -q -readnow --args ./ruby1.9.1 tarai.rb (gdb) b vm_exec (gdb) run (gdb) python sys.argv = [“gdb”] # GDB 側の漏れ対応 (gdb) python execfile("/usr/bin/ipython") IPython 0.10.2 -- An enhanced Interactive Python. In [1]: from gdb import * In [2]: vm = parse_and_eval("ruby_current_vm") In [3]: vm Out[3]: <gdb.Value object at 0x207bb70> In [4]: p vm['main_thread'].type struct rb_thread_struct * 補完処理が壊れたり、単に GDB/Python スクリプト 書くよりバグ率高いですが、変態的な可能性を感じる
  • 24.
    おまけ: gdb.rb –GDB/Ruby in Py* https://github.com/tmm1/gdb.rb  「 gdb hooks for MRI/REE (and some for YARV) 」 (gdb) b vm_exec if $interactive == 0 (gdb) run (gdb) set $interactive = 1 #自己呼び出しのブロック防止 (gdb) python execfile("ruby-gdb.py") (gdb) ruby eval Thread.list [#<Thread:0x00000000650cc0 run>] (gdb) ruby objects HEAPS 24 SLOTS 9816 実は金曜日に発見して LIVE 2717 (27.68%) かなりへこんだ。 FREE 7099 (72.32%) Ruby 内部の機能をフルに 使って自分自身をデバッグ complex 1 (0.04%) bignum 2 (0.07%)
  • 25.
    まとめ 1. Ruby のデバッグ・学習に使ってみた 2.GDB/Python は強力なツール 3.正直わけがわからないよ! 「明日の Ruby のために、今夜は Python 」 参考文献: ・ sourceware.org/gdb/onlinedocs/gdb/Python-API.html ・ sourceware.org/gdb/wiki/PythonGdbTutorial ・ gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python/