堆栈高度不一致 0 != 1

Inconsistent stack height 0 != 1

我正在通过十六进制编辑器修改 Java class 字节码,我想强制方法始终 return 为真。

  1. 将其所有字节码替换为 nop 以保持大小不变​​(原始大小为 1890)。
  2. 执行 pop 以恢复堆栈高度,因为它收到一个参数。
  3. Return 正确,iconst_1 后跟 ireturn
public static boolean test(java.lang.String);
        descriptor: (Ljava/lang/String;)Z
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=5, locals=12, args_size=1
             0: nop
             1: nop
             2: nop
             3: nop
             4: nop
             [...]
          1886: nop
          1887: nop
          1888: pop
          1889: iconst_1
          1890: ireturn

但是在执行的时候,出现如下错误

java.lang.VerifyError: (class: com/example/test/TestBytecode, method: test signature: (Ljava/lang/String;)Z) Inconsistent stack height 0 != 1

注意:使用或不使用 pop 结果完全相同。

pop 是不必要的,因为参数最初不在堆栈上。它们仅在使用 *load 指令时被压入堆栈,就好像它们是局部变量一样,这随时可能发生。

pop 从堆栈弹出一个值,但作为参数传递的字符串在 "local variable" 0.
您应该能够安全地忽略 pop.

此外,您应该可以省略所有 nop,而只需将指令 0 替换为 iconst_1,将指令 1 替换为 ireturn,然后将其余的全部保留方法不变。
这样你会做更少的工作,甚至可能提高性能。

如果您使用的是 Java 7 或更高版本,可能是 JVM 正在使用堆栈映射帧验证您的字节码。 (检查 this question/answer 以了解堆栈映射帧的说明)

如果您正在使用 Java 7,当您 运行 您的 class.

时,尝试在命令行中使用 -XX:-UseSplitVerifier

如果您使用的是java8,那么您还需要修改堆栈图框;这样做并不简单,所以我最好建议您使用像 javassist.

这样的字节码操作库

更新

根据@Holger 的评论,他是对的。但是据我所知,您是用 NOP 填充不需要的操作码,而不是删除它们。

您可能已经知道,机器指令位于名为 code 的属性中;这个属性可以有 "sub-attributes"(code 本身的属性)。其中之一是 StackMapTable 属性,它是 "stack maps".

的 "array"(table)

将此属性替换为零是不够的,您必须:

  • 将table(堆栈映射table)的条目数设置为零
  • 删除那个 table 上的唯一条目(并偏移以下属性和其他字段)

还想亲手做? :-)