いものやま。

雑多な知識の寄せ集め

『OS自作入門』を読んでみた。(その5)

またまた間が空いてしまったけど、前回の続き。

今回はアドレスの問題を解決していく。

アセンブル

おさらいで、何が問題だったかというと、ブートセクタはBIOSによってメモリの0x7c00に読み込まれるのだけど、このコードだと先頭が0x0000だと思ってリンクが解決されてしまうので、正しく動作しないということ。

とはいえ、実際にどのようになっているのか分からないので、まずは逆アセンブルして確認を。

アセンブルには、objdumpを使えばいい。
これもBinutilsに入っているツール。
-dオプションをつけると、逆アセンブルをしてくれる。

$ i386-elf-objdump -d hello-os.o

この結果は、以下のとおり:

hello-os.o:     file format elf32-i386

Disassembly of section .text:

00000000 <pbr>:
       0:   eb 4e                   jmp    50 <entry>
       2:   90                      nop
// 省略

00000050 <entry>:
      50:   b8 00 00 8e d0          mov    $0xd08e0000,%eax
      55:   bc 00 7c 8e d8          mov    $0xd88e7c00,%esp
      5a:   8e c0                   mov    %eax,%es
      5c:   be                      .byte 0xbe
      5d:   74 00                   je     5f <putloop>

0000005f <putloop>:
      5f:   8a 04 83                mov    (%ebx,%eax,4),%al
      62:   c6 01 3c                movb   $0x3c,(%ecx)
      65:   00 74 09 b4             add    %dh,-0x4c(%ecx,%ecx,1)
      69:   0e                      push   %cs
      6a:   bb 0f 00 cd 10          mov    $0x10cd000f,%ebx
      6f:   eb ee                   jmp    5f <putloop>

00000071 <fin>:
      71:   f4                      hlt    
      72:   eb fd                   jmp    71 <fin>

00000074 <message>:
// 以下略

ただ、ちょっとおかしいのに気づくかもしれない。

例えば、0x50を見てみると、直値0xd08e0000をレジスタeaxに代入している。
ここはホントは、0x00をレジスタaxに代入すると出て欲しいところ。
それに、0x5cを見てみると、命令があるはずなのになぜかデータが出てしまっている。

これはなぜかというと、32ビットモード(プロテクトモード)で逆アセンブルされてしまっているから。
前回書いたとおり、ここは.code16擬似命令を使って16ビットモード(リアルモード)の出力をしているので、逆アセンブルも16ビットモードで行ってやらないと、正しくならない。

16ビットモードであることを指定するには、-M addr16,data16オプションを追加する:

$ i386-elf-objdump -d -M addr16,data16 hello-os.o

すると、以下のような出力になる:

hello-os.o:     file format elf32-i386

Disassembly of section .text:

00000000 <pbr>:
       0:   eb 4e                   jmp    50 <entry>
       2:   90                      nop
// 省略

00000050 <entry>:
      50:   b8 00 00                mov    $0x0,%ax
      53:   8e d0                   mov    %ax,%ss
      55:   bc 00 7c                mov    $0x7c00,%sp
      58:   8e d8                   mov    %ax,%ds
      5a:   8e c0                   mov    %ax,%es
      5c:   be 74 00                mov    $0x74,%si

0000005f <putloop>:
      5f:   8a 04                   mov    (%si),%al
      61:   83 c6 01                add    $0x1,%si
      64:   3c 00                   cmp    $0x0,%al
      66:   74 09                   je     71 <fin>
      68:   b4 0e                   mov    $0xe,%ah
      6a:   bb 0f 00                mov    $0xf,%bx
      6d:   cd 10                   int    $0x10
      6f:   eb ee                   jmp    5f <putloop>

00000071 <fin>:
      71:   f4                      hlt    
      72:   eb fd                   jmp    71 <fin>

00000074 <message>:
// 以下略

このとおり、ちゃんと16ビットモードで逆アセンブルがされるようになる。

アドレスの確認

さて、注目したいのは、0x5cの部分:

      5c:   be 74 00                mov    $0x74,%si

