Погружение в Smali. Как выглядят Java и Kotlin со стороны

81a8e5b70d013b43072b58b48b363048.jpg

Эта статья — оригинальная, а не копипаст и не машинный перевод. При копировании прошу ссылаться…

Статья для начинающих исследователей файлов APK. Хотя почему только для начинающих? На некоторые моменты не обращаtim внимания, пока не разложишь все по полочкам.

Наверное есть такие люди, которые думают, что все так просто: взял APK, декомпилировал в Java и все — можно править код на понятном, высокоуровневом языке.
Таких оптимистов спешу расстроить: по моим личным наблюдениям, максимальное количество работоспособного кода Java, которое можно получить в результате декомпиляции APK не более 50%. Это конечно относится к приложениям из реального мира, а не к учебным примерам. Такие вещи, как обфускация кода и ресурсов я пока не затрагиваю — это материал для отдельных публикаций. Но, как бы вы не изворачивались, а все равно, в итоге придете к тому, что самый лучший вариант — читать код в Smali. Сначала это кажется довольно трудным занятием. Но, это — чисто психологические трудности, пока не наберетесь опыта.

В этой статье я хочу привести примеры типовых конструкций, применяемых в языках программирования. Мы посмотрим как выглядят эти конструкции в Java, Kotlin и Smali. Начнем пожалуй.

1. Операторы присваивания и арифметические операции.

Java:

public void exampleOps() {
    int a = 2;
    int b = 1;
    int c = 3;

    c = a + b;
    c = a - b;
    c = a * b;
    c = a / b;
}

Java → Smali:

.method public exampleOps()V
    .locals 3

    .line 8
    const/4 v0, 0x2

    .line 9
    .local v0, "a":I
    const/4 v1, 0x1

    .line 10
    .local v1, "b":I
    const/4 v2, 0x3

    .line 12
    .local v2, "c":I
    add-int v2, v0, v1

    .line 13
    sub-int v2, v0, v1

    .line 14
    mul-int v2, v0, v1

    .line 15
    div-int v2, v0, v1

    .line 16
    return-void
.end method

Kotlin:

fun exampleOps() {
    var a = 2
    var b = 1
    var c = 3

    c = a + b
    c = a - b
    c = a * b
    c = a / b
}

Kotlin → Smali:

.method public final exampleOps()V
    .locals 3

    .line 6
    const/4 v0, 0x2

    .line 7
    .local v0, "a":I
    const/4 v1, 0x1

    .line 8
    .local v1, "b":I
    const/4 v2, 0x3

    .line 10
    .local v2, "c":I
    add-int v2, v0, v1

    .line 11
    sub-int v2, v0, v1

    .line 12
    mul-int v2, v0, v1

    .line 13
    div-int v2, v0, v1

    .line 14
    return-void
.end method

2. Условный оператор if…else.

Java:

public void exampleIf() {
    int a = 1;
    int b = 1;
    if (a == b) {
        b = a;
    } else {
        b = 0;
    }
}

Java → Smali:

.method public exampleIf()V
    .locals 2

    .line 19
    const/4 v0, 0x1

    .line 20
    .local v0, "a":I
    const/4 v1, 0x1

    .line 21
    .local v1, "b":I
    if-ne v0, v1, :cond_0

    .line 22
    move v1, v0

    goto :goto_0

    .line 24
    :cond_0
    const/4 v1, 0x0

    .line 26
    :goto_0
    return-void
.end method

Kotlin:

fun exampleIf() {
    val a = 1
    var b = 1
    if (a == b) {
        b = a
    } else {
        b = 0
    }
}

Kotlin → Smali:

.method public final exampleIf()V
    .locals 2

    .line 17
    const/4 v0, 0x1

    .line 18
    .local v0, "a":I
    const/4 v1, 0x1

    .line 19
    .local v1, "b":I
    nop

    .line 20
    move v1, v0

    .line 24
    return-void
.end method

3. Оператор цикла for

Java:

private void exampleFor() {
    int b = 0;
    for (int i = 0; i < 10; i++) {
        b++;
    }
}

Java → Smali:

.method private exampleFor()V
    .locals 3

    .line 48
    const/4 v0, 0x0

    .line 49
    .local v0, "b":I
    const/4 v1, 0x0

    .local v1, "i":I
    :goto_0
    const/16 v2, 0xa

    if-ge v1, v2, :cond_0

    .line 50
    add-int/lit8 v0, v0, 0x1

    .line 49
    add-int/lit8 v1, v1, 0x1

    goto :goto_0

    .line 52
    .end local v1    # "i":I
    :cond_0
    return-void
