xv6を移植するときに書き換えるルーチンについて調査(5) の続き。
コンパイルはできるようになったのだが、imgファイルを確認すると、まだうまくいっていないところがある。 RISC-Vはリセットベクタが0x0200となっているのだが、imgをダンプするとそこにはよくわからない値が入っていた。
$ hexdump xv6.img | head -n 20 0000000 0000 0000 0000 0000 0000 0000 0000 0000 * 0000200 457f 464c 0101 0001 0000 0000 0000 0000 0000210 0002 00f3 0001 0000 0000 8010 0034 0000 0000220 3448 0003 0000 0000 0034 0020 0001 0028 0000230 0014 0011 0001 0000 1000 0000 0000 8010 0000240 0000 0010 d0f8 0000 6bac 0001 0007 0000 0000250 1000 0000 0000 0000 0000 0000 0000 0000 0000260 0000 0000 0000 0000 0000 0000 0000 0000 * 0001200 f073 3000 e117 0000 0113 18c1 5297 0000 0001210 8293 e882 8067 0002 0013 0000 0113 fe01 0001220 2e23 0011 2c23 0081 0413 0201 a7b7 8010
ちなみに、オリジナルのx86版のxv6でも同様の値が挿入されていた。どうやら、オブジェクトファイルをそのままimgに変換するとこれが挿入されるらしい。x86ではシステムがリセットされると0番地からブートするので特に問題ないのだが、RISC-Vの場合はちょうど0x200番地に邪魔者が入っており、ブートできない、というわけだ。
- x86版xv6のimgファイルダンプ結果
$ hexdump xv6.img | head -n 50 ... 0000140 e800 ffa3 ffff c483 810c 003d 0100 7f00 0000150 4c45 7546 a150 001c 0001 988d 0000 0001 0000160 b70f 2c35 0100 c100 05e6 de01 f339 2f73 0000170 7b8b ff0c 0473 73ff 5710 6ae8 ffff 8bff 0000180 144b 438b 8310 0cc4 c139 0c76 c701 c129 0000190 00b8 0000 fc00 aaf3 c383 3920 77de ffd1 00001a0 1815 0100 8d00 f465 5e5b 5d5f 00c3 0000 00001b0 0000 0000 0000 0000 0000 0000 0000 0000 * 00001f0 0000 0000 0000 0000 0000 0000 0000 aa55 0000200 457f 464c 0101 0001 0000 0000 0000 0000 0000210 0002 0003 0001 0000 000c 0010 0034 0000 0000220 5cc0 0002 0000 0000 0034 0020 0002 0028 0000230 0011 000e 0001 0000 1000 0000 0000 8010 0000240 0000 0010 b5b6 0000 525c 0001 0007 0000
よくよく見てみると、そういえば面倒くさがってbootasm.Sを省略していたのだった。xv6ではimgファイルを作成するのに以下のコマンドを利用している。
xv6.img: bootblock kernel fs.img dd if=/dev/zero of=xv6.img count=10000 dd if=bootblock of=xv6.img conv=notrunc dd if=kernel of=xv6.img seek=1 conv=notrunc
上から順に、
- 10000バイト分0で埋めた、xv6.imgのひな型を作成する。
- bootblock オブジェクトファイルを先頭から挿入する
- kernelオブジェクトファイルを1ブロック分シークした場所から挿入する
となっている。bootblockは、bootasm.Sとbootmain.cから構成されているので、これを作成しないと最初のブートができないというわけだ。
まずはbootasm.Sだが、RISC-Vは先ほども言った通り0x200からブートするので、先頭の0x200バイトは埋めておく必要がある。これにはgasのrept疑似コードを使ってnopを挿入しておく。実際には割り込みルーチンが入ってくるはずなのだが、とりあえず今回は保留だ。
.rept 0x80 nop .endr .globl start start: add x1, x0, x0 add x2, x0, x0 ...
あとはとりあえずレジスタフォーマットの記述でも入れておこう。x86ではブートのための複雑な処理が入っているが、これはRISC-Vでは関係ないので無視。
.globl start start: add x1, x0, x0 add x2, x0, x0 add x3, x0, x0 add x4, x0, x0 add x5, x0, x0 add x6, x0, x0 add x7, x0, x0 add x8, x0, x0 add x9, x0, x0 add x10, x0, x0 add x11, x0, x0 add x12, x0, x0 add x13, x0, x0 add x14, x0, x0 add x15, x0, x0 add x16, x0, x0 add x17, x0, x0 add x18, x0, x0 add x19, x0, x0 add x20, x0, x0 add x21, x0, x0 add x22, x0, x0 add x23, x0, x0 add x24, x0, x0 add x25, x0, x0 add x26, x0, x0 add x27, x0, x0 add x28, x0, x0 add x29, x0, x0 add x30, x0, x0 add x31, x0, x0 # Set up the stack pointer and call into C. la t0, bootmain jr t0
これで、ISSでブート可能なxv6バイナリが完成した。ちゃんとRISC-V ISSでも最初の命令を実行できた!あとはデバッグあるのみ。。。
$ ../swimmer_iss/build_riscv/swimmer_riscv --imgfile xv6.img --debug --max 59 Swimmer-RISCV Version 20160410 Revision b2f1940 developed by Masayuki Kimura <masayuki.kimura.1986@gmail.com> <Load Image xv6.img> <Info: NewMemory Region 00000000 is defined.> <Info: NewMemory Region 00001000 is defined.> StartDebug max_step=59 <= curr_step(0) + stepCount(59) Result Max_step= 59 0:M:MBar:[00000200] 000000b3 : add r01,r00,r00 r00=>00000000 r00=>00000000 r01<=00000000 1:M:MBar:[00000204] 00000133 : add r02,r00,r00 r00=>00000000 r00=>00000000 r02<=00000000 2:M:MBar:[00000208] 000001b3 : add r03,r00,r00 r00=>00000000 r00=>00000000 r03<=00000000 3:M:MBar:[0000020c] 00000233 : add r04,r00,r00 r00=>00000000 r00=>00000000 r04<=00000000 4:M:MBar:[00000210] 000002b3 : add r05,r00,r00 r00=>00000000 r00=>00000000 r05<=00000000 5:M:MBar:[00000214] 00000333 : add r06,r00,r00 r00=>00000000 r00=>00000000 r06<=00000000 6:M:MBar:[00000218] 000003b3 : add r07,r00,r00 r00=>00000000 r00=>00000000 r07<=00000000 7:M:MBar:[0000021c] 00000433 : add r08,r00,r00 r00=>00000000 r00=>00000000 r08<=00000000 8:M:MBar:[00000220] 000004b3 : add r09,r00,r00 r00=>00000000 r00=>00000000 r09<=00000000 9:M:MBar:[00000224] 00000533 : add r10,r00,r00 r00=>00000000 r00=>00000000 r10<=00000000 10:M:MBar:[00000228] 000005b3 : add r11,r00,r00 r00=>00000000 r00=>00000000 r11<=00000000 11:M:MBar:[0000022c] 00000633 : add r12,r00,r00 r00=>00000000 r00=>00000000 r12<=00000000 12:M:MBar:[00000230] 000006b3 : add r13,r00,r00 r00=>00000000 r00=>00000000 r13<=00000000 13:M:MBar:[00000234] 00000733 : add r14,r00,r00 r00=>00000000 r00=>00000000 r14<=00000000 14:M:MBar:[00000238] 000007b3 : add r15,r00,r00 r00=>00000000 r00=>00000000 r15<=00000000 15:M:MBar:[0000023c] 00000833 : add r16,r00,r00 r00=>00000000 r00=>00000000 r16<=00000000 16:M:MBar:[00000240] 000008b3 : add r17,r00,r00 r00=>00000000 r00=>00000000 r17<=00000000 17:M:MBar:[00000244] 00000933 : add r18,r00,r00 r00=>00000000 r00=>00000000 r18<=00000000 18:M:MBar:[00000248] 000009b3 : add r19,r00,r00 r00=>00000000 r00=>00000000 r19<=00000000 19:M:MBar:[0000024c] 00000a33 : add r20,r00,r00 r00=>00000000 r00=>00000000 r20<=00000000 20:M:MBar:[00000250] 00000ab3 : add r21,r00,r00 r00=>00000000 r00=>00000000 r21<=00000000 21:M:MBar:[00000254] 00000b33 : add r22,r00,r00 r00=>00000000 r00=>00000000 r22<=00000000 22:M:MBar:[00000258] 00000bb3 : add r23,r00,r00 r00=>00000000 r00=>00000000 r23<=00000000 23:M:MBar:[0000025c] 00000c33 : add r24,r00,r00 r00=>00000000 r00=>00000000 r24<=00000000 24:M:MBar:[00000260] 00000cb3 : add r25,r00,r00 r00=>00000000 r00=>00000000 r25<=00000000 25:M:MBar:[00000264] 00000d33 : add r26,r00,r00 r00=>00000000 r00=>00000000 r26<=00000000 26:M:MBar:[00000268] 00000db3 : add r27,r00,r00 r00=>00000000 r00=>00000000 r27<=00000000 27:M:MBar:[0000026c] 00000e33 : add r28,r00,r00 r00=>00000000 r00=>00000000 r28<=00000000 28:M:MBar:[00000270] 00000eb3 : add r29,r00,r00 r00=>00000000 r00=>00000000 r29<=00000000 29:M:MBar:[00000274] 00000f33 : add r30,r00,r00 r00=>00000000 r00=>00000000 r30<=00000000 30:M:MBar:[00000278] 00000fb3 : add r31,r00,r00 r00=>00000000 r00=>00000000 r31<=00000000 31:M:MBar:[0000027c] 00000297 : auipc r05,0x00000 r05<=0000027c 32:M:MBar:[00000280] 10028293 : addi r05,r05,0x100 r05=>0000027c r05<=0000037c 33:M:MBar:[00000284] 00028067 : jalr r00,r05,0x067 r05=>0000037c pc<=0000037c 34:M:MBar:[0000037c] ff010113 : addi r02,r02,0xff0 r02=>00000000 r02<=fffffffffffffff0 35:M:MBar:[00000380] 00112623 : sw r02,r01,0x00|r12 r02=>fffffffffffffff0 r01=>00000000 (fffffffffffffffc)<=00000000 36:M:MBar:[00000384] 00812423 : sw r02,r08,0x00|r08 r02=>fffffffffffffff0 r08=>00000000 (fffffffffffffff8)<=00000000 37:M:MBar:[00000388] 00912223 : sw r02,r09,0x00|r04 r02=>fffffffffffffff0 r09=>00000000 (fffffffffffffff4)<=00000000 38:M:MBar:[0000038c] 01212023 : sw r02,r18,0x00|r00 r02=>fffffffffffffff0 r18=>00000000 (fffffffffffffff0)<=00000000 39:M:MBar:[00000390] 01010413 : addi r08,r02,0x010 r02=>fffffffffffffff0 r08<=00000000 40:M:MBar:[00000394] 00000613 : addi r12,r00,0x000 r00=>00000000 r12<=00000000
x86版xv6のブートコードは何をしているのか
実装の際には一切無視したが、x86番の場合、まずCPUを立ち上げるために何が必要なのかを確認しておこう。
- まずは16bitモードから立ち上がる
.code16 # Assemble for 16-bit mode .globl start
この状態で、まずは割り込みを禁止し、余計な処理が入るのを防ぐ。さらに各種必要なレジスタの初期化が入る。
次にinbで何かを取得しているのだが、調べてみるとこれはPS/2のステータスを待っているらしい。 キーボードが認識されるのを待っているのか。wait for not busyと書いてあるから、PS/2のステータスが安定するのを待っているのね。
seta20.1: inb $0x64,%al # Wait for not busy testb $0x2,%al jnz seta20.1
次にキーボードに対していくつかコマンドを発行する。なぜキーボードに対して発行するのかがいまいちなぞだが、アドレスのモードをこれで変更するらしい。xv6の教科書にも以下のように書いてある。
If the second bit of the keyboard controller’s output port is low, the 21st physical address bit is always cleared; if high, the 21st bit acts normally. The boot loader must enable the 21st address bit using I/O to the keyboard controller on ports 0x64 and 0x60 (8920- 8936).
次にプロテクトモードに入り、仮想アドレスを32ビットに増加させる。この時はまだ、仮想アドレスと物理アドレスが同一のまま?
# Switch from real to protected mode. Use a bootstrap GDT that makes # virtual addresses map directly to physical addresses so that the # effective memory map doesn't change during the transition. lgdt gdtdesc movl %cr0, %eax orl $CR0_PE, %eax movl %eax, %cr0
32bitモードに入ると、アクセスできるようになる各種レジスタを初期化していく。
.code32 # Tell assembler to generate 32-bit code now. start32: # Set up the protected-mode data segment registers movw $(SEG_KDATA<<3), %ax # Our data segment selector movw %ax, %ds # -> DS: Data Segment movw %ax, %es # -> ES: Extra Segment movw %ax, %ss # -> SS: Stack Segment movw $0, %ax # Zero segments not ready for use movw %ax, %fs # -> FS movw %ax, %gs # -> GS
最終的に、bootmainのアドレスを設定してそこにジャンプしている。
# Set up the stack pointer and call into C. movl $start, %esp call bootmain