尝试构建 java 字节码的反编译器,但不知道在什么情况下 "stack map frame" 不会恰好是 "FRAMSAME"
try to build a decompiler of java bytecode, and don't know in what case the "stack map frame" would not happen to be the "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
似乎每当 "assignment" "operation+assignment" "follow control" "method invocation" 清空操作数堆栈时,新标签将标记新的 "statement line" 。
"follow control" 块是 "statement" 的集合,当一个块(由 "jump instructions" 标记)结束时,它也将是 "statement" 结束因此对于块的后续代码或交替分支,操作数堆栈将为空...
所以在我看来 "labeled by ' jump instruction ' code" 绝不会是已编译 java 源代码的 "FRAME SAME"
但我知道情况不会如此。否则STACK MAP FRAME不会被设计成字节码。
请专家,非常感谢,给我一个非FRAME SAME分支的例子,并直观地解释一下。
非常感谢,请帮忙。请帮忙
根据下面的 link,您可以完全忽略堆栈映射框架,因为堆栈映射框架的唯一用途是验证 class 对 运行 的安全性;堆栈映射框架与您尝试使用反编译器恢复的底层源代码无关。
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
并且,从标准本身来看:
The StackMapTable attribute is a variable-length attribute in the
attributes table of a Code (§4.7.3) attribute. This attribute is used
during the process of verification by type checking (§4.10.1). A
method's Code attribute may have at most one StackMapTable attribute.
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
帧(鼓励编译器,但不要求使用最紧凑的形式)。同样,当添加另一个后续分支时,如 in
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
实例的引用已经在堆栈中,对于第二个,对 PrintStream
和 String
实例的引用在堆栈中。
此外,异常处理程序形成具有单个操作数堆栈条目的隐式分支目标,即捕获的异常。这种情况可以使用方便定义的 F_SAME1
帧类型进行编码。
如果您打算使用堆栈帧中包含的信息并且正在使用 ASM 库,则不需要解析所有不同的帧类型。只需在构建时将标志 ClassReader.EXPAND_FRAMES
传递给 ClassReader
,您将遇到的所有内容都是包含完整堆栈状态的 F_NEW
帧,无需记住之前的帧。
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
似乎每当 "assignment" "operation+assignment" "follow control" "method invocation" 清空操作数堆栈时,新标签将标记新的 "statement line" 。
"follow control" 块是 "statement" 的集合,当一个块(由 "jump instructions" 标记)结束时,它也将是 "statement" 结束因此对于块的后续代码或交替分支,操作数堆栈将为空...
所以在我看来 "labeled by ' jump instruction ' code" 绝不会是已编译 java 源代码的 "FRAME SAME"
但我知道情况不会如此。否则STACK MAP FRAME不会被设计成字节码。
请专家,非常感谢,给我一个非FRAME SAME分支的例子,并直观地解释一下。
非常感谢,请帮忙。请帮忙
根据下面的 link,您可以完全忽略堆栈映射框架,因为堆栈映射框架的唯一用途是验证 class 对 运行 的安全性;堆栈映射框架与您尝试使用反编译器恢复的底层源代码无关。
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
并且,从标准本身来看:
The StackMapTable attribute is a variable-length attribute in the attributes table of a Code (§4.7.3) attribute. This attribute is used during the process of verification by type checking (§4.10.1). A method's Code attribute may have at most one StackMapTable attribute. 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
帧(鼓励编译器,但不要求使用最紧凑的形式)。同样,当添加另一个后续分支时,如 in
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
实例的引用已经在堆栈中,对于第二个,对 PrintStream
和 String
实例的引用在堆栈中。
此外,异常处理程序形成具有单个操作数堆栈条目的隐式分支目标,即捕获的异常。这种情况可以使用方便定义的 F_SAME1
帧类型进行编码。
如果您打算使用堆栈帧中包含的信息并且正在使用 ASM 库,则不需要解析所有不同的帧类型。只需在构建时将标志 ClassReader.EXPAND_FRAMES
传递给 ClassReader
,您将遇到的所有内容都是包含完整堆栈状态的 F_NEW
帧,无需记住之前的帧。