■
昨日嵌った事が色々と解決したのでまとめて書いてみようと思います。
また、自分が分からない所や気になった所は適宜コメントを入れてみます。
sodex
まずnchaosというのを一昨年の7月頃にhigepon氏のmona osに触発されて書いてました
http://sourceforge.jp/projects/nchaos/
これはhigeponと同様にCygwinとnasm, gccを使ってwindows上で開発してました。
今回はもう一度勉強し直し、新しい知識を得たいため開発環境をLinuxに移行します。開発環境は以下になります。
Linux FedraCore 6 Linux Kernel 2.6.18-1.2869.fc6 gcc 3.4.6 GNU assembler version 2.17.50.0.3-6 GNU ld version 2.17.50.0.3-6 20060715 QEMU PC emulator version 0.8.2 (Linux版)
新しく用意するのはQemuくらいです。Linuxを開発モードで入れていればgcc, as, ldは入ってます。
テストにはqemuを使っています。Qemuでのdebug方法は主にqemu起動後に「Ctrl - Alt - 2」を押してdebugモードに移行しregisterの中身などを検査します。(Qemuはdebugの出力が弱いような気がするのでbootloader回りの開発ではbochsも合わせて使おうかと思います。また、gdb + qemuのdebug方法がありそうなので調べてたのですが、symbolの関係やら、実際emulation後のregisterが表示されているかどうかなどの問題でうまく使えませんでした)
まずFirstブートの中身を見る前にbootバイナリの作成方法から見てみます
as bootacient.S -o boot1.o -a > boot.lst ld -Ttext 0x0 -o boot1 boot1.o --oformat binary
というshファイルを作成します。
1行目はasでのアセンブルでオプション「-o」はldでリンクする前のリンク情報(Symbol情報等)を含んだバイナリファイルの出力先を指定します。「-a」は実際にどういったマシン後を生成しているのかとか、Symbol情報を見ることが出来ます。Symbol情報っていうのは「hoge:」といったラベルの事で、C言語等では関数名に相当します(恐らく。。)
2行目ではasで生成されたsymbol情報を含んだファイルを実番地に置き換えていきます。オプションの「-Ttext」はtextセクションの開始番地(もちろん相対番地でファイルをメモリに読み込んだ時のその先頭からの番地)で「-o」はバイナリファイル生成、「--oformat binary」は生成するファイルのフォーマット指定になります。最後のフォーマット指定をしないとldが勝手に共有ライブラリなどをリンクしてしまい意図したバイナリファイルが生成されません。
※nasmではいきなりリンク情報を解決したバイナリを出力出来るのでこういったアセンブルとリンクといったまどろっこしい指定は要りません。
ではコードの解説へ。
;この3行はgasでは変数のように使えて$HOGEのようにして使います。 ;この行は実際にはバイナリ化されず、アセンブルする時に使われている ;だけのようです。 BOOTSEG = 0x07C0 INITSEG = 0x9000 SYSSEG = 0x1000 ;gasに対してこのコードは16bitモードで書かれている事を伝えます。 ;.textはセクションで主にプロセスを書きます。他に.bss, .dataなどのセクション ;が存在しますがとりあえず使いません。 .code16 .text ;; 初期処理が始まるまで ;;まずINTEL - PC AT系のマシンは電源投入後16bitモードで起動し、初めは ;;メモリの0xffff0番地を見に行きます。この番地は実際にはメモリ上ではなく ;;BIOSやマザボのどこかに引っ付いているROMを見に行ってます。 ;;その0xffff0番地ではいくつかの命令が書いてありまずは起動ディスク(通常 ;;はHDDなど)の先頭のセクタから512バイトだけを読み取りメモリの0x7c00番地 ;;へ転送し最後にIPレジスタの値を0x7c00に変える事で転送された512バイトの命令 ;;を起動しようとします。 ;;bootloader作成者はその最初の512バイトのコードを書くところから仕事が ;;始まり、以下がそのコードになります。 ;; 余談 ;;INTEL 16bitCPUでのメモリ管理はセグメント方式を採用しており16進数で5桁 ;;の値 0x00000が使われます。セグメントとは上4桁の値でさらにオフセットは ;;下4桁の値になります。当然間の3桁が重なっているので重複しますが、その辺は ;;ソフト側で適当に調整します。 ;;例: 0x07c08をセグメントとオフセットに分けると 0x07c0 + 0x0008などに ;; なります。当然分け方はいくらでも存在します。 ;.globalで先頭のSymbolが_startである事を指定します。 .global _start _start: ;このjmp命令はnasmで書くと LABEL:start2のようになります。 ;これは一見何もしてないように見えますが、codeレジスタである「cs」 ;レジスタに$BOOTSEGの値を代入する動作が行われてます。 jmpl $BOOTSEG, $start2 start2: xorw %si, %si ; 同じレジスタをxorすると値は0になります。 xorw %di, %di movw $BOOTSEG, %ax ;axに0x07c0を代入 movw %ax, %ds ;dsに0x7c0を代入(dsレジスタなどのセグメント ;レジスタへは直接値の代入は出来ないのでaxなど ;のレジスタを仲介しています) movw $INITSEG, %ax movw %ax, %es ;0x9000をesに代入 movw $0x100, %cx ;0x100(256)をcxに代入 cld rep movsw ;この命令はcxが0になるまで繰り返され、movs"w" ;なので2バイトずつ転送されます。 ;すなわちds:0からes:0へ512バイト転送されます jmpl $INITSEG, $start3 ;転送先である0x9000+start3番地へ飛びます ;;上記の処理が何故必要かというと、0x7c00番地より先の0x10000番地付近 ;;にはBIOSやら何やら重要な処理が読み込まれているのでそれを破壊する ;;のを避けるために0x7c00⇒0x90000への引越しをしています。 start3: movw %ax, %ds ;この時点でaxにはINITSEGの値0x9000が入って ;いてそれをdsにコピー subb $0x10, %ah ;axの上位8bitから0x10引いてるのでaxは ;0x8000になっている movw %ax, %ss xorw %sp, %sp ;;以下3行はbiosを使った画面のクリア方法 alに命令をセットし、 ;;int 0x10で実行 movb $0x3, %al # Clear the display movb $0, %ah int $0x10 ;;この命令はos_start_mesの相対位置をsiにコピーしている ;;printstrを呼ぶことでos_start_mesに書かれている文字を ;;ディスプレイに表示させている。 movw $os_start_mes, %si call printstr jmp hang printstr: pushw %ax printstr_start: lodsb ;ds:siの内容をalにコピーし ;siを1インクリメント cmpb $0, %al ;alと0の比較 jz printstr_end ;alが0なら終了(文字列の最後に予め0を ;仕込んである) movb $0x0e, %ah ;ah, bhにbios命令をセット movb $0, %bh int $0x10 ;alの内容をモニターに表示 jmp printstr_start printstr_end: popw %ax ret hang: jmp hang os_start_mes: .ascii "Hello. This is sodex, starting main boot loader...\r\n" .byte 0 .org 510 ;.orgはその後の数字が示すメモリ番地まで ;全て0で埋める .word 0xAA55 ;PC ATの仕様で最初の512バイトの最後2バイト ;には必ず「0xAA55」が必要
参考 BIOSの命令http://www.uv.tietgen.dk/Staff/Mlha/PC/Prog/asm/int/index.htm