2017年1月29日日曜日

Bytemanチュートリアル(日本語意訳)
稼働中に Byteman エージェントをあとからインストールする方法は?

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

実行中のプログラムをチェックするために Byteman を利用する際、Byteman エージェントをロードせずに実行していたことに気づくことがあります。例えば JBoss アプリケーションサーバのようなプログラムを長時間稼働中、何か問題だ生じたことがログに出力されていた場合などです。
少なくとも Hotspot、JRockit、OpenJDK JVM で実行してたなら、Byteman を使用するために稼働中のプログラムを再起動する必要はありません。これらのJVM の場合、稼働中のプログラムにByteman エージェントを動的に追加インストールすることができます。

Linux なら bminstall.sh スクリプトを利用します。
-----
> bminstall.sh 13101
>
-----

すべての JVM で、動的なエージェントのロードができるわけではありません。
Linux では、Oracle JVM 、OpenJDK、JRockit が、macOSX では OracleJVM と OpenJDK ならできます。
IBM JVM や Windows 上でのその他 JVM で可能かは不明です。

Windows も bminstall.bat を利用します。(リリース 2.0.1 以降)
2.0.1 以前の場合 Install.main() を必要な引数を指定して実行します。
-----
> java -classpath %BYTEMAN_HOME%\lib\byteman-install.jar org.jboss.byteman.agent.install.Install 13101
>
-----

上記の数値(13101)の箇所には、Byteman エージェントをインストールする JVM のプロセスIDを指定します。
チェックしたい稼働中の Java プログラムのその時点の PID を指定する必要があります。(PIDの確認は $JAVAHOME/bin/jps を使うと便利です。)

また、稼働中のJavaプログラムの main クラス名を指定することもできます。(jar コマンド($java -jar myapp.jar)のように起動した場合は、jar名を指定することもできます。)

bminstall.sh は、稼働中の JBoss アプリケーション・サーバ・インスタンスに Byteman エージェントをインストールするのに非常に便利です。AppMain3 を -javaagent オプションなしで再度実行し、稼働後に Byteman エージェントとルール・スクリプトを読み込ませてみましょう。
-javaagent オプションなしで起動しておきます。
    -----
    > java org.my.AppMain3
    foo
    foo
    bar
    bar
    -----

別のコマンドシェルでエージェントをインストールします。
<Linux>
    -----
    > bminstall.sh -b -Dorg.jboss.byteman.transform.all org.my.AppMain3
    >
    -----

bminstall コマンドはルール・スクリプトのロードはできませんが、エージェント・リスナーを自動的に有効にでき、さらに bmsubmt.sh でルールを送信できます。再度 bmsubmit コマンドで thread.btm ルールを送信すると AppMain3 の動作が途中から変更されたことが確認できます。

Bytemanチュートリアル(日本語意訳)
ルールのアンロード方法

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

bmsubmit.sh でルールをアンロードして、AppMain3 の Thread.start() メソッドをオリジナルの動作に戻すことができます。

    -----
    > bmsubmit.sh -u thread.btm
    uninstall RULE trace thread start

    > bmsubmit.sh
    no rules installed
    -----

-u オプションの後にルール・スクリプト名を指定します。指定したルールスクリプト内のすべてのルールがアンロードされ、メソッドに注入されたコードが削除されます。
AppMain3 が、入力テキストを繰り返すのみの最初の動作に戻りました。

    -----
    . . .
    mumble
    *** start for thread: mumble
    mumble
    grumble
    grumble
    bletch
    bletch
    end
    >
    -----

ルール・スクリプトの注入コードのみ変更したいなら、ルールのアンロード、リロードは必要ありません。ルール・スクリプトを修正して再度 submit すれば、Byteman は既存の注入コードを削除し新しいコードを再注入します。

Bytemanチュートリアル(日本語意訳)
どのルールがロードされコンパイルされたか確認したいんですが?

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

bmsubmit.sh を再度実行するとルールが正常にコンパイルされていることが確認できます。

    -----
    > bmsubmit.sh
    # File thread.btm line 4
    RULE trace thread start
    CLASS java.lang.Thread
    METHOD start()
    AT ENTRY
    IF true
    DO traceln("*** start for thread: "+ $0.getName())
    ENDRULE
    Transformed in:
    loader: sun.misc.Launcher$AppClassLoader@5acac268
    trigger method: java.lang.Thread.start() void
   
    compiled successfully
    >
    -----

「compiled successfully.」と出力されています。これはルールの型チェックに成功し適用されたことを意味します。

ルールが最初にトリガーされたときに型チェックを行った場合([baz]を含む行をタイプした後)は、
型チェック失敗するとコード注入は実行されずに失敗した詳細が出力されます。
注入されたコードはルールがアンロードされたときのみ削除されます。

