代码之家  ›  专栏  ›  技术社区  ›  sean

ASM:visitLabel生成太多标签和nop指令

  •  2
  • sean  · 技术社区  · 6 年前

    ASM文档说明标签表示一个基本块,它是控制图中的一个节点。所以我测试了 visitLabel 方法:

    public static void main(String[] args) {
        int x = 3, y = 4;
        if (x < y) {
            x++;
        }
    }
    

    对于 可视标签 setID(int id) ,其中id是增量的。在本例中,CFG应该有3个节点:一个在开头,一个用于if语句的每个分支。所以我想 setID 将在3个位置调用。然而,它被称为5次,而且有很多 nop 说明书有人能解释一下为什么吗?

    下面是上述程序的插入指令的字节码。

      public static void main(java.lang.String[]);
        Code:
           0: iconst_2
           1: invokestatic  #13                 // Method setId:(I)V
           4: iconst_3
           5: istore_1
           6: iconst_3
           7: invokestatic  #13                 // Method setId:(I)V
          10: iconst_4
          11: istore_2
          12: iconst_4
          13: invokestatic  #13                 // Method setId:(I)V
          16: iload_1
          17: iload_2
          18: if_icmpge     28
          21: iconst_5
          22: invokestatic  #13                 // Method setId:(I)V
          25: iinc          1, 1
          28: bipush        6
          30: invokestatic  #13                 // Method setId:(I)V
          33: return
          34: nop
          35: nop
          36: nop
          37: nop
          38: athrow
    

    我不明白的是为什么会有 label istore 指示没有分支使其成为CFG中的新节点。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Holger    6 年前

    一个项目的主要目的 Label LineNumberTable 属性存在,用于在 LocalVariableTable 属性存在,对于较新的类文件,它们的类型注释记录在 RuntimeVisibleTypeAnnotations try 块,所以它是一个基本块,但对于其他字节码来说,它不需要保留。

    看见

    因为局部变量的范围可能跨越最后一个 return 指令,可能在最后一条指令之后遇到标签,这就是您的情况。你在注射毒品 bipush 7, invokestatic #13 之后 返回 指令,导致无法访问的代码。

    显然,你也在使用 COMPUTE_FRAMES 允许ASM从头开始重新计算堆栈映射帧的选项,但由于未知的初始堆栈状态,无法为无法访问的代码计算帧。ASM通过将无法访问的代码替换为 nop 指示,然后是单个 athrow 陈述对于这个序列,可以指定一个有效的初始堆栈帧,并且它对执行没有影响(因为代码是不可访问的)。

    没有 阿思罗 指令跨越五个字节,与被替换的指令大小相同 bipush 7,invokestatic#13号 顺序有问题。

    通过指定 ClassReader.SKIP_DEBUG 到它的 accept method . 然后,您的示例只得到一个报告的标签,即与 if 陈述但你必须处理好 visitJumpInsn 以标识条件代码的开头。

    因此,要识别所有基本块,必须处理所有分支指令,即通过 visitLookupSwitchInsn ,和 visitTableSwitchInsn ,以及所有结束指令,即。 阿思罗 返回 . 此外,您需要处理所有 visitTryCatchBlock 电话。如果需要在单个过程中识别分支指令的潜在目标,请使用 visitFrame 对于51(Java7)或更高版本的类文件,所有分支目标都必须使用帧,而不是标签。

    顺便说一下,当您注入的都是加载常量和调用静态方法(在可到达的位置)的序列时,Id使用 COMPUTE_MAXS 计算\u帧

    推荐文章