カーネル/VM Advent Calendar50日目 の内容として,COFF→ELFコンバータの作成について説明しようと思う.
まずはコンバータ作成の背景を説明しよう.
KOZOS/H8の開発では従来はクロスコンパイラは自前でビルドするのが前提となって いて,12ステップの本でもそーいうふうに 書いてあるのだけど,クロスコンパイラのビルドってじつは初心者にはけっこう 敷居が高かったりする.実際,12ステップ本でもそこが一番の峠になってしまう ことも多いだろう.
KOZOSの開発ターゲットとして利用しているH8/3069FマイコンボードにはCD-ROMが 添付されていて,実はこのCD-ROMにはビルド済みでインストールするだけのクロス コンパイラが同包されている.なのでcygwinかGNU/Linux環境ならば,実は付属 CD-ROMからクロスコンパイラをインストールするだけで開発環境はできあがる. のだが,以下の理由で現状のKOZOSでは利用できない.
ELFのフォーマットはだいたいわかっているので問題なのはCOFFのフォーマット なのだけど,あんまり資料が無いのよね.いろいろ調べたり検索したりしてみたの だけど,ここしか見当たらなかった.
上記資料だけではよくわからんし合っているのかどうかも不明なので, とりあえず付属CD-ROMのクロスコンパイラでKOZOSのCOFFの実行モジュールを 作成してみて,ダンプを見てみることにする.やりかたは簡単で, 付属CD-ROMからクロスコンパイラをインストールした後, KOZOSの書籍版のソースをここから持ってきて 解凍して,12ステップ目のOSのMakefileを
-PREFIX = /usr/local -ARCH = h8300-elf +PREFIX = /usr +ARCH = h8300-hmsのように修正するだけ.
で,makeしてみて生成された実行形式をダンプしたのが以下.
00000000 83 01 00 0a 00 00 00 00 00 00 1a 78 00 00 01 49 |...........x...I| 00000010 00 1c 02 07 00 00 00 00 00 00 17 e4 00 00 00 2c |...............,| 00000020 00 00 02 28 00 ff c0 20 00 ff c0 20 00 ff d8 ac |...(... ... ....| 00000030 2e 73 6f 66 74 76 65 63 00 ff bf 20 00 ff bf 20 |.softvec... ... | 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 00 00 00 00 00 00 2e 74 65 78 74 00 00 00 |.........text...| 00000060 00 ff c0 20 00 ff c0 20 00 00 17 e4 00 00 01 c0 |... ... ........| 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 |............... | 00000080 2e 72 6f 64 61 74 61 00 00 ff d8 04 00 ff d8 04 |.rodata.........| 00000090 00 00 00 a8 00 00 19 a4 00 00 00 00 00 00 00 00 |................| 000000a0 00 00 00 00 00 00 00 20 2e 64 61 74 61 00 00 00 |....... .data...| 000000b0 00 ff d8 ac 00 ff d8 ac 00 00 00 2c 00 00 1a 4c |...........,...L| 000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 |...............@| 000000d0 2e 62 73 73 00 00 00 00 00 ff d8 d8 00 ff d8 d8 |.bss............| 000000e0 00 00 02 28 00 00 00 00 00 00 00 00 00 00 00 00 |...(............| 000000f0 00 00 00 00 00 00 00 80 2e 66 72 65 65 61 72 65 |.........freeare| 00000100 00 ff db 00 00 ff db 00 00 00 00 00 00 00 00 00 |................| 00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000120 2e 75 73 65 72 73 74 61 00 ff f4 00 00 ff f4 00 |.usersta........| 00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000140 00 00 00 00 00 00 00 00 2e 62 6f 6f 74 73 74 61 |.........bootsta| 00000150 00 ff ff 00 00 ff ff 00 00 00 00 00 00 00 00 00 |................| 00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000170 2e 69 6e 74 72 73 74 61 00 ff ff 00 00 ff ff 00 |.intrsta........| 00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000190 00 00 00 00 00 00 00 00 2e 76 65 63 74 6f 72 73 |.........vectors| 000001a0 00 ff bf 20 00 ff bf 20 00 00 00 00 00 00 00 00 |... ... ........| 000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 20 |............... |見たところ,明らかにELFではない.付属CD-ROMのクロスコンパイラが使われる ことで,COFF形式のモジュールが生成されているようだ.
パッと見,セクション名が先頭のドットも含めて8文字に切られて,ところどころに 入っている.どうもセクション情報がファイル先頭付近に固まっているようだ.
ここで先ほどのCOFFの情報サイト を見てみよう.どうもファイル先頭にはファイルヘッダが付加されるようだ. 私流に書くと,ファイルヘッダは以下のような感じだ.
struct coff_header { short magic; short section_num; long time; long symbol_table; long symbol_num; short optional_header_size; short flags; };これを実際のダンプデータに当てはめてみよう. 上記のファイルヘッダのサイズは20バイトなので,ダンプデータの先頭20バイトを 見てみることにする.
00000000 83 01 00 0a 00 00 00 00 00 00 1a 78 00 00 01 49 |...........x...I| 00000010 00 1c 02 07 ...マジックナンバはまあいいとして,まずはセクション数 (struct coff_header の section_num メンバ)だ.ダンプでは「00 0a」となっている. ビッグエンディアンで読むならば,10だ.実際にダンプデータを見ると, 右側のアスキー文字のところにセクション名が出ていて, .softvec, .text, .rodata, .data, .bss, .freeare, .usersta, .bootsta, .intrsta, .vectors の10個のセクション名が 出ている.うん,あっていそうだ.
ファイルヘッダの他のメンバに関しては,シンボルテーブル関連とかなので よくわからんのでまあいいとする.
次に気になるのは,ファイルヘッダの optional_header_size というメンバだ. COFFのサイトの説明を見ると,ファイルヘッダの後にはオプションヘッダがあって, さらにその後にセクションヘッダが続くらしい.この手のオプションヘッダは おそらくオプション扱いで,サイズは可変なのだろう.で,ファイルヘッダに そのサイズが格納されていて,セクションヘッダを参照したいときには そのサイズをスキップして参照する,という仕組みになっているのだと思われる.
ここでファイルのダンプからオプションヘッダのサイズを見ると,「00 1c」と なっている.つまり28バイトだ.ダンプの先頭20バイトはファイルヘッダで, さらにオプションヘッダを28バイトとすると,先頭48バイト以降がセクションヘッダ ということになる.ダンプを見てみよう.
00000000 83 01 00 0a 00 00 00 00 00 00 1a 78 00 00 01 49 |...........x...I| 00000010 00 1c 02 07 00 00 00 00 00 00 17 e4 00 00 00 2c |...............,| 00000020 00 00 02 28 00 ff c0 20 00 ff c0 20 00 ff d8 ac |...(... ... ....| 00000030 2e 73 6f 66 74 76 65 63 00 ff bf 20 00 ff bf 20 |.softvec... ... | 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|00000030 以降がセクションヘッダだ.右側のアスキー文字を見ると,.softvecの 文字列が見える.ちゃんとセクション情報が開始されているようだ.なんか問題 なさそう.
ということは21バイト目〜48バイト目がオプションヘッダということになる. COFFの説明サイトでは,オプションヘッダは以下のような感じで説明されている. 以下は私流に書いたもの.
struct coff_optional_header { short magic; short version; long text_size; /* .textのサイズ */ long data_size; /* .dataのサイズ */ long bss_size; /* .bssのサイズ */ long entry_point; long text_offset; /* .textのVA */ long data_offset; /* .dataのVA */ };ダンプデータと比較して見てみよう.
00000010 ... 00 00 00 00 00 00 17 e4 00 00 00 2c |...............,| 00000020 00 00 02 28 00 ff c0 20 00 ff c0 20 00 ff d8 ac |...(... ... ....|マジックナンバはいいとして,まずは .text のサイズだ.上記ダンプでは, オプションヘッダの text_size にあたる値は「00 00 17 e4」となっている. ビッグエンディアンで読むと,0x17e4だ.うーん,これが妥当な値なのかどうか いまいちよくわからん(COFF用のobjdumpで解析は可能なので,その結果と照らし 合わせて確認はできる.でも今は面倒なのでやらない)..dataのサイズは0x2c, .bssのサイズは0x228となっている. これはまあこれくらいの値のような気がするので,まあ合っているような気がする.
次に entry_point だが,ダンプデータでは 0xffc020 になっている.これは KOZOSのリンカスクリプトで指定したRAMの先頭アドレスに一致しているので, 合っているようだ.続く text_offset も 0xffc020 となっており,.textの先頭 アドレスを指しているようだ.ということは data_offset は.dataの先頭アドレスを 指しているのだろう.
ということでファイルヘッダとオプションヘッダは解析できた. 次はセクションヘッダだ.
セクションヘッダはCOFFの説明サイトを見ると,以下のような感じで説明されている. 以下は私流に書いたもの.
struct coff_section_header { char name[8]; long physical_addr; long virtual_addr; long size; long offset; long relocation; long line_number; short relocation_num; short line_number_num; long flags; };.textに相当するセクションヘッダ部分は以下だ.
00000050 ... 2e 74 65 78 74 00 00 00 |.........text...| 00000060 00 ff c0 20 00 ff c0 20 00 00 17 e4 00 00 01 c0 |... ... ........| 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 |............... |セクションヘッダのメンバを見ると,まず先頭に name[] として8文字のセクション名 格納域がある.ここには .text という文字列が格納されている.
次に physical_addr と virtual_addr というメンバがある. ダンプデータではこれらはどちらも 0xffc020 になっていて, 先ほどのオプションヘッダの text_offset の値と一致している. これは問題なさそう.
さらに size なのだけど,0x17e4という値になっている.オプションヘッダの text_sizeも 0x17e4 で同じ値になっているから,やはりこれらは .text のサイズを 表しているようだ.
さらにさらに offset というメンバだが,これはおそらくファイル中でのその セクションの本体の位置を指しているのだと思われる.ダンプデータでは, 値は 0x1c0 になっている.実際のダンプデータで,0x1c0 付近を見てみよう.
00000190 00 00 00 00 00 00 00 00 2e 76 65 63 74 6f 72 73 |.........vectors| 000001a0 00 ff bf 20 00 ff bf 20 00 00 00 00 00 00 00 00 |... ... ........| 000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 20 |............... | 000001c0 7a 07 00 ff ff 00 5e ff c0 b2 40 fe 01 00 69 07 |z.....^...@...i.| 000001d0 01 00 6d 70 01 00 6d 71 01 00 6d 72 01 00 6d 73 |..mp..mq..mr..ms| 000001e0 01 00 6d 74 01 00 6d 75 01 00 6d 76 56 70 01 00 |..mt..mu..mvVp..| 000001f0 6d f6 0f f6 01 00 6d f4 01 00 6d f5 7a 01 00 ff |m.....m...m.z...|ちょうど最後のセクションの「.vector」のセクションヘッダが終わったところだ. 000001c0 の位置からは「7a 07 00 ff ...」という値が格納されている. これは .text の先頭なので,機械語コードのはずだ.たぶんKOZOSのスタートアップ 付近のコードだと思われる.逆アセンブルした値と照らし合わせることで確認は 可能なのだが面倒なのでここではやらない.
ということで,COFFの解析はだいたいできた.とはいっても情報サイトのとおり だったので,あとは値の正当性とか細かい意味とかを確認するだけだったのだが.
ここまでくればCOFF→ELFコンバータは簡単にできる...と言いたいところだが, じつは実際にはちょっといろいろ指向錯誤が必要だったりする. というのはCOFF形式はセクションとセグメントの区別が無く,ELFに比べて 情報量が少ないため,こういうときはこのパラメータのサイズがゼロになるとか そんなふうな感じで,暗黙の了解みたいに情報を持っているものが多いのだ.
まあそんなわけでいろいろ試してみて結果的にできあがったものは以下にある. 利用方法などもまとめておいた.
作りとしてはH8用の実行形式のみ,ビッグエンディアン決めうちになっているのだが, まあ今回の用途としては十分使える.COFF→ELFだけでなくELF→COFFへの変換も 可能で,COFF→ELF→COFF→ELFのような変換を何度も繰り返して同じものに戻ることを 確認してある.パパッと作ったコンバータなのでちょっとなんだかなあみたいな 箇所も残っているのだけど,まあ興味のあるかたは見てみてくださいな.
雑感として,COFFはやはりELFに比べると情報量が少ないというか,セグメントと セクションの区別のようなものも無く,不便に感じることもあるように思う (いちおうVAとPAを区別することはできるが).ファイル種別やエンディアン情報や プラットホーム情報を格納する領域もないし,セクション名の領域は8文字までだし (拡張して格納することはできるのだろうか?不明),やはりいろんな面で,ELFが最も 万能的なフォーマットだなあ,と思う.
あとCOFFは情報が少なすぎ.私の調べかたが悪いのかしら?