OS自作入門 -Advent16-

NO IMAGE

Step5 ELFフォーマットの展開 前半

これまででファイルの転送ができるようになった。そこで次は実行形式ファイルを転送しメモリ上に展開することをやってみる。

オブジェクト・ファイル・フォーマット

詳細に触れていなかったがelf形式のような内部がヘッダなどによって特定のフォーマットで記述されているものをオブジェクト・ファイル・フォーマットと呼ぶ。

ELF形式

gccで何も指定しないでコンパイル・リンクを行うとELF形式となる。ELF形式がもつ「セクション」「セグメント」はstep3で説明があったが、ELF形式の内部フォーマットを理解する必要がある。ただしメモリ上にロードするため「セグメント」の情報だけわかれば十分である。

セクション、セグメント

実行液式ファイルは、コンパイルして作成された複数のオブジェクト・ファイルを結合することで生成される。これをリンクと呼ぶ。リンクは機械語コードはテキスト領域に、データはデータ領域に、などと行ったように同じ領域にまとめていく。この領域のことをセクションと呼ぶ。一方でセグメントとは、リンク後に生成され、ロードの際にメモリ展開する単位のことを指す。ローダーが担うのは実行形式ファイルのセグメント情報を見て、そこに書かれている通りに実メモリ上に展開を行う。

ELF形式の構造

セクションはセクション・ヘッダ、セグメントはプログラム・ヘッダと呼ばれるヘッダで管理されている。ELF形式の内部構造は以下のような感じになっている。

------------
ELFヘッダ
------------
プログラムヘッダ
------------
プログラムヘッダテーブル
------------
セグメント1
      ------
      セクションa
      ------
      セクションb
------------
セグメント2
      ------
      セクションc
      ------
      セクションd
------------
.........
------------
セクションヘッダ
------------
セクションヘッダテーブル
------------

いくつかのセクションがセグメントに含まれるような構成になっている。これらは各々独立しているため必ずしも含まれる必要はないのだが、セグメントの目的がローダに対してメモリ上への展開を指示することであり、複数のセクションをひとつにまとめることによって似た役割のセクションを一括で管理できるようになるというメリットがある。

※リンク前のオブジェクト・ファイルはセクションを持っているがセグメントを持っていない。セクションはリンクのための情報であるから当然である。また、実行形式ファイルからはセクションの情報を取り除いても構わない。セグメントはローダーのための情報であるからだ。

では、ELFヘッダ、セクション・ヘッダ・テーブル、プログラム・ヘッダ・テーブルをそれぞれ見ていこう。

ELFヘッダ

step4で作成したkzload.elfを元にreadelfの実行結果を読んでみる。

ELF ヘッダ:
  マジック:  7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
  クラス:                            ELF32
  データ:                            2 の補数、ビッグエンディアン
  バージョン:                        1 (current)
  OS/ABI:                            UNIX - System V
  ABI バージョン:                    0
  型:                                EXEC (実行可能ファイル)
  マシン:                            Renesas H8/300
  バージョン:                        0x1
  エントリポイントアドレス:          0x100
  プログラムの開始ヘッダ:            52 (バイト)
  セクションヘッダ始点:              4864 (バイト)
  フラグ:                            0x810000
  このヘッダのサイズ:                52 (バイト)
  プログラムヘッダサイズ:            32 (バイト)
  プログラムヘッダ数:                4
  セクションヘッダ:                  40 (バイト)
  セクションヘッダサイズ:            11
  セクションヘッダ文字列表索引:      10

マジック: ELFフォーマットのバージョンやOSの種別が載っている。
最初の0x7f 0x45 0x4c 0x46という4byteでファイルがELF形式であることを判断している。

7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00はそれぞれ
7f 45 4c 46: 上述のマジックナンバ
01: 32/64bitの区別
02: エンディアン
01: ELFのバージョン
00: OSの種別
00: OSのバージョン
残りの部分: 予約

あとはだいたい日本語の通り、最後のセクションヘッダ文字列表索引というのは英語だとSection header string table indexとなっていて、セクション名を格納するセクションの番号のことらしい。

セクション・ヘッダ・テーブル

同様にreadelfの実行結果から該当箇所を抜粋すると

