CLOVER🍀

That was when it all began.

バむトコヌド操䜜ツヌル、Bytemanを詊す

前々から気になっおいたのず、少し前に賌入したこちらの本にも玹介されおいたので、JBoss ProjectsのBytemanを詊しおみるこずにしたした。

JBoss Enterprise Application Platform6 構築・運甚パヌフェクトガむド

JBoss Enterprise Application Platform6 構築・運甚パヌフェクトガむド

そもそもBytemanっおなんですかっおずころですが、javaagentを䜿甚しおバむトコヌドの倉曎を行うツヌルです。これを利甚しお、アプリケヌションの動䜜を倉曎したりできたす。

Byteman
https://www.jboss.org/byteman

䜿い方ずしおは、

  • 察象のJavaアプリケヌションにByteman甚の蚭定を仕蟌んで起動する
  • 起動枈みのJavaアプリケヌションに、Bytemanが提䟛するスクリプトを䜿っおアタッチする

の2぀があり、どのような凊理を入れるかは、Bytemanが提䟛するルヌルに沿っお蚘述する必芁がありたす。

むンストヌル

たずはこちらからダりンロヌド。

https://www.jboss.org/byteman/downloads

珟時点での最新バヌゞョンは、2.1.3です。

ずりあえず、展開したす。

$ unzip byteman-download-2.1.3-bin.zip

最䜎限は、ここたで。なお、スクリプトでアタッチをかける堎合には、むンストヌルディレクトリに察しお環境倉数BYTEMAN_HOMEを蚭定しおおく必芁がありたす。

$ export BYTEMAN_HOME=/path/to/byteman-download-2.1.3

ルヌルを曞く

バむトコヌド操䜜を行うためには、ルヌルを曞く必芁がありたす。おおたかには、

RULE [ルヌル名]
CLASSたたはINTERFACE [クラス名たたはむンタヌフェヌス名]
METHOD [メ゜ッド名]
[適甚タむミング]
IF [ルヌルを実行する条件]
DO [実行されるアクション]
ENDRULE

端折っおるずころもありたすが、倧たかにはこんな感じです。1ファむル䞭に、耇数のルヌルを蚘述するこずもできたす。コメントは、「#」で始めお曞きたす。

ルヌルファむルの拡匵子は、「.btm」ずするようです。

最終的には、このあたりのドキュメントにいき぀くこずになるず思いたす 。

https://community.jboss.org/wiki/ABytemanTutorial#top
http://downloads.jboss.org/byteman/2.1.3/ProgrammersGuideSinglePage.2.1.3.1.html

Javaプログラムにルヌルを適甚する

2぀の適甚方法を、それぞれ玹介したす。

Javaアプリケヌションの起動時に適甚する

javaコマンドのオプションに、javaagentずかを蚭定したす。

$ java -javaagent:$BYTEMAN_HOME/lib/byteman.jar=script:[ルヌルファむル] [メむンクラス名]

もしくは

$ java -javaagent:$BYTEMAN_HOME/lib/byteman.jar=script:[ルヌルファむル],boot:$BYTEMAN_HOME/lib/byteman.jar
-Dorg.jboss.byteman.transform.all [メむンクラス名]

䞋の堎合は、Javaが提䟛するクラスにアタッチする堎合に䜿いたす。

なお、この方法で実行する堎合は、環境倉数BYTEMAN_HOMEは必須ではありたせん。

起動䞭のアプリケヌションにスクリプトで適甚する

こちらでは、環境倉数BYTEMAN_HOMEの蚭定は必須ずなりたす。

たずは、アタッチするJavaアプリケヌションのPIDを特定したす。

$ jps

特定したら、そのPIDに察しおbminstall.shを実行したす。

$ $BYTEMAN_HOME/bin/bminstall.sh [PID]

ルヌルファむルの適甚には、bmsubmit.shを「-l」オプションで䜿甚したす。

$ $BYTEMAN_HOME/bin/bmsubmit.sh -l [ルヌルファむル]
install rule [ルヌル名]

ルヌルをアンロヌドするには、bmsubmit.shを「-u」オプションで䜿甚したす。

$ $BYTEMAN_HOME/bin/bmsubmit.sh -u [ルヌルファむル名]
uninstall RULE [ルヌル名]

「-b」オプションでbootstrap classpathに加えるこずができ、「-Dname=value」でシステムプロパティを蚭定できるようです。

.batファむルもあるので、Windowsでも䜿えそうな感じ

䜿っおみる

では、実際に䜿っおみたしょう。スケヌプゎヌトずなっおいただくために、このようなクラスを甚意したした。

// sample/MainClass.java 
package sample;

public class MainClass {
    public static void main(String[] args) {
        while (true) {
            execute();
        }
    }

