はじめに
YouDebugという、Hudson作者のid:kkawaさんによるデバッガツールがあるんですが、これGroovyみたいなスクリプト on JVM系のデバッグに非常に便利っすね。すごいっす。
- 非対話的デバッガ YouDebug - 川口耕介の日記
- YouDebug本家
YouDebugの何が便利か
環境を整える
起動オプションとかいちいち覚えてられないので、自分の場合は下のようなシェルスクリプトを用意しました。自分用なのでいい加減な作りです。どちらも、~/bin に突っ込んであります。
デバッグ対象プログラムの起動用
$ cat ~/bin/with_ydb #!/bin/sh if [ "$2" == "" ];then echo "Usage: `basename $0` <PORT> <TARGET_COMMAND>..." exit 1 fi PORT=$1 shift 1 env JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,address=$PORT $JAVA_OPTS" $*
使うときはこんな感じ。
上記の通りJAVA_OPTSをつけてるだけなのでたいていのJava系プログラムはなんでもいけると思います。
$ with_ydb 5005 groovy hoge.groovy $ with_ydb 5005 gradle test
5005ポートを使うのがkkawa流?
YouDebugスクリプトの起動用
あらかじめ、http://maven.dyndns.org/2/org/kohsuke/youdebug/ からDLした依存関係のライブラリ全部入りのyoudebug-1.x-jar-with-dependencies.jarを~/binに突っ込んでます。
$ cat ~/bin/ydb #!/bin/sh if [ "$2" == "" ];then echo "Usage: `basename $0` <PORT> <YDB_SCRIPT>" exit 1 fi PORT=$1 shift 1 java -jar ~/bin/youdebug-1.3-jar-with-dependencies.jar -socket $PORT $*
使うときはこんな感じ。
直接YouDebugスクリプトを第2引数に指定します。
$ ydb 5005 debugScript.ydb
拡張子はydbとかつけるとそれっぽいみたいです。
試してみる(1): Hello World
Debug対象のスクリプトを用意する
Hello worldでOk。
$ cat hello.groovy def message = "Hello, world" println message
あえて、message変数を使ってるのは後で値の参照・変更をするため。
普通に実行するとこんな感じ。
$ groovy hello.groovy Hello, world
YouDebugスクリプトを用意する
こんな感じで。
$ cat hello.ydb vm.breakpoint("hello", 2) { // arg1: 対象のクラス名(FQCN)、arg2:ブレークポイントをしかける行番号 println "YDB: $message" // hello.groovyの1行目で宣言されたmessage変数の値を出力 message = "Goodbye, world?" // message変数の中身を書き換える println "YDB: Modified!" }
デバッグ実行してみる
まず対象スクリプトをデバッグオプション付きで実行します。
$ with_ydb 5005 groovy hello.groovy Listening for transport dt_socket at address: 5005 (待ち状態)
Listeningという行がでた後、プロンプトがかえってこなくなります。待ち状態。
この状態でもう一枚ターミナルを用意してYouDebugスクリプトを起動すると・・・
$ ydb 5005 hello.ydb YDB: Hello, world! YDB: Modified! $
と出力されます。
さっきのwith_ydbを実行したターミナルを見てみると、
$ with_ydb 5005 groovy hello.groovy Listening for transport dt_socket at address: 5005 Goodbye, world $
と出力されてプロンプトになってます。
メッセージが差し替えられてますね。
試してみる(2): GroovyのコアAPIの挙動を変えてみる
次は、同じhello.groovyを使いますが、GroovyのコアAPIにブレークポイントをしかけて、挙動を変更してみます。
YouDebugスクリプトを用意する
変更対象の情報としてGroovyのソースコードを入手して見比べながらYouDebugスクリプトを書きます。
当然、ソースのバージョンと、使っているgroovyバイナリのバージョンは一致していないなければだめです。行番号とか、すぐずれますしね。
今回は↓のSVNの最新ソースを前提にしてますので、同じYouDebugスクリプトが他の人の環境でそのまま動くわけではないです。ご注意を。
- URL: http://svn.codehaus.org/groovy/trunk/groovy/groovy-core
- Revision: 19328
さて、今回はこんな感じにしてみます。
$ cat groovy.ydb vm.breakpoint("groovy.lang.Script", 153) { value = "YDB> " + value println "YDB: Modified!" }
groovy.lang.Scriptの153行目は↓のように、スクリプト上で書いたprintlnの実装になってます。
つまり、上のYouDebugスクリプトは、出力される文字に"YDB> "というプレフィックスを強引につけてしまおう、というGroovyコアAPIへの改変を意味してます。
149 public void println(Object value) { 150 Object object; 151 152 try { 153 object = getProperty("out"); 154 } catch (MissingPropertyException e) { 155 DefaultGroovyMethods.println(System.out,value); 156 return; 157 } 158 159 InvokerHelper.invokeMethod(object, "println", new Object[]{value}); 160 }
ちなみに、↑をみるとブレークポイント対象は150行目でもよくね?とか思いますが、実際にはNGです。空振ります。
宣言行とかtryの行はだめなんですね。Eclipseでブレークポイントしかけるときと同じで、実処理があるところじゃないとだめなようです。
デバッグ実行してみる
同じように対象スクリプトをデバッグオプション付きで実行します。
$ with_ydb 5005 groovy hello.groovy Listening for transport dt_socket at address: 5005 (待ち状態)
この状態でもう一枚ターミナルを用意してYouDebugスクリプトを起動すると・・・
$ ydb 5005 groovy.ydb YDB: Modified! $
と出力されます。
万が一ここでModified!がでないってことは、FQCNや行番号誤りでブレークポイントがうまくかからなかったってことになります。ソースとにらめっこしてクラス名を見直したり、行番号をずらしたりしてヒットするまで頑張りましょう。
さて、さっきのwith_ydbを実行したターミナルを見てみましょう。
$ with_ydb 5005 groovy hello.groovy Listening for transport dt_socket at address: 5005 YDB> Hello, world! $
やりました!!
というわけで、GroovyのコアAPIの挙動までいじれるわけですね。
TIPS: YouDebugスクリプトで試行錯誤する場合
デバッグ対象プログラムを起動→YouDebugスクリプト実行を何度も実行するのがメンドイので、whileとか使ってやると、デバッグ用のターミナル側でばしばしYouDebugスクリプトを実行するだけで済みます。
$ while TRUE; do with_ydb 5005 groovy src/hello.groovy ; done Listening for transport dt_socket at address: 5005 Goodbye, world? ←裏で1回目のYouDebugスクリプト実行 Listening for transport dt_socket at address: 5005 Goodbye, world? ←裏で2回目のYouDebugスクリプト実行 Listening for transport dt_socket at address: 5005 ←プロンプトに戻らずに次のYouDebugスクリプトの実行待ち