Эта статья — оригинальная, а не копипаст и не машинный перевод. При копировании прошу ссылаться…
Статья для начинающих исследователей файлов APK. Хотя почему только для начинающих? На некоторые моменты не обращаешь внимания, пока не разложишь все по полочкам.
Наверное есть такие люди, которые думают, что все так просто: взял 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<Integer> 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;-><init>()V .line 56 .local v0, "a":Ljava/util/ArrayList;, "Ljava/util/ArrayList<Ljava/lang/Integer;>;" 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<Int>() 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;-><init>()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, а в байт-коде, на уровне простейших операций, они практически равны. Разве что Kotlin больше внимания уделяет оптимизации. В следующих статьях посмотрим на объ��вления классов и интерфейсов, создание объектов, а так же на то, как выглядят в Smali некоторые идиомы Kotlin. Посмотрим с боку на Corutines ,Rx, Architecture Components и DI. Отдельно поговорим про ресурсы (шаблоны XML и Compose).
Нужно еще сказать, что по сути, фрагменты кода в Smali довольно типовые, поэтому со временем ваш глаз научится сам находить нужный фрагмент на лету, не останавливая прокрутки экрана. Как в «Матрице»)).
P.S.
Я за чистоту Русского Языка, и терпеть не могу, когда иностранные слова используют в кириллице, типа «кейс» или «сниппет» и вставляют их в устную речь, даже когда общаются с широкой и разнородной аудиторией, искажая Великий и Могучий. Считаю это признаком недалекости ума (Личное мнение автора статьи).
