FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

xv6のブートプロセスで学ぶコンピュータの起動(まだ勉強中)

mipsに移植中のxv6のカーネルパニックですっかり止まってしまった、xv6とOSの勉強。とりあえずブートしないと話にならないので、ブートプロセスを必死に勉強中だ。

まずは、xv6を使って、何が起きているのかを調査してみることにする。

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

xv6を起動するとき、何を読み込んでいるのか

Makefileには、以下のように記述してある。

QEMUOPTS = -hdb fs.img xv6.img -smp $(CPUS) -m 512 $(QEMUEXTRA)
...
qemu-nox: fs.img xv6.img
        $(QEMU) -nographic $(QEMUOPTS)

むむ、fs.imgとxv6.imgで構成されている。ところで、このimgってどういうフォーマットなんだろう?どのようにして作成されているのかを調査する。

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

ddコマンドは単なるディスクのコピーだよな。ならば、

  1. 10000バイトまでをゼロで埋める
  2. bootblockを先頭から挿入する
  3. kernelをブロック1から挿入する

ということになりそうだ。では、まず読み込まれるのはbootblockということになる。

基本的に、コンピュータが起動するときは、BIOSがプライマリディスクの先頭のコードをメモリにロードする。

xv6のテキストには、

When an x86 PC boots, it starts executing a program called the BIOS, which is
stored in non-volatile memory on the motherboard. The BIOS’s job is to prepare the
hardware and then transfer control to the operating system. Specifically, it transfers
control to code loaded from the boot sector, the first 512-byte sector of the boot disk

512バイトをコピーか。なら、それに則ってみるか。 ちなみに、作成されたxv6.imgをhexdumpしてみると、以下のようになっている。

0000000 31fa 8ec0 8ed8 8ec0 e4d0 a864 7502 b0fa
0000010 e6d1 e464 a864 7502 b0fa e6df 0f60 1601
0000020 7c78 200f 66c0 c883 0f01 c022 31ea 087c
0000030 6600 10b8 8e00 8ed8 8ec0 66d0 00b8 8e00
0000040 8ee0 bce8 7c00 0000 dfe8 0000 6600 00b8
0000050 668a c289 ef66 b866 8ae0 ef66 feeb 9066
0000060 0000 0000 0000 0000 ffff 0000 9a00 00cf
0000070 ffff 0000 9200 00cf 0017 7c60 0000 8955
0000080 bae5 01f7 0000 83ec c0e0 403c f875 c35d
0000090 8955 57e5 8b53 0c5d e1e8 ffff baff 01f2
00000a0 0000 01b8 0000 ee00 f3b2 d889 89ee c1d8
00000b0 08e8 f4b2 89ee c1d8 10e8 f5b2 89ee c1d8
00000c0 18e8 c883 b2e0 eef6 f7b2 20b8 0000 ee00
00000d0 a9e8 ffff 8bff 087d 80b9 0000 ba00 01f0
00000e0 0000 f3fc 5b6d 5d5f 55c3 e589 5657 8b53
00000f0 085d 758b 8910 03df 0c7d f089 ff25 0001
0000100 2900 c1c3 09ee c683 3901 76df 5617 e853
0000110 ff7c ffff c381 0200 0000 c683 8301 08c4
0000120 df39 e977 658d 5bf4 5f5e c35d 8955 57e5
0000130 5356 ec83 6a0c 6800 1000 0000 0068 0100
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

これってあってるの?bootblockをobjdumpしてみよう。

objdump -D bootblock


bootblock.o:     file format elf32-i386


Disassembly of section .text:

00007c00 <start>:
    7c00:       fa                      cli
    7c01:       31 c0                   xor    %eax,%eax
    7c03:       8e d8                   mov    %eax,%ds
    7c05:       8e c0                   mov    %eax,%es
    7c07:       8e d0                   mov    %eax,%ss

00007c09 <seta20.1>:
    7c09:       e4 64                   in     $0x64,%al
    7c0b:       a8 02                   test   $0x2,%al
    7c0d:       75 fa                   jne    7c09 <seta20.1>
    7c0f:       b0 d1                   mov    $0xd1,%al
    7c11:       e6 64                   out    %al,$0x64

00007c13 <seta20.2>:
    7c13:       e4 64                   in     $0x64,%al
    7c15:       a8 02                   test   $0x2,%al
    7c17:       75 fa                   jne    7c13 <seta20.2>
    7c19:       b0 df                   mov    $0xdf,%al
    7c1b:       e6 60                   out    %al,$0x60
    7c1d:       0f 01 16                lgdtl  (%esi)
    7c20:       78 7c                   js     7c9e <readsect+0xe>
    7c22:       0f 20 c0                mov    %cr0,%eax
    7c25:       66 83 c8 01             or     $0x1,%ax
    7c29:       0f 22 c0                mov    %eax,%cr0
    7c2c:       ea 31 7c 08 00 66 b8    ljmp   $0xb866,$0x87c31

00007c31 <start32>:
    7c31:       66 b8 10 00             mov    $0x10,%ax
    7c35:       8e d8                   mov    %eax,%ds
    7c37:       8e c0                   mov    %eax,%es
    7c39:       8e d0                   mov    %eax,%ss
    7c3b:       66 b8 00 00             mov    $0x0,%ax
    7c3f:       8e e0                   mov    %eax,%fs
    7c41:       8e e8                   mov    %eax,%gs
    7c43:       bc 00 7c 00 00          mov    $0x7c00,%esp
    7c48:       e8 df 00 00 00          call   7d2c <bootmain>
    7c4d:       66 b8 00 8a             mov    $0x8a00,%ax
    7c51:       66 89 c2                mov    %ax,%dx
    7c54:       66 ef                   out    %ax,(%dx)
    7c56:       66 b8 e0 8a             mov    $0x8ae0,%ax
    7c5a:       66 ef                   out    %ax,(%dx)

00007c5c <spin>:
    7c5c:       eb fe                   jmp    7c5c <spin>
    7c5e:       66 90                   xchg   %ax,%ax

00007c60 <gdt>:

あー、何となくあってる気がするね。ちなみに、512バイトの最後のバイトがaa55だったら、起動ディスクとして認識されるって聞いたことがある。 この本に書いてあった。

自作エミュレータで学ぶx86アーキテクチャ-コンピュータが動く仕組みを徹底理解!

自作エミュレータで学ぶx86アーキテクチャ-コンピュータが動く仕組みを徹底理解!

00001f0 0000 0000 0000 0000 0000 0000 0000 aa55

あっているね。ちなみに、このaa55の挿入はどうやって実現しているかと言うと、

bootblock: bootasm.S bootmain.c
        $(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c bootmain.c
        $(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S
        $(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
        $(OBJDUMP) -S bootblock.o > bootblock.asm
        $(OBJCOPY) -S -O binary -j .text bootblock.o bootblock
        ./sign.pl bootblock

sign.plが怪しいな。

#!/usr/bin/perl

open(SIG, $ARGV[0]) || die "open $ARGV[0]: $!";

$n = sysread(SIG, $buf, 1000);

if($n > 510){
  print STDERR "boot block too large: $n bytes (max 510)\n";
  exit 1;
}

print STDERR "boot block is $n bytes (max 510)\n";

$buf .= "\0" x (510-$n);
$buf .= "\x55\xAA";

open(SIG, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
print SIG $buf;
close SIG;

Perlってそんなことが出来るんかい。sign.plが55aaの挿入を実現していることが分かった。