尝试构建Java字节码的反编译器,并且不知道在什么情况下"stack map frame"不会碰巧是"FRAMSAME"



Java代码段

    int x4(int a) {
    if(a>7){
        System.out.println("a>7");
    }

    if(a==0){
        System.out.println("a==0");
    }else if(a>77){
        System.out.println(" a>77");
    }else if(a>44){
        System.out.println(" a>44");
    }else{
        System.out.println("otherwise");
    }
    return 44;
}

字节码大纲:

// access flags 0x0
x4(I)I
// parameter  a
L0
ILOAD 1
BIPUSH 7
IF_ICMPLE L1  ---- operand stack is empty here , end of a statement
L2 ---- new statement mark
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a>7"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
IFNE L3
L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a==0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
GOTO L6
L3
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 77
IF_ICMPLE L7
L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>77"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
GOTO L6
L7
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 44
IF_ICMPLE L10
L11
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>44"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L12
GOTO L6
L10
FRAME SAME ---- branching place start with empty operand stack
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "otherwise"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
FRAME SAME ---- branching place start with empty operand stack
BIPUSH 44
IRETURN
L13
// access flags 0x0
x4(I)I
// parameter  a
L0
ILOAD 1
BIPUSH 7
IF_ICMPLE L1
L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a>7"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
IFNE L3
L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "a==0"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L5
GOTO L6
L3
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 77
IF_ICMPLE L7
L8
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>77"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L9
GOTO L6
L7
FRAME SAME ---- branching place start with empty operand stack
ILOAD 1
BIPUSH 44
IF_ICMPLE L10
L11
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC " a>44"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L12
GOTO L6
L10
FRAME SAME ---- branching place start with empty operand stack
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "otherwise"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
FRAME SAME ---- branching place start with empty operand stack
BIPUSH 44
IRETURN
L13

似乎每当操作数堆栈被"赋值"操作+赋值"跟随控制"方法调用"清空时,一个新的标签将标记一个新的"语句行"。

"跟随控制"块是"语句"的集合,当一个块(由"跳转指令"标记)结束时,它也将是一个"语句"结束,因此操作数堆栈将为块的以下代码或交替分支空…

所以在我看来,没有任何情况下"由'跳转指令'代码标记"不会是编译后的java源代码的"FRAME SAME"

但我知道情况不会是这样。否则STACK MAP FRAME不会被设计成字节码。

请专家,非常感谢,给我一个非框架相同分支的例子,并直观地解释它。

非常感谢,请帮忙。请帮助。

根据下面的链接,您可以完全忽略堆栈映射帧,因为堆栈映射帧的唯一用途是验证类是否安全运行;堆栈映射框架与您试图用反编译器恢复的底层源代码没有关系。

有更好的解释堆栈地图框架吗?

参见:http://chrononsystems.com/blog/java-7-design-flaw-leads-to-huge-backward-step-for-the-jvm

并且,从标准本身:

StackMapTable属性是类中的可变长度属性Code(§4.7.3)属性的属性表。使用此属性在类型检查验证过程中(§4.10.1)。一个方法的Code属性最多只能有一个StackMapTable属性。https://docs.oracle.com/javase/specs/jvms/se7/html/jvms - 4. - html # jvms-4.7.4

如果你只想反编译,即打印指令序列,你可以简单地忽略堆栈帧。它们只提供有关操作数堆栈和局部变量上的项的类型的信息。如果不打印这些类型,则不需要解析它们。

如果您只使用分支语句,那么在分支的两端确实没有操作数堆栈条目,但是您没有遇到局部变量更改的原因仅仅是因为您在示例代码中没有这样的更改。

考虑:

for(int i=0; i<10; i++)
    System.out.println(i);

在这里,变量i已经被添加到循环分支的堆栈帧中,因此您可能会在那里遇到F_APPEND帧(鼓励编译器使用最紧凑的形式,但不要求使用最紧凑的形式)。同样,当添加另一个后续分支时,如

for(int i=0; i<10; i++)
  System.out.println(i);
if(a==0)
  System.out.println();

您可能会遇到F_CHOP帧,因为在随后的分支中,i不再在作用域中。

请注意,语句内部分支也是可能的,允许有不同的操作数堆栈条目,例如

System.out.println(a==0? "zero": "non-zero");

有两个分支。对于第一个,对PrintStream实例的引用已经在堆栈上,对于第二个,对PrintStreamString实例的引用都在堆栈上。

同样,异常处理程序形成具有单个操作数堆栈入口的隐式分支目标,即捕获的异常。这种情况可以用方便定义的F_SAME1帧类型进行编码。


如果您计划使用堆栈帧中包含的信息,并且正在使用ASM库,则不需要解析所有不同的帧类型。只要在构造时将标志ClassReader.EXPAND_FRAMES传递给ClassReader,您将遇到的所有F_NEW帧都包含完整的堆栈状态,无需记住以前的帧。

最新更新