JVM-Bytecode-Ausführungsmaschine, die in Java/Kotlin geschrieben wurde

JAVA-Bytecode-Ausführungs-Engine

Das herkömmliche dynamische Java-Debugging kann nur auf der Quellcodeebene basieren und kann ohne Quellcode oder verschleierte Java-Klassendateien nicht dynamisch debuggt werden.

Die Ausführung von Java-Programmen basiert auf der virtuellen JVM-Maschine, die Bytecode ausführt. Wir haben mit Kotlin eine JVM-Bytecode-Ausführungs-Engine erstellt, die es uns ermöglicht, Java-Programme auf Bytecode-Ebene mithilfe moderner IDEs wie IntelliJ IDEA zu debuggen, um das Programmverhalten während der Ausführung zu beobachten.

Achtung, dieses Projekt dient nur der Untersuchung und Erforschung des Funktionsprinzips von JVM und der Analyse von Schadprogrammen. Es ist strengstens untersagt, es für illegale Zwecke zu verwenden.

Voraussetzung Wissensstiftung

Bevor Sie dieses Projekt verwenden, stellen Sie bitte sicher, dass Sie über die folgenden Wissensgrundlagen verfügen.

  1. Verständnis des Formats von Java-Klassendateien
  2. Verständnis der Rolle und Bedeutung jedes Bytecodes in JVM

Debuggen auf Bytecode-Ebene mit IDEA

git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git

Öffnen Sie dieses Projekt mit IDEA (erfordert JDK 17) und navigieren Sie zu TestCases

Es gibt zwei Testfälle in TestCases, einer zum Ausführen von statischen Methoden und einer zum Ausführen von Instanzmethoden, die jeweils benannt sindexecuteStaticMethodundexecuteVirtualMethod.

Füllen Sie auf der entsprechenden Methode die Informationen wieclassPath,className,methodNameundmethodSignature. Die detaillierten Informationen der Klassendatei können mit angezeigt werdenClassViewer.

Direkt ausführen

Verwenden Sie die kompilierte Klassendatei des folgenden Codes als Beispiel

public class Hello {

    public void hello() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

AusführenexecuteVirtualMethod, führen Sie die Hallo-Methode dieser Klasse aus.

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)

Sie können die folgende Ausgabe auf der Konsole erhalten.

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"

Die Konsolenausgabe zeigt alle Bytecode-Anweisungen dieser Methode, die Änderungen im Stapel während der Befehlsausführung und die Ergebnisse jeder Bytecode-Anweisung an.

Debuggen

Wenn Sie Bytecode-Anweisungen debuggen müssen, können Sie Breakpoints vor und nach dem setzenexecute()Methode inVMExecutor.

 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)
        }

    }
}

Fehlerbehebung von Untermethoden-Bytecode

Standardmäßig interpretiert und führt die virtuelle Engine nur den Bytecode der angegebenen Methode aus. Die innerhalb der angegebenen Methode aufgerufenen Untermethoden werden weiterhin in der JVM ausgeführt, um einen erheblichen Leistungsaufwand durch mehrere Aufrufebenen zu vermeiden. Wenn Sie möchten, dass alle Methoden von der virtuellen Engine interpretiert und ausgeführt werden, ändern Sie sie bitteio.vlinx.vmengine.Optionsund ändernhandleSubMethod zu wahr.