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の環境で実行して自分自身をデバッグは無茶だから別プロセスで動かせないかなー?って思って、やってみた、という感じである。
まあ、そんなたいそうなものがほんとに作れるのかというとさっぱりわからないのだが、技術的に可能ではありそうだ、という結果が得られたのでひとまず満足。