boot.sys から C 言語で printf もどきする

x86_64 な boot.sys」の続きです。
前回は、アセンブラで書いたちっちゃな x86_64 コードで VGA テキストへ書き込んでみておしまいでした。ここまでたどり着いたら、アセンブラを離れて C 言語で書いたコードを動かしてみたくなりました。幸い、ubuntu-i386 で配布される gcc は -m64 オプションをつければ、x86_64 のオブジェクト・ファイルにコンパイルできます。自前のライブラリさえ準備できればという前提はありますが……。
そこで、エイプリルフールネタで作成した「文字列・整数限定の cputch_printf」をアレンジし、curses ライブラリっぽい VGA テキストへの出力ルーチンを追加して、x86_64 のスーパーバイザ・レベルで使えるようにしてみました。入力はまだできません。
https://tociyuki.sakura.ne.jp/archive/ipl-x86_64-0.03.tgz
https://tociyuki.sakura.ne.jp/archive/ipl-x86_64-0.02.tgz
vtport.o と vtprintw.o をリンクすると、13個の出力ルーチンが使えるようになります。vtsetch を除くと、cursesライブラリの wxxx 関数に合わせてあります。色の指定を ncurses に合わせるとうっとうしいので、これだけは vt 構造体の color メンバに PC-BIOS の属性バイトを直接を書き込むようにするという手抜き方針にしました。

int vtinit(vtport_t *vt);
int vtmove(vtport_t *vt, unsigned int y, unsigned int x);
int vtsetch(vtport_t *vt, unsigned int y, unsigned int x, unsigned char ch);
int vtgetch(vtport_t *vt, unsigned int y, unsigned int x);
int vtaddch(vtport_t *vt, unsigned char ch);
int vtaddstr(vtport_t *vt, unsigned char *p);
int vtprintw(vtport_t *vt, const unsigned char *fmt, ...);
int vtclear(vtport_t *vt);
int vtclrtobot(vtport_t *vt);
int vtclrtoeol(vtport_t *vt);
int vtdeleteln(vtport_t *vt);
int vtinsertln(vtport_t *vt);
int vtscroll(vtport_t *vt);

vt->color = bgcolor << 4 + fgcolor;

boot.s のアセンブラから vtxxx ルーチンを直接コールするのは避けて、いったん C 言語で記述した bootrun 関数をコールするするように boot.s の 64 ビット・モードの箇所を変更しました。

        .code64
        .align  16
/* 64bit long mode entry point */
entry64:
        call    bootrun
        
/* work down, loop forever */
2:      hlt
        jmp     2b

呼び出される bootrun.c で、あきもせず Hello, World させてみます。ついでに、bootrun 関数のアドレスも表示させることにしてみました。

#include "vtport.h"

vtport_t vtstdscr;

void
bootrun(void)
{
    vtport_t *vt = &vtstdscr;
	
    vtinit(vt);
    vtclear(vt);
    vtmove(vt, 10, 33);
    vtaddstr(vt, "Hello, World!\n");
    vtmove(vt, 13, 27);
    vt->color = 0x07;
    vtprintw(vt, "bootrun 0x%016x", bootrun);
    vtmove(vt, 14, 39);
}

x86_32 で boot.sys を作るには、次のようにクロス・コンパイルとリンクをおこないます。gcc のオプション -mno-red-zone は、特権レベルのコードをコンパイルするときはつけておきます。といっても、まだ割り込みを許可していないので、このオプションをつけ忘れていても動作します。また、オプション -mno-sse は va_start や va_arg に SSE2 レジスタを使わせないようにするためのおまじないです。さらに、オプション -fno-stack-protector は、まだ割り付けていない物理ページの fs セグメントへアクセスをしないようにするためにつけています。その他に、ローダに ipl.bin を使うときは、バイナリの先頭から実行を開始するので、ld リンクするときに、必ず最初に boot.o を並べるようにしなければなりません。

$ as --64 -a=boot.lst -o boot.o boot.s
$ gcc -m64 -mno-red-zone -mno-sse -fno-stack-protector -O2 -c vtport.c
$ gcc -m64 -mno-red-zone -mno-sse -fno-stack-protector -O2 -c vtprintw.c
$ gcc -m64 -mno-red-zone -mno-sse -fno-stack-protector -O2 -c bootrun.c
$ ld -N -e start -S -melf_x86_64 --oformat binary -Ttext 0x00600 -o boot.sys \
    boot.o vtport.o vtprintw.o bootrun.o

できた boot.sys をフロッピ・ディスク・イメージに書き込んで、qemu で動かしてみると、期待したとおりにテキスト表示してくれました。

$ mcopy -D o -i fd.img boot.sys ::
$ qemu-system-x86_64 -fda fd.img &