Java/Kotlin으로 작성된 JVM 바이트 코드 실행 엔진

자바 바이트 코드 실행 엔진

기존 Java 동적 디버깅은 소스 코드 수준에만 기반할 수 있으며 소스 코드나 난독화된 Java 클래스 파일 없이는 동적으로 디버깅할 수 없습니다.

Java 프로그램의 실행은 바이트코드를 실행하는 JVM 가상 머신을 기반으로 합니다. 우리는 Kotlin을 사용하여 JVM 바이트코드 실행 엔진을 구축했습니다. 이를 통해 IntelliJ IDEA와 같은 최신 IDE를 사용하여 바이트코드 수준에서 Java 프로그램을 디버깅하여 실행 중 프로그램 동작을 관찰할 수 있습니다.

참고로 이 프로젝트는 JVM의 작동 원리를 연구하고 연구하며 악성 프로그램을 분석하는 용도로만 사용됩니다. 불법적인 목적으로 사용하는 것을 엄격히 금지합니다.

필수 지식 기반

이 프로젝트를 사용하기 전에 다음과 같은 지식 기반을 갖추고 있는지 확인하십시오.

  1. Java 클래스 파일의 형식 이해
  2. JVM에서 각 바이트 코드의 역할과 의미를 이해하기

IDEA를 사용하여 바이트 코드 수준에서 디버깅

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

이 프로젝트를 JDK 17을 사용하여 IDEA에서 열고 TestCases로 이동하십시오

TestCases에는 정적 메서드를 실행하는 두 가지 테스트 케이스와 인스턴스 메서드를 실행하는 두 가지 테스트 케이스가 있습니다.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 사실로.