脳ざらし紀行


2005-02-20

_ [Ruby] irb でオブジェクトの中に入る

irb を使えばオブジェクトと対話するだけでなく、オブジェクトの中に入ることもできます。

irb(main):002:0> class Foo
irb(main):003:1>   def initialize
irb(main):004:2>     @a = "I am an instance var."
irb(main):005:2>   end
irb(main):006:1> end
=> nil

とクラス Foo を定義して以下のように実行します。

irb(main):007:0> irb Foo.new

すると以後 irb は Foo オブジェクトの中で実行されます。

irb#1(#<Foo:0x40444b28>):001:0> @a
=> "I am an instance var."

この機能と drb を一緒に使えば、リモートのオブジェクトの中にだって入れるかもしれません。ナイスなアイディアです。

以下のようにコマンドラインから実行して、

$ ruby -r drb/drb -e 'class Foo
                    def initialize
                      @a = "I am an instance var."
                    end
                    
                    def foo
                      "foo foo bar"
                    end
                  end
DRb.start_service("druby://:12250", Foo.new)
sleep
'

別の端末から以下のように実行すると

$ irb -rdrb/drb
irb(main):001:0> irb DRbObject.new(nil, "druby://:12250")
irb#1(#<Foo:0x4022d5dc>):001:0> @a
=> nil

あら nil しか返ってきませんね。これは @a が Foo オブジェクトのインスタンス変数ではなく、DRbObject オブジェクトのインスタンス変数として実行されちゃうからです。でもメソッドなら呼び出せます。

irb#1(#<Foo:0x4022d5dc>):002:0> foo
=> "foo foo bar"

どうせならインスタンス変数も直接呼べるようにしたいですよね。

_ [Ruby] irb でリモートのオブジェクトの中に入る

irb を適当に変更すれば、リモートのインスタンス変数を直接触ったり、メソッドを定義したり、実行したりする事は簡単にできます。ただ問題がひとつ出てきます。ローカルの irb のプロセスで定義されていないクラスのインスタンスを返り値として渡された時どうするかという問題です。これに関して『dRubyによる分散オブジェクトプログラミング』では次のように書かれています。

値渡しには大きな問題があります。オブジェクトを渡される側、つまり読み手の知らないクラスのオブジェクトは渡せません。読み手が Marshal.load できないためです。dRuby ではこれを解決する決め手が見つからず、あきらめています。値渡しをするアプリケーション間では、そのオブジェクトのクラスをお互いに知っているように注意してください。

『dRubyによる分散オブジェクトプログラミング』 P40-41

しかし、今回の場合はローカルの irb のプロセスにリモートのオブジェクトのコピーが必要というわけではありません。ローカルの irb のプロセスが知っておく必要があるのはオブジェクトを inspect した文字列だけです。これさえあれば、irb を操作している人間は、リモートのオブジェクトを本当に直接触っている感を得ることができます。

_ [Ruby] irb と drb でデバッグ

ここからが本題です。もし irb でリモートのオブジェクトを操作できるようになれば、それはデバッグに使えるはずです。ruby にも debug.rb というデバッガが付属しています。が、僕が愛用しているデバッガは print だとか p とかだったりするわけです。debug.rb はどうも難しくて使えません。たぶんそれは以下のような理由なんじゃないかなと思います。

  1. debug.rb が何をしているのか良く分からない。
  2. だから自分が書いたプログラムも debug.rb と一緒になった場合、どう動いているのか良く分からない。
  3. いちいちブレークポイントをコマンドラインから設定するのが面倒くさい。

ブレークポイントを設定するのにどうして直接ソースに書かないのか前から不思議に思っていました。コンパイルの必要な言語ならまだしも、コンパイルの必要のないスクリプト言語なわけですから、ソースなんて必要になったら書き直して良いはずです。この方法だと、やっていることは print デバッグと同じですから、人間に分かりやすいはずです。

というわけで、

  • 直接ソースにブレークポイントを書く。
  • irb をデバッガのインターフェースとして使う。
  • drb を使って実行中の他のプロセスを直接覗き見て、実行結果を変えることができる。

というような仕組みを作ってみました。デバッガに drb を組み合わせるのは yard の真似です。

class Hoge
  attr_accessor :somevar
end
Thread.new{
  loop do
    i = 'foo'
    j = 'another thread'
    IRBDB::SetBreakePoint(binding) if $DEBUG
  end
}

loop do
  i = rand()
  j = Hoge.new
  IRBDB::SetBreakePoint(binding) if $DEBUG
  i = 'hoge'
  IRBDB::SetBreakePoint(binding) if $DEBUG
end

みたいなデバッグ対象のソース sample.rb を用意します。コマンドラインから以下のように実行します。

$ ruby -rdebuggee -d sample.rb

別の端末から以下のように実行します。

$ irb -r irbdb
irb(main):001:0> irb DRbObject.new(nil, "druby://:12250")
irb#1("sample.rb:8"):001:0> 

sample.rb の8行目で止まっていることが分かります。例えばローカル変数を参照できます。

irb#1("sample.rb:8"):001:0> j
=> "another thread"

irbdb_c (or irbdb_continue) でスクリプトの実行を継続します。

irb#1("sample.rb:8"):002:0> irbdb_c
continue ...
=> reach another break point
irb#1("sample.rb:15"):003:0> j
=> #<Hoge:0x402186c8>
irb#1("sample.rb:15"):004:0> j.somevar = "foo foo bar"
=> "foo foo bar"
irb#1("sample.rb:15"):005:0> j.somevar
=> "foo foo bar"

ローカルの irb プロセスが知らない Hoge クラスのインスタンスも扱えます。メソッドも動的に定義できます。

irb#1("sample.rb:15"):006:0> def hoge
irb#1("sample.rb:15"):007:1>   'hoge'
irb#1("sample.rb:15"):008:1> end
=> nil
irb#1("sample.rb:15"):009:0> hoge
=> 'hoge'

irbdb からゲットできるので試してみてください。一応デフォルトで drb/acl を使ってアクセス制御はしていますが、他の悪意あるマシンから drb サーバに接続された場合なんでも実行可能なので注意してください。参考

この90行程度のスクリプトを作成するのに2回ほど print デバッグを行ないました。

_ [Ruby] irbdb の利点

  • set_trace_func を使っていないのでその分だけ debug.rb などより速いはず。
  • 標準出入力を邪魔しないので、例えば CGI スクリプトのデバッグにも使えるはず。

_ [ネット] デバッガに関するページ on c2.com

_ [Ruby] Breakpoint on Rails

Howto Debug With Breakpoint

binding_of_caller.rb すげえ。callcc と set_trace_func を使って、メソッドを呼び出した側の binding を取得する。some_method(binding) じゃ駄目ですかね。

お名前:
E-mail:
コメント:
本日のリンク元

最近のコメント

2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|04|05|
2011|04|
2012|03|07|
2013|01|02|07|
トップ «前の日記(2005-02-19) 最新 次の日記(2005-02-23)» 編集