最小限のオブジェクトファイル
自作コンパイラを作成するにあたって、まずは最小限のオブジェクトファイルを作成することから始めました。
文字列定数"Hello, World!\0"を.rdataセクションに生成して、オブジェクトファイルの_main関数から_puts関数を呼び出すという単純なものです。
この記事は内容が不十分であるか間違っている箇所がある可能性があるため実行時には注意してください。
最小限のファイル構成
- COFFファイルヘッダー(0x00~0x13)
- セクションテーブル(0x14~0x63)
- .textセクション(0x64~0x7a)
- .rdataセクション(0x7b~0x88)
- 再配置テーブル(0x89~0x9c)
- シンボルテーブル(0x9d~0xd2)
最終的なバイナリデータ
test.o
00:0000 4C 01 02 00 00 00 00 00-9D 00 00 00 03 00 00 00
00:0010 00 00 00 01 2E 74 65 78-74 00 00 00 00 00 00 00
00:0020 00 00 00 00 17 00 00 00-64 00 00 00 89 00 00 00
00:0030 00 00 00 00 02 00 00 00-20 00 10 60 2E 72 64 61
00:0040 74 61 00 00 00 00 00 00-00 00 00 00 0E 00 00 00
00:0050 7B 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00:0060 40 00 10 40 55 89 E5 83-E4 F0 68 00 00 00 00 E8
00:0070 00 00 00 00 B8 00 00 00-00 C9 C3 48 65 6C 6C 6F
00:0080 2C 20 57 6F 72 6C 64 21-00 07 00 00 00 01 00 00
00:0090 00 06 00 0C 00 00 00 02-00 00 00 14 00 5F 6D 61
00:00A0 69 6E 00 00 00 00 00 00-00 01 00 20 00 02 00 2E
00:00B0 72 64 61 74 61 00 00 00-00 00 00 02 00 00 00 03
00:00C0 00 5F 70 75 74 73 00 00-00 00 00 00 00 00 00 20
00:00D0 00 02 00
$ gcc test.o
$ .\a.exe
Hello, world!
COFFファイルヘッダ(0x00~0x13)
test.o
00:0000 4C 01 02 00 00 00 00 00-9D 00 00 00 03 00 00 00
00:0010 00 00 00 01
winnt.h
typedef struct _IMAGE_FILE_HEADER
{ WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Machine | 0 | 2 | 0x014c | CPUの種類 | IA32なのでIMAGE_FILE_MACHINE_I386 |
NumberOfSections | 2 | 2 | 0x0002 | セクション数 | .textと.rdata |
TimeDateStamp | 4 | 4 | 0x00000000 | タイムスタンプ | 0でもコンパイルには問題ない模様 |
PointerToSymbolTable | 8 | 4 | 0x0000009d | シンボルテーブルのファイルオフセット | |
NumberOfSymbols | 12 | 4 | 0x00000003 | シンボル数 | _main、.rdata、_puts |
SizeOfOptionalHeader | 16 | 2 | 0x0000 | オプションテーブルのサイズ | 今回は不要 |
Characteristics | 18 | 2 | 0x0100 | ファイルの特性 | 32ビットなのでIMAGE_FILE_32BIT_MACHINE |
セクションテーブル(0x14~0x63)
winnt.h
typedef struct _IMAGE_SECTION_HEADER
{ BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union
{ DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
.textのセクションテーブルヘッダ(0x14~0x3b)
test.o
00:0010 00 00 00 01 2E 74 65 78-74 00 00 00 00 00 00 00
00:0020 00 00 00 00 17 00 00 00-64 00 00 00 89 00 00 00
00:0030 00 00 00 00 02 00 00 00-20 00 10 60
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Name | 0 | 8 | ".text" | セクション名を表すAscii文字列 | |
VirtualSize | 8 | 4 | 0x00000000 | オブジェクトファイルの場合は0 | |
VrirtualAddress | 12 | 4 | 0x00000000 | オブジェクトファイルの場合は0 | |
SizeOfRawData | 16 | 4 | 0x00000017 | セクションのサイズ | |
PointerToRawData | 20 | 4 | 0x00000064 | セクションデータのファイル上のオフセット | |
PointerToRelocations | 24 | 4 | 0x00000089 | 再配置テーブルのファイル上のオフセット | |
PointerToLinenumbers | 28 | 4 | 0x00000000 | COFF行番号を使用しないため0 | |
NumberOfRelocations | 32 | 2 | 0x0002 | 再配置テーブルのエントリ数 | |
NumberOfLineNumbers | 34 | 2 | 0x0000 | COFF行番号を使用しないため0 | |
Characteristics | 36 | 4 | 0x60100020 | セクションの特性 | 読み込み可能かつ実行可能IMAGE_SCN_CNT_CODE | IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ |
.rdataのセクションテーブルヘッダ(0x3c~0x63)
00:0030 00 00 00 00 02 00 00 00-20 00 10 60 2E 72 64 61
00:0040 74 61 00 00 00 00 00 00-00 00 00 00 0E 00 00 00
00:0050 7B 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00:0060 40 00 10 40
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Name | 0 | 8 | ".rdata" | セクション名を表すAscii文字列 | |
VirtualSize | 8 | 4 | 0x00000000 | オブジェクトファイルの場合は0 | |
VrirtualAddress | 12 | 4 | 0x00000000 | オブジェクトファイルの場合は0 | |
SizeOfRawData | 16 | 4 | 0x0000000e | セクションのサイズ | |
PointerToRawData | 20 | 4 | 0x0000007b | セクションデータのファイル上のオフセット | |
PointerToRelocations | 24 | 4 | 0x00000000 | 再配置テーブルのファイル上のオフセット | |
PointerToLinenumbers | 28 | 4 | 0x00000000 | COFF行番号を使用しないため0 | |
NumberOfRelocations | 32 | 2 | 0x00000000 | 再配置テーブルのエントリ数 | |
NumberOfLineNumbers | 34 | 2 | 0x00000000 | COFF行番号を使用しないため0 | |
Characteristics | 36 | 4 | 0x40100040 | セクションの特性 | 読み込み可能かつ初期化されるIMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_MEM_READ |
.textのデータ(0x64~0x7a)
test.o
00:0060 40 00 10 40 55 89 E5 83-E4 F0 68 00 00 00 00 E8
00:0070 00 00 00 00 B8 00 00 00-00 C9 C3
$ objdump -d -r test.o
test.o: file format pe-i386
Disassembly of section .text:
00000000 <_main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 68 00 00 00 00 push $0x0
7: dir32 .rdata
b: e8 00 00 00 00 call 10 <_main+0x10>
c: DISP32 _puts
10: b8 00 00 00 00 mov $0x0,%eax
15: c9 leave
16: c3 ret
pushの即値オペランドには再配置が適用されて.rdataの絶対アドレスとなり、callの即値オペランドは_putsへの相対アドレスとなります。
.rdataのデータ(0x7b~0x88)
test.o
00:0070 00 00 00 00 B8 00 00 00-00 C9 C3 48 65 6C 6C 6F
00:0080 2C 20 57 6F 72 6C 64 21-00
これが文字列定数"Hello, World!\0"です。
.rdataの開始アドレス即ち文字列定数の開始アドレスはpushのオペランドに渡されて_putsの引数となります。
.textの再配置テーブル(0x89~0x9c)
winnt.h
typedef struct _IMAGE_RELOCATION
{ _ANONYMOUS_UNION union
{ DWORD VirtualAddress;
DWORD RelocCount;
} DUMMYUNIONNAME;
DWORD SymbolTableIndex;
WORD Type;
} IMAGE_RELOCATION, *PIMAGE_RELOCATION;
1. .rdataの絶対アドレス(0x89~0x92)
test.o
00:0080 2C 20 57 6F 72 6C 64 21-00 07 00 00 00 01 00 00
00:0090 00 06 00
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
VirtualAddress | 0 | 4 | 0x00000007 | セクション上のアドレス | pushの即値オペランド |
SymbolTableIndex | 4 | 4 | 0x00000001 | シンボルテーブル上のインデックス | .rdataのインデックス |
Type | 8 | 2 | 0x0006 | 再配置タイプ | 32ビット絶対アドレスIMAGE_REL_I386_DIR32 |
2. _putsの相対アドレス(0x93~0x9c)
test.o
00:0090 00 06 00 0C 00 00 00 02-00 00 00 14 00
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
VirtualAddress | 0 | 4 | 0x0000000c | セクション上のアドレス | callの即値オペランド |
SymbolTableIndex | 4 | 4 | 0x00000002 | シンボルテーブル上のインデックス | _putsのインデックス |
Type | 8 | 2 | 0x0014 | 再配置タイプ | 32ビット相対アドレスIMAGE_REL_I386_REL32 |
シンボルテーブル(0x9d~0xd2)
winnt.h
typedef struct _IMAGE_SYMBOL
{ union
{ BYTE ShortName[8];
struct
{ DWORD Short;
DWORD Long;
} Name;
PBYTE LongName[2];
} N;
DWORD Value;
SHORT SectionNumber;
WORD Type;
BYTE StorageClass;
BYTE NumberOfAuxSymbols;
} IMAGE_SYMBOL, *PIMAGE_SYMBOL;
_mainのシンボルテーブルエントリ(0x9d~0xae)
test.o
00:0090 00 06 00 0C 00 00 00 02-00 00 00 14 00 5F 6D 61
00:00A0 69 6E 00 00 00 00 00 00-00 01 00 20 00 02 00
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Name | 0 | 8 | "_main" | シンボル名のAscii文字列 | |
Value | 8 | 4 | 0x00000000 | ||
SectionNumber | 12 | 2 | 0x0001 | セクションのインデックス+1 | .textのインデックス(0)+1 |
Type | 14 | 2 | 0x0020 | 関数なら0x20それ以外は0 | |
StorageClass | 16 | 1 | 0x02 | ストレージクラス | IMAGE_SYM_CLASS_EXTERNAL 外部に公開されるためGCCに参照される |
NumberOfAuxSymbols | 17 | 1 | 0x00 | 補助シンボルテーブルエントリの数 | 今回は不要 |
.rdataのシンボルテーブルエントリ(0xaf~0xc0)
test.o
00:00A0 69 6E 00 00 00 00 00 00-00 01 00 20 00 02 00 2E
00:00B0 72 64 61 74 61 00 00 00-00 00 00 02 00 00 00 03
00:00C0 00
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Name | 0 | 8 | ".rdata" | シンボル名のAscii文字列 | |
Value | 8 | 4 | 0x00000000 | ||
SectionNumber | 12 | 2 | 0x0002 | セクションのインデックス+1 | .rdataのインデックス(1)+1 |
Type | 14 | 2 | 0x0000 | 関数なら0x20それ以外は0 | |
StorageClass | 16 | 1 | 0x03 | ストレージクラス | IMAGE_SYM_CLASS_STATIC 外部に公開されずかつValueが0なのでセクション名を表す |
NumberOfAuxSymbols | 17 | 1 | 0x00 | 補助シンボルテーブルエントリの数 | 今回は不要 |
_putsのシンボルテーブルエントリ(0xc1~0xd2)
test.o
00:00C0 00 5F 70 75 74 73 00 00-00 00 00 00 00 00 00 20
00:00D0 00 02 00
フィールド名 | オフセット | サイズ | 値 | 意味 | 補足 |
---|---|---|---|---|---|
Name | 0 | 8 | "_puts" | シンボル名のAscii文字列 | |
Value | 8 | 4 | 0x00000000 | ||
SectionNumber | 12 | 2 | 0x0000 | セクションのインデックス+1 | 外部シンボルなので0となる |
Type | 14 | 2 | 0x0000 | 関数なら0x20それ以外は0 | |
StorageClass | 16 | 1 | 0x02 | ストレージクラス | IMAGE_SYM_CLASS_EXTERNAL |
NumberOfAuxSymbols | 17 | 1 | 0x00 | 補助シンボルテーブルエントリの数 | 今回は不要 |