[英]How to generate looping bytecode using Javassist?
我正在尝试为一种编译为 Java 字节码的深奥编程语言编写编译器。 我正在尝试使用 Javassist 生成字节码。
我在尝试生成分支/循环代码时卡住了。 例如,假设我正在为以下内容生成代码:
while (true) System.out.println("Hello World!");
这是我的尝试:
var mainClass = new ClassFile(false, "Main", null);
var constPool = mainClass.getConstPool();
var mainMethodCode = new Bytecode(constPool);
int label = mainMethodCode.currentPc();
mainMethodCode.addGetstatic(ClassPool.getDefault().get("java.lang.System"), "out", "Ljava/io/PrintStream;");
mainMethodCode.addLdc("Hello World!");
mainMethodCode.addInvokevirtual("java.io.PrintStream", "println", "(Ljava/lang/String;)V");
mainMethodCode.addOpcode(Opcode.GOTO);
// I know that branch instructions take a PC-relative offset
// and after some trial and error, this seems to be the correct formula
var offset = label - mainMethodCode.currentPc() + 1;
mainMethodCode.addIndex(offset);
mainMethodCode.setMaxLocals(1);
var mainMethodInfo = new MethodInfo(constPool, "main", "([Ljava/lang/String;)V");
mainMethodInfo.setCodeAttribute(mainMethodCode.toCodeAttribute());
mainClass.addMethod(mainMethodInfo);
mainClass.setAccessFlags(AccessFlag.PUBLIC);
mainMethodInfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
ClassPool.getDefault().makeClass(mainClass).writeFile(...);
通过检查 class 文件,我可以看到生成了预期的字节码:
Code:
stack=2, locals=1, args_size=1
0: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #14 // String Hello World!
5: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: goto 0
但是,当使用java Main
运行 class 文件时,我得到一个VerifyError
。 我显然需要将goto
目标添加到堆栈 map(无论这意味着什么)。
期望在分支目标 0 处有堆栈图帧
我在 Javassist 中找到了StackMap.Writer
class,所以我尝试了
var stackMap = new StackMap.Writer();
stackMap.write16bit(label); // does this add 0 (the value of label) to the stack map?
...
var codeAttr = mainMethodCode.toCodeAttribute();
codeAttr.setAttribute(stackMap.toStackMap(constPool));
mainMethodInfo.setCodeAttribute(codeAttr);
...
但是,当我尝试运行 class 时,同样的VerifyError
发生了。
在 Javassist 中生成分支代码的预期方式是什么?
感谢Holger 的评论,我能够弄清楚我实际上需要一个StackMapTable
,而不是StackMap
。 而且我确实需要在每个分支目的地都有一个堆栈 map 表条目。
var stackMap = new StackMapTable.Writer(0);
stackMap.sameFrame(0);
// ...
var codeAttr = mainMethodCode.toCodeAttribute();
codeAttr.setAttribute(stackMap.toStackMapTable(constPool));
请注意,有不同类型的帧,它们都对操作数堆栈和可用的局部变量有不同的说明。 sameFrame
是表示局部变量与上一帧相同的类型,操作数栈为空。 其他类型包括appendFrame
、 chopFrame
、 fullFrame
。 有关详细信息,请参阅JVMS 4.7.4 。
通常,传递给sameFrame
的参数0
不是我希望应用堆栈 map 表条目的字节码偏移量。 相反,它是“偏移增量”。 该条目适用的字节码偏移量是通过将 (offset delta + 1) 添加到前一帧适用的字节码偏移量来计算的。 仅对于第一帧,偏移量增量与其应用的字节码偏移量相同。
似乎ASM库更适合这样的字节码生成。 我不需要手动计算 PC 偏移量。 它甚至可以选择 ( COMPUTE_FRAMES
) 来确定您应该使用哪种类型的帧,但要以性能为代价。
var cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_6,
ACC_PUBLIC + ACC_SUPER,
"Main",
null,
"java/lang/Object",
null);
cw.visitSource("Main.java", null);
var mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC,
"main",
"([Ljava/lang/String;)V",
null,
null);
mv.visitCode();
var start = new Label();
mv.visitLabel(start);
mv.visitFieldInsn(GETSTATIC,
"java/lang/System",
"out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello World!");
mv.visitMethodInsn(INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false);
mv.visitJumpInsn(GOTO, start);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
var bytes = cw.toByteArray();
var stream = new FileOutputStream("...");
stream.write(bytes);
stream.close();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.