.end method

Kotlin:

private fun exampleFor() {
    var b = 0
    for (i in 0..10) {
        b++
    }
}

Kotlin → Smali:

.method private final exampleFor()V
    .locals 3

    .line 39
    const/4 v0, 0x0

    .line 40
    .local v0, "b":I
    const/4 v1, 0x0

    .local v1, "i":I
    :goto_0
    const/16 v2, 0xb

    if-ge v1, v2, :cond_0

    .line 41
    add-int/lit8 v0, v0, 0x1

    .line 40
    add-int/lit8 v1, v1, 0x1

    goto :goto_0

    .line 43
    .end local v1    # "i":I
    :cond_0
    return-void

4. Перечисление forEach

Java:

private void exampleForEach() {
    ArrayList a = new ArrayList<>();
    int b = 0;
    for (Integer it : a) {
        b += it;
    }
}

Java → Smali:

.method private exampleForEach()V
    .locals 5

    .line 55
    new-instance v0, Ljava/util/ArrayList;

    invoke-direct {v0}, Ljava/util/ArrayList;->()V

    .line 56
    .local v0, "a":Ljava/util/ArrayList;, "Ljava/util/ArrayList;"
    const/4 v1, 0x0

    .line 57
    .local v1, "b":I
    invoke-virtual {v0}, Ljava/util/ArrayList;->iterator()Ljava/util/Iterator;

    move-result-object v2

    :goto_0
    invoke-interface {v2}, Ljava/util/Iterator;->hasNext()Z

    move-result v3

    if-eqz v3, :cond_0

    invoke-interface {v2}, Ljava/util/Iterator;->next()Ljava/lang/Object;

    move-result-object v3

    check-cast v3, Ljava/lang/Integer;

    .line 58
    .local v3, "it":Ljava/lang/Integer;
    invoke-virtual {v3}, Ljava/lang/Integer;->intValue()I

    move-result v4

    add-int/2addr v1, v4

    .line 59
    .end local v3    # "it":Ljava/lang/Integer;
    goto :goto_0

    .line 60
    :cond_0
    return-void
.end method

Kotlin:

private fun exampleForEach() {
    val a = arrayListOf()
    var b = 0
    a.forEach { b += it }
}

Kotlin → Smali:

.method private final exampleForEach()V
    .locals 8

    .line 46
    new-instance v0, Ljava/util/ArrayList;

    invoke-direct {v0}, Ljava/util/ArrayList;->()V

    .line 47
    .local v0, "a":Ljava/util/ArrayList;
    const/4 v1, 0x0

    .line 48
    .local v1, "b":I
    move-object v2, v0

    check-cast v2, Ljava/lang/Iterable;

    .local v2, "$this$forEach$iv":Ljava/lang/Iterable;
    const/4 v3, 0x0

    .line 57
    .local v3, "$i$f$forEach":I
    invoke-interface {v2}, Ljava/lang/Iterable;->iterator()Ljava/util/Iterator;

    move-result-object v4

    :goto_0
    invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z

    move-result v5

    if-eqz v5, :cond_0

    invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object;

    move-result-object v5

    .local v5, "element$iv":Ljava/lang/Object;
    move-object v6, v5

    check-cast v6, Ljava/lang/Number;

    invoke-virtual {v6}, Ljava/lang/Number;->intValue()I

    move-result v6

    .local v6, "it":I
    const/4 v7, 0x0

    .line 48
    .local v7, "$i$a$-forEach-ExamplesKotlin$exampleForEach$1":I
    add-int/2addr v1, v6

    .line 57
    .end local v6    # "it":I
    .end local v7    # "$i$a$-forEach-ExamplesKotlin$exampleForEach$1":I
    nop

    .end local v5    # "element$iv":Ljava/lang/Object;
    goto :goto_0

    .line 58
    :cond_0
    nop

    .line 49
    .end local v2    # "$this$forEach$iv":Ljava/lang/Iterable;
    .end local v3    # "$i$f$forEach":I
    return-void
.end method

5. Операторы switch…case (Java) и when () (Kotlin)

Java:

