nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

BytemanによるJava黒魔術

クリスマスも近いですね。さて、クリスマスといえばどういうわけか黒魔術への需要が一気に高まる時期のようですので、Java Advent Calendar -ja 2010の12月20日はJavaの黒魔術をお送りします。昨日はid:celitanでした。

今日紹介する黒魔術はバイトコードインジェクションツールであるBytemanです。

この前ですね、お仕事で「HTTPレスポンスのヘッダが勝手に想定外のものに書き換わる」という不思議現象の相談を受けたんですね。Servletの中ではsetHeader("Foo", "bar")ってしてるのに、実際のレスポンスは"Foo: hoge"とか返ってる。アプリのJavaソース調べてもそんなことしてなさそうだし、Tomcatのソース見てもsetHeader()呼び出しでは何のログも出さないっぽいのでログを有効にしても原因がわからなさそう。なんだこれはとか思いつつBytemanで黒魔術開始。

RULE setHeader debug
INTERFACE javax.servlet.http.HttpServletResponse
METHOD setHeader
AT EXIT
BIND name = $1, value = $2
IF TRUE
DO traceln("setHeader(" + name + ": " + value + ")"), traceStack()
ENDRULE

Bytemanでは上のようなルール(ECA Rule, Event Condition Action)というスクリプトを書きます。HttpServletResponse.setHeader()の呼び出しのケツで第一引数と第二引数をログ出力してさらにスタックトレースも吐くよ、というルールです。Byteman有効にして起動してテストしてみたら、なんということでしょう、呼び出し元がJSP。フォワードしているJSPの画面の中にものすごくこっそりと<% response.setHeader("Foo", "hoge"); %>と書いてありましたとさ。さすがにそこにあるとは思わないですよ。これはひどい

Bytemanを使うとこんな風にスクリプトでランタイム上のバイトコードを自由に操作することができます。AOPを知っている人は、それをかなり自由に適用できるもの、と思ってもらえばわかりやすいかな。今回はデバッガとしてのユースケースを説明しましたが、デバッガとして使うにしてもソースコードを用意する必要がないという点と、ログベースのデバッグができるのでマニュアルインタラクティブGUIデバッガではできないマルチスレッド系や「たまにしか起きない」系の問題のデバッグもできるという面で通常のデバッガより優れてます。また、他にも実行統計機能を追加したり(例はsamplesにいっぱい入ってる)、モニタリングツールですという名目でByteman有効化しておいて、小さい単純なバグが発見されたときにBytemanでこっそり直したり、などとより黒いことも可能になります。

他に、Fault Injectionツールとしてとても役立ちます。元々Bytemanはこのために作られました。テスト目的などで正常パスではない、エラーとなるパスを実行させたいという要件があったときに、エラーとなる条件を作るのが超絶面倒なときがあります。ネットワーク読み取りエラーとか、12回目の呼び出しでエラーにしたい、だとかユーザ名がid:yoshioriだったらエラーにしたいとかいろいろありますよね。こういったときにBytemanでごにょごにょして、Mockしたり入力をごまかしたりします。

インストールとかは面白みがないのでばっさり省略しますが、以下のような感じでJava VMオプションを設定します。Bytemanはjava.lang.instrument APIを利用するJava言語エージェントとして作成されているので、-javaagentに指定します。

BYTEMAN_HOME=/path/to/byteman-1.4.1
BYTEMAN_RULE=/path/to/byteman.rule
BYTEMAN_OPTS="-javaagent:$BYTEMAN_HOME/lib/byteman.jar=listener:true,
script:$BYTEMAN_RULE,boot:$BYTEMAN_HOME/lib/byteman.jar
-Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.debug"

JAVA_OPTS="$BYTEMAN_OPTS $JAVA_OPTS"

というわけで、今日の「とあるjava-jaの黒魔術」も終わりです。既存のAOPバイトコードマニピュレーションツールに比べると、格段に手軽に黒魔術が詠唱できるようになります。あ、ちなみに殺戮の呪文killJVM()というのがビルトインで用意されているので、必要であれば魔法に組み込んでください。明日はid:yuroyoroです。