もともとのコードは、次のようになっていた:

    mov     $message, %si

つまり、messageのアドレスをsiレジスタに代入していたわけだけど、messageのアドレスが0x74として解決されてしまっている。
このファイルだけ見れば、それは正しそうに思えるんだけど、実際にはこのファイルの先頭が0x7c00にロードされるので、messageのアドレスは、0x7c00 + 0x74 = 0x7c74でないといけない。
なので、このままでは正しく動作しない。
(messageが置かれている0x7c74ではなく、何が置かれているか分からない0x74のデータを読み込んで出力しようとする)

リンカとリンカスクリプト

ということで、このセクションが0x7c00にロードされて動作することを伝え、正しくアドレスの解決を行わないといけない。

そこで必要になるのが、リンカとリンカスクリプト

リンカは、各オブジェクトファイルのセクションをまとめ、それが動作するアドレスにもとづいてアドレスの解決を行ってくれる。
そのとき、どのセクションをどのアドレスにロードし動作させるか記述するのがリンカスクリプト

今回の場合、このセクションは0x7c00にロードされ、そのまま動作するので、そのようなリンカスクリプトを書いてリンクを行えばいい。

ということで、以下のような簡単なリンカスクリプトhello-os.ldscriptを用意した:

SECTIONS
{
    .text 0x7c00 : { *(.text) }
}

このリンカスクリプトを使うようにリンカ(ld、これもBinutilsに入っている)に指示してリンクを行うには、以下のようにすればいい:

$ i386-elf-ld -o hello-os.elf -T hello-os.ldscript hello-os.o

こうして出来たELFファイルを逆アセンブルしてみると、以下のような感じ:

hello-os.elf:     file format elf32-i386
    
Disassembly of section .text:
  
00007c00 <pbr>:
    7c00:       eb 4e                   jmp    7c50 <entry>
    7c02:       90                      nop
// 省略

00007c50 <entry>:
    7c50:       b8 00 00                mov    $0x0,%ax
    7c53:       8e d0                   mov    %ax,%ss
    7c55:       bc 00 7c                mov    $0x7c00,%sp
    7c58:       8e d8                   mov    %ax,%ds
    7c5a:       8e c0                   mov    %ax,%es
    7c5c:       be 74 7c                mov    $0x7c74,%si

00007c5f <putloop>:
    7c5f:       8a 04                   mov    (%si),%al
    7c61:       83 c6 01                add    $0x1,%si
    7c64:       3c 00                   cmp    $0x0,%al
    7c66:       74 09                   je     7c71 <fin>
    7c68:       b4 0e                   mov    $0xe,%ah
    7c6a:       bb 0f 00                mov    $0xf,%bx
    7c6d:       cd 10                   int    $0x10
    7c6f:       eb ee                   jmp    7c5f <putloop>

00007c71 <fin>:
    7c71:       f4                      hlt
    7c72:       eb fd                   jmp    7c71 <fin>

00007c74 <message>:
// 以下略

ちゃんと正しくアドレスが解決されているのが分かると思う。

ちなみに、このELFファイルをobjcopyでバイナリファイルに変換した場合、どう出力されるのか気になるところだけど、objcopyはロードされるアドレスの一番先頭をファイルの先頭として出力してくれるようなので、ちゃんと期待したバイナリファイルになってくれる。

Makefileの修正

最後の仕上げで、以前書いたMakefileを修正して、リンクも行うように。

.PHONY: all clean do

all: hello-os.img

hello-os.img: hello-os.elf
  i386-elf-objcopy -O binary $< $@

hello-os.elf: hello-os.ldscript

hello-os.elf: hello-os.o
  i386-elf-ld -o $@ -T hello-os.ldscript $<

hello-os.o: hello-os.s
  i386-elf-as -o $@ $<

clean:
   -rm *.img *.elf *.o

do: hello-os.img
  qemu-system-i386 -fda $<

これでmakeを実行したときに、リンクを行ってELFファイルを作成し、そこからフロッピーディスクのイメージを作成するようになる。

今日はここまで!

30日でできる! OS自作入門

30日でできる! OS自作入門