PE ファイルについて (2) - IMAGE_FILE_HEADER

この記事は Windows & Microsoft技術 基礎 Advent Calendar 2015 の 23 日目です。
今度は期日前に書き上げましたよ!

前回は肝心なところで終わってしまいました。
今回は続きの NT ヘッダー編から。
と言ってもですね、NT ヘッダーを全部解説し終わったら、PE ファイルの解説なんて 9 割方終わったようなもんです。
なので、しばらくは NT ヘッダーの解説が続きます。

dumpbin

ヘッダー構造の解説の前に dumpbin ツールについて紹介します。
dumpbin は PE ファイル*1の構造をダンプ出来るツールで、Windows SDK に含まれています。

dumpbin /headers <PEファイル>

と打つと、PE ファイルのヘッダーを表示することができます。

NT ヘッダー

NT ヘッダーは IMAGE_NT_HEADERS という構造体で表される構造をしています。

typedef struct _IMAGE_NT_HEADERS {
  DWORD                 Signature;
  IMAGE_FILE_HEADER     FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

3 つのメンバーから成りますが、うち 2 つはさらに構造体になっています。
順番に解説していきますが、今回は最初の 2 つまでにします。

そうそう。前回載せたコードの一部を再掲しますが、NT ヘッダーは(ファイルの先頭を起点として)IMAGE_DOS_HEADER::e_lfanew が指す位置にあります。

// pvBase はメモリマップドファイルの先頭を指すポインターだとする
auto pDosHeader = static_cast<IMAGE_DOS_HEADER *>(pvBase);
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
	printf("Invalid MS-DOS signature.");
	return;
}

// ここが NT ヘッダー
auto pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(static_cast<LPBYTE>(pvBase) + pDosHeader->e_lfanew);

Signature

NT ヘッダーの先頭にある 4 バイトです。
ASCII コードで言うと "PE\0\0" という 4 文字です(\0 は NUL 文字)。
これは winnt.h 中で IMAGE_NT_SIGNATURE という名前で定義されています。
前回紹介した IMAGE_DOS_SIGNATURE と併せて、このファイルが PE ファイルかどうかを判定するのに使います。

FileHeader

IMAGE_FILE_HEADER 構造体で表されます。
PE ファイルは COFF (Common Object File Format)という形式を拡張しており、IMAGE_FILE_HEADER 構造体は COFF ファイルのヘッダーでもあります。
ちなみに COFF 形式は VC++ソースコードコンパイルした後、リンクする前の *.obj ファイルの形式としても利用されています。

以下、各メンバーの解説。

Machine

この PE ファイルがターゲットとする CPU のアーキテクチャーを表します。
x86(32bit)の場合は 0x014c、x64(64bit)の場合は 0x8664 です。
それ以外にも IA64 や ARM から MIPS、ALPHA、PowerPC、SH5 など、様々な値が定義されています。
VC++ では、リンカーオプション /MACHINE で指定します。

NumberOfSections

このファイルに含まれるセクションの数を表します。
セクションとは何かについては、次々回あたりで説明します。
簡単に言うと、ヘッダーの後にあるデータ本体領域を用途別に区切ったものです。

TimeDateStamp

このファイルの作成時刻です。
32bit の Unix 形式で表されます。

PointerToSymbolTable

COFF シンボルテーブル(よくわかんない)へのポインターです。
PE ファイルの場合は 0 のようです。

NumberOfSymbols

COFF シンボルテーブルのシンボルの数です。
PE ファイルの場合は 0 のようです。

SizeOfOptionalHeader

オプショナル ヘッダーのサイズです。
次回取り上げますが、IMAGE_OPTIONAL_HEADER 構造体のサイズになります。
PE ファイルとしては必須のヘッダーですが、Optional という名前なのは、COFF ファイルでは必須ではないからです。
*.obj ファイルではこのメンバーは 0 になります。

Characteristics

いろいろなフラグです。

IMAGE_FILE_RELOCS_STRIPPED (0x0001)
このファイルにベース再配置情報が含まれないことを意味します。
再配置についてはそのうち取り上げます。*2
VC++ ではリンカーオプション /FIXED を有効にするとこのフラグが立ちます。
IMAGE_FILE_EXECUTABLE_IMAGE (0x0002)
このファイルが実行可能であることを意味します。
PE ファイルでは通常 ON です。
IMAGE_FILE_LINE_NUMS_STRIPPED (0x0004)
このファイルに COFF 行番号情報が含まれないことを意味します。
PE ファイルでは OFF のようですが、おそらく COFF ファイルの場合にのみ意味があるフラグだと思います。
IMAGE_FILE_LOCAL_SYMS_STRIPPED (0x0008)
このファイルに COFF シンボルテーブルが含まれないことを意味します。
PE ファイルでは OFF のようですが、おそらく COFF ファイルの場合にのみ意味があるフラグだと思います。
IMAGE_FILE_AGGRESIVE_WS_TRIM (0x0010)
このファイルが動作中にメモリ(ワーキング セット)を積極的に縮小することを意味する…んでしょうか?
廃止された(Obsolete)フラグです。
IMAGE_FILE_LARGE_ADDRESS_AWARE (0x0020)
アプリケーションが 2GB を超えるアドレスをサポートしていることを意味します。
64bit アプリケーションでは既定で ON になります。
VC++ ではリンカーオプション /LARGEADDRESSAWARE で制御できます。
IMAGE_FILE_BYTES_REVERSED_LO (0x0080)
よくわかりません。
廃止されたフラグです。
IMAGE_FILE_32BIT_MACHINE (0x0100)
32bit 用のファイルであることを意味します。
x86 用のファイルではこのフラグが立ちます。
IMAGE_FILE_DEBUG_STRIPPED (0x0200)
このファイルにデバッグ情報が含まれないことを意味します。
VC++ では *.pdb ファイルを生成しない設定にしても、このフラグは立たないようです。
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP (0x0400)
このファイルを CD-ROM などのリムーバブルメディアから実行する時に、スワップファイル上にコピーしてから実行します。
通常、PE ファイルはメモリマップドファイルとしてロードされますが、一旦スワップファイル上にコピーすることで高速に動作するようになります。
VC++ ではリンカーオプション /SWAPRUN:CD で有効にできます。
IMAGE_FILE_NET_RUN_FROM_SWAP (0x0800)
このファイルをネットワーク上から実行する時に、スワップファイル上にコピーしてから実行します。
VC++ ではリンカーオプション /SWAPRUN:NET で有効にできます。
IMAGE_FILE_SYSTEM (0x1000)
システムファイルであることを表します。
どういう意味があるのかはよくわかりません。
IMAGE_FILE_DLL (0x2000)
このファイルが EXE ではなく DLL であることを意味します。
IMAGE_FILE_UP_SYSTEM_ONLY
このファイルが単一 CPU のマシンでのみ実行できることを意味します。
VC++ ではリンカーオプションの /DRIVER:UPONLY で制御できそうですが、EXE ファイルでは出番はないと思います。
IMAGE_FILE_BYTES_REVERSED_HI (0x8000)
よくわかりません。
廃止されたフラグです。

次回予告

NT ヘッダーの本丸、IMAGE_OPTIONAL_HEADER に切り込みます。

qiita.com

*1:COFF ファイルも可

*2:kekyoさんの記事にちらっと出てくる「リロケーション」もこれのことです。