在字节码级别解析 Java 方法调用的接收器
Parse receiver of Java method invocation on the Bytecode level
我正在寻找解决方案,以便在分析 Java 字节码时识别方法调用的正确接收者。即识别接收者是来自哪个class字段成员或参数。
以下面的字节码为例,有两个字段成员:_caller1
和_caller2
public Class MyClass{
test.code.jit.asm.classInline.CI_Caller1 _caller1;
flags:
test.code.jit.asm.classInline.CI_Caller1 _caller2;
flags:
public int test(java.lang.String, java.lang.String, test.code.jit.asm.classInline.CI_Caller1);
flags: ACC_PUBLIC
Code:
stack=4, locals=5, args_size=3
0: aload_0
1: getfield #14 // Field _caller1:Ltest/code/jit/asm/classInline/CI_Caller1;
4: invokevirtual #26 // Method test/code/jit/asm/classInline/CI_Caller1.test_two_fields_callee:()I
7: istore_3
8: aload_0
9: getfield #16 // Field _caller2:Ltest/code/jit/asm/classInline/CI_Caller1;
12: invokevirtual #26 // Method test/code/jit/asm/classInline/CI_Caller1.test_two_fields_callee:()I
15: istore 4
17: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
20: new #38 // class java/lang/StringBuilder
23: dup
.....
72: ireturn
我想知道的是如何识别#4、#12 方法调用的正确接收者。接收者 class 字段成员(哪一个)或方法参数?人眼阅读相对容易,但我如何通过 Java 代码实现它(如果已有工具则更好)。
目前我正在使用Java ASM 框架来解析class 字节码序列。如果可以提供一些想法(看来我必须在这里构建字节码 AST)或一些 Java util/related 链接也很有帮助,我们将不胜感激。
当执行 invokevirtual
指令时,所有参数都会从堆栈中弹出,然后弹出接收者对象。所以你的例子是最微不足道的:该方法没有 pop 的参数,所以在它提供接收器之前的指令,但即使对于无参数方法,它也是最微不足道的情况,因为理论上,可能有提供接收器的指令和调用之间的堆栈中性指令序列。此外,前面的字段读取是最简单的情况,因为幸运的是它前面有 aload_0
指令,该指令提供正在读取其字段的实例。只要前面没有写入变量 0
,它仍将包含 this
实例,如果我们正在查看非 static
方法...
在列举了所有的幸运巧合之后,应该提到的是,对于普通 Java 代码和主流编译器,这些先决条件中的大部分都将成立,所以如果你能忍受覆盖,比如说 99%所有代码,最大的障碍是堆栈顶部的参数,它可能由任意表达式产生,包括条件,因此接收器实例的提供者和实际调用之间的代码可能很长。
追踪推送方法接收器的指令的唯一方法是向前扫描代码并将操作数堆栈建模为存储其源指令的对象堆栈,并解释所有指令对该操作数堆栈的影响。请注意,这种解释器的基础 already exists.
我正在寻找解决方案,以便在分析 Java 字节码时识别方法调用的正确接收者。即识别接收者是来自哪个class字段成员或参数。
以下面的字节码为例,有两个字段成员:_caller1
和_caller2
public Class MyClass{
test.code.jit.asm.classInline.CI_Caller1 _caller1;
flags:
test.code.jit.asm.classInline.CI_Caller1 _caller2;
flags:
public int test(java.lang.String, java.lang.String, test.code.jit.asm.classInline.CI_Caller1);
flags: ACC_PUBLIC
Code:
stack=4, locals=5, args_size=3
0: aload_0
1: getfield #14 // Field _caller1:Ltest/code/jit/asm/classInline/CI_Caller1;
4: invokevirtual #26 // Method test/code/jit/asm/classInline/CI_Caller1.test_two_fields_callee:()I
7: istore_3
8: aload_0
9: getfield #16 // Field _caller2:Ltest/code/jit/asm/classInline/CI_Caller1;
12: invokevirtual #26 // Method test/code/jit/asm/classInline/CI_Caller1.test_two_fields_callee:()I
15: istore 4
17: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
20: new #38 // class java/lang/StringBuilder
23: dup
.....
72: ireturn
我想知道的是如何识别#4、#12 方法调用的正确接收者。接收者 class 字段成员(哪一个)或方法参数?人眼阅读相对容易,但我如何通过 Java 代码实现它(如果已有工具则更好)。
目前我正在使用Java ASM 框架来解析class 字节码序列。如果可以提供一些想法(看来我必须在这里构建字节码 AST)或一些 Java util/related 链接也很有帮助,我们将不胜感激。
当执行 invokevirtual
指令时,所有参数都会从堆栈中弹出,然后弹出接收者对象。所以你的例子是最微不足道的:该方法没有 pop 的参数,所以在它提供接收器之前的指令,但即使对于无参数方法,它也是最微不足道的情况,因为理论上,可能有提供接收器的指令和调用之间的堆栈中性指令序列。此外,前面的字段读取是最简单的情况,因为幸运的是它前面有 aload_0
指令,该指令提供正在读取其字段的实例。只要前面没有写入变量 0
,它仍将包含 this
实例,如果我们正在查看非 static
方法...
在列举了所有的幸运巧合之后,应该提到的是,对于普通 Java 代码和主流编译器,这些先决条件中的大部分都将成立,所以如果你能忍受覆盖,比如说 99%所有代码,最大的障碍是堆栈顶部的参数,它可能由任意表达式产生,包括条件,因此接收器实例的提供者和实际调用之间的代码可能很长。
追踪推送方法接收器的指令的唯一方法是向前扫描代码并将操作数堆栈建模为存储其源指令的对象堆栈,并解释所有指令对该操作数堆栈的影响。请注意,这种解释器的基础 already exists.