OS自作入門 -Advent16-
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は書き込み可能を示している。
今日はここまで。割と既出なことが多くてそんなに詰まることもなかった。次は実装を行なってプログラム・ヘッダに従いメモリ内にロードをするための実装を行なっていくことになりそうだ。
- 前の記事
OS自作入門 -Advent15- 2017.12.15
- 次の記事
英語の勉強「agree」「disagree」-Advent17- 2017.12.17