我尝试构建一个java字节码的反编译器,不知道在什么情况下“堆栈Map帧”不会碰巧是“FRAMSAME”

xriantvc  于 2023-06-04  发布在  Java
关注(0)|答案(2)|浏览(135)

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将不会被设计成字节码。
请Maven们,非常感谢,给予一个非FRAME SAME分支的例子,并直观地解释它。
非常感谢,请帮助。请帮帮忙。

lh80um4z

lh80um4z1#

根据下面的链接,你可以完全忽略堆栈Map帧,因为堆栈Map帧的唯一用途是验证一个类是否可以安全运行;堆栈Map帧与您试图用反编译器恢复的底层源代码没有关系。
Is there a better explanation of stack map frames?
标签: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

yquaqz18

yquaqz182#

如果你只想反编译,即。打印指令序列时,您可以简单地忽略堆栈帧。它们只提供有关操作数堆栈和局部变量上的项的类型的信息。如果不打印这些类型,则不需要解析它们。
如果你只使用分支 statements,分支的两端确实没有操作数栈入口,但你没有遇到局部变量变化的原因仅仅是你的示例代码中没有这样的变化。
考虑:

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帧,无需记住以前的帧。

相关问题