java - ASM 中的类编写器 COMPUTE_FRAMES

标签 java java-bytecode-asm bytecode-manipulation jvm-bytecode

我一直在尝试通过使用 ASM 中的跳转来了解堆栈映射框架在 Java 中的工作方式。我创建了一个简单的方法来尝试一些事情:(用 Krakatau 反汇编):

    L0:     ldc 'hello' 
    L2:     astore_1 
    L3:     getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L6:     new java/lang/StringBuilder 
    L9:     dup 
    L10:    invokespecial Method java/lang/StringBuilder <init> ()V 
    L13:    ldc 'concat1' 
    L15:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L18:    aload_1 
    L19:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L22:    invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String; 
    L25:    invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L28:    getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L31:    new java/lang/StringBuilder 
    L34:    dup 
    L35:    invokespecial Method java/lang/StringBuilder <init> ()V 
    L38:    ldc 'concat2' 
    L40:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L43:    aload_1 
    L44:    invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L47:    invokevirtual Method java/lang/StringBuilder toString ()Ljava/lang/String; 
    L50:    invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L53:    return 

它所做的只是创建一个 StringBuilder用变量加入一些字符串。

由于 L35 的 invokespecial 调用与 L10 的 invokespecial 调用具有完全相同的堆栈,我决定添加一个 ICONST_1; IFEQ L10使用 ASM 的 L35 之前的序列。

当我反汇编时(再次使用 Krakatau),我发现结果很奇怪。 ASM 已将 L10 处的堆栈帧计算为:

.stack full
    locals Object [Ljava/lang/String; Object java/lang/String 
    stack Object java/io/PrintStream Top Top 
.end stack

代替

    stack Object java/io/PrintStream Object java/lang/StringBuilder Object java/lang/StringBuilder

如我所料。

此外,这个类也不会通过验证,因为不能调用StringBuilder#<init>。在 Top .根据 ASM 手册,Top指的是一个未初始化的值,但是从跳转位置和之前的代码来看,它在代码中似乎并不是未初始化的。我不明白跳跃有什么问题。

我插入的跳转有什么问题导致类无法计算帧吗?这可能是 ASM 的 ClassWriter 的错误吗?

最佳答案

未初始化的实例是特殊的。考虑一下,当你 dup引用,您已经有两个对堆栈上同一实例的引用,您可能会执行更多的堆栈操作或将引用转移到局部变量,然后从那里将其复制到其他变量或再次将其压入。尽管如此,在以任何方式使用它之前,引用的目标应该只被初始化一次。为了验证这一点,必须跟踪对象的身份,以便所有这些对同一对象的引用在未初始化变为已初始化时你执行 invokespecial <init>在上面。

Java 编程语言并没有使用所有的可能性,但是对于像这样的合法代码
new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c))) , 它不应该失去对哪个 Foo 的追踪当创建分支时,实例已经初始化,但还没有初始化。

因此每个未初始化的实例堆栈帧条目都绑定(bind)到new创建它的指令。在传输或复制时,所有条目都保留引用(可以像 remembering the byte code offset of the new instruction 一样简单地处理)。仅在 invokespecial <init> 之后已对其调用,所有引用都指向相同的 new指令转向声明类的普通实例,随后可以与其他类型兼容的条目合并。

这意味着像您尝试实现的分支是不可能的。相同类型但由不同的 new 创建的两个 Uninitialized Instance 条目说明,不兼容。并且不兼容的类型被合并到一个 Top条目,这基本上是一个不可用的条目。它甚至可能是正确的代码,如果您不尝试在分支目标中使用该条目,那么 ASM 在将它们合并到 Top 时没有做任何错误。没有提示。

请注意,这也意味着任何一种循环都可能导致堆栈帧具有多个由相同的 new 创建的未初始化实例。指令,是不允许的。

关于java - ASM 中的类编写器 COMPUTE_FRAMES,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41208039/

相关文章:

java - 如何在该本地主机 :8080/test/index. html 中打开我的资源?

java - 在 ByteBuddy rebase 期间拦截构造函数

java - 推荐的字节码操作库,用于重写类文件以更改类型 e。 G。领域?

programming-languages - 哪些编程语言可以让我操纵方法中的指令序列?

java - 使用 Apache Cayenne 向数据库表执行批量插入

java - 不同包中的两个导出类可以在不公开其成员的情况下进行交互吗?

java - Controller HttpServletRequest 语言环境没有改变

java - Asm 字节码查询

java - 在方法调用中访问变量/常量值

Java 字节码 - ASM - 获取标签偏移量