Погружение в Smali. Как выглядят Java и Kotlin со стороны
Эта статья — оригинальная, а не копипаст и не машинный перевод. При копировании прошу ссылаться…
Статья для начинающих исследователей файлов 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.
Я за чистоту Русского Языка, и терпеть не могу, когда иностранные слова используют в кириллице, типа «кейс» или «сниппет» и вставляют их в устную речь, даже когда общаются с широкой и разнородной аудиторией, искажая Великий и Могучий. Считаю это признаком недалекости ума (Личное мнение автора статьи).