Bytemanチュートリアル(日本語意訳)
実行中のプログラムにルールを動的適用する方法は?

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

長時間動くプログラムの場合、プログラム実行後にルールをロードしたり、ルールの再ロードをしたいことがあります。これらは Byteman にエージェント・リスナーを起動されば可能になります。エージェント・リスナーはルールのロード状況の確認もできます。

AppMain3 を使って、リスナーの動作を確認しましょう。

    ----- AppMain3.java -----
    package org.my;
    import java.io.DataInputStream;
 
    class AppMain3  {
        public static void main(String[] args) {
            try {
                DataInputStream in = new DataInputStream(System.in);
                String next = in.readLine();
                while (next != null && next.length() > 0 && !next.contains("end")) {
                    final String arg = next;
                    Thread thread = new Thread(arg) {
                        public void run() {
                        System.out.println(arg);
                        }
                    };

                    thread.start();

                    try {
                        thread.join();
                    } catch (Exception e){ }

                    next = in.readLine();
                }
            } catch (Exception e) {}
        }
    }
    -----

AppMain3 は入力されたテキストをそのまま出力します。"end" 文字列、または EOF が入力されると止まります。Byteman エージェント・リスナーと一緒に AppMain3 を起動すると、AppMain3 の読み込み待機中にルールのロード/アンロードができます。またプログラムに解析され注入されたルールの型チェックや実行された状態チェックもできます。

エージェント・リスナーを有効にするには、エージェント・オプション「listener:true」を使います。

<Linux>
 -----
 $ java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=listener:true,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
-----

<Windows>
-----
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=listener:true,boot:%BYTEMAN_HOME%\lib\byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
-----

エージェント・リスナーは通信口(サーバー・ソケット)を開いてコマンド入力を待ちます。エージェント・オプションにルールスクリプトが指定しなかったので、まずは入力内容が繰り返されるのみです(★)。

----- 実行例 ---
> java  -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=listener:true,boot:${BYTEMAN_HOME}/lib/byteman.jar -Dorg.jboss.byteman.transform.all org.my.AppMain3
    foo
    foo  --★
    bar
    bar  --★
-----

<Linux>
エージェント・リスナーに処理させる際は bmsubmit.sh を使います。bmsubmit.sh を引数なしで実行すると、インストールされているルールをすべて表示します。

-----
> bmsubmit.sh
 no rules installed  
>
(※現在、ルール未インストール)
-----

※ bmsubmit.sh は別シェルを立ち上げ実行してください。また、AppMain3 は先に起動すして、 bmsubmit.sh と Byteman エージェントがサーバーソケットを経由して対話できるようしておきます。

<Windows>
bmsubmit.bat を実行します。(Byteman リリース 2.0.1 以降)

-----
> bmsubmit
  no rules installed
>
(※現在、ルール未インストール)
-----

(2.0.1 以前のリリースは、リスナーにコマンドを送信するために Java コマンドで Submit クラスを呼び出す必要があります。)
-----
> java -classpath %BYTEMAN_HOME%\lib\byteman-submit.jar org.jboss.byteman.agent.submit.Submit
  no rules installed
>
-----

次に bmsubmit.sh を使って実行中のプログラムにルール・スクリプトをロードします。

<Linux>
-----
> bmsubmit.sh -l thread.btm
 install rule trace thread start
>
(※ルール・スクリプト trace がインストールされました。)
-----

<Windows>
bmsubmit.bat を使います。
 
