JVM用アセンブラJasminでバイトコードを書こう

classファイルの中身

Javaって書いたことあるけど、.classってどんな感じになっているのか知らなかったので、ちょっといじってみました。

javapで逆アセ

.classの逆アセンブルはjavapコマンドを利用すると可能です。

class Foo {
}

のようなFoo.javajavac Foo.javaすると

> javap -c Foo
Compiled from "Foo.java"
class Foo {
  Foo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
}

のように命令列を見ることが可能です。

Jasminでアセンブラ

Jasminを使うと、aload_0とかinvokespecialのような命令を直接書いて.classファイルにコンパイルすることができます。

インストールやHelloWorldはJavaアセンブラ「Jasmin」を使おう [Javaプログラミング] All Aboutが参考になりました。

JVMの具体的な命令セットに関しては、 Java仮想マシン - Wikipediaに解説があります。

また、Jasmin Examples にはオブジェクトのインスタンスを生成したり、継承関係をどう表現するかなどの実践的な内容があります。

Classfile Verify

JVMではclassファイルを実行する前に、そのclassファイルが安全かどうかをある程度チェックする機構があります。たとえば定義していない変数が参照されていないかどうか、やスタックが膨れ上がってスタックオーバーフローを引き起こさないかどうかをチェックしてくれます。

これによってjasmin経由で生成したclassファイルでもある程度安全に動作することが担保できます。

最初はjavaコンパイラを通らないようなやばいコードをアセンブラで書いて、ヤバい感じにjvmをぶっこわしてやるぜー₍₍ (ง ˘ω˘ )ว ⁾⁾と思っていたのですが世の中はそんなに甘くないですね(´・_・`)

詳しくはこちらのスライドの47p以降が参考になります。

www.slideshare.net

色々と参考にしましたが、何故か2引数のメソッドを作る例がなかったりしたので、 デモを増やそうと思い、簡単なサンプルを作成しました。 

コマンドライン引数から計算方法 引数1 引数2を指定すると計算してくれる電卓アプリです。

> java ExeCalculator add 1 2
3

> java ExeCalculator mul 1 2
2

コードはこちら。

gist.github.com

二引数メソッドの例ですが、

.method public add(II)I
~~~
.end method

とすると

public int add(int a, int b) {
~~~
}

となります。

カンソウ(⁰⊖⁰)

JVMはスタックマシンなので複数レジスタを管理しなければならない通常のアセンブラよりも面倒かなと思ったのですが、 逆にどのレジスタに値が入っているかを管理しなくて済み、シンプルに治まるケースもあって書きやすかったです。

gotoを乱用すると先述したclassfile verify機構に怒られてしまう(同じpcなのにスタックサイズが変わってしまうケースがある。)のでinvokevirtualでのインスタンスメソッドやinvokestaticでのスタティックメソッド呼び出しを使うことになりますが、せっかくアセンブリ書くならなるべく使いたくないなと思い、ExeCalculatorのほうは冗長に書いてみました。

文字列比較に==を使って爆死していたのは秘密です。

メソッドごとにlocal変数の数やstackサイズを指定するのですが面倒臭かったので適当です。

こういうのはコンパイラが自動でやってくれ〜(∩´﹏`∩)という気持ちになりましたが、 普通はコンパイラが自動でやってくれているので今後は日々感謝の気持ちを持ちながらjavacしたいと思いました。(´・_・`)