使用 Krakatau 自动填充 StackMapTable

Automatically filling StackMapTable with Krakatau

我正在使用 Krakatau 从 Jasmin 语法生成字节码。我的 Jasmin 代码是通过直接翻译三地址代码 (TAC) 形式的中间代码创建的。我的问题是我不能确定,只是 查看 TAC,在翻译跳转语句时我应该将我的 stack 指令放在哪里。

关于其汇编程序的 Krakatau 文档说明如下:

The content of a StackMapTable attribute is automatically filled in based on the stack directives in the enclosing code attribute. If this attribute’s contents are nonempty and the attribute isn’t specified explicitly, one will be added implicitly.

但它也说:

Krakatau will not calculate a new stack map for you from bytecode that does not have any stack information. If you want to do this, you should try using ASM.

我对哪种指令以及我应该将它们添加到我的翻译中的什么位置感到困惑,以便汇编器知道如何隐式添加属性。如有任何帮助,我们将不胜感激。

例如,我用类似于Java的语法编写了这段代码(但不一样,所以我需要使用另一个编译器):

我从编译器的前端得到的是左边的 TAC,我的翻译器在右边生成 Jasmin 代码(我正在删除页眉和页脚,只留下字节码本身,缺少 return 指令):

当我尝试 运行 它时,我得到类似这样的信息:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 24
Exception Details:
  Location:
    Main.main([Ljava/lang/String;)V @16: iflt
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: 1103 e8bc 073a 050f 4814 000a 4a27 2997
    0x0000010: 9b00 0819 0503 2752 b1

    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
    at java.lang.Class.getMethod0(Class.java:3018)
    at java.lang.Class.getMethod(Class.java:1784)
    at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)

我知道会发生这种情况,因为它期望在带有标签 L23 的行之后出现 .stack append Double Double Object [D,但是,正如我之前所说,这个示例很简单,并且我不能总是推断出这些指令的位置。如果 Krakatau 可以通过在封闭代码属性的开头附加一些指令为我推断,那就太好了。

最简单的方法是完全避免对堆栈映射的需要。仅当您想使用 51.0+ 版功能(即 invokedynamic)时才需要堆栈映射。如果您不使用 invokedynamic,您可以将类文件版本设置为 50 或更低,并且根本不需要堆栈映射。事实上,如果你没有明确指定,Krakatau 默认版本为 49.0,所以你不需要在那里做任何事情。

如果您使用的是 invokedynamic,事情会变得更加棘手,因为您必须生成堆栈映射。基本规则是,只要一条指令可以从前一条指令以外的任何地方到达,就需要一个堆栈映射条目。 (我认为您还需要死代码条目,但我没有检查过)。

至于实际生成条目,有几种不同类型的堆栈帧,但您不必担心。最简单的方法是每次只使用一个 full 帧。这涉及列出局部变量 ("register") 槽和操作数堆栈中每个活动值的当前类型,因此您必须跟踪这些值。

是的,计算和生成所有类型的信息是一件很痛苦的事情,但是您必须为此责怪 Oracle,而不是我。或者,您可以按照文档中的建议尝试使用 ASM 为您生成堆栈映射。