ウォンツテック

そでやまのーと

OS作成 バグ解明

謎は全て解けた

カーネルサイズに依存して挙動がおかしくなるバグの正体それは、、、ありえないミスだった。。
0x9036bから0x90374の10バイトが0で埋まる現象を突き止めてから、逆アセンブル、hexdump、そして正常時、異常時のオブジェクトのhexdumpのdiff、bochsデバッグ機能などありとあらゆる手段を駆使して以下のコード中の「xorl %ebx, %ebx」部分を分岐点として挙動の正常異常が分かれるところまで突き止めた。

middle_start:
        movw    %cs, %ax
        movw    %ax, %ds
        movw    %ax, %es

        # Get the max size of the physical memory,
        # and we'll set it at 0x90000.
        xorl    %ecx, %ecx
        xorl    %edx, %edx
        movw    $0x9000, %ax
        movw    %ax, %fs
        xorl    %eax, %eax
        movl    %eax, %fs:(0x0)

/* We have to get the phisical memory map.
 * Using "Function E820" and "Function E801" but not "Function 88H",
 * we'll get it.
 *  EAX 0xE820 - BIOS command to get the memory map
 *  EDX "SMAP" - special string to get the memory map
 *  ES:DI - entry buffer address to set the memory map
 *  ECX - the size of the entry buffer
 */
        e820_mmap_maxsize: .byte 32
SMAP = 0x534d4150 # "SMAP"

        xorl    %ebx, %ebx
        movw    $MEMORY_MAP, %di

MEMORY_MAPのアドレスがおかしいのかなと思いつつよくよくみてみると。。。
movl %eax, %fs:(0x0)からxorl %ebx, %ebxに命令が移行する間に1バイト命令じゃないのが入っているじゃん!!なんでjmp命令入れてなかったんだOTL。結構悩まされたこのバグ、結果的にはあほミスによるバグでした。。(;;


さて、ではなんでこんなコードで今まで動いていたのか?!またこのバグが何でカーネルのサイズに依存するの?!不思議ですね。きっちり解明しました。
まず異常時の上記コードをバイナリファイルから逆アセンブルしてみると本来命令ではない「e820_mmap_maxsize: .byte 32」の部分は以下のような命令になっていました。

  11:   66 31 c0                xor    %eax,%eax
  14:   66 64 a3 00 00          mov    %eax,%fs:0x0
  19:   20 66 31                and    %ah,49(%bp)
  1c:   db bf 6e 05             fstpt  1390(%bx)
  20:   66 b8 20 e8 00 00       mov    $0xe820,%eax

1,2行目と5行目はそのままなので3行目と4行目が異常な部分です。
and %ah, 49(%bp)はahに対する論理積なので何も起きません。
fstpt 1390(%bx)はST(0)レジスタの値をデスティネーションに対して10byteコピーする命令です。この命令がたまたま0x9036bから10バイト分ST(0)の値「0」をコピーしていました。
そんでこの%bxというのがbootacient.Sで、カーネルの最後のコピー先のオフセットアドレスである0xfe00となっていたので1390(%bx)は0x36e(この部分は16bitモードなので上位桁は切り捨て)となり%ds:$0x36e = 0x9036eが転送先という事になります。(数バイトの誤差はテスト用にちょっとしたコードを入れたため)


たまたま動いていたのは1390(%bx)が上書きしても大丈夫なメモリ領域を示していたからです。これを放置しておいたら0x10000を周期として192000Byte+n*64KByte(n整数)の大きさのカーネルでこのバグが発生するところでした。