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.
- Verständnis des Formats von Java-Klassendateien
- 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 sindexecuteStaticMethod
undexecuteVirtualMethod
.
Füllen Sie auf der entsprechenden Methode die Informationen wieclassPath
,className
,methodName
undmethodSignature
. 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.Options
und ändernhandleSubMethod
zu wahr.