diff --git a/src/main/java/org/moonlight/jvm/Jvm.java b/src/main/java/org/moonlight/jvm/Jvm.java new file mode 100644 index 0000000000000000000000000000000000000000..5c7dbbfcc9a1e077c404bf56feb1bd0203eb0690 --- /dev/null +++ b/src/main/java/org/moonlight/jvm/Jvm.java @@ -0,0 +1,80 @@ +package org.moonlight.jvm; + +import org.moonlight.jvm.classpath.Classpath; +import org.moonlight.jvm.cmd.Cmd; +import org.moonlight.jvm.interpret.Interpret; +import org.moonlight.jvm.rtda.exclusive.Frame; +import org.moonlight.jvm.rtda.exclusive.Thread; +import org.moonlight.jvm.rtda.share.Object; +import org.moonlight.jvm.rtda.share.heap.Class; +import org.moonlight.jvm.rtda.share.heap.ClassLoader; +import org.moonlight.jvm.rtda.share.heap.classmember.Method; +import org.moonlight.jvm.rtda.share.methodarea.StringPool; + +/** + * 逻辑描述: + * 注意事项: + * 需求来源: + * 修改记录: 修改者 - 修改时间 - 修改内容 + * + * @author moonlight + * @since 2023/11/10 14:45 星期五 + **/ +public class Jvm { + + private final Cmd cmd; + private final ClassLoader classLoader; + private final Thread mainThread; + + public Jvm(Cmd cmd) { + this.cmd = cmd; + + Classpath cp = new Classpath(cmd.getJre(), cmd.getClasspath()); + this.classLoader = new ClassLoader(cp, cmd.isVerboseClass()); + + this.mainThread = new Thread(); + } + + public void start() { + // 先加载 sun.misc.VM类, 然后执行它的类初始化方法 + this.initVm(); + // 再加载主类并执行 main 方法 + this.execMain(); + } + + private void initVm() { + Class vmClass = this.classLoader.loadClass("sun/misc/VM"); + vmClass.classInit(this.mainThread); + new Interpret().interpret(this.mainThread, this.cmd.isVerboseInst()); + } + + private void execMain() { + String mainClassName = this.cmd.getMainClass().replace(".", "/"); + Class mainClass = this.classLoader.loadClass(mainClassName); + Method mainMethod = mainClass.getMainMethod(); + if (mainMethod == null) { + throw new RuntimeException("Main method not found in class " + this.cmd.getMainClass()); + } + Object args = this.createArgsArray(); + Frame frame = this.mainThread.newFrame(mainMethod); + frame.getLocalVars().setRef(0, args); + this.mainThread.pushFrame(frame); + new Interpret().interpret(this.mainThread, this.cmd.isVerboseInst()); + } + + private Object createArgsArray() { + Class stringClass = this.classLoader.loadClass("java/lang/String"); + + String[] args = this.cmd.getArgs(); + int argsLength = args.length; + Object array = stringClass.arrayClass().newArray(argsLength); + + Object[] refs = array.refs(); + + for (int i = 0; i < argsLength; i++) { + refs[i] = StringPool.myString(this.classLoader, args[i]); + } + return array; + } + +} diff --git a/src/main/java/org/moonlight/jvm/JvmApplication.java b/src/main/java/org/moonlight/jvm/JvmApplication.java index 65a116726a1d09267e15f88c58292b60d76a6137..d346b728626143a0bb6d269e7c6d6f9c3189119a 100644 --- a/src/main/java/org/moonlight/jvm/JvmApplication.java +++ b/src/main/java/org/moonlight/jvm/JvmApplication.java @@ -20,6 +20,8 @@ import org.moonlight.jvm.natives.java.lang.object.GetClass; import org.moonlight.jvm.natives.java.lang.object.HashCode; import org.moonlight.jvm.natives.java.lang.string.Intern; import org.moonlight.jvm.natives.java.lang.system.ArrayCopy; +import org.moonlight.jvm.natives.java.lang.throwable.FillInStackTrace; +import org.moonlight.jvm.natives.sun.misc.vm.Initialize; import org.moonlight.jvm.rtda.share.heap.Class; import org.moonlight.jvm.rtda.share.heap.ClassLoader; import org.moonlight.jvm.rtda.share.heap.classmember.Method; @@ -53,9 +55,15 @@ public class JvmApplication { new Intern(); // system new ArrayCopy(); + // sun.misc.vm + new Initialize(); + // java.lang.throwable + new FillInStackTrace(); } public static void main(String[] args) { + System.out.println(~1); + System.out.println(Arrays.toString(args)); Cmd cmd = Cmd.parse(args); System.out.println(new Gson().toJson(cmd)); @@ -67,7 +75,8 @@ public class JvmApplication { System.out.println("java version \"1.8.0\""); return; } - startJvm(cmd); +// startJvm(cmd); + new Jvm(cmd).start(); } public static void startJvm(Cmd cmd) { @@ -83,6 +92,8 @@ public class JvmApplication { // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java GetClassTest // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java StringTest // -inst -verbose -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java ObjectClone + // -inst -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java BoxTest + // -Xjre "C:\Program Files\Java\jre1.8.0_333" -cp D:\code\java\jvm\src\test\java ParseIntTest Classpath cp = new Classpath(cmd.getJre(), cmd.getClasspath()); System.out.printf("classpath:%s class:%s args:%s\n", cmd.getClasspath(), cmd.getMainClass(), cmd.getAppArgs()); diff --git a/src/main/java/org/moonlight/jvm/classfile/ClassFile.java b/src/main/java/org/moonlight/jvm/classfile/ClassFile.java index 04cde15649cb01de299a4b6ca96ccdec78c00620..6c0b34dbe31a7e4a2349ca47f66e4937cb8fa166 100644 --- a/src/main/java/org/moonlight/jvm/classfile/ClassFile.java +++ b/src/main/java/org/moonlight/jvm/classfile/ClassFile.java @@ -2,6 +2,7 @@ package org.moonlight.jvm.classfile; import lombok.Getter; import org.moonlight.jvm.classfile.attribute.AttributeInfo; +import org.moonlight.jvm.classfile.attribute.debuginfo.SourceFileAttribute; import org.moonlight.jvm.classfile.constant.StaticConstantPool; import org.moonlight.jvm.common.Constant; @@ -138,5 +139,13 @@ public class ClassFile { } return names; } - + + public SourceFileAttribute sourceFileAttribute() { + for (AttributeInfo attr : attributes) { + if (attr instanceof SourceFileAttribute) { + return (SourceFileAttribute) attr; + } + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/org/moonlight/jvm/classfile/attribute/CodeAttribute.java b/src/main/java/org/moonlight/jvm/classfile/attribute/CodeAttribute.java index 684b5c1d3b95403b8dc5e99dc76c452105e8304e..8a1d7228548744f827286ef2dfeb4f13b2076c44 100644 --- a/src/main/java/org/moonlight/jvm/classfile/attribute/CodeAttribute.java +++ b/src/main/java/org/moonlight/jvm/classfile/attribute/CodeAttribute.java @@ -2,6 +2,7 @@ package org.moonlight.jvm.classfile.attribute; import lombok.Getter; import org.moonlight.jvm.classfile.ClassReader; +import org.moonlight.jvm.classfile.attribute.debuginfo.LineNumberTableAttribute; import org.moonlight.jvm.classfile.constant.StaticConstantPool; /** @@ -50,8 +51,17 @@ public class CodeAttribute implements AttributeInfo { this.attributes = AttributeInfo.readAttributes(reader, this.scp); } + public LineNumberTableAttribute lineNumberTableAttribute() { + for (AttributeInfo attr : attributes) { + if (attr instanceof LineNumberTableAttribute) { + return (LineNumberTableAttribute) attr; + } + } + return null; + } + @Getter - static class ExceptionTableEntry { + public static class ExceptionTableEntry { private final int startPc; private final int endPc; private final int handlerPc; diff --git a/src/main/java/org/moonlight/jvm/classfile/attribute/debuginfo/LineNumberTableAttribute.java b/src/main/java/org/moonlight/jvm/classfile/attribute/debuginfo/LineNumberTableAttribute.java index 0872ef2a6ab1411037b2ebfe12d8c3c09c5b68a3..1832aa80d716c78dd22b2203956e76988886f1aa 100644 --- a/src/main/java/org/moonlight/jvm/classfile/attribute/debuginfo/LineNumberTableAttribute.java +++ b/src/main/java/org/moonlight/jvm/classfile/attribute/debuginfo/LineNumberTableAttribute.java @@ -38,6 +38,16 @@ public class LineNumberTableAttribute implements AttributeInfo { this.lineNumberTable = lineNumberTable; } + public int getLineNumber(int pc) { + for (int i = this.lineNumberTable.length - 1; i >= 0; i--) { + LineNumberTableEntry entry = this.lineNumberTable[i]; + if (pc >= entry.startPc) { + return entry.lineNumber; + } + } + return -1; + } + @Getter static class LineNumberTableEntry { private final int startPc; diff --git a/src/main/java/org/moonlight/jvm/instructions/Factory.java b/src/main/java/org/moonlight/jvm/instructions/Factory.java index 2fe2abdebe513ba42b5184d013e1048c5d41de8f..1e8d2753254a3329384f80b79ff4b100749519b0 100644 --- a/src/main/java/org/moonlight/jvm/instructions/Factory.java +++ b/src/main/java/org/moonlight/jvm/instructions/Factory.java @@ -246,6 +246,7 @@ public class Factory { private static final LAStore LA_STORE = new LAStore(); private static final SAStore SA_STORE = new SAStore(); private static final InvokeNative INVOKE_NATIVE = new InvokeNative(); + private static final AThrow A_THROW = new AThrow(); public static Instruction newInstruction(int opcode) { @@ -632,8 +633,8 @@ public class Factory { return new ANewArray(); case 0xbe: return new ArrayLength(); - // case 0xbf: - // return athrow + case 0xbf: + return A_THROW; case 0xc0: return new CheckCast(); case 0xc1: diff --git a/src/main/java/org/moonlight/jvm/instructions/base/BytecodeReader.java b/src/main/java/org/moonlight/jvm/instructions/base/BytecodeReader.java index 08fb1cac4b05c8ee3a1adff986870aeb9efaacaa..3883bc3300e933ebb3f4a3b6ce7e75b976e1cf9f 100644 --- a/src/main/java/org/moonlight/jvm/instructions/base/BytecodeReader.java +++ b/src/main/java/org/moonlight/jvm/instructions/base/BytecodeReader.java @@ -61,10 +61,11 @@ public class BytecodeReader { * @author moonlight **/ public int readInt16() { - // TODO 这里需要 无符号数字进行运算,不然特殊情况下会出现解析错误 + // 这里需要 无符号数字进行运算,不然遇见边界值会出现解析错误 int high = readOpCode(); int low = readOpCode(); - return (high << 8) | low; + // 这里只需要2字节,而这样子经过运算后可能超过了两字节,所以转为short抹去溢出的,再自动转型为int返回 + return (short)((high << 8) | low); } /** diff --git a/src/main/java/org/moonlight/jvm/instructions/base/MethodInvokeLogic.java b/src/main/java/org/moonlight/jvm/instructions/base/MethodInvokeLogic.java index cc4b20dd4e936a11d5160866be701b6f52793623..d7de91b5d289782216ae234a156de459089e0ae8 100644 --- a/src/main/java/org/moonlight/jvm/instructions/base/MethodInvokeLogic.java +++ b/src/main/java/org/moonlight/jvm/instructions/base/MethodInvokeLogic.java @@ -21,7 +21,7 @@ import org.moonlight.jvm.rtda.share.heap.classmember.Method; **/ public abstract class MethodInvokeLogic extends Index16Instruction { - protected void invokeMethod(Frame invokeFrame, Method method) { + public static void invokeMethod(Frame invokeFrame, Method method) { // 前三行代码创建新的帧并推入Java虚拟机栈, Thread thread = invokeFrame.getThread(); Frame frame = thread.newFrame(method); diff --git a/src/main/java/org/moonlight/jvm/instructions/references/AThrow.java b/src/main/java/org/moonlight/jvm/instructions/references/AThrow.java new file mode 100644 index 0000000000000000000000000000000000000000..423e58b096cf08ea0deb4406220cbf4c1d8fab6d --- /dev/null +++ b/src/main/java/org/moonlight/jvm/instructions/references/AThrow.java @@ -0,0 +1,73 @@ +package org.moonlight.jvm.instructions.references; + +import org.moonlight.jvm.instructions.base.NoOperandsInstruction; +import org.moonlight.jvm.natives.java.lang.throwable.Throwable; +import org.moonlight.jvm.rtda.exclusive.Frame; +import org.moonlight.jvm.rtda.exclusive.OperandStack; +import org.moonlight.jvm.rtda.exclusive.Thread; +import org.moonlight.jvm.rtda.share.Object; +import org.moonlight.jvm.rtda.share.methodarea.StringPool; + +/** + * A_THROW: throw 一个 exception 或 error, 属于引用类指令. 操作数是一个异常对象引用,从操作数栈弹出 + * + * @author Moonlight + * @createTime 2023/11/10 9:00 + **/ +public class AThrow extends NoOperandsInstruction { + + @Override + public void execute(Frame frame) { + Object ex = frame.getOperandStack().popRef(); + if (ex == null) { + throw new NullPointerException(); + } + Thread thread = frame.getThread(); + // 如果遍历完Java虚拟机栈还是找不到异常处理代码,则handleUncaughtException()函数打印出Java虚拟机栈信息 + if (!findAndGotoExceptionHandler(thread, ex)) { + handleUncaughtException(thread, ex); + } + } + + private boolean findAndGotoExceptionHandler(Thread thread, Object ex) { + do { + // 从当前帧开始,遍历Java虚拟机栈,查找方法的异常处理表。 + // 假设遍历到帧F,如果在F对应的方法中找不到异常处理项,则把F弹出,继续遍历。 + // 反之如果找到了异常处理项,在跳转到异常处理代码之前,要先把F的操作数栈清空,然后把异常对象引用推入栈顶。 + Frame curFrame = thread.currentFrame(); + int pc = curFrame.getNextPc() - 1; + + int handlerPc = curFrame.getMethod().findExceptionHandler(ex.getClazz(), pc); + if (handlerPc > 0) { + OperandStack operandStack = curFrame.getOperandStack(); + // 要先把F的操作数栈清空 + operandStack.clear(); + // 然后把异常对象引用推入栈顶 + operandStack.pushRef(ex); + curFrame.setNextPc(handlerPc); + + return true; + } + // 如果在F对应的方法中找不到异常处理项,则把F弹出,继续遍历 + thread.popFrame(); + } while (!thread.isStackEmpty()); + return false; + } + + private void handleUncaughtException(Thread thread, Object ex) { + thread.clearStack(); + + Object detailMessage = ex.getRefVal("detailMessage", "Ljava/lang/String;"); + String javaString = StringPool.javaString(detailMessage); + + System.out.println(ex.getClazz().getJavaName() + ": " + javaString); + + java.lang.Object extra = ex.getExtra(); + + Throwable[] throwableArr = (Throwable[]) extra; + for (Throwable t : throwableArr) { + System.out.println("\t at " + t.string()); + } + } + +} diff --git a/src/main/java/org/moonlight/jvm/interpret/Interpret.java b/src/main/java/org/moonlight/jvm/interpret/Interpret.java index 618c551aec368cf031d7fdde6d4c7263958ec432..11d9e830ecd25134697f781ad0065c34e8a22810 100644 --- a/src/main/java/org/moonlight/jvm/interpret/Interpret.java +++ b/src/main/java/org/moonlight/jvm/interpret/Interpret.java @@ -39,6 +39,15 @@ public class Interpret { } } + public void interpret(Thread thread, boolean logInst) { + try { + loop(thread, logInst); + } catch (Exception e) { + logFrames(thread); + e.printStackTrace(); + } + } + private Object createArgsArray(ClassLoader classLoader, String[] args) { Class javaStringClass = classLoader.loadClass("java/lang/String"); Object myArray = javaStringClass.arrayClass().newArray(args.length); diff --git a/src/main/java/org/moonlight/jvm/natives/AbstractNativeMethod.java b/src/main/java/org/moonlight/jvm/natives/AbstractNativeMethod.java index 62d8c64e7934d962ba70cffa4f7c94875de5c853..2e9038911070f32db7b9b1fe9eb604a1350675c9 100644 --- a/src/main/java/org/moonlight/jvm/natives/AbstractNativeMethod.java +++ b/src/main/java/org/moonlight/jvm/natives/AbstractNativeMethod.java @@ -11,5 +11,4 @@ public abstract class AbstractNativeMethod implements NativeMethod { public AbstractNativeMethod(String className, String methodName, String methodDescriptor) { registry(className, methodName, methodDescriptor); } - } \ No newline at end of file diff --git a/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/FillInStackTrace.java b/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/FillInStackTrace.java new file mode 100644 index 0000000000000000000000000000000000000000..3bc315972cd2aa28c92fd6129a3bd8c6b8299845 --- /dev/null +++ b/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/FillInStackTrace.java @@ -0,0 +1,69 @@ +package org.moonlight.jvm.natives.java.lang.throwable; + +import org.moonlight.jvm.natives.AbstractNativeMethod; +import org.moonlight.jvm.rtda.exclusive.Frame; +import org.moonlight.jvm.rtda.exclusive.Thread; +import org.moonlight.jvm.rtda.share.Object; +import org.moonlight.jvm.rtda.share.heap.Class; +import org.moonlight.jvm.rtda.share.heap.classmember.Method; + +/** + * java.lang.Throwable private native Throwable fillInStackTrace(int dummy); + * + * @author Moonlight + * @createTime 2023/11/9 15:48 + **/ +public class FillInStackTrace extends AbstractNativeMethod { + + public FillInStackTrace() { + super("java/lang/Throwable", "fillInStackTrace", "(I)Ljava/lang/Throwable;"); + } + + @Override + public void nativeMethod(Frame frame) { + Object that = frame.getLocalVars().getThis(); + frame.getOperandStack().pushRef(that); + + Throwable[] stackTraceElements = createStackTraceElements(that, frame.getThread()); + that.setExtra(stackTraceElements); + } + + public Throwable[] createStackTraceElements(Object obj, Thread thread) { + // 由于栈顶两帧正在执行fillInStackTrace(int)和fillInStackTrace()方法,所以需要跳过这两帧。 + // 这两帧下面的几帧正在执行异常类的构造函数,所以也要跳过,具体要跳过多少帧数则要看异常类的继承层次。 + int skip = distanceToObject(obj.getClazz()) + 2; + + Frame[] frames = thread.getFrames(); + Throwable[] throwableArr = new Throwable[frames.length - skip]; + int idx = 0; + for (int i = skip; i < frames.length; i++) { + throwableArr[idx] = createStackTraceElement(frames[i]); + } + return throwableArr; + } + + public Throwable createStackTraceElement(Frame frame) { + Method method = frame.getMethod(); + Class clazz = method.getClazz(); + + Throwable.StackTraceElement se = new Throwable.StackTraceElement(); + se.setFileName(clazz.getSourceFile()); + se.setClassName(clazz.getJavaName()); + se.setMethodName(method.getName()); + se.setLineNumber(method.getLineNumber(frame.getNextPc() - 1)); + + Throwable throwable = new Throwable(); + throwable.setStackTraceElement(se); + + return throwable; + } + + public int distanceToObject(Class clazz) { + int dis = 0; + for (Class c = clazz.getSuperClass(); c != null; c = c.getSuperClass()) { + dis++; + } + return dis; + } + +} diff --git a/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/Throwable.java b/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/Throwable.java new file mode 100644 index 0000000000000000000000000000000000000000..b82c0746876a2bcdf27205207e12fcae84cab1f3 --- /dev/null +++ b/src/main/java/org/moonlight/jvm/natives/java/lang/throwable/Throwable.java @@ -0,0 +1,31 @@ +package org.moonlight.jvm.natives.java.lang.throwable; + +import lombok.Data; + +/** + * + * + * @author Moonlight + * @createTime 2023/11/10 9:53 + **/ +@Data +public class Throwable { + + private StackTraceElement stackTraceElement; + + public String string() { + return String.format("%s.%s(%s:%d)", + this.stackTraceElement.getClassName(), this.stackTraceElement.getMethodName(), + this.stackTraceElement.getFileName(), this.stackTraceElement.getLineNumber()); + } + + @Data + public static class StackTraceElement { + private String fileName; + private String className; + private String methodName; + private int lineNumber; + } + + +} diff --git a/src/main/java/org/moonlight/jvm/natives/sun/misc/vm/Initialize.java b/src/main/java/org/moonlight/jvm/natives/sun/misc/vm/Initialize.java new file mode 100644 index 0000000000000000000000000000000000000000..966c292d67121d858caad2922e1415961acc81ee --- /dev/null +++ b/src/main/java/org/moonlight/jvm/natives/sun/misc/vm/Initialize.java @@ -0,0 +1,49 @@ +package org.moonlight.jvm.natives.sun.misc.vm; + +import org.moonlight.jvm.instructions.base.MethodInvokeLogic; +import org.moonlight.jvm.natives.AbstractNativeMethod; +import org.moonlight.jvm.rtda.exclusive.Frame; +import org.moonlight.jvm.rtda.share.Object; +import org.moonlight.jvm.rtda.share.heap.Class; +import org.moonlight.jvm.rtda.share.heap.ClassLoader; +import org.moonlight.jvm.rtda.share.heap.classmember.Method; +import org.moonlight.jvm.rtda.share.methodarea.StringPool; + +/** + * sun.misc.VM private static native void initialize(); + * + * 自动拆箱装箱: + * Integer.valueOf()方法并不是每次都创建Integer()对象,而是维护了一个缓存池IntegerCache。对于比较小(默认是-128~127)的int变量, + * 在IntegerCache初始化时就预先加载到了池中,需要用时直接从池里取即可。IntegerCache是Integer类的内部类 + * IntegerCache在初始化时需要确定缓存池中Integer对象的上限值,为此它调用了sun.misc.VM类的getSavedProperty()方法。 + * + * @author Moonlight + * @createTime 2023/11/9 16:04 + **/ +public class Initialize extends AbstractNativeMethod { + + public Initialize() { + super("sun/misc/VM", "initialize", "()V"); + } + + @Override + public void nativeMethod(Frame frame) { + Class clazz = frame.getMethod().getClazz(); + + Object savedProps = clazz.getRefVar("savedProps", "Ljava/util/Properties;"); + frame.getOperandStack().pushRef(savedProps); + + Object key = StringPool.myString(clazz.getLoader(), "Key"); + frame.getOperandStack().pushRef(key); + + Object val = StringPool.myString(clazz.getLoader(), "Val"); + frame.getOperandStack().pushRef(val); + + Class properties = clazz.getLoader().loadClass("java/util/Properties"); + Method setProperty = properties.getInstanceMethod( + "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;" + ); + + MethodInvokeLogic.invokeMethod(frame, setProperty); + } +} diff --git a/src/main/java/org/moonlight/jvm/rtda/exclusive/JvmStack.java b/src/main/java/org/moonlight/jvm/rtda/exclusive/JvmStack.java index ae32d067dc88de0545b2a21ba50fe40f2bdbf90b..e05a41b525605849604be71f145f65844bad970c 100644 --- a/src/main/java/org/moonlight/jvm/rtda/exclusive/JvmStack.java +++ b/src/main/java/org/moonlight/jvm/rtda/exclusive/JvmStack.java @@ -82,4 +82,19 @@ public class JvmStack { public boolean isEmpty() { return this.top == null; } + + public void clear() { + while (!this.isEmpty()) { + this.pop(); + } + } + + public Frame[] getFrames() { + Frame[] frames = new Frame[this.size]; + int i = 0; + for (Frame frame = this.top; frame != null; frame = frame.getNext()) { + frames[i++] = frame; + } + return frames; + } } \ No newline at end of file diff --git a/src/main/java/org/moonlight/jvm/rtda/exclusive/LocalVars.java b/src/main/java/org/moonlight/jvm/rtda/exclusive/LocalVars.java index 75aacbf28d04ff5ba141197c090d9306d465db52..b200f1f11c8c1c6b129b27efaa56d66af1f8ed45 100644 --- a/src/main/java/org/moonlight/jvm/rtda/exclusive/LocalVars.java +++ b/src/main/java/org/moonlight/jvm/rtda/exclusive/LocalVars.java @@ -79,6 +79,9 @@ public class LocalVars { return this.slots[0].getRef(); } + public boolean getBoolean(int idx) { + return getInt(idx) == 1; + } public static void main(String[] args) { LocalVars vars = new LocalVars(10); diff --git a/src/main/java/org/moonlight/jvm/rtda/exclusive/OperandStack.java b/src/main/java/org/moonlight/jvm/rtda/exclusive/OperandStack.java index fafc67b3859b25f1e1697df385c226173a41e2ef..f79c3837d7f337b00cbd741ecb0ad8baca7d5f77 100644 --- a/src/main/java/org/moonlight/jvm/rtda/exclusive/OperandStack.java +++ b/src/main/java/org/moonlight/jvm/rtda/exclusive/OperandStack.java @@ -171,6 +171,13 @@ public class OperandStack { return this.stack[this.topEleIdx - 1 - n].getRef(); } + public void clear() { + this.topEleIdx = 0; + for (Slot slot : this.stack) { + slot.setRef(null); + } + } + public static void main(String[] args) throws ParseException { // OperandStack s = new OperandStack(10); // diff --git a/src/main/java/org/moonlight/jvm/rtda/exclusive/Thread.java b/src/main/java/org/moonlight/jvm/rtda/exclusive/Thread.java index 6861967ca0efeb69b01db4bf5e57fc9a73a6327d..a2de3b6fc2995942a7cdd3add37e3114fdedd830 100644 --- a/src/main/java/org/moonlight/jvm/rtda/exclusive/Thread.java +++ b/src/main/java/org/moonlight/jvm/rtda/exclusive/Thread.java @@ -67,4 +67,13 @@ public class Thread { public boolean isStackEmpty() { return this.jvmStack.isEmpty(); } + + public void clearStack() { + this.jvmStack.clear(); + } + + public Frame[] getFrames() { + return this.jvmStack.getFrames(); + } + } diff --git a/src/main/java/org/moonlight/jvm/rtda/share/heap/Class.java b/src/main/java/org/moonlight/jvm/rtda/share/heap/Class.java index dc53db387d94e905217d11fa2eab82f4080406bd..7c0235880846f078ca972d34664c95dd82b3ebc7 100644 --- a/src/main/java/org/moonlight/jvm/rtda/share/heap/Class.java +++ b/src/main/java/org/moonlight/jvm/rtda/share/heap/Class.java @@ -1,10 +1,10 @@ package org.moonlight.jvm.rtda.share.heap; -import com.google.gson.Gson; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.moonlight.jvm.classfile.ClassFile; +import org.moonlight.jvm.classfile.attribute.debuginfo.SourceFileAttribute; import org.moonlight.jvm.rtda.exclusive.Frame; import org.moonlight.jvm.rtda.exclusive.Thread; import org.moonlight.jvm.rtda.share.Object; @@ -136,6 +136,10 @@ public class Class implements Serializable { @Setter private Object jClass; + /** 源文件名 **/ + @Setter + private String sourceFile; + public Class(ClassFile classFile) { this.accessFlags = classFile.getAccessFlags(); this.name = classFile.getClassName(); @@ -144,6 +148,7 @@ public class Class implements Serializable { this.rtCp = new RtConstantPool(this, classFile.getScp()); this.fields = Field.newFields(this, classFile.getFields()); this.methods = Method.newMethods(this, classFile.getMethods()); + this.sourceFile = getSourceFile(classFile); } public Class(int accessFlags, String className, ClassLoader loader, boolean initStarted, Class superClass, Class[] interfaces) { @@ -231,7 +236,7 @@ public class Class implements Serializable { * @author moonlight **/ public Method getMainMethod() { - return this.getStaticMethod("main", "([Ljava/lang/String;)V"); + return this.getMethod("main", "([Ljava/lang/String;)V", true); } /** @@ -243,9 +248,9 @@ public class Class implements Serializable { * @createTime 16:01 2023/8/30 * @author moonlight **/ - private Method getStaticMethod(String name, String desc) { + private Method getMethod(String name, String desc, boolean isStatic) { for (Method m : this.getMethods()) { - if (name.equals(m.getName()) && desc.equals(m.getDescriptor())) { + if (m.isStatic() == isStatic && name.equals(m.getName()) && desc.equals(m.getDescriptor())) { return m; } } @@ -441,9 +446,11 @@ public class Class implements Serializable { * @author moonlight **/ private boolean isSubInterfaceOf(Class interFace) { - for (Class c : this.interfaces) { - if (c.equals(interFace) || c.isSubInterfaceOf(interFace)) { - return true; + if (this.interfaces != null) { + for (Class c : this.interfaces) { + if (c.equals(interFace) || c.isSubInterfaceOf(interFace)) { + return true; + } } } return false; @@ -548,7 +555,7 @@ public class Class implements Serializable { * @author moonlight **/ private Method getClinit() { - return this.getStaticMethod("", "()V"); + return this.getMethod("", "()V", true); } /** @@ -630,6 +637,16 @@ public class Class implements Serializable { return f; } + public Object getRefVar(String fieldName, String fieldDesc) { + Field field = getField(fieldName, fieldDesc, true); + return this.staticVars.getRef(field.getSlotId()); + } + + public void setRefVar(String fieldName, String fieldDesc, Object obj) { + Field field = getField(fieldName, fieldDesc, true); + this.staticVars.setRef(field.getSlotId(), obj); + } + public String getJavaName() { return this.name.replaceAll("/", "."); } @@ -637,4 +654,20 @@ public class Class implements Serializable { public boolean isPrimitive() { return primitiveClassDescMapping.containsKey(this.name); } + + public Method getInstanceMethod(String methodName, String methodDesc) { + return getMethod(methodName, methodDesc, false); + } + + public String getSourceFile(ClassFile classFile) { + SourceFileAttribute sourceFileAttribute = classFile.sourceFileAttribute(); + if (sourceFileAttribute != null) { + return sourceFileAttribute.fileName(); + } + return "Unknown"; + } + + public Method getStaticMethod(String methodName, String methodDesc) { + return getMethod(methodName, methodDesc, true); + } } diff --git a/src/main/java/org/moonlight/jvm/rtda/share/heap/classmember/Method.java b/src/main/java/org/moonlight/jvm/rtda/share/heap/classmember/Method.java index 8da7d7c40c00373df4d901ce73c3a7916f806c22..a6f7822737169eff44063d55aac25adb50db0fad 100644 --- a/src/main/java/org/moonlight/jvm/rtda/share/heap/classmember/Method.java +++ b/src/main/java/org/moonlight/jvm/rtda/share/heap/classmember/Method.java @@ -4,7 +4,9 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import org.moonlight.jvm.classfile.MemberInfo; import org.moonlight.jvm.classfile.attribute.CodeAttribute; +import org.moonlight.jvm.classfile.attribute.debuginfo.LineNumberTableAttribute; import org.moonlight.jvm.rtda.share.heap.Class; +import org.moonlight.jvm.rtda.share.heap.exception.ExceptionTable; import org.moonlight.jvm.utils.MethodDescriptor; import java.util.List; @@ -32,6 +34,10 @@ public class Method extends ClassMember { private List parameterTypes; /** 返回值 **/ private String returnType; + /** 异常处理表 **/ + private ExceptionTable exceptionTable; + /** 行号信息 **/ + private LineNumberTableAttribute lineNumberTable; public void copyAttributes(MemberInfo memberInfo) { CodeAttribute codeAttribute = memberInfo.codeAttribute(); @@ -39,6 +45,8 @@ public class Method extends ClassMember { this.maxStack = codeAttribute.getMaxStack(); this.maxLocals = codeAttribute.getMaxLocals(); this.code = codeAttribute.getCode(); + this.exceptionTable = new ExceptionTable(codeAttribute.getExceptionTable(), this.clazz.getRtCp()); + this.lineNumberTable = codeAttribute.lineNumberTableAttribute(); } } @@ -146,7 +154,6 @@ public class Method extends ClassMember { } } - public boolean isAbstract() { return (this.accessFlags & AccessFlags.ACC_ABSTRACT) != 0; } @@ -154,4 +161,25 @@ public class Method extends ClassMember { public boolean isNative() { return (this.accessFlags & AccessFlags.ACC_NATIVE) != 0; } -} + + public int findExceptionHandler(Class exClass, int pc) { + // 搜索异常处理表,如果能够找到对应的异常处理项,则返回它的handlerPc字段,否则返回-1 + ExceptionTable.ExceptionHandler handler = this.exceptionTable.findExceptionHandler(exClass, pc); + if (handler != null) { + return handler.getHandlerPc(); + } + return -1; + } + + public int getLineNumber(int pc) { + // 并不是每个方法都有行号表。如果方法没有行号表,自然也就查不到pc对应的行号,这种情况下返回-1。本地方法没有字节码,这种情况下返回-2 + if (this.isNative()) { + return -2; + } + if (this.lineNumberTable == null) { + return -1; + } + return this.lineNumberTable.getLineNumber(pc); + } + +} \ No newline at end of file diff --git a/src/main/java/org/moonlight/jvm/rtda/share/heap/exception/ExceptionTable.java b/src/main/java/org/moonlight/jvm/rtda/share/heap/exception/ExceptionTable.java new file mode 100644 index 0000000000000000000000000000000000000000..a6eb4852d047de635d49d205875000ed7e792283 --- /dev/null +++ b/src/main/java/org/moonlight/jvm/rtda/share/heap/exception/ExceptionTable.java @@ -0,0 +1,71 @@ +package org.moonlight.jvm.rtda.share.heap.exception; + +import lombok.Data; +import org.moonlight.jvm.classfile.attribute.CodeAttribute; +import org.moonlight.jvm.rtda.share.heap.Class; +import org.moonlight.jvm.rtda.share.heap.symref.ClassRef; +import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool; + +/** + * 异常信息表 + * + * @author Moonlight + * @createTime 2023/11/9 17:55 + **/ +public class ExceptionTable { + + private final ExceptionHandler[] exceptionTable; + + public ExceptionTable(CodeAttribute.ExceptionTableEntry[] entries, RtConstantPool rtCp) { + this.exceptionTable = new ExceptionHandler[entries.length]; + for (int i = 0; i < entries.length; i++) { + ExceptionHandler handler = new ExceptionHandler( + entries[i].getStartPc(), + entries[i].getEndPc(), + entries[i].getHandlerPc(), + getCatchType(entries[i].getCatchType(), rtCp) + ); + this.exceptionTable[i] = handler; + } + } + + public ClassRef getCatchType(int catchType, RtConstantPool rtCp) { + if (catchType == 0) { + return null; + } + return (ClassRef) rtCp.getConstant(catchType); + } + + public ExceptionHandler findExceptionHandler(Class exClass, int pc) { + for (ExceptionHandler handler : exceptionTable) { + if (pc >= handler.startPc && pc <= handler.endPc) { + // 如果catchType为null,那么说明它在class文件中是0,表示可以处理所有异常,这是用来实现finally子句的 + if (handler.catchType == null) { + return handler; + } + Class catchClass = handler.catchType.resolvedClass(); + if (catchClass == exClass || catchClass.isSubClassOf(exClass)) { + return handler; + } + } + } + return null; + } + + @Data + public static class ExceptionHandler { + int startPc; + int endPc; + int handlerPc; + ClassRef catchType; + + ExceptionHandler(int startPc, int endPc, int handlerPc, ClassRef catchType) { + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.catchType = catchType; + } + } + + +} \ No newline at end of file diff --git a/src/test/java/BoxTest.class b/src/test/java/BoxTest.class new file mode 100644 index 0000000000000000000000000000000000000000..65bd3601424d3668eba5101d1a8b6e53ed1fe01d Binary files /dev/null and b/src/test/java/BoxTest.class differ diff --git a/src/test/java/BoxTest.java b/src/test/java/BoxTest.java new file mode 100644 index 0000000000000000000000000000000000000000..63543a1fc036cc63551140d12479dafaf59a0be8 --- /dev/null +++ b/src/test/java/BoxTest.java @@ -0,0 +1,16 @@ +import java.util.ArrayList; +import java.util.List; + +public class BoxTest { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + System.out.println(list.toString()); + for (int x : list) { + System.out.println(x); + } + } +} diff --git a/src/test/java/ParseIntTest.class b/src/test/java/ParseIntTest.class new file mode 100644 index 0000000000000000000000000000000000000000..1d4566f66b42aca548392095ffc9cd0ab11b7b90 Binary files /dev/null and b/src/test/java/ParseIntTest.class differ diff --git a/src/test/java/ParseIntTest.java b/src/test/java/ParseIntTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5e5843a056d389ada4fb58fc0ac8a2e90dfe9e85 --- /dev/null +++ b/src/test/java/ParseIntTest.java @@ -0,0 +1,28 @@ +public class ParseIntTest { + public static void main(String[] args) { + //foo(args); + throw new RuntimeException("test throw runtime exception test123123132123"); + //try { + //throw new RuntimeException("test throw runtime exception test123123132123"); + //} catch (RuntimeException e) { + //System.out.println("test catch runtime exception e.getMessage() ==> " + e.getMessage()); + //} + } + + private static void foo(String[] args) { + try { + bar(args); + } catch (NumberFormatException e) { + System.out.println(e.getMessage()); + } + } + + private static void bar(String[] args) { + if (args.length == 0) { + throw new IndexOutOfBoundsException("args length is 0"); + } + int x = Integer.parseInt(args[0]); + System.out.println(x); + } + +}