Công cụ thực thi bytecode JVM viết bằng Java/Kotlin
Công cụ thực thi Bytecode JAVA
Gỡ lỗi động Java truyền thống chỉ có thể dựa trên cấp độ mã nguồn, và không thể gỡ lỗi động nếu không có mã nguồn hoặc các tệp class Java đã bị làm rối.
Việc chạy chương trình Java dựa trên máy ảo JVM, thực thi bytecode. Chúng tôi đã xây dựng một công cụ thực thi bytecode JVM sử dụng Kotlin, cho phép chúng tôi gỡ lỗi chương trình Java ở cấp độ bytecode bằng các IDE hiện đại như IntelliJ IDEA để quan sát hành vi chương trình trong quá trình thực thi.
Lưu ý, dự án này chỉ dành cho việc nghiên cứu và tìm hiểu nguyên lý hoạt động của JVM cũng như phân tích các chương trình độc hại. Nghiêm cấm sử dụng cho mục đích bất hợp pháp.
Kiến thức nền tảng cần có
Trước khi sử dụng dự án này, vui lòng đảm bảo bạn có kiến thức nền tảng sau.
- Hiểu định dạng tệp class Java
- Hiểu vai trò và ý nghĩa của từng bytecode trong JVM
Gỡ lỗi ở cấp độ bytecode sử dụng IDEA
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
Mở dự án này bằng IDEA (yêu cầu JDK 17) và điều hướng đến TestCases
Có hai test case trong TestCases, một cho thực thi phương thức tĩnh và một cho thực thi phương thức instance, lần lượt tên là executeStaticMethod và executeVirtualMethod.
Trên phương thức tương ứng, điền thông tin như classPath, className, methodName, và methodSignature. Thông tin chi tiết của tệp class có thể xem bằng ClassViewer.
Chạy trực tiếp
Sử dụng tệp class đã biên dịch của mã sau làm ví dụ
public class Hello {
public void hello() {
System.out.println("hello");
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
Thực thi executeVirtualMethod, chạy phương thức hello của class này.
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)
Bạn có thể nhận được đầu ra sau trên console.
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"
Đầu ra console hiển thị tất cả các lệnh bytecode của phương thức này, sự thay đổi trong ngăn xếp trong quá trình thực thi lệnh, và kết quả của từng lệnh bytecode.
Gỡ lỗi
Nếu bạn cần gỡ lỗi các lệnh bytecode, bạn có thể đặt breakpoint trước và sau phương thức execute() trong 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)
}
}
}
Gỡ lỗi bytecode phương thức con
Theo mặc định, công cụ ảo chỉ thông dịch và thực thi bytecode của phương thức được chỉ định. Các phương thức con được gọi trong phương thức được chỉ định vẫn chạy trong JVM để tránh chi phí hiệu suất đáng kể từ nhiều lớp gọi. Nếu bạn muốn tất cả các phương thức được thông dịch và thực thi bởi công cụ ảo, vui lòng sửa đổi io.vlinx.vmengine.Options và thay đổi handleSubMethod thành true.