Die Verwendung von Java/Kotln und der JVM-Lizenz ist nicht möglich
JAVA Bytecode-Ausführungs-Engine
Das traditionelle dynamische Debugging von Java kann nur auf der Ebene des Quellcodes erfolgen und ist ohne Quellcode oder verschleierte Java-Klassendateien nicht dynamisch durchführbar.
Die Ausführung von Java-Programmen basiert auf der JVM (Java Virtual Machine), die Bytecode ausführt. Wir haben eine JVM-Bytecode-Ausführungs-Engine mit Kotlin entwickelt, die es uns ermöglicht, Java-Programme auf Bytecode-Ebene mithilfe moderner IDEs wie IntelliJ IDEA zu debuggen und so das Programmverhalten während der Ausführung zu beobachten.
Achtung: Dieses Projekt dient ausschließlich dem Studium und der Erforschung der Funktionsweise der JVM und der Analyse von Schadprogrammen. Jegliche Nutzung zu illegalen Zwecken ist strengstens untersagt.
Grundlagenwissen
Bevor Sie dieses Projekt nutzen, stellen Sie bitte sicher, dass Sie über die folgenden Kenntnisse verfügen.
- Das Format von Java-Klassendateien verstehen
- Die Rolle und Bedeutung jedes Bytecodes in der JVM verstehen
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 Testfälle.
Es gibt zwei Testfälle in TestCases, einen für die Ausführung statischer Methoden und einen für die Ausführung von Instanzmethoden, die jeweils benannt sind executeStaticMethod Und executeVirtualMethodDie
Füllen Sie bei der entsprechenden Methode die Informationen wie z. B. aus classPath, className, methodName, Und methodSignatureDie detaillierten Informationen der Klassendatei können angezeigt werden mit ClassViewerDie
Direkt ausführen
Anhand der kompilierten 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ühren executeVirtualMethodFühre die Hello-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 erhalten folgende Ausgabe in der Konsole.
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 Stack während der Anweisungsausführung und die Ergebnisse jeder Bytecode-Anweisung an.
Debugging
Wenn Sie Bytecode-Anweisungen debuggen müssen, können Sie Haltepunkte vor und nach dem Befehl setzen. execute() Methode in VMExecutorDie
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)
}
}
}
Debugging des Submethoden-Bytecodes
Standardmäßig interpretiert und führt die virtuelle Engine nur den Bytecode der angegebenen Methode aus. Die innerhalb dieser Methode aufgerufenen Untermethoden werden weiterhin in der JVM ausgeführt, um einen erheblichen Leistungsverlust durch mehrere Aufrufebenen zu vermeiden. Wenn Sie möchten, dass alle Methoden von der virtuellen Engine interpretiert und ausgeführt werden, ändern Sie bitte die entsprechende Konfiguration. io.vlinx.vmengine.Options und Veränderung handleSubMethod zu wahr.