Dexファイル形式は非常にシンプルです。下の図を参照してください。
上の画像は、データ構造の観点から描いたDexファイル形式です。各データ構造は、androidのソースコードdalvik / libdexディレクトリで定義されています。上の画像には、いくつか注意点があります。
1.図で(エンコード)とマークされているすべてのデータ構造は、Leb128によってエンコードされたファイル内のデータに対応しています。詳細については、自分で確認できます。エンコードルール:
- バイト単位で、リトルエンディアンの規則に従って配置
- 各バイトの最上位ビットはフラグです。最上位ビットが1の場合、新しいバイトがあることを意味し、0の場合、バイトシーケンスの終わりを意味します。
- 残りの7ビットを順番に結合し、7ビットを順番に左にシフトして整数を生成します。
2. DexCode構造体のdebugInfoOffは、プログラムのデバッグデータを指します。parametersSizeは、関数のパラメーターの数を示すDexCodeのinsSizeと同じである必要があります。parameterSizeフィールドの後には、各パラメーターの名前に対応するstringIdx値が続きます。これはパラメータの名前です。各パラメータのタイプは、DexMethodIdに対応するDexProtoId :: parametersOffで確認できます。Java関数呼び出しにもパラメータを渡すルールがあるため、2つのレコードのパラメータの順序は同じです。さらに、dalvikの各Java関数は特定の数のレジスターを使用することに注意してください。Dalvikは、Java関数が呼び出されると、パラメーターが次のN個のレジスターに配置されることを規定しています。たとえば、関数は5つのレジスター(v0-v4 )、2つのパラメーターがあり、これらの2つのパラメーターはそれぞれv3とv4に配置されます。Dexファイルに対応するsmaliファイルに対して検証できます。
3.デバッグデータのopcode / valueまたはaddress / line部分の場合、address / lineはバイトコードとソースコードの行番号の対応を表し、opcode / valueはバイトコードアドレスと仮想レジスタの対応を表します。それらはインターリーブされ、dexファイルに格納されます。それで、それがオペコード/値であるか、アドレス/行であるかをどのように判断しますか?opcode> = 0 && opcode <0x0aの場合、opcode / valueを意味します。それ以外の場合、opcodeの値は、address / lineの値です。
positions :
0x0000 line=21
0x0004 line=22
0x0018 line=24
0x001c line=27
0x0022 line=29
0x0024 line=30
0x0039 line=33
0x0044 line=39
0x0045 line=34
0x0046 line=36
0x0049 line=39
locals :
0x0022 - 0x0044 reg=1 info Landroid/content/pm/PackageInfo;
0x0024 - 0x0044 reg=4 sigs [Landroid/content/pm/Signature;
0x0046 - 0x004b reg=0 e Landroid/content/pm/PackageManager$NameNotFoundException;
0x001c - 0x004b reg=2 mgr Landroid/content/pm/PackageManager;
0x0004 - 0x004b reg=3 package_name Ljava/lang/String;
0x0000 - 0x004b reg=8 ctx Landroid/content/Context;
次の2つのデータ部分の詳細な分析:
- アドレス/ラインデータの場合、dexファイル内の対応するデータは、直接のアドレスおよびライン値ではなく、オフセット値のみです。このオフセットに従って、アドレスおよびラインの値を計算できます。計算には式が必要です。
int adjopcode = opcode - DBG_FIRST_SPECIAL;
address += adjopcode / DBG_LINE_RANGE;
line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE);
さらに、オペコードがDBG_ADVANCE_LINEの場合、lineの値に現在の値(Leb128エンコード)を現在の値で追加する必要があります。
- オペコード/値は、オペコードの種類によって、値に記録される内容が異なり、オペコードの値の範囲は以下のとおりです。
/* debug info opcodes and constants */ enum { DBG_END_SEQUENCE = 0x00, /* Terminates a debug info sequence for a method. */ DBG_ADVANCE_PC = 0x01, /* Advances the program counter/address register without emitting a positions entry. */ DBG_ADVANCE_LINE = 0x02, /* Advances the line register without emitting a positions entry. */ DBG_START_LOCAL = 0x03, /* Introduces a local variable at the current address. */ DBG_START_LOCAL_EXTENDED = 0x04, /* Introduces a local variable at the current address with a type signature specified. */ DBG_END_LOCAL = 0x05, /* Marks a currently-live local variable as out of scope at the current address. */ DBG_RESTART_LOCAL = 0x06, /* Re-introduces a local variable at the current address. The name and type are the same as the last local that was live in the specified register. */ DBG_SET_PROLOGUE_END = 0x07, DBG_SET_EPILOGUE_BEGIN = 0x08, DBG_SET_FILE = 0x09, DBG_FIRST_SPECIAL = 0x0a, DBG_LINE_BASE = -4, DBG_LINE_RANGE = 15, };
特定の値の意味は次のとおりです。
-
- 0x00(DBG_END_SEQUENCE)、デバッグ情報の終わりを示します
- 0x01(DBG_ADVANCE_PC)、データは正の整数、つまりアドレス+ =データ。
- 0x02(DBG_ADVANCE_LINE)、データは正の整数で、行+ =データを示します。
- 0x03(DBG_START_LOCAL)、0x04(DBG_START_LOCAL_EXTENDED)、新しいレジスター情報を開始することを示します。
- 0x05(DBG_END_LOCAL)、登録メッセージの終わりを示します。
- 0x06(DBG_RESTART_LOCAL)、これはレジスター情報を再始動することを意味します。
- その他の値(0x07〜0x0a)は無視されます。データが空です。
dexファイルのデータのこの部分の特定の形式は次のとおりです。
上の図のregは、仮想レジスタ番号を示しています。アドレス/ラインデータとオペコード/値データはインターリーブされています。図では、オペコード/値データが故意にアドレス/ラインデータを除外していることを示すために、プログラムを作成するとき、どのデータが条件に基づいているかを判断する必要があります。
上記のデバッグ情報のアドレスと行の値、各関数のデバッグ情報は、すべて値0から始まり、オペコードに従って毎回重ね合わされます。
上の図は、各オペコードの後ろに何が格納されているかを非常に明確に示しているため、ここではこれ以上説明しません。次のように言ってください。
- DBG_START_LOCAL、DBG_START_LOCAL_EXTENDED:これらの2つのオペコードは、新しい登録メッセージの開始を示し、古い登録メッセージの終了を意味します。opcodeの後続のデータは、regインデックス、ローカル変数の名前に対応する文字列インデックス、ローカル変数のタイプに対応する型インデックス、およびローカル変数のシグネチャに対応する文字列インデックスです(opcode == DBG_START_LOCAL_EXTENDEDの場合のみ)。regのインデックスは、現在処理されているレジスタを示します。これは、このregに対応するローカル変数の範囲が終了し、regに対応する新しい変数範囲が開始したことを意味します。つまり、現在のアドレス値は、regの前のローカル変数のendAddressと新しい変数のstartAddressです。
- DBG_END_LOCAL、後続のデータはregのインデックスであり、対応するreg変数の範囲が終了することを示します。つまり、現在のアドレス値はregに対応する変数のendAddressです。
- DBG_RESTART_LOCAL後続のデータはregのインデックスです。つまり、regに対応する変数の範囲がリセットされます。つまり、現在のアドレス値はregに対応する変数のstartAddressです。
とりあえず、Dexファイル形式の紹介はここで行いますが、特定の関数の内部コードに関連する命令のデコードと注釈については最初に紹介せず、後で説明します。実際、私が描いた絵は非常に詳細だと個人的には思っていますが、上記のコンテンツを見ただけではまだ比較的抽象的です。Androidシステムのソースコードに含まれているdexdumpのソースコードを非常に簡単に確認することをお勧めします。
私はダンプDexファイル情報の小さなプログラムを書く練習をしました、純粋に個人的な練習、コメントなし、デザインなし、レビューなし、エラー処理なし、単純な自己テストのみ、妥協のないエンジニアリングバージョン、参照のみ、アドレス:https:// github.com/beyond702/DexFileParser.git
私が良いと思うDexファイル形式に関連する2つのリンクを次に示します。