Motor de ejecución de bytecode JVM escrito en Java/Kotlin
Motor de Ejecución de Bytecode JAVA
La depuración dinámica tradicional de Java solo puede basarse en el nivel del código fuente, y no se puede depurar dinámicamente sin código fuente o con archivos class de Java ofuscados.
La ejecución de programas Java se basa en la máquina virtual JVM, que ejecuta bytecode. Hemos construido un motor de ejecución de bytecode JVM usando Kotlin, que nos permite depurar programas Java a nivel de bytecode usando IDEs modernos como IntelliJ IDEA para observar el comportamiento del programa durante la ejecución.
Atención, este proyecto es solo para estudiar e investigar el principio de funcionamiento del JVM y analizar programas maliciosos. Está estrictamente prohibido usarlo para fines ilegales.
Conocimientos Previos Necesarios
Antes de usar este proyecto, asegúrese de tener la siguiente base de conocimientos.
- Comprensión del formato de archivos class de Java
- Comprensión del rol y significado de cada bytecode en JVM
Depuración a nivel de bytecode usando IDEA
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
Abra este proyecto usando IDEA (requiere JDK 17) y navegue a TestCases
Hay dos casos de prueba en TestCases, uno para ejecutar métodos estáticos y otro para ejecutar métodos de instancia, respectivamente llamados executeStaticMethod y executeVirtualMethod.
En el método correspondiente, complete la información como classPath, className, methodName y methodSignature. La información detallada del archivo class puede verse usando ClassViewer.
Ejecutar directamente
Usando el archivo class compilado del siguiente código como ejemplo
public class Hello {
public void hello() {
System.out.println("hello");
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
Ejecute executeVirtualMethod, ejecute el método hello de esta clase.
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)
Puede obtener la siguiente salida en la consola.
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"
La salida de la consola muestra todas las instrucciones de bytecode de este método, los cambios en la pila durante la ejecución de instrucciones y los resultados de cada instrucción de bytecode.
Depuración
Si necesita depurar instrucciones de bytecode, puede establecer puntos de interrupción antes y después del método execute() en 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)
}
}
}
Depuración de bytecode de submétodos
Por defecto, el motor virtual solo interpreta y ejecuta el bytecode del método especificado. Los submétodos llamados dentro del método especificado aún se ejecutan en el JVM para evitar una sobrecarga significativa de rendimiento por múltiples capas de llamadas. Si desea que todos los métodos sean interpretados y ejecutados por el motor virtual, modifique io.vlinx.vmengine.Options y cambie handleSubMethod a true.