いものやま。

雑多な知識の寄せ集め

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

前回はVimバイナリエディタとして使って直接フロッピーディスクのイメージを作った。

今回はアセンブラを使ってフロッピーディスクのイメージを作ってみる。

Binutilsのインストー

本ではNASMに似たnaskという著者作のアセンブラを使ってるけど、やっぱりアセンブラでよく知られているのは、GNUアセンブラ(gas)。
ということで、自分はnaskではなくgasを使ってみた。

gasはBinutilsというツール群に含まれている。
そこで、Binutilsをインストール。

GNU Binutils

インストールは簡単で、tarボールを落としてきて展開したら、configureスクリプトを実行してmakeするだけ。

$ tar zxvf binutils-2.28.tar.gz
$ cd binutils-2.28
$ ./configure --program-prefix=i386-elf- --target=i386-elf --disable-nls
$ make
$ sudo make install

configureスクリプト--program-prefixオプションをつけると、各ツールの頭に指定したプレフィクスがつく。
今回はi386-elf用のアセンブラその他を作るので、i386-elf-というプレフィクスをつけている。
(そうしないと、他のアーキテクチャ向けのアセンブラを作ったときに、名前がぶつかる)

また、--targetオプションで対象のアーキテクチャを指定している。

あと、--disable-nlsというオプションをつけているけど、これはNative Language Supportを無効にするもの。
このオプションをつけなかった場合、libintl.hが見つからないというエラーが出たので、つけている。
(このヘッダファイルはMacでQEMUのビルドをしてみた。 - いものやま。に書いたgettextの提供するヘッダファイルなので、同じようにインクルードパスを通してやるのでもOKのはず)

何はともあれ、これでBintuilsのインストールは完了。
i386-elf向けのアセンブラi386-elf-asというコマンドでインストールされている。

アセンブラソースの作成

Binutilsもインストールできたので、次はアセンブラソースの作成。
といっても、アセンブラでコードを書くのではなく、バイナリエディタと同じように、データを直接作ってみる。

gasの場合、.byte.intといった擬似命令(ディレクティブ)を使うことで、バイナリデータを直接書くことが出来る。
また、.skipという擬似命令を使うと、指定したサイズだけ隙間を作ることが出来る。

それらを使って書いたコードが、以下:

    .byte 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
    .byte 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
    .byte 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
    .byte 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
    .byte 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
    .byte 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
    .byte 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
    .byte 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00

    .skip 16, 0x00

    .byte 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    .byte 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    .byte 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    .byte 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    .byte 0xee, 0xf4, 0xeb, 0xfd, 0x0a, 0x0a, 0x68, 0x65
    .byte 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
    .byte 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00

    .skip 368, 0x00

    .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
    .byte 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00

    .skip 4600, 0x00

    .byte 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00

    .skip 1469432, 0x00

これをhello-os.sというファイルに保存すれば、ソースは完成。

asによるアセンブル

アセンブラソースが出来たので、これをasでアセンブルしてやる。

$ i386-elf-as -o hello-os.o hello-os.s

問題なくアセンブルが終われば、hello-os.oというオブジェクトファイルが生成される。

objcopyによるバイナリファイルの生成

ただ、これでフロッピーディスクのイメージが出来たかというと、そうではなく。

実際、生成されたhello-os.oの内容を表示してみると、以下のとおり:

