繁体   English   中英

如何使用 Javassist 生成循环字节码?

[英]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是表示局部变量与上一帧相同的类型,操作数栈为空。 其他类型包括appendFramechopFramefullFrame 有关详细信息,请参阅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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM