Learn · concepts
JVMバイトコードとは?Javaクラスファイル解説
Java仮想マシンが実行する、コンパクトな命令セットがJVMバイトコードです。 .classファイルの仕組み、modsがバイトコードを書き換える理由、そしてASMがどのように関連するのかを学びましょう.
JVM バイトコードとは?
JVM バイトコードとは、Java Virtual Machine (JVM) が実行する命令セットのことです。Java ソースコードをコンパイルすると、コンパイラは CPU 向けの機械語を生成するのではなく、.class ファイルに格納される、コンパクトで移植性の高いバイトコードを生成します。JVM は実行時にこれらの命令を読み取り、必要に応じてネイティブコードに変換します。
この階層構造(ソースコードからバイトコード、そして機械語へ)が、Java の移植性の根源です。同じ .class ファイルは、互換性のある JVM を搭載した Windows、macOS、Linux、またはその他のシステム上で実行できます。なぜなら、JVM がローカルマシンで命令を実行する方法を知っている層だからです。「一度書けば、どこでも実行できる」。
ソースコードがバイトコードになるまで
.java ファイルは、人が書くテキストです。.class ファイルは、JVM が実行するコンパイルされた出力です。コンパイラはあなたのソースコードを読み取り、チェックし、クラスごとに 1 つの .class ファイルを書き込みます。各ファイルには、バイトコード命令、定数プール、メソッドテーブル、その他のメタデータが含まれています。
バイトコードは特定のプロセッサに依存しません。同じ .class ファイルは、Java ランタイムを搭載した Windows、macOS、Linux、またはあらゆるシステム上で実行できます。なぜなら、JVM がローカルマシンで命令を実行する方法を知っている層だからです。この移植性は、この形式の目的そのものです。
命令の見た目
バイトコードはスタックベースの命令セットです。ほとんどの操作は、オペランドスタックに値をプッシュし、ポップし、結果をプッシュします。考慮すべきレジスタ割り当てはなく、モデルは意図的にシンプルであるため、JVMはそれを迅速に検証および実行できます。
いくつかのopcodeの例を挙げます。
| opcode | 動作 |
|---|---|
iload | intローカル変数をスタックにプッシュ |
iadd | 2つのintをポップし、それらの合計をプッシュ |
invokevirtual | インスタンスメソッドを呼び出す |
getfield | インスタンスフィールドを読み取る |
return | メソッドから戻る |
これらのopcodeを直接読むか書くことはほとんどありません。ツールが生成および消費します。ASMのようなライブラリはその役割を果たします。
モッドにとってバイトコードが重要な理由
バイトコードは、元のソースコードなしで動作を変更できるからです。Minecraftのモッドは、ゲームのソースコードを持ちません。代わりに、ディスク上のコンパイルされたゲームがあります。機能を追加または変更するには、モッドは既存のバイトコードを読み取り、書き換え、クラスがロードされる際にJVMに結果をフィードバックします。
パッチングフレームワークが、所有していないメソッドにフックを挿入する方法です。コンパイルされたメソッド内の適切な場所を見つけ、モッドのコードへの呼び出しをspliceします。バイトコードレベルで作業することで、モッドは元のJavaの1行も見たことがないにもかかわらず、ゲームにアタッチできます.
実際に、Terminusやその他のFabricベースのクライアントがMinecraftを修正する方法は、こうです。Minecraftのソースコードを持つ代わりに、コンパイルされた.classファイルを扱い、モジュール、設定画面、カスタムレンダリングといった機能を追加するために、バイトコードを書き換えます。
ASMがどのように適合するか
ASMは、バイトコードの読み書き、変換を行うJavaライブラリです。.classファイルをイベントやツリーとして解析し、コードが命令を修正できるようにし、有効な.classファイルを再び出力します。小さく、高速で、生のフォーマットに近い形で動作するため、より高レベルのモディングツールは、クラスファイルを直接解析するのではなく、ASMの上に構築します。
ほとんどのモッダーは、ASMを直接呼び出すことはありません。彼らは、オプコードを隠し、「このメソッドの開始時に私のコードを実行する」というより使いやすい方法を提供している、より高レベルのレイヤーを使用します。そのレイヤーの下では、多くの場合ASMを通じて、バイトコードが編集されています.
バイトコードからソースコードへの逆コンパイル
FernflowerやCFRといった逆コンパイラは、コンパイルされた.classファイルを読み取り可能なJavaコードに再構築します。このプロセスは完璧ではありません。名前やコメントが失われたり、特定の構造の復元が困難であったり、難読化されたコードは読めなくなったりします。しかし、通常の難読化されていないコードの場合、逆コンパイラは通常、元のコードと十分に近く、理解し作業できるほどに近い出力結果を生成します。
これはゲームの仕組みを学ぶのに役立ちますが、実際のソースコードを持つことの代わりにはなりません。逆コンパイルされたコードは依然としてコメントのないコンパイルされたロジックであり、再構築された変数名は誤解を招く可能性があります。これは読み取りツールであり、書き込みツールではありません。
よくある質問
いいえ。機械語は特定のCPUのためのネイティブな命令です。バイトコードはJVM自身の命令セットであり、JVMが実行時に機械語に翻訳します。その余分な層が、.classファイルをどのプラットフォームでも実行できるようにするのです.
部分的に。デコンパイラはバイトコードから読みやすいJavaを再構築し、通常のコードに対してはかなり近い結果を得ます。結果は必ずしも元のものと完全に同じではありませんが、コメントや一部のローカル名が失われることはあっても、通常は追跡するのに十分です.
通常、MOD制作者はソースコードを持っていません。コンパイルされたゲームに対して配布します。 バイトコードを書き換えることで、MODはオリジナルのJavaファイルを一切触ることなく、あるいは持たないことさえなくして動作を変更できます.
通常はそうではない。モッドフレームワークやスクリプトレイヤーが、バイトコードの処理をあなたのために代行してくれる。仕組みを理解しておくと、複雑なパッチのデバッグやツール構築の際に役立つ.