KOZOS (17)
とりあえず os 側から眺めてみます。
まず気になったのはローダ側から kickoff された時のエントリポイントなのですが、ld.scr に明示してありました。以下、先頭部分です。
OUTPUT_FORMAT("elf32-h8300") OUTPUT_ARCH(h8300h) ENTRY("_start")
ここが struct elf_header 型の entry_point に格納されてナニ、という理解で良いはず。
次
他にもあるかもしれませんが、その時には上に追記する方向で bootload 側を確認。
ちなみに bootload 側の ld.scr にもエントリポイントの指定がありますが、この場合、vectors なソレが優先されるとの事。でもそこにも start なシンボルというかポインタが指定されておりますな。
extern void start(void); /* スタート・アップ */ /* * 割込みベクタの設定. * リンカ・スクリプトの定義により,先頭番地に配置される. */ void (*vectors[])(void) = { start, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
で、この start から main 手続きが呼び出されます。ちなみに ld.scr の MEMORY コマンドなソレを以下に。
MEMORY { romall(rx) : o = 0x000000, l = 0x080000 /* 512KB */ vectors(r) : o = 0x000000, l = 0x000100 /* top of ROM */ rom(rx) : o = 0x000100, l = 0x07ff00 ramall(rwx) : o = 0xffbf20, l = 0x004000 /* 16KB */ buffer(rwx) : o = 0xffdf20, l = 0x001d00 /* 8KB */ data(rwx) : o = 0xfffc20, l = 0x000300 stack(rw) : o = 0xffff00, l = 0x000000 /* end of RAM */ }
シリアル転送してきたソレを保存する領域を buffer として確保してます。
あら?
とりあえず気になったのが以下二点。
- bootload 側から buffer な領域に読みこんだ os なソレをメモリに展開してるんですが、大丈夫なのか、な件
- bootload はそれほどメモリを消費しないので ram 先頭 (0xffbf20) から 0x2000 だと 8KB 程度領域があれば OK ってこと?
- buffer な領域の容量としては 0x1d00 とあるので約 4KB という形?
- てか data と stack で 256+512 程度の領域しか確保してなさげ? や、data で 256+512B で stack が残り、という事かな
- どちらにしても先頭の 8KB は一体何なのか
- ここに os がロードされると見て領域を空けてる、という事?
- テキストの 213p に ELF ヘッダおよびプログラムヘッダもメモリ上にロードされるので 256B 程度の空き領域を先頭に確保、という記述があるがその根拠は何処だかを見つけきれてませんorz
ちなみに buffer あたりのソレについてはテキストに記述がありました。ほぼビンゴな模様。ええとリンクする際の根拠みたいなものが ld.scr として os 側の MEMORY コマンドの記述は以下。
MEMORY { ramall(rwx) : o = 0xffbf20, l = 0x004000 /* 16KB */ ram(rwx) : o = 0xffc020, l = 0x003f00 stack(rw) : o = 0xffff00, l = 0x000000 /* end of RAM */ }
あと、readelf -f な出力が以下 (一部省略)。
Entry point address: 0xffc020 (中略 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00ffc020 000074 0003e4 00 AX 0 0 2 [ 2] .rodata PROGBITS 00ffc404 000458 000036 01 AMS 0 0 1 [ 3] .data PROGBITS 00ffc43c 00048e 00000c 00 WA 0 0 4 [ 4] .bss NOBITS 00ffc448 00049a 000020 00 WA 0 0 1 [ 5] .shstrtab STRTAB 00000000 00049a 000024 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) There are no section groups in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x00ffbfac 0x00ffbfac 0x0048e 0x0048e R E 0x1 LOAD 0x00048e 0x00ffc43c 0x00ffc43c 0x0000c 0x0002c RW 0x1
うーむ。0xffbfac って何だ。と思ったらヘッダも一緒にコピーしてますな。
elf.c の elf_load_program 手続きのセグメント単位のコピーなナニが以下ですが
memcpy((char *)phdr->physical_addr, (char *)header + phdr->offset, phdr->file_size);
header は struct elf_header なポインタで、Program Header なテーブルの先頭要素は Offset が 0x0 になってますのでヘッダ含む text なセグメントをコピる、という事になるのか。
成程納得。
続き
今日はもうこれネタで新たにエントリは投入しない方向で。
そういえば readelf -a な出力によれば、あるいは ld.scr によれば、って言い方が良いのかな。何だっけ rodata なセクションは text セグメント扱いになるんですね。一応リンカスクリプト的には
.text : { _text_start = . ; *(.text) _etext = . ; } > rom .rodata : { _rodata_start = . ; *(.strings) *(.rodata) *(.rodata.*) _erodata = . ; } > rom
な書き方になってるので、ということもあるのかどうなのか。あと、リンカスクリプトの以下な記述
.data : { _data_start = . ; *(.data) _edata = . ; } > data AT> rom .bss : { _bss_start = . ; *(.bss) *(COMMON) _ebss = . ; } > data AT> rom
な部分は自分で面倒見てますね。main.c の init 手続きの該当箇所が以下。
extern int erodata, data_start, edata, bss_start, ebss; /* * データ領域とBSS領域を初期化する.この処理以降でないと, * グローバル変数が初期化されていないので注意. */ memcpy(&data_start, &erodata, (long)&edata - (long)&data_start); memset(&bss_start, 0, (long)&ebss - (long)&bss_start);
ちなみに erodata は rodata なセクションの末端。なんだけどこれは大丈夫なのかな。ROM から持ってくる、という意味では OK なのか。このあたり難しいなぁ。
あ、どこにヒッカカッたかとゆーとリンカスクリプトの
_erodata = . ; } > rom .buffer : { _buffer_start = . ; } > buffer .data : { _data_start = . ; *(.data) _edata = . ; } > data AT> rom
なあたり。buffer な領域は? ってことだったんスが、コピー元は ROM なのであれば、というか erodata のソレが text セグメントの末端という事であれば
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000094 0x00000000 0x00000000 0x00100 0x00100 RW 0x1 LOAD 0x000194 0x00000100 0x00000100 0x0085f 0x0085f R E 0x1 LOAD 0x0009f4 0x00fffc20 0x0000095f 0x00010 0x00024 RW 0x1
の 0x95f というあたりになるはずで、という理解で良いかな。
んで、ここでは data segment の初期化は自分でヤッております、という事になります。そういえば 5th step の所だったと思うのですが、load した後に dump もしたのですが、出力がばりばり捲れて全然分かりませんでしたね。
今考えればヘッダに連続してテキストセグメントがあるよね、みたいな確認だったのかもしれませんが、スクロール速すぎて分からんよ。
で、main 手続きの中の初期処理以降のメインループが以下。
while (1) { puts("kzload> "); /* プロンプト表示 */ gets(buf); /* シリアルからのコマンド受信 */ if (!strcmp(buf, "load")) { /* XMODEMでのファイルのダウンロード */ loadbuf = (char *)(&buffer_start); size = xmodem_recv(loadbuf); wait(); /* 転送アプリが終了し端末アプリに制御が戻るまで待ち合わせる */ if (size < 0) { puts("\nXMODEM receive error!\n"); } else { puts("\nXMODEM receive succeeded.\n"); } } else if (!strcmp(buf, "dump")) { /* メモリの16進ダンプ出力 */ puts("size: "); putxval(size, 0); puts("\n"); dump(loadbuf, size); } else if (!strcmp(buf, "run")) { /* ELF形式ファイルの実行 */ entry_point = elf_load(loadbuf); /* メモリ上に展開(ロード) */ if (!entry_point) { puts("run error!\n"); } else { puts("starting from entry point: "); putxval((unsigned long)entry_point, 0); puts("\n"); f = (void (*)(void))entry_point; f(); /* ここで,ロードしたプログラムに処理を渡す */ /* ここには返ってこない */ } } else { puts("unknown.\n"); } }
6th step では
- load
- dump
- run
という三つの処理しかないのか。run においては load で取得した elf イメージを使って elf_load 手続きを呼び出してメモリに elf なソレを展開します。
elf_load 手続きは
- 引数を elf_header 構造体なポインタとして
- elf_check にそれを渡してチェック
- elf_load_program にそれを渡してセグメント単位にメモリ空間にロード
- そのポインタがさすオブジェクトの entry_point 属性を戻す
という処理が記述されてます。エラーがあれば NULL 返却。で、核心の elf_load_program 手続きのメインループの記述が以下。
for (i = 0; i < header->program_header_num; i++) { /* プログラム・ヘッダを取得 */ phdr = (struct elf_program_header *) ((char *)header + header->program_header_offset + header->program_header_size * i); if (phdr->type != 1) /* ロード可能なセグメントか? */ continue; memcpy((char *)phdr->physical_addr, (char *)header + phdr->offset, phdr->file_size); memset((char *)phdr->physical_addr + phdr->file_size, 0, phdr->memory_size - phdr->file_size); }
program_header_num 属性分ループしつつ
- elf_program_header 構造体のポインタを順に取り出して
- これってもっと簡単に取り出せそうに見えるんだけどなぁ
- エラーチェックして
- physixal_addr がさす位置に header + offset な位置から始まって file_size な大きさ分をコピィして
- memory_size - file_size が正であれば 0 初期化
という処理を行なっている模様。以下に readelf -a の情報を再掲。
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0x00ffbfac 0x00ffbfac 0x0048e 0x0048e R E 0x1 LOAD 0x00048e 0x00ffc43c 0x00ffc43c 0x0000c 0x0002c RW 0x1
上記によれば
- 上側のセグメントは Offset 0x0 から 0xffbfac 番地に 0x48e 分コピィ
- memory_size と file_size は同値なので 0 初期化は行なわない
- 下側のセグメントは Offset 0x48e から 0xffc43c 番地に 0xc 分コピィ
- 0xffc43c+0xc 番地から 0x20 分の領域を 0 で埋める
という事になるのか。