我正在为16位板开发Dalvik Bytecode解释器。我已经为大多数操作码实现并测试了翻译操作,但由于我无法弄清楚如何编写生成它们的Java代码,因此无法测试其他操作码。特别是我无法生成“invoke-polymorphic”和“invoke-custom”操作码。 (以及他们在Dalvik 35中的等价物,我正在开发Dalvik 38)我试过简单地在类中运行一个方法来覆盖超类方法,如下所示:
SuperClass x = new SubClass();
x.mymethod();
这是多态的教科书简单例子,但它似乎只是生成常规的“invoke-direct”操作码。
此外,我并不完全清楚“方法句柄”或“调用站点”的含义以及为什么需要多态调用。 (Dalvik文档似乎没有详细说明这一点)。
答案 0 :(得分:2)
“多态性”是一个模糊的术语。例如,方法重载或重载通常被称为多态。对于“调用多态”指令,该术语指所谓的“签名多态”方法。
签名多态方法可以接受任何种类和数量的参数。
从Java源代码的角度来看,它们的外观和行为类似于带有一个vararg Object...
参数的任何方法,但是它们在字节码级别上有所不同。尽管通过传递带有所有其他参数的数组来调用varargs方法,但对于“调用多态”方法而言,这不是必需的。可以使用 any 描述符调用多态方法。无需创建参数数组或装箱原始类型的自变量。
存在的唯一签名多态方法是MethodHandle#invoke(Object...)和MethodHandle#invokeExact(Object...)(请参阅JvmSpec §2.9.3)。
因此,这是产生“调用多态”指令的代码:
void foo(MethodHandle handle) throws Throwable {
handle.invoke(10, 20);
handle.invokeExact("foo", "bar");
}
让我们编译它,运行dx
并用baksmali
查看它:
const/16 v0, 0xa
const/16 v1, 0x14
invoke-polymorphic {p1, v0, v1}, Ljava/lang/invoke/MethodHandle;->invoke([Ljava/lang/Object;)Ljava/lang/Object;, (II)V
const-string v0, "foo"
const-string v1, "bar"
invoke-polymorphic {p1, v0, v1}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;Ljava/lang/String;)V
“调用自定义”指令相当于dalvik的JVM invokedynamic指令。
动态调用不会调用一种静态已知的方法。它们提供了用于解析CallSite的“引导方法”。 CallSite基本上是MethodHandle周围的包装,它可能是可变的,也可能不是可变的。一旦“ invokedynamic”指令的CallSite被解析,它就会由JVM存储,并且从现在开始在执行该指令时被调用。 JVM Spec 10 §6.5中对整个过程进行了详细说明。
Bootstrap方法只是返回CallSite的常规Java方法,并且必须至少具有三个参数。
注意:不要期望bootstrap方法与上述签名完全匹配。参数例如可以是对象类型。也可以用一个vararg参数替换一些参数。
jvm 7引入了“ invokedynamic”指令以支持动态语言,但Java语言本身并未使用该指令。该指令最初在Java 8中用于编译lambda。由于Java 9字符串连接是通过“ invokedynamic”调用实现的。
下面是产生调用动态指令的示例代码:
Supplier<String> someLambda = () -> "foo";
运行javac
,dx
和baksmali
之后,我们将看到以下指示信息:
invoke-custom {}, call_site_0("get", ()Ljava/util/function/Supplier;, ()Ljava/lang/Object;, invoke-static@LTest;->lambda$foo$0()Ljava/lang/String;, ()Ljava/lang/String;)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
.method private static synthetic lambda$foo$0()Ljava/lang/String;
const-string v0, "foo"
return-object v0
.end method
让我们仔细研究一下Java 9如何编译String串联。 这是我们的示例代码:
String foo(Object a, Object b) {
return "a = " + a + ", b = " + b;
}
结果指令:
invoke-custom {p1, p2}, call_site_0("makeConcatWithConstants", (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;, "a = \u0001, b = \u0001")@Ljava/lang/invoke/StringConcatFactory;->makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
首先,我们有{p1, p2}
。这两个寄存器是被调用后作为参数传递给CallSite的值。
然后,我们得到了call_site_0(...)
块。 call_site_0
是此呼叫站点的名称,可用于区分其他相同的呼叫站点。该块存储一些信息,这些信息作为参数传递给bootstrap方法。
最后一件事(从'@'符号开始)是对bootstrap方法的引用。