$ hexdump -C hello-os.o |less
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 03 00 01 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  a4 80 16 00 00 00 00 00  34 00 00 00 00 00 28 00  |........4.....(.|
00000030  07 00 06 00 eb 4e 90 48  45 4c 4c 4f 49 50 4c 00  |.....N.HELLOIPL.|
00000040  02 01 01 00 02 e0 00 40  0b f0 09 00 12 00 02 00  |.......@........|
00000050  00 00 00 00 40 0b 00 00  00 00 29 ff ff ff ff 48  |....@.....)....H|
00000060  45 4c 4c 4f 2d 4f 53 20  20 20 46 41 54 31 32 20  |ELLO-OS   FAT12 |
00000070  20 20 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |  ..............|
00000080  00 00 00 00 b8 00 00 8e  d0 bc 00 7c 8e d8 8e c0  |...........|....|
00000090  be 74 7c 8a 04 83 c6 01  3c 00 74 09 b4 0e bb 0f  |.t|.....<.t.....|
000000a0  00 cd 10 eb ee f4 eb fd  0a 0a 68 65 6c 6c 6f 2c  |..........hello,|
000000b0  20 77 6f 72 6c 64 0a 00  00 00 00 00 00 00 00 00  | world..........|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000230  00 00 55 aa f0 ff ff 00  00 00 00 00 00 00 00 00  |..U.............|
00000240  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001430  00 00 00 00 f0 ff ff 00  00 00 00 00 00 00 00 00  |................|
00001440  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00168050  03 00 01 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00168060  03 00 02 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00168070  03 00 03 00 00 00 2e 73  79 6d 74 61 62 00 2e 73  |.......symtab..s|
00168080  74 72 74 61 62 00 2e 73  68 73 74 72 74 61 62 00  |trtab..shstrtab.|
00168090  2e 74 65 78 74 00 2e 64  61 74 61 00 2e 62 73 73  |.text..data..bss|
001680a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
001680c0  00 00 00 00 00 00 00 00  00 00 00 00 1b 00 00 00  |................|
001680d0  01 00 00 00 06 00 00 00  00 00 00 00 34 00 00 00  |............4...|
001680e0  00 80 16 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
001680f0  00 00 00 00 21 00 00 00  01 00 00 00 03 00 00 00  |....!...........|
00168100  00 00 00 00 34 80 16 00  00 00 00 00 00 00 00 00  |....4...........|
00168110  00 00 00 00 01 00 00 00  00 00 00 00 27 00 00 00  |............'...|
00168120  08 00 00 00 03 00 00 00  00 00 00 00 34 80 16 00  |............4...|
00168130  00 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00168140  00 00 00 00 01 00 00 00  02 00 00 00 00 00 00 00  |................|
00168150  00 00 00 00 34 80 16 00  40 00 00 00 05 00 00 00  |....4...@.......|
00168160  04 00 00 00 04 00 00 00  10 00 00 00 09 00 00 00  |................|
00168170  03 00 00 00 00 00 00 00  00 00 00 00 74 80 16 00  |............t...|
00168180  01 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00168190  00 00 00 00 11 00 00 00  03 00 00 00 00 00 00 00  |................|
001681a0  00 00 00 00 75 80 16 00  2c 00 00 00 00 00 00 00  |....u...,.......|
001681b0  00 00 00 00 01 00 00 00  00 00 00 00              |............|
001681bc

見てのとおり、アセンブラに書いた内容だけではなく、なんか余計なデータが追加されているのが分かると思う。

これは、オブジェクトファイルが生のバイナリデータではなく、ELF形式のデータになっているため。
どのメモリにロードすべきかや、シンボルの情報、デバッグ情報なんかが一緒に詰め込まれている。
(ちなみに、アセンブラに書いた内容は0x34から始まっている)

普通のプログラムであれば、OSがこれらの情報を読み取って、適切なアドレスにロードしてくれるんだけど、ここではそういったローダはいないので、生のバイナリデータの形に自前で展開してやらないといけない。

ただ、Binutilsにはobjcopyというバイナリファイルの形式を変換するツールが含まれているので、これを使うことでオブジェクトファイルを生のバイナリデータに変換することが出来る。
(※もちろん、リンクされてすべてのシンボルが解決されていないとダメ)

以下のコマンドを実行:

$ i386-elf-objcopy -O binary hello-os.o hello-os.img

-Oが出力形式を決めるためのオプションで、ここでbinaryを指定すると、生のバイナリデータに変換されて出力がされる。

出来上がったイメージファイルの内容を見てみると、以下のとおり:

$ hexdump -C hello-os.img 
00000000  eb 4e 90 48 45 4c 4c 4f  49 50 4c 00 02 01 01 00  |.N.HELLOIPL.....|
00000010  02 e0 00 40 0b f0 09 00  12 00 02 00 00 00 00 00  |...@............|
00000020  40 0b 00 00 00 00 29 ff  ff ff ff 48 45 4c 4c 4f  |@.....)....HELLO|
00000030  2d 4f 53 20 20 20 46 41  54 31 32 20 20 20 00 00  |-OS   FAT12   ..|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  b8 00 00 8e d0 bc 00 7c  8e d8 8e c0 be 74 7c 8a  |.......|.....t|.|
00000060  04 83 c6 01 3c 00 74 09  b4 0e bb 0f 00 cd 10 eb  |....<.t.........|
00000070  ee f4 eb fd 0a 0a 68 65  6c 6c 6f 2c 20 77 6f 72  |......hello, wor|
00000080  6c 64 0a 00 00 00 00 00  00 00 00 00 00 00 00 00  |ld..............|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200  f0 ff ff 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000210  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001400  f0 ff ff 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00001410  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00168000

ちゃんと出来上がっているのが分かると思う。

Makefileの作成

これでとりあえずフロッピーディスクのイメージが出来たわけだけど、このコマンドを毎回打つのは面倒。
打ち間違いとかも怖いし。
(昔、ShellのTabによる補完でgcc -o hoge.c hoge.cを実行してしまって、せっかく書いたソースを空にしてしまったことがある・・・)

そこで、簡単なMakefileも用意してみた:

.PHONY: all clean do

all: hello-os.img

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

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

clean:
   -rm *.img *.o

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

実行するコマンドの前にあるのは必ずタブでないといけないので、そこだけは注意。

これで、makeとすれば、イメージが作成されるし、make doとすれば、qemuが立ち上がって実行もされる。

今日はここまで!