読者です 読者をやめる 読者になる 読者になる

OS自作入門 onLinux 5日目

自作OS



ec126d58.png

ようやく5日目が終わりました!
11月頭で文化祭があって、その後も学校が相変わらず忙しかったですが、
なんとか時間の合間を縫ってこぎつけました。

まずはソースはこちら

今回は、4日目で画面表示ができるようになったので、
1.起動情報を受け取る
2.ASCII文字を表示する
3.GDTとIDTを初期化する
のが主な内容でした。

ところが…
====
起動情報を受け取るまではいいです。構造体の仕組みがわかります。

ASCII文字を表示するところで、筆者作のmakefontという自作ツールが登場しています。
面倒だったので、とりあえずLinux版のmakefontを使用しましたが、

./bim2hrb bootpack.bim bootpack.hrb 0
bim file format error


なぜエラーが…。
原因究明をするのもいいですが、自作ツールに翻弄されるくらいなら自分で作ります。

#!/usr/bin/python
# -*- coding: utf-8 -*-
# mkfont *** The Python Script making C source from font source file***
# Author    : levelfour
# Usage        : ./mkfont [font src] (-o [c src file])
import sys

def caution(msg) :
    print msg
    exit()

usage = "usage: ./mkfont [font src] (-o [c src file])"
error = "error: ./mkfont --help"

data = [0] * (16 * 256)

argv = sys.argv
argc = len(argv)

# show help
if argv[-1] == "--help" : caution(usage)
# too few argument
if argc < 2 : caution(error)
# wrong argument
if argv[1] == "-o" : caution(error)

# フォントソースファイルを開く
src = open(argv[1], "r")
# 出力するCソースファイルを開く
if argc >= 4 :
    if argv[2] == "-o" :
        dst = open(argv[3], "w")
else :
    dst = open("font.c", "w")

i = 0
c = 0
# フォントソースファイルを一行ずつ読む
for line in src :
    if i > 0 :
        tmp = 0    # 横一列の16進数データ
        j = 7
        while j >= 0 :
            # "*"->1, "."->0に変換する
            if line[j] == "*" :
                tmp += (1 << (7 - j))
            j -= 1
        # 配列に順番に格納
        data[c * 16 + (16 - i)] = tmp
        i -= 1
    # 文字コードを取得
    if line[0:4] == "char" :
        i = 16
        c = int(line[7:9], 16)

code = "char hankaku[4096] = {"
i = 0
for d in data :
    if i != 0 : code += ","
    if i % 16 == 0 : code += "\n\t"
    code += (hex(d))    # 16進数データを文字列に変換
    i += 1
code += "\n};"
dst.write(code)

src.close()
dst.close()


Pythonで書いてみました。Pythonを勉強したかったついでです
スクリプトの詳細は割愛しますが、これでhankaku.txtを下のようなhankaku.cに変換できます。

char hankaku[4096] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x38,0x44,0x82,0xaa,0xaa,0x82,0x82,0xaa,0x92,0x44,0x38,0x0,0x0,0x0,
0x0,0x0,0x38,0x7c,0xfe,0xd6,0xd6,0xfe,0xfe,0xd6,0xee,0x7c,0x38,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x6c,0xfe,0xfe,0xfe,0x7c,0x38,0x10,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x10,0x38,0x7c,0xfe,0x7c,0x38,0x10,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x10,0x38,0x54,0xfe,0x54,0x10,0x38,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x10,0x38,0x7c,0xfe,0xd6,0x10,0x38,0x0,0x0,0x0,0x0,0x0,
...(以下省略)


こいつをboot.cと一緒にコンパイルしてくっつけてあげれば、同じ動作になりますね。



ところが、前回の課題を覚えていますでしょうか。

そうです。静的変数、もっと言うとグローバル変数が使えないのです。

これでは、折角出力したフォントソースもグローバル配列なので、文字列も表示できません。
そこで、ちょっと緊急処置でフォント配列をローカルにおいて、とりあえず文字列を
表示して原因を考えることにしました。



ついでに、直後でsprintfが必要になるようなので、デバッグにもちょうど使えるし
作りました。