private void exampleSwitch() {
    int a = 1;
    int b = a;
    int c = 0;
    switch (b) {
        case 0:
            c = a + 1;
            break;
        case 1:
            c = a + 2;
            break;
        case 2:
            c = a + 3;
            break;
        default:
            break;
    }
}

Java → Smali:

.method private exampleSwitch()V
    .locals 3

    .line 29
    const/4 v0, 0x1

    .line 30
    .local v0, "a":I
    move v1, v0

    .line 31
    .local v1, "b":I
    const/4 v2, 0x0

    .line 32
    .local v2, "c":I
    packed-switch v1, :pswitch_data_0

    goto :goto_0

    .line 40
    :pswitch_0
    add-int/lit8 v2, v0, 0x3

    .line 41
    goto :goto_0

    .line 37
    :pswitch_1
    add-int/lit8 v2, v0, 0x2

    .line 38
    goto :goto_0

    .line 34
    :pswitch_2
    add-int/lit8 v2, v0, 0x1

    .line 35
    nop

    .line 45
    :goto_0
    return-void

    nop

    :pswitch_data_0
    .packed-switch 0x0
        :pswitch_2
        :pswitch_1
        :pswitch_0
    .end packed-switch
.end method

Kotlin:

private fun exampleSwitch() {
    var a = 1
    var b = a
    var c = 0
    when (b) {
        0 -> c = a + 1
        1 -> c = a + 2
        2 -> c = a + 3
        else -> c = 0
    }
}

Kotlin → Smali:

.method private final exampleSwitch()V
    .locals 3

    .line 27
    const/4 v0, 0x1

    .line 28
    .local v0, "a":I
    move v1, v0

    .line 29
    .local v1, "b":I
    const/4 v2, 0x0

    .line 30
    .local v2, "c":I
    packed-switch v1, :pswitch_data_0

    .line 34
    const/4 v2, 0x0

    goto :goto_0

    .line 33
    :pswitch_0
    add-int/lit8 v2, v0, 0x3

    goto :goto_0

    .line 32
    :pswitch_1
    add-int/lit8 v2, v0, 0x2

    goto :goto_0

    .line 31
    :pswitch_2
    add-int/lit8 v2, v0, 0x1

    .line 36
    :goto_0
    return-void

    nop

    :pswitch_data_0
    .packed-switch 0x0
        :pswitch_2
        :pswitch_1
        :pswitch_0
    .end packed-switch
.end method

6. Преобразование переменной из Integer в String

Java:

private void exampleCast() {
    int a = 1;
    String b = "";
    b = String.valueOf(a);
}

Java → Smali:

.method private exampleCast()V
    .locals 2

    .line 63
    const/4 v0, 0x1

    .line 64
    .local v0, "a":I
    const-string v1, ""

    .line 65
    .local v1, "b":Ljava/lang/String;
    invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;

    move-result-object v1

    .line 66
    return-void
.end method

Kotlin:

private fun exampleCast() {
    val a = 1
    var b = ""
    b = a.toString()
}

Kotlin → Smali:

.method private final exampleCast()V
    .locals 2

    .line 52
    const/4 v0, 0x1

    .line 53
    .local v0, "a":I
    const-string v1, ""

    .line 54
    .local v1, "b":Ljava/lang/String;
    invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;

    move-result-object v1

    .line 55
    return-void
.end method

Заключение.

На этом на сегодня — все. Вывод можно сделать такой, что как бы Kotlin не отгораживался от Java, а в байт-коде они практически равны. В следующих статьях посмотрим на объявления классов и интерфейсов, создание объектов, а так же на то, как выглядят в Smali некоторые идиомы Kotlin. Посмотрим с боку на Corutines, Rx, Architecture Components и DI. Отдельно поговорим про ресурсы (шаблоны XML и Compose).
Нужно еще сказать, что по сути, фрагменты кода в Smali довольно типовые, поэтому со временем ваш глаз научится сам находить нужный фрагмент на лету, не останавливая прокрутки экрана. Как в «Матрице»)).

P.S.
Я за чистоту Русского Языка, и терпеть не могу, когда иностранные слова используют в кириллице, типа «кейс» или «сниппет» и вставляют их в устную речь, даже когда общаются с широкой и разнородной аудиторией, искажая Великий и Могучий. Считаю это признаком недалекости ума (Личное мнение автора статьи).

© Habrahabr.ru