Java/Kotlinで書かれたJVMバイトコード実行エンジン
JAVAバイトコード実行エンジン
従来の Java 動的デバッグはソース コード レベルにのみ基づくことができ、ソース コードまたは難読化された Java クラス ファイルなしでは動的デバッグできません。
Java プログラムの実行は、バイトコードを実行する JVM 仮想マシンに基づいています。 Kotlin を使用して JVM バイトコード実行エンジンを構築しました。これにより、IntelliJ IDEA などの最新の IDE を使用して Java プログラムをバイトコード レベルでデバッグし、実行中のプログラムの動作を観察できるようになります。
注意してください、このプロジェクトは JVM の動作原理を調査研究し、悪意のあるプログラムを分析することのみを目的としています。違法な目的で使用することは固く禁じられています。
前提知識の基礎
このプロジェクトを使用する前に、次の知識基盤があることを確認してください。
- Javaクラスファイルの形式の理解
- JVM内の各バイトコードの役割と意味を理解する
IDEAを使用してバイトコードレベルでデバッグする
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
IDEAを使用してこのプロジェクトを開きます(JDK 17が必要です)そしてTestCasesに移動します
TestCasesには2つのテストケースがあります。1つは静的メソッドを実行するためのもので、もう1つはインスタンスメソッドを実行するためのものです。executeStaticMethod
とexecuteVirtualMethod
。
対応するメソッドには、情報を入力してくださいclassPath
、className
、methodName
そしてmethodSignature
。クラスファイルの詳細情報は、クラスビューア。
直接実行
以下のコードのコンパイルされたクラスファイルを例として使用する
public class Hello {
public void hello() {
System.out.println("hello");
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
実行するexecuteVirtualMethod
、このクラスの hello メソッドを実行します。
val classPath = "your-classpath"
val className = "Hello"
val methodName = "hello"
val methodSignature = "()V"
val args = listOf<Any?>()
val url = File(classPath).toURI().toURL()
val urls = arrayOf(url)
val loader = VlxClassLoader(urls)
val clazz = loader.loadClass(className)
val method = ClassUtils.getMethod(clazz, methodName, methodSignature, loader)
val instance = clazz.getDeclaredConstructor().newInstance()
val thread = VMThread(VMEngine.instance, loader)
thread.execute(instance, method!!, args, true, 0)
コンソールに次の出力が表示されます。
2023-05-21 17:51:10 [DEBUG] Execute method: public void Hello.hello()
2023-05-21 17:51:10 [DEBUG] Receiver: Hello@3daf7722
2023-05-21 17:51:10 [DEBUG] Args: []
2023-05-21 17:51:11 [DEBUG] LocalVars: [kotlin.Unit]
2023-05-21 17:51:11 [DEBUG] "L0: GETSTATIC"
2023-05-21 17:51:11 [DEBUG] "#7"
2023-05-21 17:51:11 [DEBUG] public static final java.io.PrintStream java.lang.System.out
2023-05-21 17:51:11 [DEBUG] "push" org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] "L3: LDC"
2023-05-21 17:51:11 [DEBUG] "#13"
2023-05-21 17:51:11 [DEBUG] "hello"
2023-05-21 17:51:11 [DEBUG] "push" "hello"
2023-05-21 17:51:11 [DEBUG] "L5: INVOKEVIRTUAL"
2023-05-21 17:51:11 [DEBUG] "#15"
2023-05-21 17:51:11 [DEBUG] "class java.io.PrintStream, NameAndType(name='println', type='(Ljava/lang/String;)V')"
2023-05-21 17:51:11 [DEBUG] public void java.io.PrintStream.println(java.lang.String)
2023-05-21 17:51:11 [DEBUG] "pop" "hello"
2023-05-21 17:51:11 [DEBUG] "pop" org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] Execute method: public void org.gradle.internal.io.LinePerThreadBufferingOutputStream.println(java.lang.String)
2023-05-21 17:51:11 [DEBUG] Receiver: org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] Args: [org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905, hello]
2023-05-21 17:51:11 [ERROR] Can't parse class class org.gradle.internal.io.LinePerThreadBufferingOutputStream
hello
2023-05-21 17:51:11 [DEBUG] "L8: RETURN"
コンソール出力には、このメソッドのすべてのバイトコード命令、命令実行中のスタックの変更、および各バイトコード命令の結果が表示されます。
デバッグ
バイトコード命令をデバッグする必要がある場合は、前後のブレークポイントを設定することができますexecute()
メソッド内VMExecutor
。
fun execute() {
while (true) {
try {
pc = sequence.index()
val opcode = sequence.readUnsignedByte().toShort()
if (execute(opcode)) {
break
}
} catch (vme: VlxVmException) {
Logger.FATAL(vme)
exitProcess(1)
} catch (t: Throwable) {
handleException(t)
}
}
}
サブメソッドのバイトコードのデバッグ
デフォルトでは、仮想エンジンは指定されたメソッドのバイトコードのみを解釈して実行します。指定されたメソッド内で呼び出されるサブメソッドは、複数の呼び出し層による重大なパフォーマンスのオーバーヘッドを回避するために、引き続き JVM 内で実行されます。すべてのメソッドを仮想エンジンで解釈して実行したい場合は、次のように変更してください。io.vlinx.vmengine.Options
そして変更するhandleSubMethod
本当のこと。