セクションヘッダ:
  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .vectors          PROGBITS        00000000 0000b4 000100 00  WA  0   0  4
  [ 2] .text             PROGBITS        00000100 0001b4 0004dc 00  AX  0   0  2
  [ 3] .text.startup     PROGBITS        000005dc 000690 00018e 00  AX  0   0  2
  [ 4] .rodata           PROGBITS        0000076c 000820 0000a5 00   A  0   0  4
  [ 5] .data             PROGBITS        00fffc20 0008c8 000004 00  WA  0   0  4
  [ 6] .bss              NOBITS          00fffc24 0008cc 000014 00  WA  0   0  4
  [ 7] .comment          PROGBITS        00000000 0008cc 000011 01  MS  0   0  1
  [ 8] .symtab           SYMTAB          00000000 0008e0 000750 10      9  84  4
  [ 9] .strtab           STRTAB          00000000 001030 00027b 00      0   0  1
  [10] .shstrtab         STRTAB          00000000 0012ab 000054 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

パッと見て分かりにくそうなのは、Off: セクションファイル中の位置、Lk: 関連するセクションの番号、Inf: セクション依存の情報、ES: セクション内のエントリのサイズくらいか。

  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 2] .text             PROGBITS        00000100 0001b4 0004dc 00  AX  0   0  2
  [ 5] .data             PROGBITS        00fffc20 0008c8 000004 00  WA  0   0  4

この二つに着目して見てみる。

まず最初の2,5はセクションのインデックス、名前は本来だと4byteなので格納しきれないはずだが.shstrtabというセクションにまとめて格納されている。ELFヘッダにあったセクションヘッダ文字列表索引(Section header string table index)が.shstrtabのセクション番号になっている。
タイプはそのセクションの属性であり、タイプはプログラム本体となるセクション、文字列を格納するセクション、リンク用にシンボルが格納されているセクション等に別れる。
Flgも属性の一つであり、書き込み可能や実行可能などを表している。
アドレスはそのセクションが配置されているアドレス。step3で出てきたVAのことを指している。そのセクションが動作するアドレスのこと。.textセクションは0x00000100なのでROM、.dataセクションは0x00fffc20なのでRAM上にある。
セクションの位置(Off)とセクションのサイズ(Size)はELFファイル内でどの部分がそのセクションとなるのかを表している。

プログラム・ヘッダ・テーブル

同様にreadelfの実行結果から該当箇所を抜粋すると

プログラムヘッダ:
  タイプ       オフセット 仮想Addr   物理Addr   FileSiz MemSiz  Flg Align
  LOAD           0x0000b4 0x00000000 0x00000000 0x00100 0x00100 RW  0x1
  LOAD           0x0001b4 0x00000100 0x00000100 0x0066a 0x0066a R E 0x1
  LOAD           0x000820 0x0000076c 0x0000076c 0x000a5 0x000a5 R   0x1
  LOAD           0x0008c8 0x00fffc20 0x00000811 0x00004 0x00018 RW  0x1

 セグメントマッピングへのセクション:
  セグメントセクション...
   00     .vectors 
   01     .text .text.startup 
   02     .rodata 
   03     .data .bss 

ほぼ全て既出な気がする。

プログラムヘッダ:
  タイプ       オフセット 仮想Addr   物理Addr   FileSiz MemSiz  Flg Align
  LOAD           0x0001b4 0x00000100 0x00000100 0x0066a 0x0066a R E 0x1
  LOAD           0x0008c8 0x00fffc20 0x00000811 0x00004 0x00018 RW  0x1

この二つを取り上げて見てみよう。

タイプはLOAD。これはメモリ上にロードされるセグメントであることを示している。他には「動的リンク情報」「プログラム・ヘッダ自身」というのもあるらしい。
オフセットは位置を示していて、二つ目の0x0008c8はファイルの先頭から0x0008c8バイト目からがこのセグメントであることを示している。
仮想アドレス(仮想Addr: VA)と物理アドレス(物理Addr: PA)を見てみると、一つ目の方は等しく0x00000100になっているが、二つ目の方はPAが0x00000811でVAが0x00fffc20という大きな値になっている。これはこのセグメントに.dataセクションと.bssセクションが格納されておりVA≠PAとなっているからである。
ファイルサイズ(FileSiz)とメモリサイズ(MemSiz)はそれぞれファイル上のサイズとメモリ上に展開した時のサイズを示していて、例えばBSS領域はメモリ上に展開されるがファイルサイズは0である。なので等しいあたりにならないことがある。二つ目のセグメントがそれに該当している。
フラグはEは実行可能、Rは読み込み可能、Wは書き込み可能を示している。


今日はここまで。割と既出なことが多くてそんなに詰まることもなかった。次は実装を行なってプログラム・ヘッダに従いメモリ内にロードをするための実装を行なっていくことになりそうだ。