(リリース 2.0.1 以前は、Submitクラスに -l 引数を付与して実行します。
-----
> java -classpath %BYTEMAN_HOME%\lib\byteman-submit.jar org.jboss.byteman.agent.submit.Submit -l thread.btm
  install rule trace thread start
>
(※ルール・スクリプト trace がインストールされました。)
-----

インストールされたルールのステータスを再確認すれば、ルールがロードされ解析されて Thread.start() メソッドに注入されていることが確認できます。もしルールの完全な解析に失敗した場合は、注入が阻止されたことと詳細を含む解析エラーが出力されます。
-----解析エラー-----
> bmsubmit.sh
  # File thread.btm line 4
  RULE trace thread start
  CLASS java.lang.Thread
  METHOD start()
  AT ENTRY
  IF true
  DO traceln("*** start for thread: "+ $0.getName())
  ENDRULE
  Transformed in:
  loader: sun.misc.Launcher$AppClassLoader@5acac268
  trigger method: java.lang.Thread.start() void
>
-----

実行中のAppMain3で入力を進めるとルールが実行されたことが確認できます。
    -----
    . . .
    bar
    bar
    baz
    *** start for thread: baz
    baz
    mumble
    *** start for thread: mumble
    mumble
    -----

Bytemanチュートリアル(日本語意訳)
Byteman を簡単に実行する方法はないの?

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

Linux では、-javaagent オプションをラップした $BYTEMAN_HOME/bin/bmjava.sh を利用できます。java コマンドのように見えますが、 ルール・スクリプトをコマンドラインオプションで渡すと、-javaagent script:<ルールスクリプト> として付与してくれます。また、自動で byteman.jar ファイルを boot:<エージェント> オプションとして付与してくれます。
bmjava.sh を使うと前項のコマンドラインを簡素化することができるのです。-l オプションでロードするスクリプトを渡します。
 
-----
> bmjava.sh -l thread.btm org.my.AppMain2 foo bar baz
-----

Windows には bmjava.bat という同等のスクリプトが用意されています(Byteman 2.0.1 以降)。
こちらも同様にシンプルに実行できます。
 
-----
> bmjava -l thread.btm org.my.AppMain2 foo bar baz
 -----

スクリプト名で実行できるよう以下を path 環境変数に通しておいてください。
<Linux>
exprot path=$path:${BYTEMAN_HOME}/bin

<windows>
set path=%path%;%BYETMAN_HOME%/bin

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
>
-----

2017年1月3日火曜日

Bytemanチュートリアル(日本語意訳)
どうやって Byteman を使ってプログラムを実行するの?

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


Byteman を使って Java アプリケーションを実行する方法はいくつかあります。
一番簡単なのは java をコマンドラインで実行する際に -javaagent オプションを付与する方法です。
簡単な class を使って Byteman の動きを見てみます。


---- AppMain.java ----- 
package org.my; 

class AppMain { 
  public static void main(String[] args) { 
     for (int i = 0; i < args.length; i++) { 
         System.out.println(args[i]); 
     } 
  } 

----

コンパイルして実行してみます。

----- プロンプト -----
> javac org/my/AppMain.java
> java  org.my.AppMain foo bar baz
foo
bar
baz
>
-----

では、Byteman で main メソッドの入り口と出口にトレースコードを注入してみましょう。

はじめに、Byteman ルールスクリプト・ファイル(rule script file)を作成します。任意のディレクトリに 以下の内容の appmain.btm というファイルを作成します。

----- appmain.btm -----
RULE trace main entry
CLASS AppMain
METHOD main
AT ENTRY
IF true
DO traceln("entering main")
ENDRULE

RULE trace main exit
CLASS AppMain
METHOD main
AT EXIT
IF true
DO traceln("exiting main")
ENDRULE
-----

appmain.btm には、AppMain「CLASS」の main() 「METHOD」にコードを注入する、2つのルール「RULE」が含まれています。1つ目のルールは 「AT ENTRY」、すなわち メソッド(METHOD)の開始時に注入されます。注入されるコードは「IF DO」に記載されたものです。1つ目では Byteman のビルトイン関数の traceln(String)(与えられた String 引数を System.out() で新しい行に出力する)を注入しています。2つ目は「AT EXIT」、つまり main ()メソッドがリターンしたタイミングに注入しています。
どちらのルールも「IF true」と定義されています。(これは「DO」が常に実行されることを意味します。)「IF」には「DO」の内容を処理するかどうか制御するために別の Java の式を書くことができます。

これらの Byteman ルールを適用してアプリケーションを実行する -javaagent オプションは、主要なデスクトップ版/サーバ版の JVM でサポートされています。構文は以下のとおりです。

-----
-javaagent : path_to_agent_jar = agent_option1 , agent_option_2 , . . .
-----

Linux にて、アプリケーションを Byteman で appmain.btm ルールを適用して実行するコマンドは以下のとおりです。

-----
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
-----

Windwos の場合は以下の通りです。

-----
> java -javaagent:%BYTEMAN_HOME%\lib\byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
-----

「:」の後ろで Byteman エージェントの jar ファイルの参照場所をJVM に教えます。(${BYTEMAN_HOME}/lib/byteman.jar)Byteman エージェント・オプションとして「=」の後ろに Byteman ルールスクリプトファイルの場所( script:appmain.btm )を指定します。すると Byteman エージェントがこのオプションを参照し、appmain.btm ファイルからルールをロードして注入します。もし複数のルールスクリプトをロードしたい場合「script:file」エージェントオプションをカンマ区切りで指定することができます。

-----
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm,script:appmain2.btm org.my.AppMain foo bar baz
-----

Java 実行時のコマンドラインで -javaagent オプションを指定すると、JVM が起動時してアプリケーションが実行される前に、Byteman エージェントが開始されます。Byteman エージェントによりルールが読み込まれ、AppMain クラスの main()メソッドが呼ばれる前に副作用が注入されます。結果、以下の出力になります。

-----
> java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain foo bar baz
entering main
foo
bar
baz
exiting main
>

-----