rirb を github に登録して gem 化した

はるか昔に rirb (Remote IRB) というのを作って放置していましたが、github に登録して gem を公開してみました。せっかくなので再紹介。

rirb (Remote IRB) とは

実行中の Ruby プログラムにアタッチして irb プロンプトを開かせることができます。グローバルな状態を観察したり変更したりできます。

インストール

$ gem install mame-rirb --source=http://gems.github.com/

使い方

以下は数字をカウントアップするだけのサンプルコード。

$i = 0
loop do
  p $i
  $i += 1
  sleep 0.5
end

これを -rrirb 付きで実行します *1

$ ruby -rrirb count.rb
0
1
2
3

普通にカウントアップしますね。
別のターミナルから同じディレクトリで rirb を起動すると、count.rb にアタッチします。

$ rirb
irb(rirb:count.rb):001:0>

普通の irb に見えますが、実はターゲットプロセス内で動いています。グローバル変数などを参照したり変更したりできます。

irb(rirb:count.rb):001:0> $i
202
irb(rirb:count.rb):002:0> $i = 10000
=> 10000
irb(rirb:count.rb):003:0>

10000 を代入したので元のターミナルにも影響します。

240
241
242
10000
10001
10002
10003

p とかを呼ぶと、irb ではなくターゲットプロセスの出力になります。

irb(rirb:count.rb):003:0> p :foo
=> :foo
irb(rirb:count.rb):004:0>
10123
10124
10125
:foo
10126
10127
10128

どうしても irb に出力させたいときは rp を使います。

irb(rirb:count.rb):004:0> rp :foo
:foo
=> :foo
irb(rirb:count.rb):005:0>

rirb を終了してもターゲットプロセス自体は終了しません。終了後には再接続できます。

irb(rirb:count.rb):005:0> exit
$ rirb
irb(rirb:count.rb):001:0>

いじめてみる。

irb(rirb:count.rb):001:0> def p(*args); raise; end
=> nil
irb(rirb:count.rb):002:0>
10282
10283
10284
(irb):1:in `p': unhandled exception
        from count.rb:3:in `block in <main>'
        from count.rb:2:in `loop'
        from count.rb:2:in `<main>'

rirb の使いどころ

常駐型のプログラム (チャットの無能とか、Web アプリとか) を動的にデバッグしたり、その場しのぎの修正を適用したり、ちょっとした修正を再起動前にテストしたりできるかも。
再現性のないバグが出て今の状態がどうなってるか知りたいけど、ログを取るコードを書き足して再起動する必要があって、困ったなーというときとか。
再起動するのに結構時間がかかるから、ちょっとメソッド再定義して修正できるか試してみたいなー、というときとか。

rirb のやってること

ターゲットを -rrirb つきで起動すると、rirb 接続用の情報を記録したファイル count-.rirb をカレントディレクトリに、通信用の Unix ソケットを /tmp などに作ります。スレッドを立てて、この Unix ソケットを待ち受けます。
クライアントの rirb を起動すると、カレントディレクトリから *.rirb というファイルを探して、Unix ソケットに接続します。
すると -rrirb は接続を accept して、irb を require して、その入出力と Unix ソケットをつなぎます。以上。

rirb の問題点

グローバル変数や定数など、グローバルに参照できるところはアクセスしやすいけど、そうでないところは何かとアクセスしにくいです。ObjectSpace 、callcc 、set_trace_func などを駆使して頑張れば何とかなるところも結構あります。失敗したら致命的な変更しちゃって例外とかで落ちますけど。君の黒魔術力が試されている。

あと、セキュリティは一切考えていません。たぶん unix ソケットに読み書きできる人は誰でも乗っ取れます。バックドアを仕込んでるようなもんです。というかバックドアそのものです。

1.8 で使うために

もう 1.8 を使ってる人なんてほとんどいない (ことにしたい) 今日この頃ですが、一応 1.8 に対応しています。ただし -rrirb ではロードできません。

require "rubygems"
require "rirb"

という start-rirb.rb とかいうファイルを作って、-rstart-rirb とでもしてください。ruby 1.8 の制約で -rrubygems -rrirb としてもダメなので注意。

余談

github の gem 生成はどうにもはまるなあ。gemspec を消して入れ直すとできた。
あと remote irb って名前はあんまりよくなかったなあ。

*1:RUBYOPT=-rrirb などとしとくといいかもしれません。どうなっても責任は取りませんが。