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 で埋める

という事になるのか。