我正在寻找解决方案,以便在分析Java字节码时识别方法调用的正确接收器。也就是说,识别接收者是来自哪个类字段成员或参数。
以下字节码为例,有两个字段成员:_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方法调用的正确接收者。接收者类字段成员(哪一个)或方法参数?人眼阅读相对容易,但我是如何通过Java代码实现的(如果已有工具,则更好)。
目前我正在使用Java ASM框架来解析类字节码序列。如果可以提供一些想法(我似乎必须在这里构建Bytecode AST),或者某些Java util /相关链接也很有用,我将不胜感激。
答案 0 :(得分:2)
执行invokevirtual
指令时,所有参数都会从堆栈中弹出,然后弹出接收器对象。所以你的例子是最简单的例子:该方法没有pop的参数,所以在它提供接收器之前的指令,但即使对于无参数方法,它是最简单的情况,因为在理论上,可能有一个在提供接收器的指令和调用之间堆栈中性指令序列。此外,前面的字段读取是最简单的情况,因为幸运地在aload_0
指令之前提供了正在读取其字段的实例。只要没有先前对变量0
的写入,如果我们正在查看非this
方法,它仍将包含static
实例...
在命名所有幸运巧合之后,应该提到的是,对于普通的Java代码和主流编译器来说,大多数这些先决条件都会成立,所以如果你可以忍受覆盖,比如99%的代码,最大的障碍是堆栈顶部的参数,它可能由任意表达式产生,包括条件,因此接收器实例的提供者和实际调用之间的代码可能很长。
跟踪推送方法接收器的指令的唯一方法是向前扫描代码并将操作数堆栈建模为存储其源指令的对象堆栈,并解释所有指令对该操作数堆栈的影响。请注意这种口译员的基础already exists。