Androidのapkを
apktoolを用いて逆コンパイルするとsmaliファイルが出てきます.
先日,そのsmaliファイルを読む機会があったので,そのときに学んだsmaliの文法をまとめておきます.
注意
- 説明の簡略化のために,メソッドとフィールドの説明におけるJavaのコードにおいて,文法を守っていない部分があります
- レジスタの説明などにおいて,一部smaliとdex,Dalvik VMを同一視しています
型
Javaにおける型は,基本データ型,クラス型,配列型の3種類に分けられます.
それぞれの型について,smaliではどのように型を表記するかを示します.
- 基本データ型smaliでは,基本データ型は大文字のアルファベット1文字で表します.
smali | Java |
V | void |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
L | long |
F | float |
D | double |
- クラス型
smaliでは,Javaと比較してクラス名の表記が以下の点で異なります.
- クラス名の最初が"L"
- パッケージ名の区切り文字が"."ではなく"/"
- クラス名の最後が";"
例
smali | Java |
Ljava/lang/Object; | java.lang.Object |
Ljava/lang/String; | java.lang.String |
Lpackage/name/ObjectName; | package.name.ObjectName |
- 配列型
smaliでは,配列型は"["から始まる文字列で表します.
例
smali | Java |
[I | int[] |
[[I | int[][] |
[[[I | int[][][] |
[Ljava/lang/String; | java.lang.String[] |
メソッド
smaliでは,メソッドを以下のように表記します.
オブジェクト名->メソッド名(引数の型)戻り値の型
例
smali |
Lpackage/name/ObjectName;->MethodName(III)Z |
Java |
boolean package.name.ObjectName.MethodName(int, int, int) |
smali |
Lpackage/name/ObjectName;->MethodName(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; |
Java |
String package.name.ObjectName.MethodName(int, int[][], int, String, Object[]) |
フィールド(メンバ変数)
smaliでは,フィールドを以下のように表記します.
オブジェクト名->フィールド名:フィールドの型
例
smali |
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String; |
Java |
String package.name.ObjectName.FieldName |
レジスタ
smaliでは,レジスタとよばれる変数を介して,引数の受け渡しなどを行っています.
ビット数
レジスタの大きさは32ビットで,boolean,intなどのほとんどの型を1つのレジスタに格納することができます.
ただし,long型,double型のみは,格納に64ビット必要なため,レジスタが2つ必要になります.
種類と表記
レジスタは,ローカルレジスタ,引数レジスタの2種類に分けられます.
ローカルレジスタはsmali上でv0,v1,v2…と表記され,レジスタの最大数は.localsディレクティブなどによって変動します.
たとえば".locals 5"であれば,v0~v4のレジスタを使用することができます.
引数レジスタはsmali上でp0,p1,p2…と表記されます.
レジスタの最大数や値は,staticメソッドかどうかによって,少し挙動が変わってきます.
- staticメソッドの場合
- 引数レジスタの最大数は(引数の数)個になります.
たとえば method(int, int, int, int) というメソッドであれば,引数の数は4個なので,p0~p3の4個のレジスタを使用することができます.
- 引数レジスタは名前の通り,メソッドに渡された引数が格納されます.
たとえば method(1, 3, 5, 7) のようにメソッドを呼び出した場合,引数レジスタに格納される値はp0 = 1, p1 = 3, p2 = 5, p3 = 7になります.
- staticメソッドでない場合
- 引数レジスタの最大数は(引数の数+1)個になります.
たとえば method(int, int, int, int) というメソッドであれば,引数の数は4個なので,p0~p4の5個のレジスタを使用することができます.
- 引数レジスタは名前の通り,メソッドに渡された引数が格納されます.
ただし,staticメソッドでない場合は,p0にthisへの参照が格納されます.
たとえば method(1, 3, 5, 7) のようにメソッドを呼び出した場合,引数レジスタに格納される値はp0 = this, p1 = 1, p2 = 3, p3 = 5, p4 = 7になります.
なお,long型,double型はレジスタが2つ必要になるので,引数の数とレジスタの最大数がずれることに注意してください.
たとえばstaticメソッド method(long) の場合,引数は1個ですが,long型の格納にレジスタが2個必要になるため,レジスタの最大数は2個となり,第一引数はp0とp1に格納されます.
smaliの読み方
smaliの各行は大きく「コメント」「ラベル」「ディレクティブ」「命令」に分かれます.
「コメント」は,コンパイル時に無視される部分です.
Javaでは"
//"以降がコメントになります.
smaliでは"
#"以降がコメントになります.
「ラベル」はジャンプ命令などで飛ばされる先になります.
"
:"から始まる行がラベル行です.
"
:label1"のようにしてラベルを定義します.
「ディレクティブ」と「命令」の違いは気にしなくてもいいと思います.
文法的には"
."から始まるものが「ディレクティブ」,そうでないものが「命令」となります.
内部的には,定数情報の格納を行うのが「ディレクティブ」,情報の操作などを行うのが「命令」という感じがします(あんまりよくわかってない).
命令一覧に関しては,
Dalvik opcodesをご覧ください.
ディレクティブ一覧
私が見たこと無いディレクティブはわからないので省略します….
ディレクティブ | 説明 |
.class | クラス名の定義 |
.class public Lpackage/name/ObjectName; |
.super | スーパークラス |
.super Ljava/lang/Object; |
.implements | 実装インターフェース |
.implements Ljava/io/Serializable; |
.source | Javaソースファイル名 |
.source "ObjectName.java" |
.field | フィールド定義 |
.field private FieldName:Ljava/lang/String; |
.annotation | アノテーション |
.annotation runtime Lorg/junit/Test;
.end annotation |
.method | メソッド定義 |
.method public static main([Ljava/lang/String;)V メソッドの中身
.end method |
.locals | ローカルレジスタ数の設定 |
.locals 5 #ローカル変数の個数は5個 |
.array-data | 配列の初期値の定義 |
.array-data 4 #要素が4バイトの配列
0x800000
0x400000
0x200000
0x100000
.end array-data
#設定された値はfill-array-data命令で書き込みできる |
.packed-switch | 連続した値におけるswitch文の遷移先設定 |
.packed-switch 10 #値10から遷移チェックを開始
:label10 #値が10のときの遷移先ラベル
:label11 #値が11のときの遷移先ラベル
:label12 #値が12のときの遷移先ラベル
:label13 #値が13のときの遷移先ラベル
.end packed-switch #あくまで遷移先ラベルの設定のみで,実際の遷移は行われない
#実際の遷移はpacked-switch命令で行われる |
.sparse-switch | 飛び飛びの値におけるswitch文の遷移先設定 |
.sparse-switch
10 -> :label10 #値が10のときの遷移先ラベル
20 -> :label20 #値が20のときの遷移先ラベル
25 -> :label25 #値が25のときの遷移先ラベル
51 -> :label51 #値が51のときの遷移先ラベル
.end sparse-switch
#あくまで遷移先ラベルの設定のみで,実際の遷移は行われない
#実際の遷移はsparse-switch命令で行われる |
.line | Javaソースコードで何行目にあたるか(デバッグ用) |
.line 10 #Javaソースコードでは10行目 |
.local | ローカル変数の個数 |
.line 10 #Javaソースコードでは10行目 |