6.828: Operating System Engineering (12)

これ、読み込んだソレを x/i で確認せよ、ということなのかな。とりあえず

  • 0x7c00 に breakpoint 設定
  • continue
  • 次に止める場所の確認 <- イマココ

という状況なんですが、boot.asm 見たら昨晩エントリな 0x7d7a に breakpoint 設定して 0x10018 以降のナニを確認してみれば良さげなカンジ。

違うのかな

こーゆーコトなの?

(gdb) x/i 0x7c00
   0x7c00:      cli    
(gdb) x/i 0x7c01
   0x7c01:      cld    
(gdb) x/i 0x7c02
   0x7c02:      xor    %eax,%eax
(gdb) x/i 0x7c04
   0x7c04:      mov    %eax,%ds
(gdb) x/i 0x7c06
   0x7c06:      mov    %eax,%es
(gdb) x/i 0x7c08
   0x7c08:      mov    %eax,%ss

しかしよく考えてみるに、命令って 4 byte 境界で align なのかと思ったらそうではないんですね。てかそれって arm の話?
ちなみに今は 0x7d7a で止まってるのでちょっと進めてみます。

(gdb) si
=> 0x7d7f:      and    $0xffffff,%eax
0x00007d7f in ?? ()
(gdb) si
=> 0x7d84:      call   *%eax
0x00007d84 in ?? ()
(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()
(gdb) x/i 0x10000c
=> 0x10000c:    movw   $0x1234,0x472

なんで 0x1000c なのかなぁ。0x7d7a あたりの処理が以下なんですが

    7d7a:	a1 18 00 01 00       	mov    0x10018,%eax
    7d7f:	25 ff ff ff 00       	and    $0xffffff,%eax
    7d84:	ff d0                	call   *%eax

jmp ではなくて call だから、ということなのかどうか。ちなみに少しだけ si で進めてみたんですが、

(gdb) si
=> 0x100015:    lgdtl  0x110018
0x00100015 in ?? ()
(gdb) si
=> 0x10001c:    mov    $0x10,%eax
0x0010001c in ?? ()

kernel.asm によれば以下な命令、ということなのか。

_start:
	movw	$0x1234,0x472			# warm boot

	lgdt	RELOC(mygdtdesc)		# load descriptor table
f0100015:	0f 01 15 18 00 11 00 	lgdtl  0x110018

	movl	$DATA_SEL, %eax			# Data segment selector
f010001c:	b8 10 00 00 00       	mov    $0x10,%eax

なんというか色々な意味でマクロだぞ。

とりあえず

以下のソレに答えておいて次に進む。

  1. At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
  2. What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
  3. Where is the first instruction of the kernel?
  4. How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?

ええと、32-bit mode になるのは以下の命令ですよね。

  # Jump to next instruction, but in 32-bit code segment.
  # Switches processor into 32-bit mode.
  ljmp    $PROT_MODE_CSEG, $protcseg
    7c2d:	ea 32 7c 08 00 66 b8 	ljmp   $0xb866,$0x87c32

前準備として

  • cli して cld
  • ds、es、ss を 0 で初期化
  • A20 マスク解除 (?)
  • GDT セット
  • cr0 の PE フラグ設定

で ljmp してプロテクトモードに、という形なのかな。詳細は微妙です。なんとなくそう読める、という程度の理解だったりしてます。理解が微妙なので控えておくと、boot.asm の記述としては以下で

00007c0a <seta20.1>:
  # Enable A20:
  #   For backwards compatibility with the earliest PCs, physical
  #   address line 20 is tied low, so that addresses higher than
  #   1MB wrap around to zero by default.  This code undoes this.
seta20.1:
  inb     $0x64,%al               # Wait for not busy
    7c0a:	e4 64                	in     $0x64,%al
  testb   $0x2,%al
    7c0c:	a8 02                	test   $0x2,%al
  jnz     seta20.1
    7c0e:	75 fa                	jne    7c0a <seta20.1>

  movb    $0xd1,%al               # 0xd1 -> port 0x64
    7c10:	b0 d1                	mov    $0xd1,%al
  outb    %al,$0x64
    7c12:	e6 64                	out    %al,$0x64

00007c14 <seta20.2>:

seta20.2:
  inb     $0x64,%al               # Wait for not busy
    7c14:	e4 64                	in     $0x64,%al
  testb   $0x2,%al
    7c16:	a8 02                	test   $0x2,%al
  jnz     seta20.2
    7c18:	75 fa                	jne    7c14 <seta20.2>

  movb    $0xdf,%al               # 0xdf -> port 0x60
    7c1a:	b0 df                	mov    $0xdf,%al
  outb    %al,$0x60
    7c1c:	e6 60                	out    %al,$0x60

これ、手元の kernel な source によれば以下かなと。

static void enable_a20_kbc(void)
{
        empty_8042();

        outb(0xd1, 0x64);       /* Command write */
        empty_8042();

        outb(0xdf, 0x60);       /* A20 on */
        empty_8042();

        outb(0xff, 0x64);       /* Null command, but UHCI wants it */
        empty_8042();
}

あと仮で作成されていると思われる gdt は boot.S によれば以下。

# Bootstrap GDT
.p2align 2                                # force 4 byte alignment
gdt:
  SEG_NULL				# null seg
  SEG(STA_X|STA_R, 0x0, 0xffffffff)	# code seg
  SEG(STA_W, 0x0, 0xffffffff)	        # data seg

gdtdesc:
  .word   0x17                            # sizeof(gdt) - 1
  .long   gdt                             # address gdt

仮、なのかなぁ。しかもなんとなくフォーマットが微妙な気がするんですがマクロの定義を見たら大丈夫そげでした。

次次

ええと、ELF が云々とあるがスルーしても良いらしい。とりあえず Excersize 5 を確認してみましょうね。
make qemu-gdb して gdb 起動して boot loader のエントリポイントに breakpoint を設定して確認。

(gdb) b *0x7c00
Breakpoint 1 at 0x7c00
(gdb) c
Continuing.
[   0:7c00] => 0x7c00:  cli    

Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x/8x 0x00100000
0x100000:       0x00000000      0x00000000      0x00000000      0x00000000
0x100010:       0x00000000      0x00000000      0x00000000      0x00000000
(gdb)

で、Kernel が起動する所に breakpoint を設定して試験。

(gdb) b *0x7d84
Breakpoint 2 at 0x7d84
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d84:      call   *%eax

Breakpoint 2, 0x00007d84 in ?? ()
(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()
(gdb) x/8x 0x00100000
0x100000:       0x1badb002      0x00000003      0xe4524ffb      0x7205c766
0x100010:       0x34000004      0x15010f12      0x00110018      0x000010b8
(gdb) 

ええとこれ、何でしょ。obj/kern/kernel を objdump せよとか書いてるのかな。

$ objdump -f obj/kern/kernel

obj/kern/kernel:     file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0xf010000c

$

なるほど。ロードされた、ってことが分かるって意味?
てか、次の項の Link vs. Load Address ってソレにも関係するのかどうか不明ですが、start address が 0xf01000c とあるけど上記の例にもあるように実際は

(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()
(gdb)

なんスよね。次の Excersize が boot loader の

	$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o $@.out $^

なアドレスを変えてリコンパイルして実行してみれ、とある。非常に面白そうなんですが時間が無い。
もしなんとかなるようであれば追記します。