From ded08e9e15221cffc44ac07d338d6c71fd5c9c2d Mon Sep 17 00:00:00 2001 From: Moonlight Date: Fri, 10 Nov 2023 15:46:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80.=20=E8=87=AA=E5=8A=A8=E8=A3=85?= =?UTF-8?q?=E7=AE=B1=E6=8B=86=E7=AE=B1=EF=BC=8C=E5=BC=82=E5=B8=B8=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/moonlight/jvm/Jvm.java | 80 ++++++++++++++++++ .../org/moonlight/jvm/JvmApplication.java | 13 ++- .../moonlight/jvm/classfile/ClassFile.java | 11 ++- .../classfile/attribute/CodeAttribute.java | 12 ++- .../debuginfo/LineNumberTableAttribute.java | 10 +++ .../moonlight/jvm/instructions/Factory.java | 5 +- .../jvm/instructions/base/BytecodeReader.java | 5 +- .../instructions/base/MethodInvokeLogic.java | 2 +- .../jvm/instructions/references/AThrow.java | 73 ++++++++++++++++ .../moonlight/jvm/interpret/Interpret.java | 9 ++ .../jvm/natives/AbstractNativeMethod.java | 1 - .../java/lang/throwable/FillInStackTrace.java | 69 +++++++++++++++ .../java/lang/throwable/Throwable.java | 31 +++++++ .../jvm/natives/sun/misc/vm/Initialize.java | 49 +++++++++++ .../jvm/rtda/exclusive/JvmStack.java | 15 ++++ .../jvm/rtda/exclusive/LocalVars.java | 3 + .../jvm/rtda/exclusive/OperandStack.java | 7 ++ .../moonlight/jvm/rtda/exclusive/Thread.java | 9 ++ .../moonlight/jvm/rtda/share/heap/Class.java | 49 +++++++++-- .../rtda/share/heap/classmember/Method.java | 32 ++++++- .../share/heap/exception/ExceptionTable.java | 71 ++++++++++++++++ src/test/java/BoxTest.class | Bin 0 -> 936 bytes src/test/java/BoxTest.java | 16 ++++ src/test/java/ParseIntTest.class | Bin 0 -> 966 bytes src/test/java/ParseIntTest.java | 28 ++++++ 25 files changed, 581 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/moonlight/jvm/Jvm.java create mode 100644 src/main/java/org/moonlight/jvm/instructions/references/AThrow.java create mode 100644 src/main/java/org/moonlight/jvm/natives/java/lang/throwable/FillInStackTrace.java create mode 100644 src/main/java/org/moonlight/jvm/natives/java/lang/throwable/Throwable.java create mode 100644 src/main/java/org/moonlight/jvm/natives/sun/misc/vm/Initialize.java create mode 100644 src/main/java/org/moonlight/jvm/rtda/share/heap/exception/ExceptionTable.java create mode 100644 src/test/java/BoxTest.class create mode 100644 src/test/java/BoxTest.java create mode 100644 src/test/java/ParseIntTest.class create mode 100644 src/test/java/ParseIntTest.java 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 0000000..5c7dbbf --- /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 65a1167..d346b72 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 04cde15..6c0b34d 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 684b5c1..8a1d722 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 0872ef2..1832aa8 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 2fe2abd..1e8d275 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 08fb1ca..3883bc3 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 cc4b20d..d7de91b 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 0000000..423e58b --- /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 618c551..11d9e83 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 62d8c64..2e90389 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 0000000..3bc3159 --- /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 0000000..b82c074 --- /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 0000000..966c292 --- /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 ae32d06..e05a41b 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 75aacbf..b200f1f 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 fafc67b..f79c383 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 6861967..a2de3b6 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 dc53db3..7c02358 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 8da7d7c..a6f7822 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 0000000..a6eb485 --- /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 GIT binary patch literal 936 zcmY*Y+fG_R6kUTy24O_SYFjJzZb7W{VsB!#YHLD*mjr{UJ`Fgv$HF=0Fc|%meu2;J z1C};v`rO~>Uo`1u&jAI_!_1zUHG8eSW}lybzkUZWi`6g&G0qVWBY^QR1STT5jvIZr ziCgCEb{KbX*X&J3a1RMHBsr!GG-b}FIqq}JFa+mq$JPrBJ&9z2!CGm7)|Wv4&;$cm!-3x&TG$h_A};Ue_qSdhc$WV<2lkC4;aFEx8{}9 zs%=t;W%qbXRrQQXATWd&!?2&MrtNZi$@An%&aP^Khj>I81Ri6SbR0H0GRd7T=KM)js|sCkYeYBF#M^Fq zll;`=s*)8-7t(IiVTMbIPR5q+=k|49B&CNYO_`pnOG?LUh3t#6M$a?*k8oZtvGez` zx~`5%AGHR_w;X@bt)x{RX(h&jG02B!2~H*zs2>NY9nBX2y%AzG4$~@5Go!h8>J01? z1ymx9L4VUiA4V`rv4Nh_Rg31`ZwS0SLr*SceSy_LZvfk=-gX$g2tOm3s)N@NN;S~O zur}R5nBgPFD2OopfT*K?fnf1EqNo3U836%bz$8%{86j{Y4`G7VNkpg<1vb%-ZA7s{ aHC-lD6oEgO 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 GIT binary patch literal 966 zcmZ`&TTc@~6#k}NI%Qp2TBLFjSinnLghIh9#0QO-CSZvrB*dp_8J5L%x7nR4{uTd# z&-egD5_tDV8P6<8@s{13nLXz_-?{GhAK%UZEMeJ1KgKx5lbA3OgKZ*>Nsg&rOyiD; z1n!!+hx;7)Bnl=>jVNM9D<5#oa?CL#R$R|j&l%$R!WM(^(%+E`={47rZ#u2I3^ql* zNgAUiT#q4_f48fp{}?&<}ECsY+(^g91kr#LWyCz>sIl0Jk>1E7_95X61+=Vt?$c*Vi?T-yQSBl zwNIgvEo!FU(GC}Q*Dq~Q{}oL^iqyfV%wlGNx*iX9&7$7QSNedcGjF_7!~Q2+1S#9!5k4 zI0cGD0dwdfXo|ASkT#7ZWt*jEh_oEB*Brz8H=(7u%%)1&X!h~q*=O{f!rac@IEHmz ojF(T)w?eT-kC4(8T@NrwVRA%2L~@vlABh4`U1_2njlj2m0#ljFh5!Hn literal 0 HcmV?d00001 diff --git a/src/test/java/ParseIntTest.java b/src/test/java/ParseIntTest.java new file mode 100644 index 0000000..5e5843a --- /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); + } + +} -- Gitee