사용 가능한 Java/Kotln编写의 JVM 문자 설명
자바 바이트코드 실행 엔진
기존의 자바 동적 디버깅은 소스 코드 수준에서만 가능하며, 소스 코드나 난독화된 자바 클래스 파일 없이는 동적으로 디버깅할 수 없습니다.
자바 프로그램의 실행은 바이트코드를 실행하는 JVM 가상 머신을 기반으로 합니다. 저희는 Kotlin을 사용하여 JVM 바이트코드 실행 엔진을 구축했으며, 이를 통해 IntelliJ IDEA와 같은 최신 IDE를 사용하여 바이트코드 수준에서 자바 프로그램을 디버깅하고 실행 중 프로그램 동작을 관찰할 수 있습니다.
주의: 본 프로젝트는 JVM의 작동 원리를 학습하고 연구하며 악성 프로그램을 분석하는 용도로만 제작되었습니다. 불법적인 목적으로 사용하는 것은 엄격히 금지되어 있습니다.
필수 사전 지식 기초
이 프로젝트를 사용하기 전에 다음 사항에 대한 기본 지식을 갖추고 있는지 확인하십시오.
- 자바 클래스 파일의 형식 이해하기
- JVM에서 각 바이트코드의 역할과 의미를 이해하기
IDEA를 사용한 바이트코드 수준 디버깅
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
IDEA(JDK 17 필요)를 사용하여 이 프로젝트를 열고 테스트 케이스로 이동하세요.
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 사실입니다.