PEヘッダ
PEヘッダとは、EXEファイルの先頭部分の情報のことです。前項で解説したデータの一部はこのPEヘッダに格納されています。PEとは「Portable Executable」の略で、持ち運び可能な実行ファイルという意味です。同一プラットフォーム上であれば、どこからでも動作可能なことを考えると納得できるネーミングですね。なお、PEヘッダ自体はUnixなどで昔から採用されており、Windowsに対応するEXEが持ち合わせるPEヘッダは、Microsoftが拡張定義したものになっています。
EXEファイルのロード方法
OSは、先ほど説明したMZシグネチャ、マシンタイプ、ネイティブコードなどの各情報をもとに、EXEファイルをメモリ上にロードし実行します。OSがEXEファイルを実行するまでの手順は次のようになります。
- シグネチャ、マシンタイプなどを確認します。EXEファイルが正規のフォーマットで記載されているかをチェックします(なお、このタイミングでデータが間違っているとデバッグすら実行しません)。
- EXEイメージをメモリ上にコピーします。
- PEヘッダに記載されているデータをもとに、EXEイメージの初期化を行います。
- PEヘッダで指定されたスタートポジション(エントリポイント)からプログラムを実行します。
重要なメモリ指定概念「RVA」
EXEファイルに記載されるデータの中に、RVAというものがよく出現します。EXEファイルイメージは実行前にプロセスメモリ上の任意の場所にロードされますが、その任意の場所、すなわち、EXEイメージが読み込まれたメモリの先頭位置を「イメージベース」と呼びます。RVAとは「Relative Virtual Address」の略で、イメージベースからの相対オフセット値のことを言います。
ここで気を付けたいのが、ファイル上に配置されるデータと、プロセスメモリ上に呼び出されたデータの格納場所が異なるという点です。つまり、EXEファイルを実行すると、ファイルポインタで読み込みの対象となるデータ位置を指定し、RVAで実際に読み出されたプロセスメモリ上のデータを指し示すことになります。
EXEファイルに存在するデータのアドレスを、RVAを用いて指定する場合は、次の計算式が常に利用されます。
イメージベース + RVA = アドレス
PEヘッダよりも前にある情報
実は先ほど筆者は、分かりやすさを優先させるために、少々正しくない表現で解説していました。「PEヘッダがEXEファイルの先頭にある」という表現をしていましたが、厳密には、PEヘッダの前にはMS-DOSのデータと互換性を持たせるためのデータが存在します。
したがって、EXEファイルの先頭には、正しくは次のような順序で各種ヘッダデータが格納されています。
IMAGE_DOS_HEADER
- MS-DOS用スタブ
- NULL空間
- PEヘッダ
次項より、これらを一つずつ解説していきます。
IMAGE_DOS_HEADER構造体
IMAGE_DOS_HEADER構造体はMS-DOSで認識可能なデータフォーマットです。もともとWindowsはMS-DOSの上位OSとして設計されてきた経緯があるため、Windows XPが主流の現在でも、データの内部には遺物として残っているようです。このIMAGE_DOS_HEADER
構造体は、EXEファイルの0バイト目から保存されています。
ちなみに、Visual C++をお持ちの方は、「winnt.h」でIMAGE_DOS_HEADER
構造体の定義を見ることができますので、参照してみてください。次にその定義を示します。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_DOS_HEADER
構造体の中にある情報は、現在主流であるWindowsであまり扱われません。そのため、ほとんどが意味を持ちません。そもそも、e_lfanew
以外のメンバはすべて16ビットです。これでは、アドレス空間の値などを指定することができません。しかし、次に挙げる情報は重要なので、チェックしておきましょう。
e_magic
メンバ …… 必ず0x5A4Dを保持していますe_lfanew
メンバ …… PEヘッダが格納されているファイル位置を保持します
MS-DOS用スタブ
MS-DOS用スタブは、万が一、MS-DOS上でプログラムを動作させてしまった場合に、MS-DOSのプログラムを実行するためのネイティブコードを保存できるスペースです。MS-DOS用スタブは、IMAGE_DOS_HEADER
の直後に配置されます。
当たり前のことですが、PEヘッダはWindowsに対応しているデータなので、MS-DOSでは読み込むことができません。MS-DOSで解釈可能なのは、IMAGE_DOS_HEADER
構造体と、それに続くネイティブコード、つまり、このMS-DOS用スタブになります。
このスタブデータは、一般的には「This program cannot be run in DOS mode.」という文字列を表示して終了するプログラムが保持されていますが、MS-DOS用にカスタマイズされたプログラムをこのスペースに格納しておいても問題はありません。ということは、Windows用と、MS-DOS用のソフトウェアを一つのEXEファイルに持たせることができるのか? という疑問を持たれる方もいるかもしれませんが、残念ながら筆者は実験したことがありません(しかしながら、このコード空間がそろそろ必要ない感じてしまうのは筆者だけでしょうか……)。
NULL空間
MS-DOS用スタブの後にPEヘッダが配置されますが、PEヘッダの先頭位置は8バイト境界である必要があります。一般的には、0x0100バイト目までNULL空間が存在し、0x0100バイト目からPEヘッダが配置されます。
EXEファイルには、このようなNULL空間が多数存在します。それは同時に、EXEファイルの内部には明確なバイト境界が要求されていることの裏付けとなります。
おわりに
今回は、EXEファイルの概要として、MZシグネチャ、マシンタイプ、ネイティブコード、リソース、デバッグ情報などについて説明しました。これらの一部はPEヘッダと呼ばれる場所に格納されています。また、そのPEヘッダより前にあるEXEファイルの先頭には、IMAGE_DOS_HEADER
構造体、MS-DOS用スタブなどがあり、それらがWindowsの元となったMS-DOS用のものであることなどを説明しました。
次回は、「EXEファイルの特性が書き込まれているPEヘッダ部分」について解説します。
PEヘッダには、ターゲットマシン、アライメント情報、セクション管理情報、メモリサイズ情報などが格納されており、OSがプログラムをメモリにロードしてEXEファイルを実行する際には、必要なデータがこれらの情報に随時書き込まれています。次回は、このPEヘッダの核心に迫ります。