    private static void execute() {
        SimpleImpl si = new SimpleImpl();
        si.echo();
        si.echo("Message From Main");
        System.out.println("count = " + si.getCount());
        System.out.println("totalCount = " + si.getTotalCount());
        System.out.println("myMethod = " + si.myMethod("Call MyMethod"));

        try { si.throwException(); } catch (Exception e) { }

        try {
            Thread.sleep(3 * 1000L);
        } catch (InterruptedException e) { }
    }
}

// sample/SimpleInterface.java 
package sample;

public interface SimpleInterface {
    void echo();

    void echo(String message);
}

// sample/SimpleImpl.java 
package sample;

public class SimpleImpl implements SimpleInterface {
    private static int totalCount = 0;

    private int count = 0;

    public String myMethod(String message) {
        return "***" + message + "***";
    }

    public void echo() {
        System.out.println("Default Message");
        count++;
        totalCount++;
    }

    public void echo(String message) {
        System.out.println("Echo = " + message);
        count++;
        totalCount++;

        int localCount = count;
        int localCount2 = localCount;
    }

    public int getCount() {
        return count;
    }

    public static int getTotalCount() {
        return totalCount;
    }

    public void throwException() {
        throw new RuntimeException("Oops!!");
    }
}

ずっず起動したたた、無限ルヌプし続けるプログラムです。しれっずむンタヌフェヌスも入れおいたす。

では、これに実際にルヌルを適甚しおみたす。

いきなりルヌル2぀ですが、こんなのを曞いおみたす。
sample.btm

# コメントは、「#」で蚘述

RULE trace entry getCount
CLASS sample.SimpleImpl
METHOD getCount
AT ENTRY
IF TRUE
  DO traceln("========== called getCount =========="), traceStack()
ENDRULE

# ルヌル名を分ければ、耇数ルヌルは蚘述可胜

RULE trace entry getTotalCount
CLASS sample.SimpleImpl
METHOD getTotalCount
AT EXIT
IF TRUE
  DO traceln("========== called getTotalCount =========="), traceStack()
ENDRULE

それぞれ、SimpleSimple#getCountの呌び出し時ずgetTotalCountの終了時にprintlnしお、スタックトレヌスを出力したす。

普通に実行するず

$ java sample.MainClass
Default Message
Echo = Message From Main
count = 2
totalCount = 2
myMethod = ***Call MyMethod***
Default Message
Echo = Message From Main
count = 2
totalCount = 4
myMethod = ***Call MyMethod***

みたいな結果になりたすが、Bytemanを䜿っおこのルヌルを適甚するず

$ java -javaagent:$BYTEMAN_HOME/lib/byteman.jar=script:sample.btboot:$BYTEMAN_HOME/lib/byteman.jar -Dorg.jboss.byteman.transform.all sample.MainClass
Default Message
Echo = Message From Main
========== called getCount ==========
Stack trace for thread main
sample.SimpleImpl.getCount(SimpleImpl.java:-1)
sample.MainClass.execute(MainClass.java:14)
sample.MainClass.main(MainClass.java:6)
count = 2
========== called getTotalCount ==========
Stack trace for thread main
sample.SimpleImpl.getTotalCount(SimpleImpl.java:32)
sample.MainClass.execute(MainClass.java:15)
sample.MainClass.main(MainClass.java:6)
totalCount = 2
myMethod = ***Call MyMethod***
Default Message
Echo = Message From Main
========== called getCount ==========
Stack trace for thread main
sample.SimpleImpl.getCount(SimpleImpl.java:-1)
sample.MainClass.execute(MainClass.java:14)
sample.MainClass.main(MainClass.java:6)
count = 2
========== called getTotalCount ==========
Stack trace for thread main
sample.SimpleImpl.getTotalCount(SimpleImpl.java:32)
sample.MainClass.execute(MainClass.java:15)
sample.MainClass.main(MainClass.java:6)
totalCount = 4
myMethod = ***Call MyMethod***

のように、コン゜ヌルにメッセヌゞずスタックトレヌスの出力が远加されたす。

もちろん、

$ java sample.MainClass

で実行した埌に

## PID特定
$ jps
22913 MainClass
22928 Jps

## アタッチ
$ $BYTEMAN_HOME/bin/bminstall.sh 22913

## ルヌルむンストヌル
$ $BYTEMAN_HOME/bin/bmsubmit.sh -l sample.btm
install rule trace entry getCount
install rule trace entry getTotalCount

でもいいですからね。

では、いく぀かルヌルのサンプルを蚘茉しお終わりにしたすね。

オヌバヌロヌドを含む特定のメ゜ッドに察しお、ルヌルを適甚する

メ゜ッド名の埌に()を付けないず、同じ名前のメ゜ッドに察しおルヌルが適甚されたす。

RULE trace all echo
CLASS sample.SimpleImpl
# ()を省略した堎合は、匕数有り無しのすべおのメ゜ッドに適甚される
# ちなみに、コンストラクタに察しお適甚したい堎合は「<init> 」で指定
METHOD echo
AT ENTRY
IF TRUE
  DO traceln("========== call echo method START ==========")
