Javaアセンブラ「Jasmin」でバイトコードの世界を覗いてみよう(1) - 「Jasmin」で遊ぶ
「Jasmin」というJavaアセンブラをご存じでしょうか。Jasmin を使うと、Javaのバイトコードに近い命令列から class ファイルを生成することができます。仕事で直接 Jasmin を使うことはないと思いますが、Java のバイトコードがどんな命令から成り立っているのかを知ることができるので、Jasmin を使って遊んでみたいと思います。
Java のバイトコードを簡単に見る方法「javap」
Java ソースコードをコンパイルすると、class ファイルができあがります。この class ファイルがどのような構造になっているのか興味はないでしょうか。実際、Java のソースをコンパイルした class ファイルがどのようになっているのかを見るのに、特別なツールは要りません。
JDK の中に、javap というツールが用意されており、class ファイルのあるフォルダで、「javap -c クラス名」を実行すると、バイトコードを見やすく表示してくれます。
それでは、簡単に「javap」を使ってみます。簡単な例として、画面に「test」とだけ出力するものから見ていきます。まず、以下のような Java ソースファイルをコンパイルして、class ファイルを作ってみます。そして、これを「javap」にかけて、どのような出力が得られるのか考察します。
// Test01.java
public class Test01 {
public static void main(String[] args) {
System.out.print("test");
}
}
「javap -c Test01」を実行した結果:
Compiled from "Test01.java"
public class Test01 extends java.lang.Object{
public Test01();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22; //String test
5: invokevirtual #24; //Method java/io/PrintStream.print:(Ljava/lang/String;)V
8: return
}
よく意味が分からないとしても、なんだか面白いですよね。ただただ、"test"と表示するだけのプログラムなのですが、これだけのコードが生成されていることが分かります。javap の出力は、一行がバイトコードの一命令に相当するようになっています。
普段、見えないところを見るのは、ちょっと面白くありませんか。見てはいけないモノを見るかのような密かな楽しみがあります。…とは言うものの、見せかけのマニア心を満足させるだけでなくもう少し詳しく見てみます。
すると Test01 クラスが、java.lang.Object から派生するということが、2行目で明示されています。ですから例え「class Test01」とだけ書いたクラスでも「java.lang.Object」のサブクラスになるということが改めて分かります。
そして、そのすぐ下に「Test01」という public なメソッドを見つけることができます。これは、ソース中でコンストラクタを書かなかったとしても、class ファイルには、自動的に、Test01 メソッド(つまりコンストラクタ)が作られることも示しています。
その後にある残りのコードは、test と表示する main メソッドです。つまり、Javaのソースファイルに書いた1行のコードが、(returnを除いた)3行のコードに変換されたことが見て分かります。
このように、class ファイルを眺めることで、javac が行ってくれている仕事の一端を垣間見ることができました。
「Jasmin」のダウンロード
javap の出力結果を眺めるだけでは、なかなか、バイトコードの実際の動作を実感することはできません。そこで、Java アセンブラの Jasmin を使って実際に簡単なプログラムを作ってみます。
それでは、Jasmin をセットアップしてみます。Jasmin は、100% Pure Java のアプリケーションなので、以下よりアーカイブをダウンロードするだけで使えます。
- Jasmin ダウンロードページ
- Jasmin 本家
簡単な使い方ですが、次のように使います。まず、アーカイブをダウンロードすると、「jasmin.jar」というファイルがあります。コマンドラインから、jar ファイルを実行するには、次のように書きます。以下は、Jasmin の使い方を表示します。
> java -jar jasmin.jar -help
バイトコードでHello, World!
それでは、一番簡単なHello,Worldのプログラムを「hello.j」という名前で保存し、これをコンパイルしてみます。
; file:"hello.j"
; --- クラス hello の定義 ---
.class public hello
.super java/lang/Object
; --- 初期化メソッド ---
.method public <init>()V
aload_0
invokenonvirtual java/lang/Object/<init>()V
return
.end method
; --- 静的 main メソッド ---
.method public static main([Ljava/lang/String;)V
.limit stack 2
; --- 表示 ---
; java.lang.System.out("Hello, World!") と同等
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello,World!"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
コンパイルするには、以下のように打ち込みます。
> java -jar jasmin.jar hello.j
正しくコンパイルできると、hello.class という class ファイルが生成されるはずです。そこで、class ファイルを実行してみます。
> java hello.class
画面に「Hello, World!」と表示されれば成功です。
では、hello.j をもう少し詳しく見てみます。
; --- クラス hello の定義 --- .class public hello .super java/lang/Object
Jasmin のアセンブラでは、「;」から始まる行はコメントと見なされます。そして、「.class」にてクラスの宣言を行い、「.super」でスーパークラスを指定します。クラスは「java/lang/Object」のようにスラッシュ区切りで指定します。
; --- 初期化メソッド --- .method public <init>()V aload_0 invokenonvirtual java/lang/Object/<init>()V return .end method
クラスにメソッドを定義するときは「.method (定義)」から「.end method」までを記述します。「<init>」と言うのは、standard initializer と呼ばれています。そして、「invokenonvirtual」ではメソッドを実行します。ここで実行されるのは、「java.lang.Object」の<init> メソッドです。
; --- 静的 main メソッド ---
.method public static main([Ljava/lang/String;)V
.limit stack 2
; --- 表示 ---
; java.lang.System.out("Hello, World!") と同等
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello,World!"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
.end method
そして、main メソッドの定義があります。「.method」から「.end method」までがメソッドの定義です。そして、「.limit stack 2」というのは、利用するスタックのサイズを指定します。
その下の「getstatic..」の行では、(java.io.PrintStream 型である)static オブジェクト「java.lang.System.out」をスタックに乗せています。次の、「ldc ".."」で「Hello, World!」の文字列をスタックに乗せます。そして、「invokevirtual …」では、println() メソッドを実行しています。
ここで、気になるのが、VとかLとか意味不明なアルファベットです。これは、型情報を表しており、以下のように使われます。
| Type | Type Descriptor |
|---|---|
| byte | B |
| char | C |
| double | D |
| float | F |
| int | I |
| long | J |
| short | S |
| boolean | Z |
| void | V |
| One array dimension | [ |
| An instance of class | L<classname>; |
Jasmin では、スタック操作やメソッドの定義、呼び出しの際に、これらの省略文字を指定することになっています。
まとめ
以上、Hello, Wolrd を表示するだけのバイトコードを見てみました。短いコードとは言え、JVMのエッセンスが詰まっており、説明しきれない部分が多くありました。
Jasmin や JVM に関しては、こちらのページ詳しい解説があり、こちらのページに、サンプルがあります。また、Jasmin の命令セットは、基本的に、JVMと対応しているので、JVMの命令セット(Wikipedia)を見ながら書いていくと、より理解が深まります。
次回、もう少し深くJVM について考察してみたいと思います。
TrackBack URL :

