最も簡単な逆アセンブル

なんと、アセンブラが「全く」読めない。基本情報技術者試験の午後は、当然「アセンブラ」を選択し、「はじめて読む Pentium マシン語 - あしのあしあと」でも遊び、他にも色々実験したのに(軽くショック)。
なので、初心にかえって、少しずつ、アセンブラを読んでいこうと思う。最初は、最もシンプルに、足し算をするだけの plus という関数で実験した。

int __cdecl plus(int a, int b) {
    return a + b;
}

int main(void) {
    return plus(0x07, 0x0A);
}

アセンブルして出力されたコードが次。とりあえず、全部載せてみた。なお、環境は、Windows 10 上の、Visual Studio Community 2017。コンパイルオプションは、デフォルトのまま。関数の呼び出し規約も、デフォルト(のはず)の __cdecl。

  _plus:
  011C16A0  push        ebp                              ; (01)
  011C16A1  mov         ebp,esp                          ; (02)
  011C16A3  sub         esp,0C0h                         ; (03)
  011C16A9  push        ebx                              ; (04)
  011C16AA  push        esi                              ; (05)
  011C16AB  push        edi                              ; (06)
  011C16AC  lea         edi,[ebp+FFFFFF40h]              ; (07) [ebp-0C0h]
  011C16B2  mov         ecx,30h                          ; (08)
  011C16B7  mov         eax,0CCCCCCCCh                   ; (09)
  011C16BC  rep stos    dword ptr es:[edi]               ; (10)
  011C16BE  mov         eax,dword ptr [ebp+8]            ; (11) 第 1 引数 'a'
  011C16C1  add         eax,dword ptr [ebp+0Ch]          ; (12) EAX ← a + b
  011C16C4  pop         edi                              ; (13)
  011C16C5  pop         esi                              ; (14)
  011C16C6  pop         ebx                              ; (15)
  011C16C7  mov         esp,ebp                          ; (16)
  011C16C9  pop         ebp                              ; (17)
  011C16CA  ret                                          ; (18)

ステップ実行したときの、メモリとレジスタの様子をメモしたのが次の図。コメントの先頭に、行数を追記した。
グレーの部分が、plus 関数が呼び出されたときの、メモリの様子。main 関数が、引数である「7」と「A」をスタックに入れて、plus をコールしている。



(11) 行目と (12) 行目の 2 行がコア。他は、前処理と後処理のようなもの。
(1) 〜 (3) 行目と、(17) 〜 (18) 行目は、関数内でスタックを利用する場合の、お約束のコード。これまでのスタックのベースポインタを、バックアップしておいて、新しいポインタを使う。関数が終了すると、これらを元に戻す。
(4) 〜 (6) 行目、(13) 〜 (15) 行目は、もともとの値をスタックに退避させ、関数が終了する際に、元に戻す処理。


(7) 〜 (10) 行目が、謎。どこかで調べる。

  • ESI(ソースインデックス)、EDI(ディスティネーションインデックス)の使いどころ。それを言ったら、ECX(カウントレジスタ)もよくわからない
  • (10) 行目の処理。そもそも、何でメモリを「C」で埋めたいのだろう(初期化?)。とりあえず、図では、赤で示しておく


思っていたのとは少し違った。もっとシンプルだったと思ったが。

2017/7/20 追記

もう一回、環境を作らなければいけなくなった。Visual Studio Community 2017 で、空のプロジェクトを作成する。プログラムを実行したときに、コマンドプロンプトが消えないように、プロジェクトのプロパティから、次のオプションを有効にする。

2017/9/18 追記

体力的にきつく、なかなか進まないのだが、ほんの少し勉強した。しかし、ココ「assembly - Why is the stack filled with 0xCCCCCCCC - Stack Overflow」に書いてあることは、まだよくわからない。
とりあえず、わかったこと。CPU は、命令を実行するたびに、割り込み要求を確認すること。割り込み発生時には、割り込み番号に対応するサブルーチンを実行すること。この割り込み番号をオペランドとする 2 バイトの命令が int であること。ただし、割り込み割り込み番号 3 だけは、1 バイト(CC)で表されること。どうやら int 3 は、デバッガでブレークポイントを設定するのに便利であるっぽいこと。

2017/9/20 追記

あと、一応 main 関数も書いておくことにした。この先、よく出てくるから。

  _main:
  00A116A0  push        ebp                        ; (01) ベースポインタを退避(1 つ前のスタックフレームの先頭)
  00A116A1  mov         ebp,esp                    ; (02) ベースポインタを、スタックポインタの初期値に設定
  00A116A3  sub         esp,0C0h                   ; (03) スタックポインタを 48×4 先へ
  00A116A9  push        ebx                        ; (04) EBX を退避
  00A116AA  push        esi                        ; (05) ESI(ソースレジスタ)を退避
  00A116AB  push        edi                        ; (06) EDI(ディスティネーションレジスタ)を退避
  00A116AC  lea         edi,[ebp-0C0h]             ; (07) EDI ← ベースポインタの 48×4 先のアドレス
  00A116B2  mov         ecx,30h                    ; (08) ECX ← 48
  00A116B7  mov         eax,0CCCCCCCCh             ; (09) EAX ← 0xCCCCCCCC
  00A116BC  rep stos    dword ptr es:[edi]         ; (10) ES:[EDI] を EAX で埋める × 48
  00A116BE  push        0Ah                        ; (11) 第 2 引数 10('b')
  00A116C0  push        7                          ; (12) 第 1 引数  7('a')
  00A116C2  call        _plus (0A112C1h)           ; (13) plus 関数の呼び出し
  00A116C7  add         esp,8                      ; (14) スタックポインタを移動
  00A116CA  pop         edi                        ; (15) EDI を復元
  00A116CB  pop         esi                        ; (16) ESI を復元
  00A116CC  pop         ebx                        ; (17) EBX を復元
  00A116CD  add         esp,0C0h                   ; (18) スタックポインタを 48×4 戻す
  00A116D3  cmp         ebp,esp                    ; (19) EBP と ESP の比較(結果は __RTC_CheckEsp 冒頭で検証)
  00A116D5  call        __RTC_CheckEsp (0A11113h)  ; (20) ESP の整合性をチェックする
  00A116DA  mov         esp,ebp                    ; (21) スタックポインタを復元
  00A116DC  pop         ebp                        ; (22) ベースポインタを復元 → スタックフレームの破棄
  00A116DD  ret                                    ; (23)

__RTC_CheckEsp については、「c++ - How's __RTC_CheckEsp implemented? - Stack Overflow」 を参考にした。