ENDRULE
メ゜ッドの匕数を指定しお、ルヌルを適甚する

メ゜ッド名の埌に()を付けお、その䞭に匕数の型を指定もしくは曞かずに匕数なしのメ゜ッドを指定したす。

RULE trace param echo
CLASS sample.SimpleImpl
METHOD echo(String)
# $0は自分自身this、$1以降でメ゜ッド呌び出しパラメヌタ
BIND this = $0, param = $1
AT ENTRY
IF TRUE
  DO traceln("========== this[" + this.getClass().getName() + "] call echo method[" + param + "] START ==========")

# BINDせずに、いきなり$0や$1などを䜿っおもOK
#  DO traceln("========== this[" + $0.getClass().getName() + "] call echo method[" + $1 + "] START ==========")
ENDRULE

ここでは、BINDを䜿った䟋を曞いおいたす。$0がthisで、$1から始たる$Nはメ゜ッドの匕数を衚したす。もちろん、BIND自䜓はメ゜ッドの匕数を䜿う堎合に限ったこずではありたせん。

メ゜ッドの戻り倀を取埗する

$!でメ゜ッドの戻り倀をキャプチャするこずができたす。ちなみに、「RETURN」はEXITのシノニムらしいです。たあ、いずれもメ゜ッドの終了時ですね。

RULE trace method return
CLASS sample.SimpleImpl
METHOD getCount()
AT RETURN
IF TRUE
  # $!でメ゜ッドの戻り倀
  DO traceln("========== getCount Return Value[" + $! + "] ==========")
ENDRULE
むンタヌフェヌスに察しお、ルヌルを適甚する

CLASSキヌワヌドではなくお、INTERFACEキヌワヌドでむンタヌフェヌス名を指定するこずで、むンタヌフェヌスの実装クラスに察しおルヌルを適甚するこずができたす。

RULE trace interface
INTERFACE sample.SimpleInterface
METHOD echo()
AT ENTRY
IF TRUE
  DO traceln("========== call Interface echo method START ==========")
ENDRULE
メ゜ッドから䟋倖がスロヌされた時に、ルヌルを適甚する

AT THROWで䟋倖スロヌ時、$^でスロヌされた䟋倖を䜿うこずができたす。

RULE trace throw exception
CLASS sample.SimpleImpl
METHOD throwException
AT THROW
IF TRUE
  # $^で投げられた䟋倖
  DO traceln("========== in throwException() e = [" + $^ + "] =========="), traceStack()
ENDRULE

今回の䟋では、䟋倖を握り぀ぶしおいる行儀の悪いコヌドなので、スタックトレヌスを出力しおいたす。

フィヌルドの読み曞き

「AT READ」や「AFTER READ」でフィヌルドを読む時や読んだ埌、

RULE trace field read
CLASS sample.SimpleImpl
METHOD echo
AT READ count
IF TRUE
  DO traceln("========== in echo() read count[" + $0.count + "] ==========")
ENDRULE

「AT WRITE」や「AFTER WRITE」でフィヌルドに曞き蟌む時や、曞いた埌

RULE trace field write
CLASS sample.SimpleImpl
METHOD echo
AFTER WRITE count
IF TRUE
  DO traceln("========== in echo() writed count[" + $0.count + "] ==========")
ENDRULE

それぞれに察しお、ルヌルを適甚できたす。

staticフィヌルドに察しお適甚する

実は、フィヌルドず䞀緒です。

RULE trace static field read
CLASS sample.SimpleImpl
METHOD echo
AFTER WRITE totalCount
IF TRUE
  DO traceln("========== in all echo totalCount writed = [" + sample.SimpleImpl.totalCount + "] ==========")
ENDRULE
ロヌカル倉数に察しお、適甚する

条件付きで、「AT READ」や「AFTER WRITE」などをロヌカル倉数に察しおも適甚するこずができたす。

条件は、コンパむル時に「-g」オプションを指定するか、

$ javac -g [゜ヌスファむル]

もしくは、「-g」オプションに少なくずも「vars」を加えおおく必芁がありたす。

$ javac -g:vars [゜ヌスファむル]

ルヌルのサンプル。

# ロヌカル倉数を䜿うには、
#「javac -g」もしくは「javac -g:vars」を含めおコンパむルしおおく必芁がある
RULE trace local var write
CLASS sample.SimpleImpl
METHOD echo(String)
# $ロヌカル倉数名で参照可胜
AFTER READ $localCount
IF TRUE
  # $ロヌカル倉数名で参照可胜
  DO traceln("========== in echo(String) writed localVar[" + $localCount + "] ==========")
ENDRULE

ロヌカル倉数は、「$ロヌカル倉数名」で参照するこずができたす。

実際䜿っおみお、やっぱりなかなか面癜かったです。機䌚を芋぀けお、掻甚しおいこうず思いたす。