2017年1月5日木曜日

Bytemanチュートリアル(日本語意訳)
どうやって JVM 上のクラスにコードを注入するの?

Byteman (http://byteman.jboss.org/docs.html)のチュートリアルを日本語意訳したものです。


それでは JVM への操作をいくつか行ってみましょう。
以下のような thread.btm ルールファイルを作成してください。

----- thread.btm -----
RULE trace thread start
CLASS java.lang.Thread
METHOD start()
IF true
DO traceln("*** start for thread: "+ $0.getName())
ENDRULE
-----

このルールは JVM のランタイムクラス java.lang.Thread の start() メソッドに処理を注入しています。String 文字列とともに「+」演算子で貼り付けられたトレースメッセージを出力します。

java.lang.Thread#start() は、ある Thread インスタンスのメソッド呼び出し時に呼ばれるインスタンスメソッドです。特殊変数 $0 はこのターゲットオブジェクト自身を参照するために使います。今回の Thread#start() メソッドは引数がありませんが、ルール適用時に引数を伴って呼ばれるメソッドでであれば、その引数を $1、$2 として参照することができます。

$0 が Thread オブジェクトを参照すると認識され、$0.getName() メソッド呼び出しの際にチェックされ、結果が String 型であることが検証されます。このメソッドが呼ばれると、注入されたコードが作成され、String 定数として追加されます。traceln() メソッドに渡されて標準出力(System.out)に書き込まれます。

AppMain を少し変更した AppMain2 にルールを適用してみましょう。AppMain2 はスレッドを複数生成して System.out.println() するクラスです。

----- AppMain2.java -----
package org.my;

class AppMain2
{
    public static void main(String[] args)
    {
        for (int i = 0; i < args.length; i++) {
            final String arg = args[i];
            Thread thread = new Thread(arg) {
                public void run() {
                System.out.println(arg);
                }
            };
            thread.start();
            try {
                thread.join()
            } catch (Exception e) {
            }
        }
    }
}
-----

AppMain2 実行時に、script:<agent> オプションで thread.btm を参照させます。
ただ今回はこれだけでは不十分です。特別なオプションと引数を指定する必要があります。

 <Linux の場合>

-----
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
-----

<Windows の場合>

-----
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:thread.btm,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
-----

java.lang.Thread クラスはブートストラップ・クラスローダによってロードされます。(ブートストラップ・クラスローダとは、Java の初期クラスローダです。コア・パッケージなど根幹のクラスををロードします)
ブートストラップ・クラスローダでロードされるクラスに処理を注入する場合、Byteman エージェントクラス自体もブートストラップ・クラスローダにロードさせることが必要です。

この場合、特殊なエージェントオプション "boot:${BYTEMAN_HOME}/lib/byteman.jar" を script:<ルールスクリプト> オプションの後ろに追加します。(セパレータとしてカンマ「,」を付与します。)
これで、Byteman エージェントの jar が、ブートストラップ・クラスローダ用のクラスパスに通ります。

また、java.lang.Thread のように java.lang. に所属するようなクラスについて、Byteman ではこのような JVM を中断してしまうようなものはとても慎重に扱っており、デフォルトではコードを注入しない動作をします。このようなコア・パッケージに所属するクラス、メソッドへの変更がどうしても必要な場合、org.jboss.byteman.transform.all システムプロパティの付与が必要です。

AppMain2 を実行してみると、以下の様な出力が得られます。

-----
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:thread.btm,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain2 foo bar baz
*** start for thread: foo
foo
*** start for thread: bar
bar
*** start for thread: baz
baz
>
-----

0 件のコメント:

コメントを投稿