int lsprintf(char *str, const char *fmt, ...) {
        int *arg = (int *)(&str + 2);        // 可変個引数の配列
        int cnt, i, argc = 0;
        char buf[20];
        const char *p = fmt;
        for(cnt = 0; *p != '\0'; p++) {
                if(*p == '%') {
                        strcls(buf);        // バッファの初期化
                        // フォーマット指定子の場合は引数の数値を文字列へ変換
                        switch(p[1]) {
                                case 'd': int2dec(buf, arg[argc++]); break;
                                case 'x': int2hex(buf, arg[argc++]); break;
                        }
                        // 変換した数値を生成文字列にコピー
                        for(i = 0; buf[i] != '\0'; i++,cnt++) *str++ = buf[i];
                        p++;
                } else {
                        // フォーマット指定子以外はそのままコピー
                        *str++ = *p; cnt++;
                }
        }
        return cnt;
}

// ヌル文字で埋める
void strcls(char *str) {
        while(*str != '\0') *str++ = '\0';
}

// 数値を16進数文字列に変換する
void int2hex(char *s, int value) {
        s[0] = '0', s[1] = 'x';
        int i, filter = 0x0000000f;
        s += 2;
        for(i = 0; i < 8; i++) {
                if(((value >> (7-i)*4) & filter) >= 10) {
                        *s++ = 'A' + ((value >> (7-i)*4) & filter) - 10;
                } else {
                        *s++ = '0' + ((value >> (7-i)*4) & filter);
                }
        }
        *s = '\0';
}

// 10進数valueのn桁目を返す
int figure(int value, int n) {
        int i;
        for(i = 0; i < n-1; i++) value /= 10;
        return value % 10;
}

// 数値を10進数文字列に変換する
void int2dec(char *s, int value) {
        int i;
        char zero = 1;
        for(i = 0; i < 10; i++) {
                if(zero && figure(value, 10-i) != 0) zero = 0;
                if(!zero) *s++ = '0' + figure(value, 10-i);
        }
}


os/src/c/lib.cのほぼ全部の抜粋です。
とても読みづらいのは御愛嬌ですが、まあ特別にトリッキーなことをやっているとしたら
2行目のargですね。
Cで普通printfのような可変個引数を処理するには、stdarg.hのマクロを使って
処理しますが、勿論今回は標準ライブラリが使えません。
そこで、x86のスタックフレームの仕組みを逆手にとって可変個引数を取得します。

stack01.png
x86では、関数を呼ぶ前にまず、引数を逆順でスタックにプッシュします。
そして、関数を呼ぶときにcall命令を使います。call命令は、関数が終わったら引き続き
実行されるアドレス、つまりリターンアドレスをプッシュして関数にジャンプします。

ちなみにret命令はそのアドレスをポップして元の位置に戻ります。

そして、関数に入るとまず、%ebpの値をスタックに保存します。
今、第一引数strが図中の引数1です。そのアドレスに2を足すと、引数3のアドレスです。
これが、argの値です。

そうすることで、arg[0]、arg[1]、arg[2]、…で可変個部分の引数が取得できます。

引数のない関数で同様のことがしたい場合は、適当なローカル変数をつくって
同じようにアドレスを足すことで引数が取得できます。



そんなわけで、sprintfも駆使しながらデバッグした結果…
boot.cのエントリアドレスを0x00280000に設定すればよいだけでしたw
なんともあっさりな…

OUTPUT_FORMAT("binary")
ENTRY("Main")
SECTIONS {
         . = 0x280000;
}

ついでに、Makefileのldのオプションがごちゃごちゃ多くて目障りなので、こっちに持ってきました。

さて、head.sでは%dsレジスタに1*8を代入していました。
これは、8日目の説明によるとセグメント番号1番を表すそうです。
セグメント番号1番とは…サイズ4GB、開始番地0x000000、システム専用の実行可能セグメントです。
つまり、%ds=1*8の状態では、(%ebx)みたいにアドレッシングモードを使うと
(%ds:%ebx)と解釈されますから、%ebxの値をそのままアドレスとして解釈します。

ところが、ldにエントリアドレスを示していなかったので、リンクしたときに
グローバル変数等のアドレスが再配置によって、Linuxのelfフォーマットに従った
アドレスに配置されます(多分…^^;)。

それが大体0x08080000辺りなので、グローバル変数の中身は空っぽも同然でした。

これは、そろそろELFも勉強しろという神のお告げかなw