2008年6月28日土曜日

Visual C++ 2005 Expressでアセンブラ入門 Part-1

VC++ 2005 Expressの逆アセンブル表示を使ってみる

Visual C++ 2005 ExpressでWin32コンソールアプリケーションHelloWorldを作ります。新規プロジェクト作成で何もしない_tmainが自動生成されます(下記コード)。

// HelloWorld.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
     return 0;
}

とりあえず、「ビルド」メニューから「ソリューションのビルド」を選択してビルドしますが、その前にコンパイルの警告レベルをレベル4に上げておきましょう。

TIPS: 開発中(デバッグ中)は、コンパイルの警告レベルはレベル4にする

ソリューションのビルドができたら、「デバッグ」メニューから「ステップイン」を選択し実行を開始します。_tmainの最初で実行が止まります。 ソースコード上で右クリックメニューから「逆アセンブルを表示」を選択します。すると、こんな表示になると思います(下記コード)。

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
00411380 push ebp ; (1)
00411381 mov ebp,esp ; (2)
00411383 sub esp,0C0h ; (3)
00411389 push ebx ; (4)
0041138A push esi ; (5)
0041138B push edi ; (6)
0041138C lea edi,[ebp-0C0h] ; (7)
00411392 mov ecx,30h ; (8)
00411397 mov eax,0CCCCCCCCh ; (9)
0041139C rep stos dword ptr es:[edi] ; (10)
return 0;
0041139E xor eax,eax              ; (11)
}
004113A0 pop edi ; (12)
004113A1 pop esi
004113A2 pop ebx
004113A3 mov esp,ebp ; (13)
004113A5 pop ebp
004113A6 ret

詳細を説明する前に、スタックについて簡単に説明しておきます。スタックというのは、整理が苦手な人の机のようなもので、書類が平積みになっています。一番下をスタックのボトムと言い、一番上をスタックのトップといいます。新しい書類は、スタックのトップに積まれていきます。古い書類にアクセスするには、上の書類から順番にどかす必要があります。

ebpというのはスタックベースポインタと呼ばれるレジスタで、現在のスタックのボトム(スタックフレームと呼ぶ)がメモリ上のどこにあるかを指しています。コンピュータではスタックに積まれるのは書類ではなく値ですが。スタックに値を積むには、push命令を使います。(1) push ebpではebpレジスタの内容をスタックに積んでいます。

espというのはスタックポインタと呼ばれるレジスタで、現在のスタックのトップがメモリ上のどこにあるかを指しています。(2)の時点では、スタックのトップは、先ほどebpレジスタの内容保存した場所を指しています。1つだけ注意があります。机の上では、書類が積まれるたびに高さが増えていきます。ところが、espレジスタの値は、スタックに値が積まれるたびに小さくなります。ちょうど、天井から床方向へ書類を積んでいるような感じです。

(2)では、mov命令でespレジスタの内容をebpレジスタにコピーしています。新しいスタックフレームを定義したという意味です。

次に(3)では、espの値から0xC0(10進数で192)を引き、スタックのトップの位置を下位アドレス方向に変更します。これで192バイトの空き地ができます。

(4), (5), (6)ではebx, esi, ediという3つのレジスタの内容を順番にスタックに保存して行きます。(7)leaでediレジスタの値をebp-0C0h番地に設定します。空き地の先頭を指しています(下位アドレス側)。

(10) rep stos dword ptr es:[edi] というのは、edi(空き地の先頭)からecxで指定された回数だけ、eaxの値で埋める、という意味です。ecxには0x30(10進数で48)、eaxはDWORDで0xCCCCCCCCという値が入っています。ちょうど、上位アドレス方向に向かって、192バイト分の空き地を0xCCで埋められることになります。

(11) xor eax, eaxはちょっとトリッキーですが、eaxレジスタの値を0にしているだけです。これはreturn値に相当します。

(12)以降のpop命令はスタックに積んでいた値をレジスタに戻しています。また、(13)mov esp,ebpではスタックポインタをスタックベースポインタに戻しています。

ここまでの考察

まだ何もしてないアプリケーションなのに、アセンブラレベルではやけにコード量が多いと思いませんでしたか? そうなんです、 _tmainは関数なので、ここで実行されているのはまぎれもなく関数呼び出しなのです。そして関数呼び出しでは、いくつかのレジスタの値をスタックに積んだり、スタックからレジスタに値を戻したりとコード量が増えるのです。ただし、これはコードの最適化が行われないデバッグビルドの話であって、デフォルトでコードの最適化が行われるリリースビルドではこうはいきません。

ところで、192バイトの空き地は何なのでしょうか?わざわざ0xCCで埋めたりして。続きは次回。