堆栈高度不一致0!= 1

时间:2015-08-18 10:30:39

标签: java bytecode bytecode-manipulation

我正在通过十六进制编辑器修改Java类字节码,我想强制一个方法始终返回true。

  1. 用nops替换所有字节码以保持大小不变​​(原始大小为1890)。
  2. 执行pop以恢复堆栈高度,因为它收到一个参数。
  3. 使用iconst_1后跟ireturn返回true。
  4.     
    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,结果都完全相同。

3 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

pop从堆栈中弹出一个值,但作为参数传递的字符串位于“局部变量”0中。
您应该能够安全地省略pop

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

答案 2 :(得分:1)

如果您使用的是Java 7或更高版本,JVM可能正在使用堆栈映射帧验证您的字节码。 (检查this问题/答案是否有关于堆栈映射框的说明)

如果您使用的是Java 7,请在运行课程时在命令行中使用-XX:-UseSplitVerifier

如果您使用的是java 8,那么您还将修改堆栈映射帧;这样做并不简单,所以我建议你使用像javassist这样的字节码操作库。

更新

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

您可能已经知道,机器说明位于名为代码的属性中;此属性可以具有“子属性”(代码本身的属性)。其中一个是属性 StackMapTable ,它是“堆栈映射”的“数组”(表)。

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

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

还想手工做吗? : - )