Rubyによるリモートデバッグ
IDEとかでリモートデバッグするためのgemなどはあって、それを使えば便利で簡単にリモートデバッグができるようなのだが。
それはそれとして、その手のものを自分で作ってみるという興味本位の行動があっても特に問題は無かろう。
さて、Rubyにはset_trace_funcとかTracePointクラスとかのトレース用インターフェイスが用意されていて、これを使うとデバッガが作れる。真面目に作ると大変そうなのだが、とりあえずいま実行している行番号を表示しながらステップ実行するぐらいなら簡単だろう。
そういう動作をするのは標準でdebug.rbが入っていて、ただ、そうじゃなくて、別プロセスでプログラムを起動して、こっちからステップ実行の指示をしたりしたい。という思いがあるじゃろ?ないか。あったとするじゃろ?
以下のようなコードを用意する。プログラム3本。
#d_server.rb require 'drb/drb' class DebugServer attr_accessor :line_no, :path def run p "#{path} : #{line_no}" gets end end URI="druby://localhost:8787" FRONT_OBJECT = DebugServer.new DRb.start_service(URI, FRONT_OBJECT, :safe_level => 1) spawn('ruby d:\test\d_client.rb') DRb.thread.join
#d_client.rb require 'drb/drb' SERVER_URI="druby://localhost:8787" debugserver = DRbObject.new_with_uri(SERVER_URI) TracePoint.trace(:line) do |tp| debugserver.line_no = tp.lineno debugserver.path = tp.path debugserver.run end require_relative './d_test.rb'
#d_test.rb total = 0 (1..5).each do |i| total += i p total end
それぞれ、サーバとクライアントとテストコードとなっている。サーバを実行するとクライアントをspawnして、クライアントはサーバに接続してTracePointを作ってからテストコードをrequireする。
サーバとクライアントは(面倒だったから)dRubyで連携していて、原理としてはクライアントがテストコードを1行動かすたびにサーバにファイル名と行番号を渡して、サーバはエンターキーが押されたらクライアントに戻る。だけだ。コマンド類はまだ実装されていない。
起動はサーバからで、これを実行してエンターキーを連打すると、
"d:/test/d_client.rb : 13" "d:/test/d_test.rb : 2" "d:/test/d_test.rb : 3" "d:/test/d_test.rb : 3" "d:/test/d_test.rb : 4" "d:/test/d_test.rb : 5" 1 "d:/test/d_test.rb : 4" "d:/test/d_test.rb : 5" 3 "d:/test/d_test.rb : 4" "d:/test/d_test.rb : 5" 6 "d:/test/d_test.rb : 4" "d:/test/d_test.rb : 5" 10 "d:/test/d_test.rb : 4" "d:/test/d_test.rb : 5" 15
という出力が得られる。
サーバとクライアントの間で何かしらコマンドをやりとりすれば、例えばローカル変数の中身を確認するだとか、値を書き換えるだとか、動的にメソッドを再定義するだとか(!?)、そういうことが可能になるかもしれない。
ところで何でこんなことをしてるのかと言うと、DXRubyWSでフォームデザイナなどを作ってみていて、ここにテキストエディタコントロールを追加してコードの編集ができるようになったら統合開発環境みたいになるよなーって考えてて、そしたらデバッガが欲しくなるよね?ってなって、WSの環境で実行して自分自身をデバッグは無茶だから別プロセスで動かせないかなー?って思って、やってみた、という感じである。
まあ、そんなたいそうなものがほんとに作れるのかというとさっぱりわからないのだが、技術的に可能ではありそうだ、という結果が得られたのでひとまず満足。