添付CDの開発環境で開発できるようにした

(カーネル/VM Advent Calendar 50日目)

2011/01/25

あなたは 人目のお客様です.

カーネル/VM Advent Calendar50日目 の内容として,COFF→ELFコンバータの作成について説明しようと思う.

まずはコンバータ作成の背景を説明しよう.

KOZOS/H8の開発では従来はクロスコンパイラは自前でビルドするのが前提となって いて,12ステップの本でもそーいうふうに 書いてあるのだけど,クロスコンパイラのビルドってじつは初心者にはけっこう 敷居が高かったりする.実際,12ステップ本でもそこが一番の峠になってしまう ことも多いだろう.

KOZOSの開発ターゲットとして利用しているH8/3069FマイコンボードにはCD-ROMが 添付されていて,実はこのCD-ROMにはビルド済みでインストールするだけのクロス コンパイラが同包されている.なのでcygwinかGNU/Linux環境ならば,実は付属 CD-ROMからクロスコンパイラをインストールするだけで開発環境はできあがる. のだが,以下の理由で現状のKOZOSでは利用できない.

ブートローダーに関しては*.mot形式で転送するので問題なくビルドできるし, KOZOSのブートローダーも実は*.mot形式に対応したコードがあるので,実は付属 CD-ROMで開発できるように対応することはそれほど面倒ではないのだが, 12ステップ本のほうはELF前提で書いてあるし,まあなんとなくいいかと思って いたのだが,COFF→ELFのコンバータが作成できれば付属CD-ROMの環境で作成した モジュールをELFに変換して利用できるなあと思い,作ってみることにした.

ELFのフォーマットはだいたいわかっているので問題なのはCOFFのフォーマット なのだけど,あんまり資料が無いのよね.いろいろ調べたり検索したりしてみたの だけど,ここしか見当たらなかった.

DJGPP COFF Spec

上記資料だけではよくわからんし合っているのかどうかも不明なので, とりあえず付属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に比べて 情報量が少ないため,こういうときはこのパラメータのサイズがゼロになるとか そんなふうな感じで,暗黙の了解みたいに情報を持っているものが多いのだ.

まあそんなわけでいろいろ試してみて結果的にできあがったものは以下にある. 利用方法などもまとめておいた.

添付CD-ROMの開発環境を利用する方法

作りとしてはH8用の実行形式のみ,ビッグエンディアン決めうちになっているのだが, まあ今回の用途としては十分使える.COFF→ELFだけでなくELF→COFFへの変換も 可能で,COFF→ELF→COFF→ELFのような変換を何度も繰り返して同じものに戻ることを 確認してある.パパッと作ったコンバータなのでちょっとなんだかなあみたいな 箇所も残っているのだけど,まあ興味のあるかたは見てみてくださいな.

雑感として,COFFはやはりELFに比べると情報量が少ないというか,セグメントと セクションの区別のようなものも無く,不便に感じることもあるように思う (いちおうVAとPAを区別することはできるが).ファイル種別やエンディアン情報や プラットホーム情報を格納する領域もないし,セクション名の領域は8文字までだし (拡張して格納することはできるのだろうか?不明),やはりいろんな面で,ELFが最も 万能的なフォーマットだなあ,と思う.

あとCOFFは情報が少なすぎ.私の調べかたが悪いのかしら?


メールは kozos(アットマーク)kozos.jp まで