elf_exec関数は、引数として渡されるKernelのELF Headerを解析し、Kernelのセグメントやセッションを読込みメモリー上に展開します。
- プログラム ヘッダ テーブルを参照して、TEXTまたはDATAセグメントをメモリ上に展開する。
- セクション ヘッダ テーブルを参照して、SYMBOLまたはSTRINGテーブルをメモリ上に展開する。
- ELFヘッダをメモリ上に展開する。
elf_exec関数は、引数として渡されるKernelのELF Headerを解析し、Kernelのセグメントやセッションを読込みメモリー上に展開します。
ELF Headerはboot関数から渡され、OpenBSD 3.6 GENERIC Kernel(i386)のELF Headerの場合、次の値になります。
構造体名 | メンバー名 | 詳細 | ||
---|---|---|---|---|
elf | e_ident[EI_NIDENT] | index | 値 | 内容 |
EI_MAG0 | '\177' | マジックNO.0byte目 | ||
EI_MAG1 | 'E' | マジックNO.1byte目 | ||
EI_MAG2 | 'L' | マジックNO.2byte目 | ||
EI_MAG3 | 'F' | マジックNO.3byte目 | ||
EI_CLASS | ELFCLASS32 | バイナリファイルは32bit アーキテクチャ ファイル空間と仮想アドレス空間が 4 ギガバイトまでのマシン | ||
EI_DATA | ELFDATA2LSB| | プロセッサのバイトオーダーはリトルエンディアン | ||
EI_VERSION | EV_CURRENT | ELFのHeader Versionはカレントバージョン | ||
EI_OSABI | 0 (未使用?) | オペレーティングシステムとABIを識別 | ||
EI_ABIVERSION | 0 (未使用?) | ABIのバージョンを識別 | ||
EI_PAD | 0 | パディングの始め | ||
10-15 | 0 | 未定義(0 パディング) | ||
e_type | ET_EXEC | オブジェクトファイルタイプが実行可能ファイル | ||
e_machine | EM_386 | アーキテクチャはIntel 80386 | ||
e_version | EV_CURRENT | ELFのFile Versionはカレントバージョン | ||
e_entry | 0xD0100120 | プロセスを開始する仮想アドレス | ||
e_phoff | 0x34 | プログラムヘッダテーブルが存在する場所のファイルオフセット値(byte) | ||
e_shoff | 0x4B0D14 | セクションヘッダテーブルが存在する場所のファイルオフセット値(byte) | ||
e_flags | 0x00 | ファイルに関連するプロセッサに固有なフラグ(未定義) | ||
e_ehsize | 0x34 | ELF ヘッダのサイズ(byte) | ||
e_phentsize | 0x20 | プログラムヘッダテーブルにあるエントリ 1個のサイズ | ||
e_phnum | 0x01 | プログラムヘッダテーブル中のエントリの個数 | ||
e_shentsize | 0x28 | セクションヘッダテーブルにあるエントリ 1個のサイズ | ||
e_shnum | 0x08 | セクションヘッダテーブル中のエントリの個数 | ||
e_shstrndx | 0x05 | セクションヘッダテーブルの、 セクション名文字列テーブルに結びつけられたエントリへのインデックス |
このデータを見ると、Kernel Fileの構造が次のように構成されているのがわかります。
不明なエリアが存在しますが、このエリアの構成情報はProgram HeaderとSection Headerに記述されています。Kernelをロードするためにはこれらのヘッダを読込み解析する必要があります。
Program Headerは、プログラム実行にシステムが必要とするセグメントなどの情報が定義されています。この関数ではProgram Headerの情報をもとに実行コードの読込みを行ないます。
Program Header Lengthを求めるには、ELF Headerにあるe_phentsize(Program Header Size) と e_phnum(Program Headerの個数)の積を求めればよい。求めたサイズ分読込むためのメモリを確保する。
読出し位置をProgram Headerが存在するe_phoffに移動し、確保したメモリにProgram Headerを読込む。読込んだ値は次のようになる
変数 | 値 | 詳細 |
---|---|---|
p_type | PT_LOAD | この配列要素は、ロード可能なセグメントであること示す。 |
p_offset | 0x120 | このセグメントが存在するファイルのオフセット。 |
p_vaddr | 0xD0100120 | このセグメントの仮想アドレス。 |
p_paddr | 0xD0100120 | 物理アドレッシング上の物理アドレス。 |
p_filesz | 0x4B0BBC | セグメントのファイルイメージのバイト数をを示す。 |
p_memsz | 0x5829B0 | セグメントのメモリイメージのバイト数を示す。 |
p_flags | 0x07 (PF_X | PF_W | PF_R) | セグメントの属性を表すフラグ。(実行・書込・読取可) |
p_align | 0x20 | alignment値 |
Program Headerを読込んだ事によりKernel Fileの不明エリアの構造がわかる。
読込み対象のセグメントが、ロード可能(p_type == PT_LOAD)で実行・書込・読取可(p_flags & (PF_W|PF_R|PF_X))であるかチェック該当しないセグメントはスキップする。
現在のセグメントが、引数で渡されたLoad Flagの要求とマッチするか調べ、マッチするならセグメントの読込みを行なう。
最初に、ロードの進捗状況(p_fileszの値)を画面に表示する。
Using drive 0 , partition 4 . Loading........ probing:pc0 com1 com2 apm pci mem[639K 253M a20=on] disk:fd0 hd0+ >>OpenBSD/i386 BOOT 2.06 boot> booting /dev/rhd0a:/bsd: 4918204
次に、読出し位置をp_offsetに移動し、p_filesz byteをMemory上のp_vaddr番地に読込む。
読込みに使われる、READはマクロになっていて(ヘッダを読込む関数はread関数であることに注意)
次のように定義されている
#define READ(f, b, c) read((f), (void *)LOADADDR(b), (c))
このマクロでは、読出し位置がLOADADDRマクロにより変更される。
LOADADDRマクロは、下記のように定義されていて仮想アドレスからリニアアドレスへの変換を行なう。
#define LOADADDR(a) ((((u_long)(a)) + offset)&0xfffffff)
phdr[i].p_vaddr=0xd0100120でoffset=0(DEFAULT_KERNEL_ADDRESS)の場合、読出し先アドレスは次のように変換される。
((((u_long)(0xd0100120)) + 0)&0xfffffff) = 0x100120
何故このような変換を行なっているかと言うと、現段階では仮想メモリを制御する機能が働いていない。このため、データを一度リニアアドレス上に置く必要がある。この後、仮想メモリを設定する所で、読出した先のリニアアドレスを仮想アドレスの番地(この場合0xd0100120)に再マッピングを行なう。
リニアアドレス上の読込んだデータは次のようになる。
読込んだセグメントの仮想メモリ上の最上位(maxp)・最下位アドレス(minp)のアドレスを求める。
メモリ上に読込んだセグメントデータの中にはbssのエリアは含まれていない。何故ならば、bssは初期値を持たない変数の領域なのでファイル中に値として持っていても意味がない(というかdiskの無駄)だからだ。ではどのように作成するかというと、Program Headerは次の変数を持っている。
p_filesz | 0x4B0BBC | セグメントのファイルイメージのバイト数をを示す。 |
---|---|---|
p_memsz | 0x5829B0 | セグメントのメモリイメージのバイト数を示す。 |
これらの、メモリ上のサイズ(p_memsz)とファイルのサイズ(p_filesz)の差分がbss領域の大きさとなる。
最初に、クリアする前に進捗としてbssのサイズを画面に表示
Using drive 0 , partition 4 . Loading........ probing:pc0 com1 com2 apm pci mem[639K 253M a20=on] disk:fd0 hd0+ >>OpenBSD/i386 BOOT 2.06 boot> booting /dev/rhd0a:/bsd: 4918204+859636
その後、bss領域をzeroクリアする。
zeroクリアする際に使われる、BZEROもマクロになっており、次のように定義されています。
#define BZERO(d, c) memset((void *)LOADADDR(d), 0, (c))
READマクロと同じように、仮想アドレスからリニアアドレスへの変換を行なっている。
最上位アドレス(maxp)にbssのサイズを加算
最上位アドレス(maxp)をsizeof(long)の倍数アドレスに切り上げる。
次の条件を満たしていれば、ELF Headerの領域を確保してmaxpを更新する。
if((flag=="ELF Headerを読込") && (flag=="ELF Headerをカウント")) maxp += sizeof(ELF Header)
Section Headerは、プログラムコードや読取り専用データ、読書き可能データ、再配置エントリー、シンボル等の情報が種類ごとに格納されている。ここではSection Headerをファイルから読込み、さらにSection Headerが示すSymbol Tableの内容も読込む
if( (flagがシンボルを読込) || (flagがシンボル sizeをカウント)){ Disk 読取り位置をelf->e_shoffに移動 Section Header Lengthを計算 ( elf->e_shnum * sizeof(Elf_Shdr) ) Section Header Read for(Section Hederの数だけループ){ if(現在のSection Headerがシンボルテーブルか文字列テーブル ){ Disk 読取り位置をshp[i].sh_offsetに移動 Table Read } } }
読取り位置をe_shoffに移動させ、Section Headerサイズの領域をアロケートしてから、アロケート領域に読込みを行う。読込んだ値は下記のようになる。
構造体名 | 変数 | shdr[0] | shdr[1] | shdr[2] | shdr[3] | shdr[4] | shdr[5] | shdr[6] | shdr[7] |
---|---|---|---|---|---|---|---|---|---|
shdr | sh_name | 0x0 | 0x1B | 0x21 | 0x29 | 0x2F | 0x11 | 0x1 | 0x9 |
sh_type | SHT_NULL | SHT_PROGBITS | SHT_PROGBITS | SHT_PROGBITS | SHT_NOBITS | SHT_STRTAB | SHT_SYMTAB | SHT_STRTAB | |
sh_flags | 0x0 | 0x7 | 0x2 | 0x3 | 0x3 | 0x0 | 0x0 | 0x0 | |
sh_addr | 0x0 | 0xD0100120 | 0xD048FCE0 | 0xD0592420 | 0xD05B0CE0 | 0x0 | 0x0 | 0x0 | |
sh_offset | 0x0 | 0x120 | 0x38FCE0 | 0x492420 | 0x4B0CE0 | 0x4B0CE0 | 0x4B0E54 | 0x4E92D4 | |
sh_size | 0x0 | 0x38FBA8 | 0x10273D | 0x1E8BC | 0xD1DF0 | 0x34 | 0x38480 | 0x33123 | |
sh_link | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x7 | 0x0 | |
sh_info | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x8 | 0x0 | |
sh_addralign | 0x0 | 0x10 | 0x20 | 0x20 | 0x20 | 0x1 | 0x4 | 0x1 | |
sh_entsize | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x10 | 0x0 |
Section Headerを読込んだ事によりKernel FileのSection構造がわかる。
maxpにSection Headerの値(丸めたもの)を加算し、off(ELF HeaderとSection Headerの合計サイズを丸めたもの)を求める
Section Headerを検索してSymbol table(sh_type = SHT_SYMTAB)が存在するかチェック。見つかった場合は、変数havesymsに1をセット。
Section Headerを検索して、Symbol table(sh_type = SHT_SYMTAB)とString table(sh_type = SHT_STRTAB)が見つかったならば、メモリ上に展開する。読込んだ後に変数maxpと変数offを更新する。またSection Headerのsh_offsetをメモリ上のアドレスに更新する。
読込んだtableのサイズを画面に表示
Using drive 0 , partition 4 . Loading........ probing:pc0 com1 com2 apm pci mem[639K 253M a20=on] disk:fd0 hd0+ >>OpenBSD/i386 BOOT 2.06 boot> booting /dev/rhd0a:/bsd: 4918204+859636 [52+230528+209187]
アロケートしたSection Headerをshppの示すエリアにコピーしメモリを解放する。
ELF Headerのポインタelfpが示すエリアにコピーする。
ELF Headerをコピーする前に、Headerを変更する。Program Headerは存在しないのでこれらの情報は削除し、Section Headerへのポインタを変更する。
e_phoff = 0; e_shoff = sizeof(Elf_Ehdr); e_phentsize = 0; e_phnum = 0;
変更後のメモリ上のヘッダ情報は下記のような関係になる。
marks配列に以下の値をセットする。この配列は上位の関数で使用される。
marks[MARK_START] = LOADADDR(minp); /* リニアアドレス上のKernel先頭アドレス */ marks[MARK_ENTRY] = LOADADDR(elf->e_entry); /* リニアアドレス上のプロセスを開始するアドレス */ marks[MARK_NSYM] = 1; /* XXX: Kernel needs >= 0 */ marks[MARK_SYM] = LOADADDR(elfp); /* リニアアドレス上ELF Headerのアドレス */ marks[MARK_END] = LOADADDR(maxp); /* リニアアドレス上のKernelの最後尾アドレス */
loadfile関数に戻る。