如何在Dalvik中生成INVOKE-POLYMORPHIC操作码

时间:2018-02-28 10:38:07

标签: android dalvik dex

我正在为16位板开发Dalvik Bytecode解释器。我已经为大多数操作码实现并测试了翻译操作,但由于我无法弄清楚如何编写生成它们的Java代码,因此无法测试其他操作码。特别是我无法生成“invoke-polymorphic”和“invoke-custom”操作码。 (以及他们在Dalvik 35中的等价物,我正在开发Dalvik 38)我试过简单地在类中运行一个方法来覆盖超类方法,如下所示:

SuperClass x = new SubClass();
x.mymethod();

这是多态的教科书简单例子,但它似乎只是生成常规的“invoke-direct”操作码。

此外,我并不完全清楚“方法句柄”或“调用站点”的含义以及为什么需要多态调用。 (Dalvik文档似乎没有详细说明这一点)。

1 个答案:

答案 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方法,并且必须至少具有三个参数。

  • 第一个参数是一个Lookup实例,该实例可以访问所有类/字段/方法,这些类/字段/方法可以从包含invokedynamic指令的方法访问。
  • 第二个参数通常称为“名称”。它是由invokedynamic指令定义的任意String。
  • 第三个参数是MethodType。返回的CallSite必须与该类型完全匹配。这可以通过MethodHandle#asType方法来实现。
  • 可以传递其他参数。它们必须是存储在invokedynamic指令中的常量值。

注意:不要期望bootstrap方法与上述签名完全匹配。参数例如可以是对象类型。也可以用一个vararg参数替换一些参数。

jvm 7引入了“ invokedynamic”指令以支持动态语言,但Java语言本身并未使用该指令。该指令最初在Java 8中用于编译lambda。由于Java 9字符串连接是通过“ invokedynamic”调用实现的。

下面是产生调用动态指令的示例代码:

Supplier<String> someLambda = () -> "foo";

运行javacdxbaksmali之后,我们将看到以下指示信息:

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方法。

  • 第一个值(“ makeConcatWithConstants”)是作为第二个参数传递给bootstrap方法的任意String。
  • 第二个值是MethodType常量。它作为第三个参数传递给bootstrap方法,并且与返回的CallSite的类型完全匹配。在这种情况下,我们有一个方法可以接受两个对象(p1和p2)并返回一个String(串联的String)。
  • 以下所有值都是传递给bootstrap方法的其他常量参数。在这种情况下,只有一个字符串是最终串联字符串的模式。

最后一件事(从'@'符号开始)是对bootstrap方法的引用。