FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

テキストを読んで、xv6のブートプロセスを理解する(4: MIPS用にバイナリの配置を変更)

※ この記事はまだ勉強中のため、いろいろ間違いがあるかもしれません。

さて、前回まででページの初期化が出来るようになったのだが、途中で落ちてしまう。 ユーザプロセス用の初期化ルーチンで落ちているようだった。具体的には、以下のvm.c内のinituvm()内でパニックを起こしてしまう。何だろう?

void
inituvm(pde_t *pgdir, char asid, char *init, uint sz)
{
  char *mem;

  if(sz >= PGSIZE)
    panic("inituvm: more than a page");
...

inituvm()はproc.cから呼ばれている。

//PAGEBREAK: 32
// Set up first user process.
void
userinit(void)
{
  struct proc *p;
  extern char _binary_initcode_start[], _binary_initcode_size[];

  p = allocproc();
  initproc = p;
  if((p->pgdir = setupkvm()) == 0)
    panic("userinit: out of memory?");
  inituvm(p->pgdir, p->asid, _binary_initcode_start, (int)_binary_initcode_size);
...

}

binary_initcode_start, binary_initcode_size とは何だろう? 自分で翻訳していた。

大まかに見れば、setupkvmとuserinitは図1-2に示すようなアドレス空間を生成する。 
最初のプロセスのメモリの初期内容は、initcode.Sからコピーされる。これはカーネル
のビルドプロセスの一部であり、リンカがこのバイナリをカーネルに埋め込み、2つの
特別なシンボルを定義する。これが_binary_initcode_startと_binary_initcode_size
である。これらはバイナリの場所とサイズを示している。userinitはinituvmを呼び出
すことによりこのバイナリを新しいプロセスのメモリ空間にコピーする。inituvmは物
理メモリのページを割り当て、そのメモリ空間を仮想アドレスの0番にマッピングする
。そしてバイナリをページの領域にコピーするのである(1903行目)。 

github.com

そして、このbinary_initcode_startとbinary_initcode_sizeはどうやって定義しているんだろう。調べていると、以下のサイトを発見した。

stackoverflow.com

ああ、binaryを指定することにより、start/stop/sizeの定数が生成されるのか。実際に生成しているのは、Makefileだ。

initcode: initcode.S
        $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S
        $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o
        $(OBJCOPY) -S -O binary initcode.out initcode
...

実際にmakeしてみると、initcodeのサイズがやたらと大きいし、_binary_initcode_sizeも大きい!

00400018 g       *ABS*  00000000 _binary_initcode_size

あれま!調査していると、initcodeをリンクするときに、いろんなデバッグ情報が付いていて、バイナリサイズが大きくなっていることが分かった。

mipsel-linux-elf-objdump -D -x initcode.out
...
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000038  00000000  00000000  00000094  2**2
                  CONTENTS, ALLOC, LOAD, CODE
  1 .MIPS.abiflags 00000018  00400000  00400000  000000d0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_SIZE
  2 .reginfo      00000018  00400018  00400018  000000e8  2**2
                  CONTENTS, READONLY, LINK_ONCE_SAME_SIZE
  3 .debug_aranges 00000020  00000000  00000000  00000100  2**3
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_info   00000048  00000000  00000000  00000120  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_abbrev 00000014  00000000  00000000  00000168  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_line   00000040  00000000  00000000  0000017c  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .gnu.attributes 00000010  00000000  00000000  000001bc  2**0
...

0x00400000とか使ってるし!そりゃ、バイナリサイズも大きくなるよね。 仕方が無いので、ストリップしよう。このセクションは、おそらく使わない。

initcode: initcode.S
    $(CC) $(CFLAGS) -nostdinc -I. -c initcode.S
    $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o initcode.out initcode.o
    $(STRIP) --remove-section=.MIPS.abiflags --remove-section=.reginfo initcode.out
    $(OBJCOPY) -S -O binary initcode.out initcode
    $(OBJDUMP) -S initcode.o > initcode.asm

MIPS.abiflagsとreginfoセクションを切り取った。これで、サイズが小さくなる。

という訳で、これにて実行すると、cpu0がスタートするところまで行った。やったー!

$ make iss_debug_run
swimmer_mips --imgfile xv6.img --max 80000000 --mem_flat=false --binfile kernel --only_info_load --reg_abi soft --script init.lua \
                         --trace_hier --trace_out flow.log \
                         --debug --out debug.log --debug_gvar --debug_func
<Error: CSR Address 018 is invalid.>
<Error: CSR Address 028 is invalid.>

xv6...
cpu0: starting
<Error: CSR Address 018 is invalid.>

とは言え、完全デバッグモードだとここまで到達するのに10分くらいはかかる。高速デバッグモードだと2分くらいだが、もうちょっとデバッガビリティを上げる必要があるな。